github.com/grafana/pyroscope@v1.18.0/pkg/test/integration/ingest_jfr_test.go (about) 1 package integration 2 3 import ( 4 "fmt" 5 "os" 6 "strings" 7 "testing" 8 9 "connectrpc.com/connect" 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 13 profilev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1" 14 phlaremodel "github.com/grafana/pyroscope/pkg/model" 15 "github.com/grafana/pyroscope/pkg/og/convert/pprof/bench" 16 "github.com/grafana/pyroscope/pkg/pprof" 17 ) 18 19 type jfrTestData struct { 20 jfr string 21 labels string 22 23 expectedMetrics []expectedMetric 24 expectStatus int 25 } 26 27 const ( 28 testdataDirJFR = repoRoot + "pkg/og/convert/jfr/testdata" 29 ) 30 31 var jfrTestDatas = []jfrTestData{ 32 { 33 jfr: testdataDirJFR + "/cortex-dev-01__kafka-0__cpu__0.jfr.gz", 34 expectStatus: 200, 35 expectedMetrics: []expectedMetric{{"process_cpu:cpu:nanoseconds:cpu:nanoseconds", nil, 0}}, 36 }, 37 { 38 jfr: testdataDirJFR + "/cortex-dev-01__kafka-0__cpu__1.jfr.gz", 39 expectStatus: 200, 40 expectedMetrics: []expectedMetric{{"process_cpu:cpu:nanoseconds:cpu:nanoseconds", nil, 0}}, 41 }, 42 { 43 jfr: testdataDirJFR + "/cortex-dev-01__kafka-0__cpu__2.jfr.gz", 44 expectStatus: 200, 45 expectedMetrics: []expectedMetric{{"process_cpu:cpu:nanoseconds:cpu:nanoseconds", nil, 0}}, 46 }, 47 { 48 jfr: testdataDirJFR + "/cortex-dev-01__kafka-0__cpu__3.jfr.gz", 49 expectStatus: 200, 50 expectedMetrics: []expectedMetric{{"process_cpu:cpu:nanoseconds:cpu:nanoseconds", nil, 0}}, 51 }, 52 { 53 jfr: testdataDirJFR + "/cortex-dev-01__kafka-0__cpu_lock_alloc__0.jfr.gz", 54 expectStatus: 200, 55 expectedMetrics: []expectedMetric{ 56 {"process_cpu:cpu:nanoseconds:cpu:nanoseconds", nil, 0}, 57 {"memory:alloc_in_new_tlab_objects:count:space:bytes", nil, 0}, 58 {"memory:alloc_in_new_tlab_bytes:bytes:space:bytes", nil, 0}, 59 }, 60 }, 61 { 62 jfr: testdataDirJFR + "/cortex-dev-01__kafka-0__cpu_lock_alloc__1.jfr.gz", 63 expectStatus: 200, 64 expectedMetrics: []expectedMetric{ 65 {"process_cpu:cpu:nanoseconds:cpu:nanoseconds", nil, 0}, 66 {"memory:alloc_in_new_tlab_objects:count:space:bytes", nil, 0}, 67 {"memory:alloc_in_new_tlab_bytes:bytes:space:bytes", nil, 0}, 68 }, 69 }, 70 { 71 jfr: testdataDirJFR + "/cortex-dev-01__kafka-0__cpu_lock_alloc__2.jfr.gz", 72 expectStatus: 200, 73 expectedMetrics: []expectedMetric{ 74 {"process_cpu:cpu:nanoseconds:cpu:nanoseconds", nil, 0}, 75 {"memory:alloc_in_new_tlab_objects:count:space:bytes", nil, 0}, 76 {"memory:alloc_in_new_tlab_bytes:bytes:space:bytes", nil, 0}, 77 }, 78 }, 79 { 80 jfr: testdataDirJFR + "/cortex-dev-01__kafka-0__cpu_lock_alloc__3.jfr.gz", 81 expectStatus: 200, 82 expectedMetrics: []expectedMetric{ 83 {"process_cpu:cpu:nanoseconds:cpu:nanoseconds", nil, 0}, 84 {"memory:alloc_in_new_tlab_objects:count:space:bytes", nil, 0}, 85 {"memory:alloc_in_new_tlab_bytes:bytes:space:bytes", nil, 0}, 86 }, 87 }, 88 { 89 jfr: testdataDirJFR + "/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0.jfr.gz", 90 expectStatus: 200, 91 expectedMetrics: []expectedMetric{ 92 {"process_cpu:cpu:nanoseconds:cpu:nanoseconds", nil, 0}, 93 {"memory:alloc_in_new_tlab_objects:count:space:bytes", nil, 0}, 94 {"memory:alloc_in_new_tlab_bytes:bytes:space:bytes", nil, 0}, 95 {"memory:alloc_outside_tlab_objects:count:space:bytes", nil, 0}, 96 {"memory:alloc_outside_tlab_bytes:bytes:space:bytes", nil, 0}, 97 {"mutex:contentions:count:mutex:count", nil, 0}, 98 {"mutex:delay:nanoseconds:mutex:count", nil, 0}, 99 {"block:contentions:count:block:count", nil, 0}, 100 {"block:delay:nanoseconds:block:count", nil, 0}, 101 }, 102 }, 103 { 104 jfr: testdataDirJFR + "/dump1.jfr.gz", labels: testdataDirJFR + "/dump1.labels.pb.gz", 105 expectStatus: 200, 106 expectedMetrics: []expectedMetric{ 107 108 {"process_cpu:cpu:nanoseconds:cpu:nanoseconds", map[string]string{}, 0}, 109 {"process_cpu:cpu:nanoseconds:cpu:nanoseconds", map[string]string{"thread_name": "pool-2-thread-7"}, 0}, 110 {"process_cpu:cpu:nanoseconds:cpu:nanoseconds", map[string]string{"thread_name": "pool-2-thread-3"}, 0}, 111 {"process_cpu:cpu:nanoseconds:cpu:nanoseconds", map[string]string{"thread_name": "pool-2-thread-5"}, 0}, 112 {"process_cpu:cpu:nanoseconds:cpu:nanoseconds", map[string]string{"thread_name": "pool-2-thread-1"}, 0}, 113 }, 114 }, 115 { 116 jfr: testdataDirJFR + "/dump2.jfr.gz", labels: testdataDirJFR + "/dump2.labels.pb.gz", 117 expectStatus: 200, 118 expectedMetrics: []expectedMetric{ 119 {"wall:wall:nanoseconds:wall:nanoseconds", map[string]string{}, 0}, 120 {"wall:wall:nanoseconds:wall:nanoseconds", map[string]string{"thread_name": "pool-2-thread-3"}, 0}, 121 {"wall:wall:nanoseconds:wall:nanoseconds", map[string]string{"thread_name": "pool-2-thread-6"}, 0}, 122 {"wall:wall:nanoseconds:wall:nanoseconds", map[string]string{"thread_name": "pool-2-thread-4"}, 0}, 123 {"wall:wall:nanoseconds:wall:nanoseconds", map[string]string{"thread_name": "pool-2-thread-5"}, 0}, 124 {"wall:wall:nanoseconds:wall:nanoseconds", map[string]string{"thread_name": "pool-2-thread-1"}, 0}, 125 {"wall:wall:nanoseconds:wall:nanoseconds", map[string]string{"thread_name": "pool-2-thread-8"}, 0}, 126 {"wall:wall:nanoseconds:wall:nanoseconds", map[string]string{"thread_name": "pool-2-thread-2"}, 0}, 127 {"wall:wall:nanoseconds:wall:nanoseconds", map[string]string{"thread_name": "pool-2-thread-7"}, 0}, 128 {"process_cpu:cpu:nanoseconds:cpu:nanoseconds", map[string]string{}, 0}, 129 {"process_cpu:cpu:nanoseconds:cpu:nanoseconds", map[string]string{"thread_name": "pool-2-thread-3"}, 0}, 130 {"process_cpu:cpu:nanoseconds:cpu:nanoseconds", map[string]string{"thread_name": "pool-2-thread-6"}, 0}, 131 {"process_cpu:cpu:nanoseconds:cpu:nanoseconds", map[string]string{"thread_name": "pool-2-thread-4"}, 0}, 132 {"process_cpu:cpu:nanoseconds:cpu:nanoseconds", map[string]string{"thread_name": "pool-2-thread-5"}, 0}, 133 {"process_cpu:cpu:nanoseconds:cpu:nanoseconds", map[string]string{"thread_name": "pool-2-thread-1"}, 0}, 134 {"process_cpu:cpu:nanoseconds:cpu:nanoseconds", map[string]string{"thread_name": "pool-2-thread-8"}, 0}, 135 {"process_cpu:cpu:nanoseconds:cpu:nanoseconds", map[string]string{"thread_name": "pool-2-thread-2"}, 0}, 136 {"process_cpu:cpu:nanoseconds:cpu:nanoseconds", map[string]string{"thread_name": "pool-2-thread-7"}, 0}, 137 {"memory:alloc_in_new_tlab_objects:count:space:bytes", map[string]string{}, 0}, 138 {"memory:alloc_in_new_tlab_bytes:bytes:space:bytes", map[string]string{}, 0}, 139 {"memory:live:count:objects:count", map[string]string{}, 0}, 140 }, 141 }, 142 } 143 144 const dump = false 145 146 func TestNOJFRDump(t *testing.T) { 147 assert.False(t, dump) 148 } 149 150 func TestIngestJFR(t *testing.T) { 151 EachPyroscopeTest(t, func(p *PyroscopeTest, t *testing.T) { 152 for _, testdatum := range jfrTestDatas { 153 t.Run(testdatum.jfr, func(t *testing.T) { 154 155 rb := p.NewRequestBuilder(t) 156 req := rb.IngestJFRRequestFiles(testdatum.jfr, testdatum.labels) 157 p.Ingest(t, req, testdatum.expectStatus) 158 159 if testdatum.expectStatus == 200 { 160 assert.NotEqual(t, len(testdatum.expectedMetrics), 0) 161 for _, metric := range testdatum.expectedMetrics { 162 goldFile := expectedPPROFFile(testdatum, metric) 163 t.Logf("%v gold file %s", metric, goldFile) 164 rb.Render(metric.name) 165 profile := rb.SelectMergeProfile(metric.name, metric.query) 166 verifyPPROF(t, profile, goldFile, metric) 167 } 168 } 169 }) 170 } 171 }) 172 } 173 174 func expectedPPROFFile(testdatum jfrTestData, metric expectedMetric) string { 175 lbls := phlaremodel.NewLabelsBuilder(nil) 176 for k, v := range metric.query { 177 lbls.Set(k, v) 178 } 179 ls := lbls.Labels() 180 h := ls.Hash() 181 182 return fmt.Sprintf("%s.%s.%d.pb.gz", testdatum.jfr, strings.ReplaceAll(metric.name, ":", "_"), h) 183 } 184 185 func TestCorruptedJFR422(t *testing.T) { 186 EachPyroscopeTest(t, func(p *PyroscopeTest, t *testing.T) { 187 td := jfrTestDatas[0] 188 jfr, err := bench.ReadGzipFile(td.jfr) 189 require.NoError(t, err) 190 jfr[0] = 0 // corrupt jfr 191 192 rb := p.NewRequestBuilder(t) 193 req := rb.IngestJFRRequestBody(jfr, nil) 194 p.Ingest(t, req, 422) 195 }) 196 } 197 198 func verifyPPROF(t *testing.T, resp *connect.Response[profilev1.Profile], expectedPPROF string, metric expectedMetric) { 199 var err error 200 201 require.NoError(t, err) 202 assert.Equal(t, 1, len(resp.Msg.SampleType)) 203 204 if dump { 205 bs, err := resp.Msg.MarshalVT() 206 require.NoError(t, err) 207 err = bench.WriteGzipFile(expectedPPROF, bs) 208 require.NoError(t, err) 209 } else { 210 profileBytes, err := os.ReadFile(expectedPPROF) 211 require.NoError(t, err) 212 expectedProfile, err := pprof.RawFromBytes(profileBytes) 213 require.NoError(t, err) 214 215 actualStacktraces := bench.StackCollapseProto(resp.Msg, 0, 1) 216 expectedStacktraces := bench.StackCollapseProto(expectedProfile.Profile, metric.valueIDX, 1) 217 218 require.Equal(t, expectedStacktraces, actualStacktraces) 219 } 220 }