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  }