Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix flamegraph file extension #151

Merged
merged 2 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ Dropping a requirement of a major version of a dependency is a new contract.
## [Unreleased]
[Unreleased]: https://github.com/atlassian/infrastructure/compare/release-4.26.1...master

### Added
- Add `AsyncProfiler.Builder` to expose more options.

### Deprecated
- Deprecate `AsyncProfiler.<init>` and `WallClockProfiler` in favor of `AsyncProfiler.Builder`.

### Fixed
- Fix `AsyncProfiler` flamegraph file extension from `.svg` to `.html`.
- Bump `WallClockProfiler` to async-profiler v2.9.

## [4.26.1] - 2023-07-20
[4.26.1]: https://github.com/atlassian/infrastructure/compare/release-4.26.0...release-4.26.1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ import java.time.Duration.ofSeconds
/**
* Asynchronous profiler. See https://github.com/jvm-profiling-tools/async-profiler#basic-usage
*/
class AsyncProfiler : Profiler {
class AsyncProfiler private constructor(
private val startParams: List<String>,
private val outputFile: String
) : Profiler {

@Deprecated("Use AsyncProfiler.Builder instead")
constructor() : this(emptyList(), "flamegraph.html")

private val release = "async-profiler-2.9-linux-x64"

Expand All @@ -27,24 +33,55 @@ class AsyncProfiler : Profiler {
pid: Int
): RemoteMonitoringProcess {
val script = "./$release/profiler.sh"
val params = startParams.joinToString(separator = " ")
IdempotentAction("start async-profiler") {
ssh.execute("$script start $pid")
ssh.execute("$script start $params $pid")
}.retry(2, StaticBackoff(ofSeconds(5)))
return ProfilerProcess(script, pid)
return ProfilerProcess(script, pid, outputFile)
}

private class ProfilerProcess(
private val script: String,
private val pid: Int
private val pid: Int,
private val outputFile: String
) : RemoteMonitoringProcess {
private val flameGraphFile = "flamegraph.svg"

override fun stop(ssh: SshConnection) {
ssh.execute("$script stop $pid -o flamegraph > $flameGraphFile", timeout = ofSeconds(50))
ssh.execute("$script stop $pid -o flamegraph > $outputFile", timeout = ofSeconds(50))
}

override fun getResultPath(): String {
return flameGraphFile
return outputFile
}
}

class Builder {

private var outputFile: String = "flamegraph.html"
private val startParams = mutableListOf<String>()

fun outputFile(outputFile: String) = apply { this.outputFile = outputFile }

fun wallClockMode() = apply {
startParams.add("-e")
startParams.add("wall")
}

fun interval(interval: Duration) = apply {
startParams.add("-i")
if (interval < ofSeconds(1)) {
startParams.add(interval.nano.toString())
} else {
throw Exception("The interval $interval seems to big. Usually it's counted in milliseconds or nanoseconds. Try an interval under a second.")
}
}

fun extraParams(vararg extraParams: String) = apply {
startParams.addAll(extraParams)
}

fun build(): Profiler {
return AsyncProfiler(ArrayList(startParams), outputFile)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ArrayList(startParams)

This should make the Builder reusable 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch BTW

}
}
}
Original file line number Diff line number Diff line change
@@ -1,37 +1,13 @@
package com.atlassian.performance.tools.infrastructure.api.profiler

import com.atlassian.performance.tools.infrastructure.api.process.RemoteMonitoringProcess
import com.atlassian.performance.tools.ssh.api.SshConnection
import java.time.Duration

/**
* A profiler to sample all threads equally every given period of time regardless of thread status.
*/
class WallClockProfiler : Profiler {
override fun install(ssh: SshConnection) {
ssh.execute("wget -q https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.5/async-profiler-1.5-linux-x64.tar.gz")
ssh.execute("mkdir async-profiler")
ssh.execute("tar -xzf async-profiler-1.5-linux-x64.tar.gz -C async-profiler")
ssh.execute("sudo sh -c 'echo 1 > /proc/sys/kernel/perf_event_paranoid'")
ssh.execute("sudo sh -c 'echo 0 > /proc/sys/kernel/kptr_restrict'")
}

override fun start(
ssh: SshConnection,
pid: Int
): RemoteMonitoringProcess {
ssh.execute("./async-profiler/profiler.sh -e wall -i 9ms -b 160000000 start $pid")
return ProfilerProcess(pid)
}

private class ProfilerProcess(private val pid: Int) : RemoteMonitoringProcess {
private val flameGraphFile = "wall-clock-flamegraph.svg"

override fun stop(ssh: SshConnection) {
ssh.execute("./async-profiler/profiler.sh stop $pid -o svg > $flameGraphFile")
}

override fun getResultPath(): String {
return flameGraphFile
}
}
}
@Deprecated("Use AsyncProfiler.Builder instead")
class WallClockProfiler : Profiler by AsyncProfiler.Builder()
.wallClockMode()
.interval(Duration.ofMillis(9))
.outputFile("wall-clock-flamegraph.html")
.build()
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,34 @@ import com.atlassian.performance.tools.sshubuntu.api.SshUbuntuContainer
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.nio.file.Files.createTempFile
import java.time.Duration
import java.util.function.Consumer

class AsyncProfilerIT {

@Test
fun shouldWorkOnXenial() {
testOn("16.04")
val profiler = AsyncProfiler.Builder().build()
testOn(profiler, "16.04")
}

@Test
fun shouldWorkOnFocal() {
testOn("20.04")
val profiler = AsyncProfiler.Builder().build()
testOn(profiler, "20.04")
}

private fun testOn(ubuntuVersion: String) {
// given
val profiler = AsyncProfiler()
@Test
fun shouldRunInWallClockMode() {
val profiler = AsyncProfiler.Builder()
.wallClockMode()
.interval(Duration.ofMillis(9))
.build()

testOn(profiler, "20.04")
}

private fun testOn(profiler: Profiler, ubuntuVersion: String) {
testOnInstalledJira(ubuntuVersion) { installedJira ->
val sshClient = installedJira.server.ssh
sshClient.newConnection().use { ssh ->
Expand All @@ -39,7 +49,7 @@ class AsyncProfilerIT {
val startedJira = JiraLaunchScript().start(installedJira)
val process = profiler.start(ssh, startedJira.pid)
Thread.sleep(5000)
process.stop(ssh)
process!!.stop(ssh)

// then
val profilerResult = RemotePath(sshClient.host, process.getResultPath())
Expand Down
Loading