diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..16b7427 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.gradle +**/build/ +**/out/ \ No newline at end of file diff --git a/.github/workflows/itest.yml b/.github/workflows/itest.yml new file mode 100644 index 0000000..5c8141c --- /dev/null +++ b/.github/workflows/itest.yml @@ -0,0 +1,48 @@ +name: Integration smoke test + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + integration-smoke-test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + svc: + - 'alpine-3.16-8' + - 'alpine-3.16-11' + - 'alpine-3.16-17' + - 'alpine-3.17-8' + - 'alpine-3.17-11' + - 'alpine-3.17-17' + - 'alpine-3.18-8' + - 'alpine-3.18-11' + - 'alpine-3.18-17' + - 'alpine-3.19-8' + - 'alpine-3.19-11' + - 'alpine-3.19-17' + - 'ubuntu-18.04-8' + - 'ubuntu-18.04-11' + - 'ubuntu-18.04-17' + - 'ubuntu-20.04-8' + - 'ubuntu-20.04-11' + - 'ubuntu-20.04-17' + - 'ubuntu-20.04-21' + - 'ubuntu-22.04-8' + - 'ubuntu-22.04-11' + - 'ubuntu-22.04-17' + - 'ubuntu-22.04-21' + steps: + - uses: actions/checkout@v2 + - name: Install Go + uses: actions/setup-go@v4 + with: + go-version: 1.21.6 + - run: make itest + shell: bash + env: + ITEST_SERVICE: ${{ matrix.svc }} diff --git a/Makefile b/Makefile index f2929c9..e208d2c 100644 --- a/Makefile +++ b/Makefile @@ -33,3 +33,11 @@ docker-example-expt: build cp agent/build/libs/pyroscope.jar examples docker-compose -f examples/docker-compose-expt.yml build docker-compose -f examples/docker-compose-expt.yml up + +ITEST_SERVICE ?= + +.PHONY: itest +itest: + docker compose -f docker-compose-itest.yaml up --build --force-recreate -d pyroscope $(ITEST_SERVICE) + cd itest/query && go run . $(ITEST_SERVICE) + docker compose -f docker-compose-itest.yaml down pyroscope $(ITEST_SERVICE) diff --git a/alpine-test.Dockerfile b/alpine-test.Dockerfile new file mode 100644 index 0000000..ebbcabf --- /dev/null +++ b/alpine-test.Dockerfile @@ -0,0 +1,27 @@ +ARG IMAGE_VERSION +FROM alpine:${IMAGE_VERSION} +ARG IMAGE_VERSION +ARG JAVA_VERSION +RUN apk add openjdk${JAVA_VERSION} +WORKDIR /app +ADD gradlew build.gradle settings.gradle /app/ +ADD gradle gradle +RUN ./gradlew --no-daemon --version +ADD agent agent +ADD async-profiler-context async-profiler-context +ADD demo/build.gradle demo/ + +RUN ./gradlew --no-daemon shadowJar + +ADD demo demo + +ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/jvm/default-jvm/jre/bin + + +RUN javac demo/src/main/java/Fib.java + +ENV PYROSCOPE_LOG_LEVEL=debug +ENV PYROSCOPE_SERVER_ADDRESS=http://pyroscope:4040 +ENV PYROSCOPE_APPLICATION_NAME=alpine-${IMAGE_VERSION}-${JAVA_VERSION} +ENV PYROSCOPE_UPLOAD_INTERVAL=15s +CMD ["java", "-javaagent:/app/agent/build/libs/pyroscope.jar", "-cp", "demo/src/main/java/", "Fib"] diff --git a/async-profiler-context/build.gradle b/async-profiler-context/build.gradle index 5cecf78..aa66c49 100644 --- a/async-profiler-context/build.gradle +++ b/async-profiler-context/build.gradle @@ -21,7 +21,7 @@ repositories { mavenCentral() } -def asyncProfilerVersion = "2.9.0.2" +def asyncProfilerVersion = "3.0.0.0" def pyroscopeVersion = project.properties['pyroscope_version'] dependencies { api files("$buildDir/async-profiler/async-profiler.jar") @@ -75,6 +75,7 @@ task asyncProfilerLib { ['linux-arm64', 'tar.gz'], ['linux-x64', 'tar.gz'], ['linux-musl-x64', 'tar.gz'], + ['linux-musl-arm64', 'tar.gz'], ['macos', 'zip'] ] @@ -86,8 +87,7 @@ task asyncProfilerLib { doLast { suffixes.forEach { suffix, ext -> -// def repo = "https://github.com/jvm-profiling-tools/async-profiler" - def repo = "https://github.com/pyroscope-io/async-profiler" + def repo = "https://github.com/grafana/async-profiler" download { src "$repo/releases/download/v${asyncProfilerVersion}/async-profiler-${asyncProfilerVersion}-${suffix}.${ext}" dest new File(asyncProfilerDir, "async-profiler-${asyncProfilerVersion}-${suffix}.${ext}") @@ -101,7 +101,7 @@ task asyncProfilerLib { } else { from tarTree(resources.gzip("$asyncProfilerDir/async-profiler-${asyncProfilerVersion}-${suffix}.${ext}")) } - include '**/libasyncProfiler.so' + include '**/libasyncProfiler.*' eachFile { it.relativePath = new RelativePath(true, "native/", "libasyncProfiler-${suffix}.so") } diff --git a/async-profiler-context/src/main/java/io/pyroscope/labels/io/pyroscope/PyroscopeAsyncProfiler.java b/async-profiler-context/src/main/java/io/pyroscope/labels/io/pyroscope/PyroscopeAsyncProfiler.java index 6d01c6e..c365c92 100644 --- a/async-profiler-context/src/main/java/io/pyroscope/labels/io/pyroscope/PyroscopeAsyncProfiler.java +++ b/async-profiler-context/src/main/java/io/pyroscope/labels/io/pyroscope/PyroscopeAsyncProfiler.java @@ -86,7 +86,11 @@ private static String libraryFileName() { break; case "aarch64": - arch = "arm64"; + if (isMusl()) { + arch = "musl-arm64"; + } else { + arch = "arm64"; + } break; default: diff --git a/demo/src/main/java/App.java b/demo/src/main/java/App.java index cf9480e..dbf1224 100644 --- a/demo/src/main/java/App.java +++ b/demo/src/main/java/App.java @@ -1,12 +1,9 @@ import io.pyroscope.http.Format; import io.pyroscope.javaagent.PyroscopeAgent; -import io.pyroscope.javaagent.Snapshot; -import io.pyroscope.javaagent.api.Exporter; import io.pyroscope.javaagent.api.Logger; import io.pyroscope.javaagent.config.Config; -import io.pyroscope.javaagent.impl.DefaultConfigurationProvider; -import io.pyroscope.labels.Pyroscope; import io.pyroscope.labels.LabelsSet; +import io.pyroscope.labels.Pyroscope; import java.util.HashMap; import java.util.Map; @@ -26,7 +23,6 @@ public static void main(String[] args) { .setLogLevel(Logger.Level.DEBUG) .setLabels(mapOf("user", "tolyan")) .build()) -// .setExporter(new MyStdoutExporter()) .build() ); Pyroscope.setStaticLabels(mapOf("region", "us-east-1")); @@ -68,14 +64,6 @@ private static long fib(Long n) throws InterruptedException { if (n == 1L) { return 1L; } - Thread.sleep(100); return fib(n - 1) + fib(n - 2); } - - private static class MyStdoutExporter implements Exporter { - @Override - public void export(Snapshot snapshot) { - System.out.printf("Export %d %d%n", snapshot.data.length, snapshot.labels.toByteArray().length); - } - } } diff --git a/demo/src/main/java/Fib.class b/demo/src/main/java/Fib.class new file mode 100644 index 0000000..26cbb8e Binary files /dev/null and b/demo/src/main/java/Fib.class differ diff --git a/demo/src/main/java/Fib.java b/demo/src/main/java/Fib.java new file mode 100644 index 0000000..7686294 --- /dev/null +++ b/demo/src/main/java/Fib.java @@ -0,0 +1,50 @@ + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class Fib { + public static final int N_THREADS; + + static { + int n; + try { + n = Integer.parseInt(System.getenv("N_THREADS")); + } catch (NumberFormatException e) { + n = 1; + } + N_THREADS = n; + } + + public static void main(String[] args) { + appLogic(); + } + + private static void appLogic() { + ExecutorService executors = Executors.newFixedThreadPool(N_THREADS); + for (int i = 0; i < N_THREADS; i++) { + executors.submit(() -> { + while (true) { + try { + fib(32L); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + }); + } + } + + private static long fib(Long n) throws InterruptedException { + if (n == 0L) { + return 0L; + } + if (n == 1L) { + return 1L; + } + return fib(n - 1) + fib(n - 2); + } + +} diff --git a/docker-compose-itest.yaml b/docker-compose-itest.yaml new file mode 100644 index 0000000..c26f468 --- /dev/null +++ b/docker-compose-itest.yaml @@ -0,0 +1,170 @@ +version: '3.9' +services: + alpine-3.16-8: + build: + context: . + dockerfile: alpine-test.Dockerfile + args: + IMAGE_VERSION: 3.16 + JAVA_VERSION: 8 + alpine-3.16-11: + build: + context: . + dockerfile: alpine-test.Dockerfile + args: + IMAGE_VERSION: 3.16 + JAVA_VERSION: 11 + alpine-3.16-17: + build: + context: . + dockerfile: alpine-test.Dockerfile + args: + IMAGE_VERSION: 3.16 + JAVA_VERSION: 17 + alpine-3.17-8: + build: + context: . + dockerfile: alpine-test.Dockerfile + args: + IMAGE_VERSION: 3.17 + JAVA_VERSION: 8 + alpine-3.17-11: + build: + context: . + dockerfile: alpine-test.Dockerfile + args: + IMAGE_VERSION: 3.17 + JAVA_VERSION: 11 + alpine-3.17-17: + build: + context: . + dockerfile: alpine-test.Dockerfile + args: + IMAGE_VERSION: 3.17 + JAVA_VERSION: 17 + alpine-3.18-8: + build: + context: . + dockerfile: alpine-test.Dockerfile + args: + IMAGE_VERSION: 3.18 + JAVA_VERSION: 8 + alpine-3.18-11: + build: + context: . + dockerfile: alpine-test.Dockerfile + args: + IMAGE_VERSION: 3.18 + JAVA_VERSION: 11 + alpine-3.18-17: + build: + context: . + dockerfile: alpine-test.Dockerfile + args: + IMAGE_VERSION: 3.18 + JAVA_VERSION: 17 + alpine-3.19-8: + build: + context: . + dockerfile: alpine-test.Dockerfile + args: + IMAGE_VERSION: 3.19 + JAVA_VERSION: 8 + alpine-3.19-11: + build: + context: . + dockerfile: alpine-test.Dockerfile + args: + IMAGE_VERSION: 3.19 + JAVA_VERSION: 11 + alpine-3.19-17: + build: + context: . + dockerfile: alpine-test.Dockerfile + args: + IMAGE_VERSION: 3.19 + JAVA_VERSION: 17 + + ubuntu-18.04-8: + build: + context: . + dockerfile: ubuntu-test.Dockerfile + args: + IMAGE_VERSION: 18.04 + JAVA_VERSION: 8 + ubuntu-18.04-11: + build: + context: . + dockerfile: ubuntu-test.Dockerfile + args: + IMAGE_VERSION: 18.04 + JAVA_VERSION: 11 + ubuntu-18.04-17: + build: + context: . + dockerfile: ubuntu-test.Dockerfile + args: + IMAGE_VERSION: 18.04 + JAVA_VERSION: 17 + ubuntu-20.04-8: + build: + context: . + dockerfile: ubuntu-test.Dockerfile + args: + IMAGE_VERSION: 20.04 + JAVA_VERSION: 8 + ubuntu-20.04-11: + build: + context: . + dockerfile: ubuntu-test.Dockerfile + args: + IMAGE_VERSION: 20.04 + JAVA_VERSION: 11 + ubuntu-20.04-17: + build: + context: . + dockerfile: ubuntu-test.Dockerfile + args: + IMAGE_VERSION: 20.04 + JAVA_VERSION: 17 + ubuntu-20.04-21: + build: + context: . + dockerfile: ubuntu-test.Dockerfile + args: + IMAGE_VERSION: 20.04 + JAVA_VERSION: 21 + ubuntu-22.04-8: + build: + context: . + dockerfile: ubuntu-test.Dockerfile + args: + IMAGE_VERSION: 22.04 + JAVA_VERSION: 8 + ubuntu-22.04-11: + build: + context: . + dockerfile: ubuntu-test.Dockerfile + args: + IMAGE_VERSION: 22.04 + JAVA_VERSION: 11 + ubuntu-22.04-17: + build: + context: . + dockerfile: ubuntu-test.Dockerfile + args: + IMAGE_VERSION: 22.04 + JAVA_VERSION: 17 + ubuntu-22.04-21: + build: + context: . + dockerfile: ubuntu-test.Dockerfile + args: + IMAGE_VERSION: 22.04 + JAVA_VERSION: 21 + pyroscope: + image: 'grafana/pyroscope:latest' + ports: + - 4040:4040 + + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index aa991fc..a595206 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/itest/query/go.mod b/itest/query/go.mod new file mode 100644 index 0000000..4ebbd92 --- /dev/null +++ b/itest/query/go.mod @@ -0,0 +1,19 @@ +module java-test-querier + +go 1.21 + +require ( + connectrpc.com/connect v1.14.0 + github.com/grafana/pyroscope/api v0.4.0 +) + +require ( + github.com/golang/protobuf v1.5.3 // indirect + github.com/gorilla/mux v1.8.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect + google.golang.org/grpc v1.59.0 // indirect + google.golang.org/protobuf v1.32.0 // indirect +) diff --git a/itest/query/go.sum b/itest/query/go.sum new file mode 100644 index 0000000..2f60bbe --- /dev/null +++ b/itest/query/go.sum @@ -0,0 +1,27 @@ +connectrpc.com/connect v1.14.0 h1:PDS+J7uoz5Oui2VEOMcfz6Qft7opQM9hPiKvtGC01pA= +connectrpc.com/connect v1.14.0/go.mod h1:uoAq5bmhhn43TwhaKdGKN/bZcGtzPW1v+ngDTn5u+8s= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/grafana/pyroscope/api v0.4.0 h1:J86DxoNeLOvtJhB1Cn65JMZkXe682D+RqeoIUiYc/eo= +github.com/grafana/pyroscope/api v0.4.0/go.mod h1:MFnZNeUM4RDsDOnbgKW3GWoLSBpLzMMT9nkvhHHo81o= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= diff --git a/itest/query/main.go b/itest/query/main.go new file mode 100644 index 0000000..9ff42dd --- /dev/null +++ b/itest/query/main.go @@ -0,0 +1,200 @@ +package main + +import ( + "context" + "fmt" + "net/http" + "os" + "slices" + "strings" + "sync" + "time" + + "connectrpc.com/connect" + profilev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1" + querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1" + + "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1/querierv1connect" +) + +func main() { + type result struct { + target string + err error + } + targets := os.Args[1:] + if len(targets) == 0 { + targets = []string{ + "alpine-3.16-8", + "alpine-3.16-11", + "alpine-3.16-17", + "alpine-3.17-8", + "alpine-3.17-11", + "alpine-3.17-17", + "alpine-3.18-8", + "alpine-3.18-11", + "alpine-3.18-17", + "alpine-3.19-8", + "alpine-3.19-11", + "alpine-3.19-17", + "ubuntu-18.04-8", + "ubuntu-18.04-11", + "ubuntu-18.04-17", + "ubuntu-20.04-8", + "ubuntu-20.04-11", + "ubuntu-20.04-17", + "ubuntu-20.04-21", + "ubuntu-22.04-8", + "ubuntu-22.04-11", + "ubuntu-22.04-17", + "ubuntu-22.04-21", + } + } + url := "http://localhost:4040" + qc := querierv1connect.NewQuerierServiceClient( + http.DefaultClient, + url, + ) + wg := sync.WaitGroup{} + ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(time.Minute)) + results := make(chan result, len(targets)) + for _, target := range targets { + wg.Add(1) + go func(target string) { + defer wg.Done() + err := testTarget(ctx, qc, target) + results <- result{target: target, err: err} + }(target) + } + wg.Wait() + close(results) + + failed := false + for r := range results { + if r.err != nil { + fmt.Printf("[%s] %s\n", r.target, r.err.Error()) + failed = true + } else { + fmt.Printf("[%s] OK\n", r.target) + } + } + if failed { + os.Exit(1) + } +} + +func testTarget(ctx context.Context, qc querierv1connect.QuerierServiceClient, target string) error { + //needle := "Fib$$Lambda_.run;Fib.lambda$appLogic$0;Fib.fib;Fib.fib;Fib.fib;Fib.fib;" // us this one when https://github.com/grafana/jfr-parser/pull/28/files lands pyroscope docker tag + needle := "run;Fib.lambda$appLogic$0;Fib.fib;Fib.fib;Fib.fib;Fib.fib;" + ticker := time.NewTicker(time.Second * 5) + n := 0 + for { + select { + case <-ctx.Done(): + return fmt.Errorf("timed out waiting for target %s. tried %d times", target, n) + case <-ticker.C: + n += 1 + to := time.Now() + from := to.Add(-time.Minute * 1) + + resp, err := qc.SelectMergeProfile(context.Background(), connect.NewRequest(&querierv1.SelectMergeProfileRequest{ + ProfileTypeID: "process_cpu:cpu:nanoseconds:cpu:nanoseconds", + Start: from.UnixMilli(), + End: to.UnixMilli(), + LabelSelector: fmt.Sprintf("{service_name=\"%s\"}", target), + })) + if err != nil { + fmt.Printf("[%s] %d %s\n", target, n, err.Error()) + continue + } + + ss := stackCollapseProto(resp.Msg, false) + if !strings.Contains(ss, needle) { + fmt.Printf("[%s] %d not found yet\n%s\n", target, n, ss) + continue + } + fmt.Printf("[%s] %d OK\n", target, n) + return nil + } + } + +} + +func stackCollapseProto(p *profilev1.Profile, lineNumbers bool) string { + allZeros := func(a []int64) bool { + for _, v := range a { + if v != 0 { + return false + } + } + return true + } + addValues := func(a, b []int64) { + for i := range a { + a[i] += b[i] + } + } + + type stack struct { + funcs string + value []int64 + } + locMap := make(map[int64]*profilev1.Location) + funcMap := make(map[int64]*profilev1.Function) + for _, l := range p.Location { + locMap[int64(l.Id)] = l + } + for _, f := range p.Function { + funcMap[int64(f.Id)] = f + } + + var ret []stack + for _, s := range p.Sample { + var funcs []string + for i := range s.LocationId { + locID := s.LocationId[len(s.LocationId)-1-i] + loc := locMap[int64(locID)] + for _, line := range loc.Line { + f := funcMap[int64(line.FunctionId)] + fname := p.StringTable[f.Name] + if lineNumbers { + fname = fmt.Sprintf("%s:%d", fname, line.Line) + } + funcs = append(funcs, fname) + } + } + + vv := make([]int64, len(s.Value)) + copy(vv, s.Value) + ret = append(ret, stack{ + funcs: strings.Join(funcs, ";"), + value: vv, + }) + } + slices.SortFunc(ret, func(i, j stack) int { + return strings.Compare(i.funcs, j.funcs) + }) + var unique []stack + for _, s := range ret { + if allZeros(s.value) { + continue + } + if len(unique) == 0 { + unique = append(unique, s) + continue + } + + if unique[len(unique)-1].funcs == s.funcs { + addValues(unique[len(unique)-1].value, s.value) + continue + } + unique = append(unique, s) + + } + + res := make([]string, 0, len(unique)) + for _, s := range unique { + res = append(res, fmt.Sprintf("%s %v", s.funcs, s.value)) + } + return strings.Join(res, "\n") +} diff --git a/ubuntu-test.Dockerfile b/ubuntu-test.Dockerfile new file mode 100644 index 0000000..a15966c --- /dev/null +++ b/ubuntu-test.Dockerfile @@ -0,0 +1,25 @@ +ARG IMAGE_VERSION=18.04 +FROM ubuntu:${IMAGE_VERSION} +ARG IMAGE_VERSION=18.04 +ARG JAVA_VERSION=11 +RUN apt-get update && apt-get install -y openjdk-${JAVA_VERSION}-jdk-headless +WORKDIR /app +ADD gradlew build.gradle settings.gradle /app/ +ADD gradle gradle +RUN ./gradlew --no-daemon --version +ADD agent agent +ADD async-profiler-context async-profiler-context +ADD demo/build.gradle demo/ + +RUN ./gradlew --no-daemon shadowJar + +ADD demo demo + +RUN javac demo/src/main/java/Fib.java + +ENV PYROSCOPE_LOG_LEVEL=debug +ENV PYROSCOPE_SERVER_ADDRESS=http://pyroscope:4040 +ENV PYROSCOPE_UPLOAD_INTERVAL=5s +ENV PYROSCOPE_APPLICATION_NAME=ubuntu-${IMAGE_VERSION}-${JAVA_VERSION} + +CMD ["java", "-javaagent:/app/agent/build/libs/pyroscope.jar", "-cp", "demo/src/main/java/", "Fib"]