diff --git a/examples/python/simple/Dockerfile b/examples/python/simple/Dockerfile index 9787f117f6..0f284a505e 100644 --- a/examples/python/simple/Dockerfile +++ b/examples/python/simple/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.9 +FROM python:3.11 WORKDIR /usr/src/app diff --git a/examples/python/simple/main.py b/examples/python/simple/main.py index 9f9601dadd..4208314733 100644 --- a/examples/python/simple/main.py +++ b/examples/python/simple/main.py @@ -1,12 +1,19 @@ #!/usr/bin/env python3 +import logging import os - import pyroscope +l = logging.getLogger() +l.setLevel(logging.DEBUG) + +addr = os.getenv("PYROSCOPE_SERVER_ADDRESS") or "http://pyroscope:4040" +print(addr) + pyroscope.configure( - application_name = "simple.python.app", - server_address = "http://pyroscope:4040", + application_name = "simple.python.app", + server_address = addr, + enable_logging = True, ) def work(n): diff --git a/examples/python/simple/requirements.txt b/examples/python/simple/requirements.txt index cd1cd86070..11b9387ad7 100644 --- a/examples/python/simple/requirements.txt +++ b/examples/python/simple/requirements.txt @@ -1 +1 @@ -pyroscope-io==0.7.1 +pyroscope-io==0.8.5 diff --git a/pkg/distributor/distributor.go b/pkg/distributor/distributor.go index a79ca65b6c..92d29db029 100644 --- a/pkg/distributor/distributor.go +++ b/pkg/distributor/distributor.go @@ -226,7 +226,7 @@ func (d *Distributor) PushParsed(ctx context.Context, req *distributormodel.Push } } - haveRawPprof := req.RawProfileType == "" + haveRawPprof := req.RawProfileType == distributormodel.RawProfileTypePPROF d.bytesReceivedTotalStats.Inc(int64(req.RawProfileSize)) d.bytesReceivedStats.Record(float64(req.RawProfileSize)) if !haveRawPprof { @@ -234,7 +234,7 @@ func (d *Distributor) PushParsed(ctx context.Context, req *distributormodel.Push // compressed size per profile type as all profile types are compressed once together. So we can not count // compressed bytes per profile type. Instead we count compressed bytes per profile. profName := req.RawProfileType // use "jfr" as profile name - d.metrics.receivedCompressedBytes.WithLabelValues(profName, tenantID).Observe(float64(req.RawProfileSize)) + d.metrics.receivedCompressedBytes.WithLabelValues(string(profName), tenantID).Observe(float64(req.RawProfileSize)) } for _, series := range req.Series { diff --git a/pkg/distributor/model/push.go b/pkg/distributor/model/push.go index dd5901f5bf..d7dba447b5 100644 --- a/pkg/distributor/model/push.go +++ b/pkg/distributor/model/push.go @@ -5,9 +5,14 @@ import ( "github.com/grafana/pyroscope/pkg/pprof" ) +type RawProfileType string + +const RawProfileTypePPROF = RawProfileType("pprof") +const RawProfileTypeJFR = RawProfileType("jfr") + type PushRequest struct { RawProfileSize int - RawProfileType string // should be set if not pprof, eg jfr + RawProfileType RawProfileType Series []*ProfileSeries } diff --git a/pkg/ingester/pyroscope/ingest_handler.go b/pkg/ingester/pyroscope/ingest_handler.go index 741c8c5f02..121cfcf028 100644 --- a/pkg/ingester/pyroscope/ingest_handler.go +++ b/pkg/ingester/pyroscope/ingest_handler.go @@ -172,8 +172,6 @@ func (h ingestHandler) ingestInputFromRequest(r *http.Request) (*ingestion.Inges input.Profile = &pprof.RawProfile{ FormDataContentType: contentType, RawData: b, - StreamingParser: true, - PoolStreamingParser: true, } } diff --git a/pkg/ingester/pyroscope/ingest_handler_test.go b/pkg/ingester/pyroscope/ingest_handler_test.go index 0bf4bf93cf..9b1ad3ed2d 100644 --- a/pkg/ingester/pyroscope/ingest_handler_test.go +++ b/pkg/ingester/pyroscope/ingest_handler_test.go @@ -11,28 +11,28 @@ import ( "strings" "testing" - "github.com/grafana/pyroscope/pkg/pprof" - - "github.com/grafana/pyroscope/pkg/distributor/model" - "github.com/bufbuild/connect-go" "github.com/go-kit/log" "github.com/prometheus/prometheus/model/labels" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/exp/slices" + pprof2 "github.com/grafana/pyroscope/pkg/og/convert/pprof" + profilev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1" + pushv1 "github.com/grafana/pyroscope/api/gen/proto/go/push/v1" v1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" - + "github.com/grafana/pyroscope/pkg/distributor/model" phlaremodel "github.com/grafana/pyroscope/pkg/model" - - pushv1 "github.com/grafana/pyroscope/api/gen/proto/go/push/v1" "github.com/grafana/pyroscope/pkg/og/convert/pprof/bench" + "github.com/grafana/pyroscope/pkg/pprof" ) type flatProfileSeries struct { - Labels []*v1.LabelPair - Profile *profilev1.Profile + Labels []*v1.LabelPair + Profile *profilev1.Profile + RawProfile []byte } type MockPushService struct { @@ -46,8 +46,9 @@ func (m *MockPushService) PushParsed(ctx context.Context, req *model.PushRequest for _, series := range req.Series { for _, sample := range series.Samples { m.reqPprof = append(m.reqPprof, &flatProfileSeries{ - Labels: series.Labels, - Profile: sample.Profile.Profile, + Labels: series.Labels, + Profile: sample.Profile.Profile, + RawProfile: sample.RawProfile, }) } } @@ -124,7 +125,8 @@ func (m *MockPushService) CompareDump(file string) { } } -const testdataDir = "../../../pkg/og/convert/jfr/testdata" +const repoRoot = "../../../" +const testdataDirJFR = repoRoot + "pkg/og/convert/jfr/testdata" func TestIngestJFR(b *testing.T) { testdata := []struct { @@ -148,23 +150,24 @@ func TestIngestJFR(b *testing.T) { for _, jfr := range testdata { td := jfr b.Run(td.jfr, func(t *testing.T) { - src := testdataDir + "/" + td.jfr + src := testdataDirJFR + "/" + td.jfr jfr, err := bench.ReadGzipFile(src) require.NoError(t, err) var labels []byte if td.labels != "" { - labels, err = bench.ReadGzipFile(testdataDir + "/" + td.labels) + labels, err = bench.ReadGzipFile(testdataDirJFR + "/" + td.labels) } require.NoError(t, err) svc := &MockPushService{Keep: true, T: t} h := NewPyroscopeIngestHandler(svc, l) res := httptest.NewRecorder() - body, ct := createRequestBody(t, jfr, labels) + body, ct := createJFRRequestBody(t, jfr, labels) req := httptest.NewRequest("POST", "/ingest?name=javaapp&format=jfr", bytes.NewReader(body)) req.Header.Set("Content-Type", ct) h.ServeHTTP(res, req) + assert.Equal(t, 200, res.Code) dst := strings.ReplaceAll(src, ".jfr.gz", ".pprof.json.gz") //svc.DumpTo(dst) @@ -178,7 +181,7 @@ func TestIngestJFR(b *testing.T) { func TestCorruptedJFR422(t *testing.T) { l := log.NewSyncLogger(log.NewLogfmtLogger(os.Stderr)) - src := testdataDir + "/" + "cortex-dev-01__kafka-0__cpu__0.jfr.gz" + src := testdataDirJFR + "/" + "cortex-dev-01__kafka-0__cpu__0.jfr.gz" jfr, err := bench.ReadGzipFile(src) require.NoError(t, err) @@ -188,7 +191,7 @@ func TestCorruptedJFR422(t *testing.T) { h := NewPyroscopeIngestHandler(svc, l) res := httptest.NewRecorder() - body, ct := createRequestBody(t, jfr, nil) + body, ct := createJFRRequestBody(t, jfr, nil) req := httptest.NewRequest("POST", "/ingest?name=javaapp&format=jfr", bytes.NewReader(body)) req.Header.Set("Content-Type", ct) @@ -197,7 +200,7 @@ func TestCorruptedJFR422(t *testing.T) { require.Equal(t, 422, res.Code) } -func createRequestBody(t *testing.T, jfr, labels []byte) ([]byte, string) { +func createJFRRequestBody(t *testing.T, jfr, labels []byte) ([]byte, string) { var b bytes.Buffer w := multipart.NewWriter(&b) jfrw, err := w.CreateFormFile("jfr", "jfr") @@ -232,7 +235,7 @@ func BenchmarkIngestJFR(b *testing.B) { for _, jfr := range jfrs { b.Run(jfr, func(b *testing.B) { - jfr, err := bench.ReadGzipFile(testdataDir + "/" + jfr) + jfr, err := bench.ReadGzipFile(testdataDirJFR + "/" + jfr) require.NoError(b, err) for i := 0; i < b.N; i++ { res := httptest.NewRecorder() @@ -244,3 +247,217 @@ func BenchmarkIngestJFR(b *testing.B) { } } + +func TestIngestPPROFFixtures(t *testing.T) { + testdata := []struct { + profile string + prevProfile string + sampleTypeConfig string + spyName string + + expectStatus int + expectMetric string + }{ + { + profile: repoRoot + "pkg/pprof/testdata/heap", + expectStatus: 200, + expectMetric: "memory", + }, + { + profile: repoRoot + "pkg/pprof/testdata/profile_java", + expectStatus: 200, + expectMetric: "process_cpu", + }, + { + profile: repoRoot + "pkg/og/convert/testdata/cpu.pprof", + expectStatus: 200, + expectMetric: "process_cpu", + }, + { + profile: repoRoot + "pkg/og/convert/testdata/cpu.pprof", + prevProfile: repoRoot + "pkg/og/convert/testdata/cpu.pprof", + expectStatus: 422, + }, + + { + profile: repoRoot + "pkg/og/convert/pprof/testdata/cpu.pb.gz", + prevProfile: "", + expectStatus: 200, + expectMetric: "process_cpu", + }, + { + profile: repoRoot + "pkg/og/convert/pprof/testdata/cpu-exemplars.pb.gz", + expectStatus: 200, + expectMetric: "process_cpu", + }, + { + profile: repoRoot + "pkg/og/convert/pprof/testdata/cpu-js.pb.gz", + expectStatus: 200, + expectMetric: "wall", + }, + { + profile: repoRoot + "pkg/og/convert/pprof/testdata/heap.pb", + expectStatus: 200, + expectMetric: "memory", + }, + { + profile: repoRoot + "pkg/og/convert/pprof/testdata/heap.pb.gz", + expectStatus: 200, + expectMetric: "memory", + }, + { + profile: repoRoot + "pkg/og/convert/pprof/testdata/heap-js.pprof", + expectStatus: 200, + expectMetric: "memory", + }, + { + profile: repoRoot + "pkg/og/convert/pprof/testdata/nodejs-heap.pb.gz", + expectStatus: 200, + expectMetric: "memory", + }, + { + profile: repoRoot + "pkg/og/convert/pprof/testdata/nodejs-wall.pb.gz", + expectStatus: 200, + expectMetric: "wall", + }, + { + profile: repoRoot + "pkg/og/convert/pprof/testdata/req_2.pprof", + sampleTypeConfig: repoRoot + "pkg/og/convert/pprof/testdata/req_2.st.json", + expectStatus: 200, + expectMetric: "goroutines", + }, + { + profile: repoRoot + "pkg/og/convert/pprof/testdata/req_3.pprof", + sampleTypeConfig: repoRoot + "pkg/og/convert/pprof/testdata/req_3.st.json", + expectStatus: 200, + expectMetric: "block", + }, + { + profile: repoRoot + "pkg/og/convert/pprof/testdata/req_4.pprof", + sampleTypeConfig: repoRoot + "pkg/og/convert/pprof/testdata/req_4.st.json", + expectStatus: 200, + expectMetric: "mutex", + }, + { + profile: repoRoot + "pkg/og/convert/pprof/testdata/req_5.pprof", + sampleTypeConfig: repoRoot + "pkg/og/convert/pprof/testdata/req_5.st.json", + expectStatus: 200, + expectMetric: "memory", + }, + { + // this one have milliseconds in Profile.TimeNanos + // https://github.com/grafana/pyroscope/pull/2376/files + profile: repoRoot + "pkg/og/convert/pprof/testdata/pyspy-1.pb.gz", + expectStatus: 200, + expectMetric: "process_cpu", + spyName: pprof2.SpyNameForFunctionNameRewrite(), + }, + + //todo add pprof from dotnet + + } + for _, testdatum := range testdata { + t.Run(testdatum.profile, func(t *testing.T) { + + var ( + profile, prevProfile, sampleTypeConfig []byte + err error + ) + profile, err = os.ReadFile(testdatum.profile) + require.NoError(t, err) + if testdatum.prevProfile != "" { + prevProfile, err = os.ReadFile(testdatum.prevProfile) + require.NoError(t, err) + } + if testdatum.sampleTypeConfig != "" { + sampleTypeConfig, err = os.ReadFile(testdatum.sampleTypeConfig) + require.NoError(t, err) + } + + bs, ct := createPProfRequest(t, profile, prevProfile, sampleTypeConfig) + + svc := &MockPushService{Keep: true, T: t} + h := NewPyroscopeIngestHandler(svc, log.NewSyncLogger(log.NewLogfmtLogger(os.Stderr))) + + res := httptest.NewRecorder() + spyName := "foo239" + if testdatum.spyName != "" { + spyName = testdatum.spyName + } + req := httptest.NewRequest("POST", "/ingest?name=pprof.test{qwe=asd}&spyName="+spyName, bytes.NewReader(bs)) + req.Header.Set("Content-Type", ct) + h.ServeHTTP(res, req) + assert.Equal(t, testdatum.expectStatus, res.Code) + + if testdatum.expectStatus == 200 { + require.Equal(t, 1, len(svc.reqPprof)) + actualReq := svc.reqPprof[0] + ls := phlaremodel.Labels(actualReq.Labels) + require.Equal(t, testdatum.expectMetric, ls.Get(labels.MetricName)) + require.Equal(t, "asd", ls.Get("qwe")) + require.Equal(t, spyName, ls.Get("pyroscope_spy")) + require.Equal(t, "pprof.test", ls.Get("service_name")) + require.Equal(t, "false", ls.Get("__delta__")) + require.Equal(t, profile, actualReq.RawProfile) + + if testdatum.spyName != pprof2.SpyNameForFunctionNameRewrite() { + comparePPROF(t, actualReq.Profile, actualReq.RawProfile) + } + } else { + assert.Equal(t, 0, len(svc.reqPprof)) + } + }) + } +} + +func comparePPROF(t *testing.T, actual *profilev1.Profile, profile2 []byte) { + expected, err := pprof.RawFromBytes(profile2) + require.NoError(t, err) + defer expected.Close() + + require.Equal(t, len(expected.SampleType), len(actual.SampleType)) + for i := range actual.SampleType { + require.Equal(t, expected.StringTable[expected.SampleType[i].Type], actual.StringTable[actual.SampleType[i].Type]) + require.Equal(t, expected.StringTable[expected.SampleType[i].Unit], actual.StringTable[actual.SampleType[i].Unit]) + + actualCollapsed := bench.StackCollapseProto(actual, i, 1.0) + expectedCollapsed := bench.StackCollapseProto(expected.Profile, i, 1.0) + require.Equal(t, expectedCollapsed, actualCollapsed) + } +} + +func createPProfRequest(t *testing.T, profile, prevProfile, sampleTypeConfig []byte) ([]byte, string) { + const ( + formFieldProfile = "profile" + formFieldPreviousProfile = "prev_profile" + formFieldSampleTypeConfig = "sample_type_config" + ) + + var b bytes.Buffer + w := multipart.NewWriter(&b) + + profileW, err := w.CreateFormFile(formFieldProfile, "not used") + require.NoError(t, err) + _, err = profileW.Write(profile) + require.NoError(t, err) + + if sampleTypeConfig != nil { + + sampleTypeConfigW, err := w.CreateFormFile(formFieldSampleTypeConfig, "not used") + require.NoError(t, err) + _, err = sampleTypeConfigW.Write(sampleTypeConfig) + require.NoError(t, err) + } + + if prevProfile != nil { + prevProfileW, err := w.CreateFormFile(formFieldPreviousProfile, "not used") + require.NoError(t, err) + _, err = prevProfileW.Write(prevProfile) + require.NoError(t, err) + } + err = w.Close() + require.NoError(t, err) + + return b.Bytes(), w.FormDataContentType() + +} diff --git a/pkg/og/convert/jfr/profile.go b/pkg/og/convert/jfr/profile.go index f9cafe0f6a..6b994fe4c0 100644 --- a/pkg/og/convert/jfr/profile.go +++ b/pkg/og/convert/jfr/profile.go @@ -5,11 +5,12 @@ import ( "compress/gzip" "context" "fmt" - phlaremodel "github.com/grafana/pyroscope/pkg/distributor/model" "io" "mime/multipart" "strings" + distributormodel "github.com/grafana/pyroscope/pkg/distributor/model" + "github.com/golang/protobuf/proto" "github.com/grafana/pyroscope/pkg/og/ingestion" "github.com/grafana/pyroscope/pkg/og/storage" @@ -23,7 +24,7 @@ type RawProfile struct { func (p *RawProfile) Bytes() ([]byte, error) { return p.RawData, nil } -func (p *RawProfile) ParseToPprof(ctx context.Context, md ingestion.Metadata) (*phlaremodel.PushRequest, error) { +func (p *RawProfile) ParseToPprof(_ context.Context, md ingestion.Metadata) (*distributormodel.PushRequest, error) { input := storage.PutInput{ StartTime: md.StartTime, EndTime: md.EndTime, @@ -49,7 +50,7 @@ func (p *RawProfile) ParseToPprof(ctx context.Context, md ingestion.Metadata) (* return nil, err } res.RawProfileSize = rawSize - res.RawProfileType = "jfr" + res.RawProfileType = distributormodel.RawProfileTypeJFR return res, err } diff --git a/pkg/og/convert/parser.go b/pkg/og/convert/parser.go index c4dc849ddf..9793c26bdd 100644 --- a/pkg/og/convert/parser.go +++ b/pkg/og/convert/parser.go @@ -3,13 +3,9 @@ package convert import ( "bufio" "bytes" - "compress/gzip" - "fmt" "io" "strconv" - "google.golang.org/protobuf/proto" - "github.com/grafana/pyroscope/pkg/og/storage/tree" ) @@ -28,38 +24,6 @@ func ParseTreeNoDict(r io.Reader, cb func(name []byte, val int)) error { var gzipMagicBytes = []byte{0x1f, 0x8b} -// format is pprof. See https://github.com/google/pprof/blob/master/proto/profile.proto -func ParsePprof(r io.Reader) (*tree.Profile, error) { - // this allows us to support both gzipped and not gzipped pprof - // TODO: this might be allocating too much extra memory, maybe optimize later - bufioReader := bufio.NewReader(r) - header, err := bufioReader.Peek(2) - if err != nil { - return nil, fmt.Errorf("unable to read profile file header: %w", err) - } - - if header[0] == gzipMagicBytes[0] && header[1] == gzipMagicBytes[1] { - r, err = gzip.NewReader(bufioReader) - if err != nil { - return nil, err - } - } else { - r = bufioReader - } - - b, err := io.ReadAll(r) - if err != nil { - return nil, err - } - - profile := &tree.Profile{} - if err := proto.Unmarshal(b, profile); err != nil { - return nil, err - } - - return profile, nil -} - // format: // stack-trace-foo 1 // stack-trace-bar 2 diff --git a/pkg/og/convert/parser_test.go b/pkg/og/convert/parser_test.go index 56fb55aeee..c8ad7a6778 100644 --- a/pkg/og/convert/parser_test.go +++ b/pkg/og/convert/parser_test.go @@ -2,36 +2,13 @@ package convert import ( "bytes" - "compress/gzip" "fmt" - "os" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/grafana/pyroscope/pkg/og/agent/spy" ) var _ = Describe("convert", func() { - Describe("ParsePprof", func() { - It("parses data correctly", func() { - result := []string{} - - b, err := os.ReadFile("testdata/cpu.pprof") - Expect(err).ToNot(HaveOccurred()) - r := bytes.NewReader(b) - g, err := gzip.NewReader(r) - Expect(err).ToNot(HaveOccurred()) - p, err := ParsePprof(g) - Expect(err).ToNot(HaveOccurred()) - - p.Get("samples", func(labels *spy.Labels, name []byte, val int) error { - result = append(result, fmt.Sprintf("%s %d", name, val)) - return nil - }) - Expect(result).To(ContainElement("runtime.main;main.work 1")) - }) - }) - Describe("ParseGroups", func() { It("parses data correctly", func() { r := bytes.NewReader([]byte("foo;bar 10\nfoo;baz 20\n")) diff --git a/pkg/og/convert/pprof/bench/parser_test.go b/pkg/og/convert/pprof/bench/parser_test.go deleted file mode 100644 index 11bbf2ac65..0000000000 --- a/pkg/og/convert/pprof/bench/parser_test.go +++ /dev/null @@ -1,780 +0,0 @@ -package bench - -import ( - "bufio" - "bytes" - "compress/gzip" - "context" - "encoding/json" - "fmt" - "io" - "io/fs" - "io/ioutil" - "mime/multipart" - "net/http" - "os" - "strings" - "testing" - "time" - - "github.com/google/pprof/profile" - "github.com/grafana/pyroscope/pkg/og/convert/pprof" - "github.com/grafana/pyroscope/pkg/og/convert/pprof/streaming" - "github.com/grafana/pyroscope/pkg/og/ingestion" - "github.com/grafana/pyroscope/pkg/og/stackbuilder" - "github.com/grafana/pyroscope/pkg/og/storage/metadata" - "github.com/grafana/pyroscope/pkg/og/storage/segment" - "github.com/grafana/pyroscope/pkg/og/util/form" - "golang.org/x/exp/slices" - - "github.com/grafana/pyroscope/pkg/og/storage/tree" -) - -const benchmarkCorpus = "../../../../../../pprof-testdata" -const compareCorpus = "../../../../../../pprof-testdata" - -const pprofSmall = benchmarkCorpus + - "/2022-10-08T00:44:10Z-55903298-d296-4730-a28d-9dcc7c5e25d6.txt" -const pprofBig = benchmarkCorpus + - "/2022-10-08T00:07:00Z-911c824f-a086-430c-99d7-315a53b58095.txt" - -// GOEXPERIMENT=arenas go test -v -test.count=10 -test.run=none -bench=".*Streaming.*" ./pkg/convert/pprof/bench -var putter = &MockPutter{} - -const benchWithoutGzip = true -const benchmarkCorpusSize = 5 - -var compareCorpusData = readCorpus(compareCorpus, benchWithoutGzip) - -func TestCompare(t *testing.T) { - if len(compareCorpusData) == 0 { - t.Skip("empty corpus") - return - } - for _, testType := range streamingTestTypes { - t.Run(fmt.Sprintf("TestCompare_pool_%v_arenas_%v", testType.pool, testType.arenas), func(t *testing.T) { - for _, c := range compareCorpusData { - testCompareOne(t, c, testType) - } - }) - } -} - -func TestCompareWriteBatch(t *testing.T) { - if len(compareCorpusData) == 0 { - t.Skip("empty corpus") - return - } - for _, c := range compareCorpusData { - //cur, _ := profile.Parse(bytes.NewReader(c.profile)) - //if c.prev != nil { - // prev, _ := profile.Parse(bytes.NewReader(c.prev)) - // os.WriteFile("p1", []byte(dumpPProfProfile(prev)), 0666) - //} - //os.WriteFile("p2", []byte(dumpPProfProfile(cur)), 0666) - testCompareWriteBatchOne(t, c) - } -} - -func dumpPProfProfile(p *profile.Profile) string { - var ls []string - for _, sample := range p.Sample { - s := dumpPProfStack(sample, true) - ls = append(ls, s) - } - slices.Sort(ls) - return strings.Join(ls, "\n") -} - -func dumpPProfStack(sample *profile.Sample, v bool) string { - sb := strings.Builder{} - for i := len(sample.Location) - 1; i >= 0; i-- { - location := sample.Location[i] - for j := len(location.Line) - 1; j >= 0; j-- { - line := location.Line[j] - - sb.WriteString(";") - //sb.WriteString(fmt.Sprintf("[%x %x] ", location.ID, location.Address)) - - sb.WriteString(line.Function.Name) - } - } - if v { - sb.WriteString(" ") - sb.WriteString(fmt.Sprintf("%d", sample.Value[0])) - } - s := sb.String() - return s -} - -func TestIterateWithStackBuilder(t *testing.T) { - sb := newStackBuilder() - it := tree.New() - it.Insert([]byte(""), uint64(43)) - it.Insert([]byte("a"), uint64(42)) - it.Insert([]byte("a;b"), uint64(1)) - it.Insert([]byte("a;c"), uint64(2)) - it.Insert([]byte("a;d;e"), uint64(3)) - it.Insert([]byte("a;d;f"), uint64(4)) - - it.IterateWithStackBuilder(sb, func(stackID uint64, v uint64) { - sb.stackID2Val[stackID] = v - }) - sb.expectValue(t, 0, 43) - sb.expectValue(t, 1, 42) - sb.expectValue(t, 2, 1) - sb.expectValue(t, 3, 2) - sb.expectValue(t, 4, 3) - sb.expectValue(t, 5, 4) - sb.expectStack(t, 0, "") - sb.expectStack(t, 1, "a") - sb.expectStack(t, 2, "a;b") - sb.expectStack(t, 3, "a;c") - sb.expectStack(t, 4, "a;d;e") - sb.expectStack(t, 5, "a;d;f") -} - -func TestIterateWithStackBuilderEmpty(t *testing.T) { - it := tree.New() - sb := newStackBuilder() - it.IterateWithStackBuilder(sb, func(stackID uint64, v uint64) { - t.Fatal() - }) -} - -func newStackBuilder() *mockStackBuilder { - return &mockStackBuilder{ - stackID2Stack: make(map[uint64]string), - stackID2Val: make(map[uint64]uint64), - stackID2StackBytes: make(map[uint64][][]byte), - } -} - -func TestTreeIterationCorpus(t *testing.T) { - corpus := readCorpus(compareCorpus, benchWithoutGzip) - if len(corpus) == 0 { - t.Skip("empty corpus") - return - } - for _, c := range corpus { - key, _ := segment.ParseKey("foo.bar") - mock1 := &MockPutter{Keep: true} - profile1 := pprof.RawProfile{ - Profile: c.profile, - PreviousProfile: c.prev, - SampleTypeConfig: c.config, - StreamingParser: true, - PoolStreamingParser: true, - ArenasEnabled: false, - } - - err2 := profile1.Parse(context.TODO(), mock1, nil, ingestion.Metadata{Key: key, SpyName: c.spyname}) - if err2 != nil { - t.Fatal(err2) - } - for _, put := range mock1.Puts { - testIterateOne(t, put.ValTree) - } - } -} - -func BenchmarkSmallStreaming(b *testing.B) { - t := readCorpusItemFile(pprofSmall, benchWithoutGzip) - for _, testType := range streamingTestTypes { - b.Run(fmt.Sprintf("BenchmarkSmallStreaming_pool_%v_arenas_%v", testType.pool, testType.arenas), func(b *testing.B) { - benchmarkStreamingOne(b, t, testType) - }) - } -} - -func BenchmarkBigStreaming(b *testing.B) { - t := readCorpusItemFile(pprofBig, benchWithoutGzip) - for _, testType := range streamingTestTypes { - b.Run(fmt.Sprintf("BenchmarkBigStreaming_pool_%v_arenas_%v", testType.pool, testType.arenas), func(b *testing.B) { - benchmarkStreamingOne(b, t, testType) - }) - } -} - -func BenchmarkSmallUnmarshal(b *testing.B) { - t := readCorpusItemFile(pprofSmall, benchWithoutGzip) - now := time.Now() - for i := 0; i < b.N; i++ { - parser := pprof.NewParser(pprof.ParserConfig{SampleTypes: t.config, Putter: putter}) - err := parser.ParsePprof(context.TODO(), now, now, t.profile, false) - if err != nil { - b.Fatal(err) - } - } -} - -func BenchmarkBigUnmarshal(b *testing.B) { - t := readCorpusItemFile(pprofBig, benchWithoutGzip) - now := time.Now() - for i := 0; i < b.N; i++ { - parser := pprof.NewParser(pprof.ParserConfig{SampleTypes: t.config, Putter: putter}) - err := parser.ParsePprof(context.TODO(), now, now, t.profile, false) - if err != nil { - b.Fatal(err) - } - } -} - -func BenchmarkCorpus(b *testing.B) { - corpus := readCorpus(benchmarkCorpus, benchWithoutGzip) - n := benchmarkCorpusSize - for _, testType := range streamingTestTypes { - for i := 0; i < n; i++ { - j := i - b.Run(fmt.Sprintf("BenchmarkCorpus_%d_pool_%v_arena_%v", j, testType.pool, testType.arenas), - func(b *testing.B) { - t := corpus[j] - benchmarkStreamingOne(b, t, testType) - }) - } - } -} - -func TestBugReusingSlices(t *testing.T) { - profiles := readCorpus(benchmarkCorpus+"/bugs/bug1_slice_reuse", false) - if len(profiles) == 0 { - t.Skip() - return - } - for _, p := range profiles { - parse(t, p, streamingTestType{pool: true, arenas: false}) - } -} - -func parse(t *testing.T, c *testcase, typ streamingTestType) { - mock := &MockPutter{Keep: true} - key, _ := segment.ParseKey("foo.bar") - p := pprof.RawProfile{ - Profile: c.profile, - PreviousProfile: c.prev, - SampleTypeConfig: c.config, - StreamingParser: true, - PoolStreamingParser: typ.pool, - ArenasEnabled: typ.arenas, - } - err := p.Parse(context.TODO(), mock, nil, ingestion.Metadata{Key: key, SpyName: c.spyname}) - if err != nil { - t.Fatal(err) - } -} - -func benchmarkStreamingOne(b *testing.B, t *testcase, testType streamingTestType) { - now := time.Now() - for i := 0; i < b.N; i++ { - config := t.config - pConfig := streaming.ParserConfig{SampleTypes: config, Putter: putter, ArenasEnabled: testType.arenas} - var parser *streaming.VTStreamingParser - if testType.pool { - parser = streaming.VTStreamingParserFromPool(pConfig) - } else { - parser = streaming.NewStreamingParser(pConfig) - } - err := parser.ParsePprof(context.TODO(), now, now, t.profile, false) - if err != nil { - b.Fatal(err) - } - if testType.pool { - parser.ResetCache() - parser.ReturnToPool() - } - if testType.arenas { - parser.FreeArena() - } - } -} - -var streamingTestTypes = []streamingTestType{ - {pool: false, arenas: false}, - {pool: true, arenas: false}, - {pool: false, arenas: true}, -} - -type streamingTestType struct { - pool bool - arenas bool -} - -type testcase struct { - profile, prev []byte - config map[string]*tree.SampleTypeConfig - fname string - spyname string -} - -func readCorpus(dir string, doDecompress bool) []*testcase { - files, err := ioutil.ReadDir(dir) - if err != nil { - fmt.Println(err) - return nil - } - var res []*testcase - for _, file := range files { - if strings.HasSuffix(file.Name(), ".txt") { - res = append(res, readCorpusItem(dir, file, doDecompress)) - } - } - return res -} - -func readCorpusItem(dir string, file fs.FileInfo, doDecompress bool) *testcase { - fname := dir + "/" + file.Name() - return readCorpusItemFile(fname, doDecompress) -} - -func readCorpusItemFile(fname string, doDecompress bool) *testcase { - bs, err := ioutil.ReadFile(fname) - if err != nil { - panic(err) - } - r, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(bs))) - if err != nil { - panic(err) - } - contentType := r.Header.Get("Content-Type") - rawData, _ := ioutil.ReadAll(r.Body) - decompress := func(b []byte) []byte { - if len(b) < 2 { - return b - } - if b[0] == 0x1f && b[1] == 0x8b { - gzipr, err := gzip.NewReader(bytes.NewReader(b)) - if err != nil { - panic(err) - } - defer gzipr.Close() - var buf bytes.Buffer - if _, err = io.Copy(&buf, gzipr); err != nil { - panic(err) - } - return buf.Bytes() - } - return b - } - - if contentType == "binary/octet-stream" { - return &testcase{ - profile: decompress(rawData), - config: tree.DefaultSampleTypeMapping, - fname: fname, - } - } - boundary, err := form.ParseBoundary(contentType) - if err != nil { - panic(err) - } - - f, err := multipart.NewReader(bytes.NewReader(rawData), boundary).ReadForm(32 << 20) - if err != nil { - panic(err) - } - const ( - formFieldProfile = "profile" - formFieldPreviousProfile = "prev_profile" - formFieldSampleTypeConfig = "sample_type_config" - ) - - Profile, err := form.ReadField(f, formFieldProfile) - if err != nil { - panic(err) - } - PreviousProfile, err := form.ReadField(f, formFieldPreviousProfile) - if err != nil { - panic(err) - } - - stBS, err := form.ReadField(f, formFieldSampleTypeConfig) - if err != nil { - panic(err) - } - var config map[string]*tree.SampleTypeConfig - if stBS != nil { - if err = json.Unmarshal(stBS, &config); err != nil { - panic(err) - } - } else { - config = tree.DefaultSampleTypeMapping - } - _ = Profile - _ = PreviousProfile - - if doDecompress { - Profile = decompress(Profile) - PreviousProfile = decompress(PreviousProfile) - } - elem := &testcase{Profile, PreviousProfile, config, fname, "gospy"} - return elem -} - -func testCompareOne(t *testing.T, c *testcase, typ streamingTestType) { - err := pprof.DecodePool(bytes.NewReader(c.profile), func(profile *tree.Profile) error { - return nil - }) - fmt.Println(c.fname) - key, _ := segment.ParseKey("foo.bar") - mock1 := &MockPutter{Keep: true} - profile1 := pprof.RawProfile{ - Profile: c.profile, - PreviousProfile: c.prev, - SampleTypeConfig: c.config, - StreamingParser: true, - PoolStreamingParser: typ.pool, - ArenasEnabled: typ.arenas, - } - - err2 := profile1.Parse(context.TODO(), mock1, nil, ingestion.Metadata{Key: key, SpyName: c.spyname}) - if err2 != nil { - t.Fatal(err2) - } - - mock2 := &MockPutter{Keep: true} - profile2 := pprof.RawProfile{ - Profile: c.profile, - PreviousProfile: c.prev, - SampleTypeConfig: c.config, - } - err = profile2.Parse(context.TODO(), mock2, nil, ingestion.Metadata{Key: key, SpyName: c.spyname}) - if err != nil { - t.Fatal(err) - } - - if len(mock1.Puts) != len(mock2.Puts) { - t.Fatalf("put mismatch %d %d", len(mock1.Puts), len(mock2.Puts)) - } - mock1.Sort() - mock2.Sort() - - writeGlod := false - checkGold := true - trees := map[string]string{} - gold := c.fname + ".gold.json" - if checkGold { - goldBS, err := os.ReadFile(gold) - if err != nil { - panic(err) - } - err = json.Unmarshal(goldBS, &trees) - if err != nil { - panic(err) - } - } - for i := range mock1.Puts { - p1 := mock1.Puts[i] - p2 := mock2.Puts[i] - k1 := p1.Key - k2 := p2.Key - if k1 != k2 { - t.Fatalf("key mismatch %s %s", k1, k2) - } - it := p1.Val - jit := mock2.Puts[i].Val - - if it != jit { - fmt.Println(key.SegmentKey()) - t.Fatalf("mismatch\n --- actual:\n"+ - "%s\n"+ - " --- exopected\n"+ - "%s\n====", it, jit) - } - if checkGold { - git := trees[k1] - if it != git { - t.Fatalf("mismatch ---\n"+ - "%s\n"+ - "---\n"+ - "%s\n====", it, git) - } - } - fmt.Printf("ok %s %d \n", k1, len(it)) - if p1.StartTime != p2.StartTime { - t.Fatal() - } - if p1.EndTime != p2.EndTime { - t.Fatal() - } - if p1.Units != p2.Units { - t.Fatal() - } - if p1.AggregationType != p2.AggregationType { - t.Fatal() - } - if p1.SpyName != p2.SpyName { - t.Fatal() - } - if p1.SampleRate != p2.SampleRate { - t.Fatal() - } - if writeGlod { - trees[k1] = it - } - } - if writeGlod { - marshal, err := json.Marshal(trees) - if err != nil { - panic(err) - } - err = os.WriteFile(gold, marshal, 0666) - if err != nil { - panic(err) - } - } -} - -func testCompareWriteBatchOne(t *testing.T, c *testcase) { - fmt.Println(c.fname) - key, _ := segment.ParseKey("foo.bar") - profile1 := pprof.RawProfile{ - Profile: c.profile, - PreviousProfile: c.prev, - SampleTypeConfig: c.config, - } - md := ingestion.Metadata{Key: key, SpyName: c.spyname} - wbf := &mockWriteBatchFactory{} - err := profile1.ParseWithWriteBatch(context.TODO(), wbf, md) - if err != nil { - t.Fatal(err) - } - - mock2 := &MockPutter{Keep: true} - profile2 := &pprof.RawProfile{ - Profile: c.profile, - PreviousProfile: c.prev, - SampleTypeConfig: c.config, - } - mergeCumulative(profile2) - - err = profile2.Parse(context.TODO(), mock2, nil, md) - if err != nil { - t.Fatal(err) - } - - for _, put := range mock2.Puts { - expectedCollapsed := put.Val - appenderCollapsed := "" - var found []*mockSamplesAppender - for _, batch := range wbf.wbs { - for _, appender := range batch.appenders { - labels := make(map[string]string) - labels["__name__"] = batch.appName - for _, label := range appender.labels { - labels[label.Key] = label.Value - } - k := segment.NewKey(labels) - if k.SegmentKey() == put.Key { - found = append(found, appender) - } - } - } - if len(found) != 1 { - if expectedCollapsed == "" { - continue - } - t.Fatalf("not found %s", put.Key) - } - appenderCollapsed = found[0].tree.String() - - if appenderCollapsed != expectedCollapsed { - os.WriteFile("p3", []byte(expectedCollapsed), 0666) - os.WriteFile("p4", []byte(appenderCollapsed), 0666) - t.Fatalf("%s: expected\n%s\ngot\n%s\n failed file:%s\n", put.Key, expectedCollapsed, appenderCollapsed, c.fname) - } - } -} - -func mergeCumulative(profile2 *pprof.RawProfile) { - if profile2.PreviousProfile != nil { - p1, _ := profile.Parse(bytes.NewReader(profile2.PreviousProfile)) - p2, _ := profile.Parse(bytes.NewReader(profile2.Profile)) - prev := []map[string]int64{ - make(map[string]int64), - make(map[string]int64), - } - for _, sample := range p1.Sample { - s := dumpPProfStack(sample, false) - prev[0][s] += sample.Value[0] - prev[1][s] += sample.Value[1] - } - dec := func(s string, i int, v int64) int64 { - prevV := prev[i][s] - if v > prevV { - prev[i][s] = 0 - return v - prevV - } - prev[i][s] = prevV - v - return 0 - } - for _, sample := range p2.Sample { - s := dumpPProfStack(sample, false) - sample.Value[0] = dec(s, 0, sample.Value[0]) - sample.Value[1] = dec(s, 1, sample.Value[1]) - } - - merged := p2.Compact() - - bs := bytes.NewBuffer(nil) - merged.Write(bs) - - profile2.PreviousProfile = nil - profile2.Profile = bs.Bytes() - - sampleTypeConfig := make(map[string]*tree.SampleTypeConfig) - for k, v := range profile2.SampleTypeConfig { - vv := *v - vv.Cumulative = false - sampleTypeConfig[k] = &vv - } - profile2.SampleTypeConfig = sampleTypeConfig - //os.WriteFile("merged", []byte(dumpPProfProfile(merged)), 0666) - } -} - -func testIterateOne(t *testing.T, pt *tree.Tree) { - sb := newStackBuilder() - var lines []string - pt.IterateWithStackBuilder(sb, func(stackID uint64, val uint64) { - lines = append(lines, fmt.Sprintf("%s %d", sb.stackID2Stack[stackID], val)) - }) - s := pt.String() - s = pt.String() - var expectedLines []string - if s != "" { - expectedLines = strings.Split(strings.Trim(s, "\n"), "\n") - } - slices.Sort(lines) - slices.Sort(expectedLines) - if !slices.Equal(lines, expectedLines) { - expected := strings.Join(expectedLines, "\n") - got := strings.Join(lines, "\n") - t.Fatalf("expected %v got\n%v", expected, got) - } -} - -type mockStackBuilder struct { - ss [][]byte - - stackID2Stack map[uint64]string - stackID2StackBytes map[uint64][][]byte - stackID2Val map[uint64]uint64 -} - -func (s *mockStackBuilder) Push(frame []byte) { - s.ss = append(s.ss, frame) -} - -func (s *mockStackBuilder) Pop() { - s.ss = s.ss[0 : len(s.ss)-1] -} - -func (s *mockStackBuilder) Build() (stackID uint64) { - res := "" - for _, bs := range s.ss { - if len(res) != 0 { - res += ";" - } - res += string(bs) - } - id := uint64(len(s.stackID2Stack)) - s.stackID2Stack[id] = res - - bs := make([][]byte, 0, len(s.ss)) - for _, frame := range s.ss { - bs = append(bs, append([]byte{}, frame...)) - } - s.stackID2StackBytes[id] = bs - return id -} - -func (s *mockStackBuilder) Reset() { - s.ss = s.ss[:0] -} - -func (s *mockStackBuilder) expectValue(t *testing.T, stackID, expected uint64) { - if s.stackID2Val[stackID] != expected { - t.Fatalf("expected at %d %d got %d", stackID, expected, s.stackID2Val[stackID]) - } -} -func (s *mockStackBuilder) expectStack(t *testing.T, stackID uint64, expected string) { - if s.stackID2Stack[stackID] != expected { - t.Fatalf("expected at %d %s got %s", stackID, expected, s.stackID2Stack[stackID]) - } -} - -type mockWriteBatchFactory struct { - wbs map[string]*mockWriteBatch -} - -func (m *mockWriteBatchFactory) NewWriteBatch(appName string, _ metadata.Metadata) (stackbuilder.WriteBatch, error) { - if m.wbs == nil { - m.wbs = make(map[string]*mockWriteBatch) - } - if m.wbs[appName] != nil { - panic("already exists") - } - wb := &mockWriteBatch{ - appName: appName, - sb: newStackBuilder(), - appenders: make(map[string]*mockSamplesAppender), - } - m.wbs[appName] = wb - return wb, nil -} - -type mockWriteBatch struct { - appName string - sb *mockStackBuilder - appenders map[string]*mockSamplesAppender -} - -func (m *mockWriteBatch) StackBuilder() tree.StackBuilder { - return m.sb -} - -func (m *mockWriteBatch) SamplesAppender(startTime, endTime int64, labels stackbuilder.Labels) stackbuilder.SamplesAppender { - sLabels, _ := json.Marshal(labels) - k := fmt.Sprintf("%d-%d-%s", startTime, endTime, sLabels) - a := m.appenders[k] - if a != nil { - return a - } - a = &mockSamplesAppender{ - startTime: startTime, - endTime: endTime, - labels: labels, - sb: m.sb, - } - m.appenders[k] = a - return a -} - -func (*mockWriteBatch) Flush() { - -} - -type mockSamplesAppender struct { - startTime, endTime int64 - labels stackbuilder.Labels - stacks []stackIDToVal - tree *tree.Tree - sb *mockStackBuilder -} - -type stackIDToVal struct { - stackID uint64 - val uint64 -} - -func (m *mockSamplesAppender) Append(stackID, value uint64) { - m.stacks = append(m.stacks, stackIDToVal{stackID, value}) - stack := m.sb.stackID2StackBytes[stackID] - if stack == nil { - panic("not found") - } - if m.tree == nil { - m.tree = tree.New() - } - m.tree.InsertStack(stack, value) -} diff --git a/pkg/og/convert/pprof/bench/utils.go b/pkg/og/convert/pprof/bench/utils.go index 7b41991447..e8740e45fb 100644 --- a/pkg/og/convert/pprof/bench/utils.go +++ b/pkg/og/convert/pprof/bench/utils.go @@ -2,119 +2,15 @@ package bench import ( "compress/gzip" - "context" - "encoding/json" "fmt" "io" - "math/big" "os" - "sort" "strings" - "time" profilev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1" - "github.com/grafana/pyroscope/pkg/og/storage" - "github.com/grafana/pyroscope/pkg/og/storage/metadata" - "github.com/grafana/pyroscope/pkg/og/storage/tree" "golang.org/x/exp/slices" ) -type PutInputCopy struct { - Val string // tree serialized to collapsed format - Key string - - StartTime time.Time - EndTime time.Time - SpyName string - SampleRate uint32 - Units metadata.Units - AggregationType metadata.AggregationType - ValTree *tree.Tree -} - -type MockPutter struct { - Keep bool - Puts []PutInputCopy - - JsonDump bool - JsonCheck bool - Trees map[string]string -} - -func (m *MockPutter) Sort() { - sort.Slice(m.Puts, func(i, j int) bool { - return strings.Compare(m.Puts[i].Key, m.Puts[j].Key) < 0 - }) -} - -func (m *MockPutter) DumpJson(jsonFile string) error { - m.Sort() - m.Trees = make(map[string]string) - - for i := range m.Puts { - p1 := m.Puts[i] - k1 := p1.Key - it := p1.Val - - m.Trees[k1] = it - } - - marshal, err := json.Marshal(m.Trees) - if err != nil { - return err - } - return WriteGzipFile(jsonFile, marshal) - -} -func (m *MockPutter) CompareWithJson(jsonFile string) error { - m.Sort() - goldBS, err := ReadGzipFile(jsonFile) - if err != nil { - return err - } - m.Trees = make(map[string]string) - err = json.Unmarshal(goldBS, &m.Trees) - if err != nil { - return err - } - if len(m.Trees) != len(m.Puts) { - return fmt.Errorf("json mismatch %d %d", len(m.Trees), len(m.Puts)) - } - - for i := range m.Puts { - p1 := m.Puts[i] - k1 := p1.Key - it := p1.Val - - git := m.Trees[k1] - if it != git { - return fmt.Errorf("json mismatch %s %s", it, git) - } - fmt.Printf("%s len %d ok\n", k1, len(it)) - - } - - return nil - -} - -func (m *MockPutter) Put(_ context.Context, input *storage.PutInput) error { - if m.Keep { - m.Puts = append(m.Puts, PutInputCopy{ - Val: input.Val.String(), - ValTree: input.Val.Clone(big.NewRat(1, 1)), - Key: input.Key.SegmentKey(), - StartTime: input.StartTime, - EndTime: input.EndTime, - SpyName: input.SpyName, - SampleRate: input.SampleRate, - Units: input.Units, - AggregationType: input.AggregationType, - }) - } - return nil -} - func ReadGzipFile(f string) ([]byte, error) { fd, err := os.Open(f) if err != nil { @@ -141,7 +37,6 @@ func WriteGzipFile(f string, data []byte) error { return err } return g.Close() - } func StackCollapseProto(p *profilev1.Profile, valueIDX int, scale float64) []string { diff --git a/pkg/og/convert/pprof/decoder.go b/pkg/og/convert/pprof/decoder.go deleted file mode 100644 index e3d817ca77..0000000000 --- a/pkg/og/convert/pprof/decoder.go +++ /dev/null @@ -1,45 +0,0 @@ -package pprof - -import ( - "bufio" - "compress/gzip" - "fmt" - "github.com/grafana/pyroscope/pkg/og/convert/pprof/streaming" - "io" - - "github.com/grafana/pyroscope/pkg/og/storage/tree" -) - -func Decode(r io.Reader, p *tree.Profile) error { - br := bufio.NewReader(r) - header, err := br.Peek(2) - if err != nil { - return fmt.Errorf("failed to read pprof profile header: %w", err) - } - if header[0] == 0x1f && header[1] == 0x8b { - gzipr, err := gzip.NewReader(br) - if err != nil { - return fmt.Errorf("failed to create pprof profile zip reader: %w", err) - } - r = gzipr - defer gzipr.Close() - } else { - r = br - } - buf := streaming.PPROFBufPool.Get() - defer streaming.PPROFBufPool.Put(buf) - if _, err = io.Copy(buf, r); err != nil { - return err - } - return p.UnmarshalVT(buf.Bytes()) -} - -func DecodePool(r io.Reader, fn func(*tree.Profile) error) error { - p := tree.ProfileFromVTPool() - defer p.ReturnToVTPool() - p.Reset() - if err := Decode(r, p); err != nil { - return err - } - return fn(p) -} diff --git a/pkg/og/convert/pprof/format.go b/pkg/og/convert/pprof/format.go deleted file mode 100644 index 3645f3de06..0000000000 --- a/pkg/og/convert/pprof/format.go +++ /dev/null @@ -1,40 +0,0 @@ -package pprof - -import ( - "fmt" - "github.com/grafana/pyroscope/pkg/og/storage/tree" - "reflect" - "unsafe" -) - -type StackFrameFormatter interface { - format(x *tree.Profile, fn *tree.Function, line *tree.Line) []byte -} - -func unsafeStrToSlice(s string) []byte { - return (*[0x7fff0000]byte)(unsafe.Pointer((*reflect.StringHeader)(unsafe.Pointer(&s)).Data))[:len(s):len(s)] -} - -type UnsafeFunctionNameFormatter struct { -} - -func (UnsafeFunctionNameFormatter) format(x *tree.Profile, fn *tree.Function, _ *tree.Line) []byte { - return unsafeStrToSlice(x.StringTable[fn.Name]) -} - -type RbspyFormatter struct { -} - -func (RbspyFormatter) format(x *tree.Profile, fn *tree.Function, line *tree.Line) []byte { - return []byte(fmt.Sprintf("%s:%d - %s", - x.StringTable[fn.Filename], - line.Line, - x.StringTable[fn.Name])) -} - -func StackFrameFormatterForSpyName(spyName string) StackFrameFormatter { - if spyName == "rbspy" || spyName == "pyspy" { - return RbspyFormatter{} - } - return UnsafeFunctionNameFormatter{} -} diff --git a/pkg/og/convert/pprof/parser.go b/pkg/og/convert/pprof/parser.go deleted file mode 100644 index 75a51e57b5..0000000000 --- a/pkg/og/convert/pprof/parser.go +++ /dev/null @@ -1,244 +0,0 @@ -package pprof - -import ( - "bytes" - "context" - "fmt" - "time" - - "github.com/grafana/pyroscope/pkg/og/storage" - "github.com/grafana/pyroscope/pkg/og/storage/metadata" - "github.com/grafana/pyroscope/pkg/og/storage/segment" - "github.com/grafana/pyroscope/pkg/og/storage/tree" -) - -type ParserInterface interface { - ParsePprof(ctx context.Context, startTime, endTime time.Time, bs []byte, cumulativeOnly bool) error -} - -type Parser struct { - putter storage.Putter - spyName string - labels map[string]string - skipExemplars bool - sampleTypes map[string]*tree.SampleTypeConfig - stackFrameFormatter StackFrameFormatter - - cache tree.LabelsCache[tree.Tree] - sampleTypesFilter func(string) bool -} - -type ParserConfig struct { - Putter storage.Putter - SpyName string - Labels map[string]string - SkipExemplars bool - SampleTypes map[string]*tree.SampleTypeConfig - StackFrameFormatter StackFrameFormatter -} - -func NewParser(config ParserConfig) *Parser { - if config.StackFrameFormatter == nil { - config.StackFrameFormatter = &UnsafeFunctionNameFormatter{} - } - return &Parser{ - putter: config.Putter, - spyName: config.SpyName, - labels: config.Labels, - sampleTypes: config.SampleTypes, - skipExemplars: config.SkipExemplars, - stackFrameFormatter: config.StackFrameFormatter, - - cache: tree.NewLabelsCache[tree.Tree](tree.New), - sampleTypesFilter: filterKnownSamples(config.SampleTypes), - } -} - -func filterKnownSamples(sampleTypes map[string]*tree.SampleTypeConfig) func(string) bool { - return func(s string) bool { - _, ok := sampleTypes[s] - return ok - } -} - -func (p *Parser) Reset() { p.cache = tree.NewLabelsCache[tree.Tree](tree.New) } - -func (p *Parser) ParsePprof(ctx context.Context, startTime, endTime time.Time, bs []byte, cumulativeOnly bool) error { - b := bytes.NewReader(bs) - return DecodePool(b, func(profile *tree.Profile) error { - return p.Convert(ctx, startTime, endTime, profile, cumulativeOnly) - }) -} - -func (p *Parser) Convert(ctx context.Context, startTime, endTime time.Time, profile *tree.Profile, cumulativeOnly bool) error { - return p.iterate(profile, cumulativeOnly, func(vt *tree.ValueType, l tree.Labels, t *tree.Tree) (keep bool, err error) { - if vt.Type >= int64(len(profile.StringTable)) { - return false, fmt.Errorf("sample value type is invalid: %d", vt.Type) - } - sampleType := profile.StringTable[vt.Type] - sampleTypeConfig, ok := p.sampleTypes[sampleType] - if !ok { - return false, fmt.Errorf("sample value type is unknown") - } - pi := storage.PutInput{ - StartTime: startTime, - EndTime: endTime, - SpyName: p.spyName, - Val: t, - } - // Cumulative profiles require two consecutive samples, - // therefore we have to cache this trie. - if sampleTypeConfig.Cumulative { - prev, found := p.load(vt.Type, l) - if !found { - // Keep the current entry in cache. - return true, nil - } - // Take diff with the previous tree. - // The result is written to prev, t is not changed. - pi.Val = prev.Diff(t) - } - pi.AggregationType = sampleTypeConfig.Aggregation - if sampleTypeConfig.Sampled { - pi.SampleRate = sampleRate(profile) - } - if sampleTypeConfig.DisplayName != "" { - sampleType = sampleTypeConfig.DisplayName - } - if sampleTypeConfig.Units != "" { - pi.Units = sampleTypeConfig.Units - } else { - // TODO(petethepig): this conversion is questionable - pi.Units = metadata.Units(profile.StringTable[vt.Unit]) - } - pi.Key = p.buildName(sampleType, profile.ResolveLabels(l)) - err = p.putter.Put(ctx, &pi) - return sampleTypeConfig.Cumulative, err - }) -} - -func sampleRate(p *tree.Profile) uint32 { - if p.Period <= 0 || p.PeriodType == nil { - return 0 - } - sampleUnit := time.Nanosecond - switch p.StringTable[p.PeriodType.Unit] { - case "microseconds": - sampleUnit = time.Microsecond - case "milliseconds": - sampleUnit = time.Millisecond - case "seconds": - sampleUnit = time.Second - } - return uint32(time.Second / (sampleUnit * time.Duration(p.Period))) -} - -func (p *Parser) buildName(sampleTypeName string, labels map[string]string) *segment.Key { - for k, v := range p.labels { - labels[k] = v - } - labels["__name__"] += "." + sampleTypeName - return segment.NewKey(labels) -} - -func (p *Parser) load(sampleType int64, labels tree.Labels) (*tree.Tree, bool) { - e, ok := p.cache.Get(sampleType, labels.Hash()) - if !ok { - return nil, false - } - return e.Value, true -} - -func (p *Parser) iterate(x *tree.Profile, cumulativeOnly bool, fn func(vt *tree.ValueType, l tree.Labels, t *tree.Tree) (keep bool, err error)) error { - c := tree.NewLabelsCache[tree.Tree](tree.New) - p.readTrees(x, c, tree.NewFinder(x), cumulativeOnly) - for sampleType, entries := range c.Map { - if t, ok := x.ResolveSampleType(sampleType); ok { - for h, e := range entries { - keep, err := fn(t, e.Labels, e.Value) - if err != nil { - return err - } - if !keep { - c.Remove(sampleType, h) - } - } - } - } - p.cache = c - return nil -} - -// readTrees generates trees from the profile populating c. -func (p *Parser) readTrees(x *tree.Profile, c tree.LabelsCache[tree.Tree], f tree.Finder, cumulativeOnly bool) { - // SampleType value indexes. - indexes := make([]int, 0, len(x.SampleType)) - // Corresponding type IDs used as the main cache keys. - types := make([]int64, 0, len(x.SampleType)) - for i, s := range x.SampleType { - st := x.StringTable[s.Type] - if p.sampleTypesFilter != nil && p.sampleTypesFilter(st) { - if !cumulativeOnly || (cumulativeOnly && p.sampleTypes[st].Cumulative) { - indexes = append(indexes, i) - types = append(types, s.Type) - } - } - } - if len(indexes) == 0 { - return - } - stack := make([][]byte, 0, 16) - for _, s := range x.Sample { - for i := len(s.LocationId) - 1; i >= 0; i-- { - // Resolve stack. - loc, ok := f.FindLocation(s.LocationId[i]) - if !ok { - continue - } - // Multiple line indicates this location has inlined functions, - // where the last entry represents the caller into which the - // preceding entries were inlined. - // - // E.g., if memcpy() is inlined into printf: - // line[0].function_name == "memcpy" - // line[1].function_name == "printf" - // - // Therefore iteration goes in reverse order. - for j := len(loc.Line) - 1; j >= 0; j-- { - fn, ok := f.FindFunction(loc.Line[j].FunctionId) - if !ok || x.StringTable[fn.Name] == "" { - continue - } - sf := p.stackFrameFormatter.format(x, fn, loc.Line[j]) - stack = append(stack, sf) - } - } - // Insert tree nodes. - for i, vi := range indexes { - v := uint64(s.Value[vi]) - if v == 0 { - continue - } - // If the sample has ProfileID label, it belongs to an exemplar. - if j := labelIndex(x, s.Label, segment.ProfileIDLabelName); j >= 0 { - // Regardless of whether we should skip exemplars or not, the value - // should be appended to the exemplar baseline profile (w/o ProfileID label). - c.GetOrCreateTree(types[i], tree.CutLabel(s.Label, j)).Value.InsertStack(stack, v) - if p.skipExemplars { - continue - } - } - c.GetOrCreateTree(types[i], s.Label).Value.InsertStack(stack, v) - } - stack = stack[:0] - } -} - -func labelIndex(p *tree.Profile, labels tree.Labels, key string) int { - for i, label := range labels { - if n, ok := p.ResolveLabelName(label); ok && n == key { - return i - } - } - return -1 -} diff --git a/pkg/og/convert/pprof/parser_bench_test.go b/pkg/og/convert/pprof/parser_bench_test.go deleted file mode 100644 index a05f9ad4d5..0000000000 --- a/pkg/og/convert/pprof/parser_bench_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package pprof - -import ( - "os" - "testing" - - "github.com/grafana/pyroscope/pkg/og/storage/tree" -) - -func Benchmark_ProfileParser(b *testing.B) { - p, err := readPprofFixture("testdata/heap.pb.gz") - if err != nil { - b.Error(err) - } - parser := NewParser(ParserConfig{}) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - parser.Reset() - if err = parser.iterate(p, false, readNoOp); err != nil { - b.Error(err) - } - } -} - -func readPprofFixture(path string) (*tree.Profile, error) { - f, err := os.Open(path) - if err != nil { - return nil, err - } - defer func() { - _ = f.Close() - }() - - var p tree.Profile - if err = Decode(f, &p); err != nil { - return nil, err - } - return &p, nil -} - -func readNoOp(*tree.ValueType, tree.Labels, *tree.Tree) (bool, error) { - return false, nil -} diff --git a/pkg/og/convert/pprof/pprof_suite_test.go b/pkg/og/convert/pprof/pprof_suite_test.go deleted file mode 100644 index 15dc7a1ea6..0000000000 --- a/pkg/og/convert/pprof/pprof_suite_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package pprof_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "testing" -) - -func TestConvert(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "pprof Suite") -} diff --git a/pkg/og/convert/pprof/pprof_test.go b/pkg/og/convert/pprof/pprof_test.go deleted file mode 100644 index c822f76f8e..0000000000 --- a/pkg/og/convert/pprof/pprof_test.go +++ /dev/null @@ -1,290 +0,0 @@ -package pprof - -import ( - "context" - "sort" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/grafana/pyroscope/pkg/og/storage" - "github.com/grafana/pyroscope/pkg/og/storage/tree" -) - -type mockIngester struct{ actual []*storage.PutInput } - -func (m *mockIngester) Put(_ context.Context, p *storage.PutInput) error { - m.actual = append(m.actual, p) - return nil -} - -var _ = Describe("pprof parsing", func() { - Context("Go", func() { - It("can parse CPU profiles", func() { - p, err := readPprofFixture("testdata/cpu.pb.gz") - Expect(err).ToNot(HaveOccurred()) - - ingester := new(mockIngester) - spyName := "spy-name" - now := time.Now() - start := now - end := now.Add(10 * time.Second) - labels := map[string]string{ - "__name__": "app", - "foo": "bar", - } - - w := NewParser(ParserConfig{ - Putter: ingester, - SampleTypes: tree.DefaultSampleTypeMapping, - Labels: labels, - SpyName: spyName, - }) - - err = w.Convert(context.Background(), start, end, p, false) - Expect(err).ToNot(HaveOccurred()) - - Expect(ingester.actual).To(HaveLen(1)) - input := ingester.actual[0] - Expect(input.SpyName).To(Equal(spyName)) - Expect(input.StartTime).To(Equal(start)) - Expect(input.EndTime).To(Equal(end)) - Expect(input.SampleRate).To(Equal(uint32(100))) - Expect(input.Val.Samples()).To(Equal(uint64(47))) - Expect(input.Key.Normalized()).To(Equal("app.cpu{foo=bar}")) - Expect(input.Val.String()).To(ContainSubstring("runtime.main;main.main;main.slowFunction;main.work 1")) - }) - }) - - Context("JS", func() { - It("can parse CPU profile", func() { - p, err := readPprofFixture("testdata/nodejs-wall.pb.gz") - Expect(err).ToNot(HaveOccurred()) - - ingester := new(mockIngester) - spyName := "nodespy" - now := time.Now() - start := now - end := now.Add(10 * time.Second) - labels := map[string]string{ - "__name__": "app", - "foo": "bar", - } - - w := NewParser(ParserConfig{ - Putter: ingester, - SampleTypes: tree.DefaultSampleTypeMapping, - Labels: labels, - SpyName: spyName, - }) - - err = w.Convert(context.Background(), start, end, p, false) - Expect(err).ToNot(HaveOccurred()) - - Expect(ingester.actual).To(HaveLen(1)) - input := ingester.actual[0] - Expect(input.SpyName).To(Equal(spyName)) - Expect(input.StartTime).To(Equal(start)) - Expect(input.EndTime).To(Equal(end)) - Expect(input.SampleRate).To(Equal(uint32(100))) - Expect(input.Val.Samples()).To(Equal(uint64(898))) - Expect(input.Key.Normalized()).To(Equal("app.cpu{foo=bar}")) - Expect(input.Val.String()).To(ContainSubstring("node:_http_server:resOnFinish:819;node:_http_server:detachSocket:252 1")) - }) - - It("can parse heap profiles", func() { - p, err := readPprofFixture("testdata/nodejs-heap.pb.gz") - Expect(err).ToNot(HaveOccurred()) - - ingester := new(mockIngester) - spyName := "nodespy" - now := time.Now() - start := now - end := now.Add(10 * time.Second) - labels := map[string]string{ - "__name__": "app", - "foo": "bar", - } - - Expect(tree.DefaultSampleTypeMapping["inuse_objects"].Cumulative).To(BeFalse()) - Expect(tree.DefaultSampleTypeMapping["inuse_space"].Cumulative).To(BeFalse()) - tree.DefaultSampleTypeMapping["inuse_objects"].Cumulative = false - tree.DefaultSampleTypeMapping["inuse_space"].Cumulative = false - - w := NewParser(ParserConfig{ - Putter: ingester, - SampleTypes: tree.DefaultSampleTypeMapping, - Labels: labels, - SpyName: spyName, - }) - - err = w.Convert(context.Background(), start, end, p, false) - Expect(err).ToNot(HaveOccurred()) - Expect(ingester.actual).To(HaveLen(2)) - sort.Slice(ingester.actual, func(i, j int) bool { - return ingester.actual[i].Key.Normalized() < ingester.actual[j].Key.Normalized() - }) - - input := ingester.actual[0] - Expect(input.SpyName).To(Equal(spyName)) - Expect(input.StartTime).To(Equal(start)) - Expect(input.EndTime).To(Equal(end)) - Expect(input.Val.Samples()).To(Equal(uint64(100498))) - Expect(input.Key.Normalized()).To(Equal("app.inuse_objects{foo=bar}")) - Expect(input.Val.String()).To(ContainSubstring("node:internal/streams/readable:readableAddChunk:236 138")) - - input = ingester.actual[1] - Expect(input.SpyName).To(Equal(spyName)) - Expect(input.StartTime).To(Equal(start)) - Expect(input.EndTime).To(Equal(end)) - Expect(input.Val.Samples()).To(Equal(uint64(8357762))) - Expect(input.Key.Normalized()).To(Equal("app.inuse_space{foo=bar}")) - Expect(input.Val.String()).To(ContainSubstring("node:internal/net:isIPv6:35;:test:0 555360")) - }) - }) - - Context("pprof", func() { - It("can parse uncompressed protobuf", func() { - _, err := readPprofFixture("testdata/heap.pb") - Expect(err).ToNot(HaveOccurred()) - }) - }) -}) - -var _ = Describe("pprof parser", func() { - p, err := readPprofFixture("testdata/cpu-exemplars.pb.gz") - Expect(err).ToNot(HaveOccurred()) - - m := make(map[string]*storage.PutInput) - var skipExemplars bool - - JustBeforeEach(func() { - putter := new(mockIngester) - now := time.Now() - start := now - end := now.Add(10 * time.Second) - - w := NewParser(ParserConfig{ - Putter: putter, - Labels: map[string]string{"__name__": "app"}, - SampleTypes: tree.DefaultSampleTypeMapping, - SkipExemplars: skipExemplars, - }) - - err = w.Convert(context.Background(), start, end, p, false) - Expect(err).ToNot(HaveOccurred()) - m = make(map[string]*storage.PutInput) - for _, x := range putter.actual { - m[x.Key.Normalized()] = x - } - }) - - expectBaselineProfiles := func(m map[string]*storage.PutInput) { - baseline, ok := m["app.cpu{foo=bar}"] - Expect(ok).To(BeTrue()) - Expect(baseline.Val.Samples()).To(Equal(uint64(49))) - - baseline, ok = m["app.cpu{foo=bar,function=fast}"] - Expect(ok).To(BeTrue()) - Expect(baseline.Val.Samples()).To(Equal(uint64(150))) - - baseline, ok = m["app.cpu{foo=bar,function=slow}"] - Expect(ok).To(BeTrue()) - Expect(baseline.Val.Samples()).To(Equal(uint64(674))) - } - - expectExemplarProfiles := func(m map[string]*storage.PutInput) { - exemplar, ok := m["app.cpu{foo=bar,function=slow,profile_id=72bee0038027cfb1}"] - Expect(ok).To(BeTrue()) - Expect(exemplar.Val.Samples()).To(Equal(uint64(3))) - - exemplar, ok = m["app.cpu{foo=bar,function=fast,profile_id=ff4d0449f061174f}"] - Expect(ok).To(BeTrue()) - Expect(exemplar.Val.Samples()).To(Equal(uint64(1))) - } - - Context("by default exemplars are not skipped", func() { - It("can parse all exemplars", func() { - Expect(len(m)).To(Equal(435)) - }) - - It("correctly handles labels and values", func() { - expectExemplarProfiles(m) - }) - - It("merges baseline profiles", func() { - expectBaselineProfiles(m) - }) - }) - - Context("when configured to skip exemplars", func() { - BeforeEach(func() { - skipExemplars = true - }) - - It("skip exemplars", func() { - Expect(len(m)).To(Equal(3)) - }) - - It("merges baseline profiles", func() { - expectBaselineProfiles(m) - }) - }) -}) - -var _ = Describe("custom pprof parsing", func() { - It("parses data correctly", func() { - p, err := readPprofFixture("testdata/heap-js.pprof") - Expect(err).ToNot(HaveOccurred()) - - ingester := new(mockIngester) - spyName := "spy-name" - now := time.Now() - start := now - end := now.Add(10 * time.Second) - labels := map[string]string{ - "__name__": "app", - "foo": "bar", - } - - w := NewParser(ParserConfig{ - Putter: ingester, - SampleTypes: map[string]*tree.SampleTypeConfig{ - "objects": { - Units: "objects", - Aggregation: "average", - }, - "space": { - Units: "bytes", - Aggregation: "average", - }, - }, - Labels: labels, - SpyName: spyName, - }) - - err = w.Convert(context.TODO(), start, end, p, false) - Expect(err).ToNot(HaveOccurred()) - Expect(ingester.actual).To(HaveLen(2)) - sort.Slice(ingester.actual, func(i, j int) bool { - return ingester.actual[i].Key.Normalized() < ingester.actual[j].Key.Normalized() - }) - - input := ingester.actual[0] - Expect(input.SpyName).To(Equal(spyName)) - Expect(input.StartTime).To(Equal(start)) - Expect(input.EndTime).To(Equal(end)) - Expect(input.Val.Samples()).To(Equal(uint64(66148))) - Expect(input.Key.Normalized()).To(Equal("app.objects{foo=bar}")) - Expect(input.Val.String()).To(ContainSubstring("parserOnHeadersComplete;parserOnIncoming 2428")) - - input = ingester.actual[1] - Expect(input.SpyName).To(Equal(spyName)) - Expect(input.StartTime).To(Equal(start)) - Expect(input.EndTime).To(Equal(end)) - Expect(input.Val.Samples()).To(Equal(uint64(6388384))) - Expect(input.Key.Normalized()).To(Equal("app.space{foo=bar}")) - Expect(input.Val.String()).To(ContainSubstring("parserOnHeadersComplete;parserOnIncoming 524448")) - }) -}) diff --git a/pkg/og/convert/pprof/profile.go b/pkg/og/convert/pprof/profile.go index 25beb5a7d5..11ea05eabf 100644 --- a/pkg/og/convert/pprof/profile.go +++ b/pkg/og/convert/pprof/profile.go @@ -4,39 +4,29 @@ import ( "bytes" "context" "encoding/json" - "io" + "fmt" "mime/multipart" - "sync" - - "github.com/grafana/pyroscope/pkg/og/convert/pprof/streaming" - "github.com/grafana/pyroscope/pkg/og/stackbuilder" + "strings" + "github.com/bufbuild/connect-go" + v1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" + distributormodel "github.com/grafana/pyroscope/pkg/distributor/model" + phlaremodel "github.com/grafana/pyroscope/pkg/model" "github.com/grafana/pyroscope/pkg/og/ingestion" "github.com/grafana/pyroscope/pkg/og/storage" "github.com/grafana/pyroscope/pkg/og/storage/tree" "github.com/grafana/pyroscope/pkg/og/util/form" + "github.com/grafana/pyroscope/pkg/pprof" + "github.com/prometheus/prometheus/model/labels" ) type RawProfile struct { - // parser is stateful: it holds parsed previous profile - // which is necessary for cumulative profiles that require - // two consecutive profiles. - parser ParserInterface - // References the next profile in the sequence (cumulative type only). - next *RawProfile - - m sync.Mutex - // Initializes lazily on Bytes, if not present. RawData []byte // Represents raw request body as per ingestion API. FormDataContentType string // Set optionally, if RawData is multipart form. - // Initializes lazily on Parse, if not present. - Profile []byte // Represents raw pprof data. - PreviousProfile []byte // Used for cumulative type only. - SkipExemplars bool - StreamingParser bool - PoolStreamingParser bool - ArenasEnabled bool - SampleTypeConfig map[string]*tree.SampleTypeConfig + // Initializes lazily on handleRawData, if not present. + Profile []byte // Represents raw pprof data. + + SampleTypeConfig map[string]*tree.SampleTypeConfig } func (p *RawProfile) ContentType() string { @@ -46,199 +36,70 @@ func (p *RawProfile) ContentType() string { return p.FormDataContentType } -// Push loads data from profile to RawProfile making it eligible for -// Bytes and Parse calls. -// -// Returned RawProfile should be used at the next Push: the method -// established relationship between these two RawProfiles in order -// to propagate internal pprof parser state lazily on a successful -// Parse call. This is necessary for cumulative profiles that require -// two consecutive samples to calculate the diff. If parser is not -// present due to a failure, or sequence violation, the profiles will -// be re-parsed. -func (p *RawProfile) Push(profile []byte, cumulative bool) *RawProfile { - p.m.Lock() - p.Profile = profile - p.RawData = nil - n := &RawProfile{ - SampleTypeConfig: p.SampleTypeConfig, - } - if cumulative { - // N.B the parser state is only propagated - // after successful Parse call. - n.PreviousProfile = p.Profile - p.next = n - } - p.m.Unlock() - return p.next -} - const ( - formFieldProfile, formFileProfile = "profile", "profile.pprof" - formFieldPreviousProfile, formFilePreviousProfile = "prev_profile", "profile.pprof" - formFieldSampleTypeConfig, formFileSampleTypeConfig = "sample_type_config", "sample_type_config.json" + formFieldProfile = "profile" + formFieldPreviousProfile = "prev_profile" + formFieldSampleTypeConfig = "sample_type_config" ) -func (p *RawProfile) Bytes() ([]byte, error) { - p.m.Lock() - defer p.m.Unlock() - if p.RawData != nil { - // RawProfile was initialized with RawData or - // Bytes has been already called. - return p.RawData, nil - } - // Build multipart form. - if len(p.Profile) == 0 && len(p.PreviousProfile) == 0 { - return nil, nil - } - var b bytes.Buffer - mw := multipart.NewWriter(&b) - ff, err := mw.CreateFormFile(formFieldProfile, formFileProfile) - if err != nil { - return nil, err - } - _, _ = io.Copy(ff, bytes.NewReader(p.Profile)) - if len(p.PreviousProfile) > 0 { - if ff, err = mw.CreateFormFile(formFieldPreviousProfile, formFilePreviousProfile); err != nil { - return nil, err - } - _, _ = io.Copy(ff, bytes.NewReader(p.PreviousProfile)) - } - if len(p.SampleTypeConfig) > 0 { - if ff, err = mw.CreateFormFile(formFieldSampleTypeConfig, formFileSampleTypeConfig); err != nil { - return nil, err +// ParseToPprof is not doing much now. It parses the profile with no processing/splitting, adds labels. +func (p *RawProfile) ParseToPprof(_ context.Context, md ingestion.Metadata) (res *distributormodel.PushRequest, err error) { + defer func() { + r := recover() + if r != nil { + err = fmt.Errorf("/ingest pprof.(*RawProfile).ParseToPprof panic %v", r) } - _ = json.NewEncoder(ff).Encode(p.SampleTypeConfig) - } - _ = mw.Close() - p.RawData = b.Bytes() - p.FormDataContentType = mw.FormDataContentType() - return p.RawData, nil -} - -func (p *RawProfile) Parse(ctx context.Context, putter storage.Putter, _ storage.MetricsExporter, md ingestion.Metadata) error { - p.m.Lock() - defer p.m.Unlock() - cont, err := p.handleRawData() - if err != nil || !cont { - return err + }() + err = p.handleRawData() + if err != nil { + return nil, fmt.Errorf("failed to parse pprof /ingest multipart form %w", err) } - if p.parser == nil { - sampleTypes := p.getSampleTypes() - if p.StreamingParser { - config := streaming.ParserConfig{ - SpyName: md.SpyName, - Labels: md.Key.Labels(), - Putter: putter, - SampleTypes: sampleTypes, - Formatter: streaming.StackFrameFormatterForSpyName(md.SpyName), - } - if p.PoolStreamingParser { - parser := streaming.VTStreamingParserFromPool(config) - p.parser = parser - defer func() { - parser.ResetCache() - parser.ReturnToPool() - p.parser = nil - }() - } else { - if p.ArenasEnabled { - config.ArenasEnabled = true - } - parser := streaming.NewStreamingParser(config) - p.parser = parser - if p.ArenasEnabled { - defer parser.FreeArena() - } - } - } else { - p.parser = NewParser(ParserConfig{ - SpyName: md.SpyName, - Labels: md.Key.Labels(), - Putter: putter, - SampleTypes: sampleTypes, - SkipExemplars: p.SkipExemplars, - StackFrameFormatter: StackFrameFormatterForSpyName(md.SpyName), - }) - } - - if p.PreviousProfile != nil { - // Ignore non-cumulative samples from the PreviousProfile - // to avoid duplicates: although, presence of PreviousProfile - // tells that there are cumulative sample types, it may also - // include regular ones. - cumulativeOnly := true - if err := p.parser.ParsePprof(ctx, md.StartTime, md.EndTime, p.PreviousProfile, cumulativeOnly); err != nil { - return err - } - } + profile, err := pprof.RawFromBytes(p.Profile) + if err != nil { + return nil, connect.NewError(connect.CodeInvalidArgument, err) } - if err := p.parser.ParsePprof(ctx, md.StartTime, md.EndTime, p.Profile, false); err != nil { - return err - } + fixTime(profile, md) + fixFunctionNamesForScriptingLanguages(profile, md) - // Propagate parser to the next profile, if it is present. - if p.next != nil { - p.next.m.Lock() - p.next.parser = p.parser - p.next.m.Unlock() + res = &distributormodel.PushRequest{ + RawProfileSize: len(p.Profile), + RawProfileType: distributormodel.RawProfileTypePPROF, + Series: []*distributormodel.ProfileSeries{{ + Labels: p.createLabels(profile, md), + Samples: []*distributormodel.ProfileSample{{ + Profile: profile, + RawProfile: p.Profile, + }}, + }}, } - - return nil + return } -func (p *RawProfile) ParseWithWriteBatch(c context.Context, wb stackbuilder.WriteBatchFactory, md ingestion.Metadata) error { - cont, err := p.handleRawData() - if err != nil || !cont { - return err - } - parser := streaming.NewStreamingParser(streaming.ParserConfig{ - SpyName: md.SpyName, - Labels: md.Key.Labels(), - SampleTypes: p.getSampleTypes(), - Formatter: streaming.StackFrameFormatterForSpyName(md.SpyName), - ArenasEnabled: true, - }) - defer parser.FreeArena() - return parser.ParseWithWriteBatch(streaming.ParseWriteBatchInput{ - Context: c, - StartTime: md.StartTime, EndTime: md.EndTime, - Profile: p.Profile, Previous: p.PreviousProfile, - WriteBatchFactory: wb, - }) +func fixTime(profile *pprof.Profile, md ingestion.Metadata) { + // for old versions of pyspy, rbspy, pyroscope-rs + // https://github.com/grafana/pyroscope-rs/pull/134 + profile.TimeNanos = md.StartTime.UnixNano() + profile.DurationNanos = md.EndTime.Sub(md.StartTime).Nanoseconds() } -func (p *RawProfile) getSampleTypes() map[string]*tree.SampleTypeConfig { - sampleTypes := tree.DefaultSampleTypeMapping - if p.SampleTypeConfig != nil { - sampleTypes = p.SampleTypeConfig - } - return sampleTypes +func (p *RawProfile) Parse(_ context.Context, _ storage.Putter, _ storage.MetricsExporter, md ingestion.Metadata) error { + return fmt.Errorf("parsing pprof to tree/storage.Putter is nolonger ") } -func (p *RawProfile) handleRawData() (cont bool, err error) { - if len(p.Profile) == 0 && len(p.PreviousProfile) == 0 { - // Check if RawProfile was initialized with RawData. - if p.RawData == nil { - // Zero profile, nothing to parse. - return false, nil - } - if p.FormDataContentType != "" { - // The profile was ingested as a multipart form. Load parts to - // Profile, PreviousProfile, and SampleTypeConfig. - if err := p.loadPprofFromForm(); err != nil { - return false, err - } - } else { - p.Profile = p.RawData +func (p *RawProfile) handleRawData() (err error) { + if p.FormDataContentType != "" { + // The profile was ingested as a multipart form. Load parts to + // Profile, PreviousProfile, and SampleTypeConfig. + if err := p.loadPprofFromForm(); err != nil { + return err } + } else { + p.Profile = p.RawData } - if len(p.Profile) == 0 { - return false, nil - } - return true, nil + + return nil } func (p *RawProfile) loadPprofFromForm() error { @@ -259,10 +120,14 @@ func (p *RawProfile) loadPprofFromForm() error { if err != nil { return err } - p.PreviousProfile, err = form.ReadField(f, formFieldPreviousProfile) + PreviousProfile, err := form.ReadField(f, formFieldPreviousProfile) if err != nil { return err } + if PreviousProfile != nil { + return fmt.Errorf("unsupported client version. " + + "Please update github.com/grafana/pyroscope-go to the latest version") + } r, err := form.ReadField(f, formFieldSampleTypeConfig) if err != nil || r == nil { @@ -275,3 +140,108 @@ func (p *RawProfile) loadPprofFromForm() error { p.SampleTypeConfig = config return nil } + +func (p *RawProfile) metricName(profile *pprof.Profile) string { + stConfigs := p.getSampleTypes() + var st string + for _, ist := range profile.Profile.SampleType { + st = profile.StringTable[ist.Type] + if st == "wall" { + return st + } + } + for _, ist := range profile.Profile.SampleType { + st = profile.StringTable[ist.Type] + stConfig := stConfigs[st] + + if stConfig != nil && stConfig.DisplayName != "" { + st = stConfig.DisplayName + } + if strings.Contains(st, "cpu") { + return "process_cpu" + } + if strings.Contains(st, "alloc_") || strings.Contains(st, "inuse_") || st == "space" || st == "objects" { + return "memory" + } + if strings.Contains(st, "mutex_") { + return "mutex" + } + if strings.Contains(st, "block_") { + return "block" + } + if strings.Contains(st, "goroutines") { + return "goroutines" + } + } + return st // should not happen + +} + +func (p *RawProfile) createLabels(profile *pprof.Profile, md ingestion.Metadata) []*v1.LabelPair { + ls := make([]*v1.LabelPair, 0, len(md.Key.Labels())+4) + ls = append(ls, &v1.LabelPair{ + Name: labels.MetricName, + Value: p.metricName(profile), + }, &v1.LabelPair{ + Name: phlaremodel.LabelNameDelta, + Value: "false", + }, &v1.LabelPair{ + Name: "service_name", + Value: md.Key.AppName(), + }, &v1.LabelPair{ + Name: "pyroscope_spy", + Value: md.SpyName, + }) + for k, v := range md.Key.Labels() { + if strings.HasPrefix(k, "__") { + continue + } + ls = append(ls, &v1.LabelPair{ + Name: k, + Value: v, + }) + } + return ls +} +func (p *RawProfile) getSampleTypes() map[string]*tree.SampleTypeConfig { + sampleTypes := tree.DefaultSampleTypeMapping + if p.SampleTypeConfig != nil { + sampleTypes = p.SampleTypeConfig + } + return sampleTypes +} + +func needFunctionNameRewrite(md ingestion.Metadata) bool { + return isScriptingSpy(md) +} + +func SpyNameForFunctionNameRewrite() string { + return "scripting" +} + +func isScriptingSpy(md ingestion.Metadata) bool { + return md.SpyName == "pyspy" || md.SpyName == "rbspy" || md.SpyName == "scripting" +} + +func fixFunctionNamesForScriptingLanguages(p *pprof.Profile, md ingestion.Metadata) { + if !needFunctionNameRewrite(md) { + return + } + smap := map[string]int{} + for _, fn := range p.Function { + // obtaining correct line number will require rewriting functions and slices + // lets not do it and wait until we render line numbers on frontend + const lineNumber = -1 + name := fmt.Sprintf("%s:%d - %s", + p.StringTable[fn.Filename], + lineNumber, + p.StringTable[fn.Name]) + sid := smap[name] + if sid == 0 { + sid = len(p.StringTable) + p.StringTable = append(p.StringTable, name) + smap[name] = sid + } + fn.Name = int64(sid) + } +} diff --git a/pkg/og/convert/pprof/streaming/labels_cache.go b/pkg/og/convert/pprof/streaming/labels_cache.go deleted file mode 100644 index 295f263a20..0000000000 --- a/pkg/og/convert/pprof/streaming/labels_cache.go +++ /dev/null @@ -1,145 +0,0 @@ -package streaming - -import ( - "github.com/cespare/xxhash/v2" - "github.com/grafana/pyroscope/pkg/og/storage/tree" - "github.com/grafana/pyroscope/pkg/og/util/arenahelper" - "golang.org/x/exp/slices" - "reflect" - "unsafe" -) - -type Labels []uint64 - -func (l Labels) Len() int { return len(l) } -func (l Labels) Less(i, j int) bool { return l[i] < l[j] } -func (l Labels) Swap(i, j int) { l[i], l[j] = l[j], l[i] } - -var zeroHash = xxhash.Sum64(nil) - -func (l Labels) Hash() uint64 { - if len(l) == 0 { - return zeroHash - } - slices.Sort(l) //sort.Sort(l) // slice to interface conversion does an allocation for a slice header copy - var raw []byte - sh := (*reflect.SliceHeader)(unsafe.Pointer(&raw)) - sh.Data = uintptr(unsafe.Pointer(&l[0])) - sh.Len = len(l) * 8 - sh.Cap = len(l) * 8 - return xxhash.Sum64(raw) -} - -type LabelsCache struct { - // sample type -> labels hash -> index to labelRefs and trees - indices []map[uint64]int - // A label reference points to the subset in labels: - // Hight 32 bits is the start offset, lower 32 bits is the subset size. - labelRefs []uint64 - labels []uint64 // Packed label Key and Value indices - trees []*tree.Tree - - arena arenahelper.ArenaWrapper -} - -func (c *LabelsCache) Reset() { - if c.indices == nil { - c.indices = arenahelper.MakeSlice[map[uint64]int](c.arena, 4, 4) - } else { - for i := range c.indices { - c.indices[i] = nil - } - for i := range c.trees { - c.trees[i] = nil - } - c.labelRefs = c.labelRefs[:0] - c.labels = c.labels[:0] - c.trees = c.trees[:0] - } -} - -func (c *LabelsCache) GetOrCreateTree(sampleTypeIndex int, l Labels) *tree.Tree { - if sampleTypeIndex >= len(c.indices) { - newSampleTypes := make([]map[uint64]int, sampleTypeIndex+1, sampleTypeIndex+1) - copy(newSampleTypes, c.indices) - c.indices = newSampleTypes - } - p := c.indices[sampleTypeIndex] - if p == nil { - e, t := c.newCacheEntryA(l) - c.indices[sampleTypeIndex] = map[uint64]int{l.Hash(): e} - return t - } - h := l.Hash() - e, found := p[h] - if found { - return c.trees[e] - } - e, t := c.newCacheEntryA(l) - p[h] = e - return t -} - -func (c *LabelsCache) Get(sampleTypeIndex int, h uint64) (*tree.Tree, bool) { - if sampleTypeIndex >= len(c.indices) { - return nil, false - } - p := c.indices[sampleTypeIndex] - if p == nil { - return nil, false - } - x, ok := p[h] - return c.trees[x], ok -} - -func (c *LabelsCache) Remove(sampleTypeIndex int, h uint64) { - if sampleTypeIndex >= len(c.indices) { - return - } - p := c.indices[sampleTypeIndex] - if p == nil { - return - } - delete(p, h) - if len(p) == 0 { - c.indices[sampleTypeIndex] = nil - } -} - -func (c *LabelsCache) newCacheEntry(l Labels) (int, *tree.Tree) { - from := len(c.labels) - for _, u := range l { - c.labels = append(c.labels, u) - } - to := len(c.labels) - res := len(c.labelRefs) - c.labelRefs = append(c.labelRefs, uint64(from<<32|to)) - t := tree.New() - c.trees = append(c.trees, t) - return res, t -} - -func (c *LabelsCache) iterate(f func(sampleTypeIndex int, l Labels, lh uint64, t *tree.Tree) error) error { - for sampleTypeIndex, p := range c.indices { - if p == nil { - continue - } - for h, x := range p { - labelRef := c.labelRefs[x] - l := c.labels[labelRef>>32 : labelRef&0xffffffff] - err := f(sampleTypeIndex, l, h, c.trees[x]) - if err != nil { - return err - } - } - } - return nil -} - -// CutLabel creates a copy of labels without label i. -func CutLabel(a arenahelper.ArenaWrapper, labels Labels, i int) Labels { - c := arenahelper.MakeSlice[uint64](a, len(labels)-1, len(labels)-1) - copy(c[:i], labels[:i]) - copy(c[i:], labels[i+1:]) - return c -} diff --git a/pkg/og/convert/pprof/streaming/labels_cache_arenas_disabled.go b/pkg/og/convert/pprof/streaming/labels_cache_arenas_disabled.go deleted file mode 100644 index 413a8f0e0f..0000000000 --- a/pkg/og/convert/pprof/streaming/labels_cache_arenas_disabled.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !goexperiment.arenas - -package streaming - -import "github.com/grafana/pyroscope/pkg/og/storage/tree" - -func (c *LabelsCache) newCacheEntryA(l Labels) (int, *tree.Tree) { - return c.newCacheEntry(l) -} diff --git a/pkg/og/convert/pprof/streaming/labels_cache_arenas_enabled.go b/pkg/og/convert/pprof/streaming/labels_cache_arenas_enabled.go deleted file mode 100644 index e757153eb7..0000000000 --- a/pkg/og/convert/pprof/streaming/labels_cache_arenas_enabled.go +++ /dev/null @@ -1,38 +0,0 @@ -//go:build goexperiment.arenas - -package streaming - -import ( - "github.com/grafana/pyroscope/pkg/og/storage/tree" - "github.com/grafana/pyroscope/pkg/og/util/arenahelper" -) - -func (c *LabelsCache) newCacheEntryA(l Labels) (int, *tree.Tree) { - a := c.arena - if a == nil { - return c.newCacheEntry(l) - } - from := len(c.labels) - for _, u := range l { - if len(c.labels) < cap(c.labels) { - c.labels = append(c.labels, u) - } else { - c.labels = arenahelper.AppendA(c.labels, u, a) - } - } - to := len(c.labels) - res := len(c.labelRefs) - r := uint64(from<<32 | to) - if len(c.labelRefs) < cap(c.labelRefs) { - c.labelRefs = append(c.labelRefs, r) - } else { - c.labelRefs = arenahelper.AppendA(c.labelRefs, r, a) - } - t := tree.NewA(a) - if len(c.trees) < cap(c.trees) { - c.trees = append(c.trees, t) - } else { - c.trees = arenahelper.AppendA(c.trees, t, a) - } - return res, t -} diff --git a/pkg/og/convert/pprof/streaming/parser_streaming.go b/pkg/og/convert/pprof/streaming/parser_streaming.go deleted file mode 100644 index 7791a4e51d..0000000000 --- a/pkg/og/convert/pprof/streaming/parser_streaming.go +++ /dev/null @@ -1,507 +0,0 @@ -package streaming - -import ( - "bytes" - "compress/gzip" - "context" - "fmt" - "github.com/grafana/pyroscope/pkg/og/stackbuilder" - "github.com/grafana/pyroscope/pkg/og/storage" - "github.com/grafana/pyroscope/pkg/og/storage/metadata" - "github.com/grafana/pyroscope/pkg/og/storage/segment" - "github.com/grafana/pyroscope/pkg/og/storage/tree" - "github.com/grafana/pyroscope/pkg/og/util/arenahelper" - "github.com/valyala/bytebufferpool" - "io" - "runtime/debug" - "strings" - "sync" - "time" -) - -type StackFormatter int - -const ( - // StackFrameFormatterGo use only function name - StackFrameFormatterGo = 0 - // StackFrameFormatterRuby use function name, line number, function name - StackFrameFormatterRuby = 1 -) - -var PPROFBufPool = bytebufferpool.Pool{} - -type ParserConfig struct { - Putter storage.Putter - SpyName string - Labels map[string]string - SampleTypes map[string]*tree.SampleTypeConfig - Formatter StackFormatter - ArenasEnabled bool -} - -type VTStreamingParser struct { - putter storage.Putter - wbf stackbuilder.WriteBatchFactory - spyName string - labels map[string]string - - sampleTypesConfig map[string]*tree.SampleTypeConfig - Formatter StackFormatter - ArenasEnabled bool - - sampleTypesFilter func(string) bool - - startTime time.Time - endTime time.Time - ctx context.Context - profile []byte - prev bool - cumulative bool - cumulativeOnly bool - - nStrings int - profileIDLabelIndex int64 - nFunctions int - nLocations int - nSampleTypes int - period int64 - periodType valueType - sampleTypes []valueType - strings []istr - functions []function - locations []location - - lineRefs locationFunctions - - indexes []int - types []int64 - - tmpSample sample - - finder finder - previousCache LabelsCache - newCache LabelsCache - wbCache writeBatchCache - arena arenahelper.ArenaWrapper -} - -func NewStreamingParser(config ParserConfig) *VTStreamingParser { - res := &VTStreamingParser{} - res.Reset(config) - return res -} -func (p *VTStreamingParser) FreeArena() { - arenahelper.Free(p.arena) -} -func (p *VTStreamingParser) ParsePprof(ctx context.Context, startTime, endTime time.Time, bs []byte, cumulativeOnly bool) (err error) { - p.startTime = startTime - p.endTime = endTime - p.ctx = ctx - p.cumulativeOnly = cumulativeOnly - - err = decompress(bs, func(profile []byte) error { - p.profile = profile - err := p.parsePprofDecompressed() - p.profile = nil - return err - }) - p.ctx = nil - return err -} - -func (p *VTStreamingParser) parsePprofDecompressed() (err error) { - defer func() { - if recover() != nil { - err = fmt.Errorf(fmt.Sprintf("parse panic %s", debug.Stack())) - } - }() - - if err = p.countStructs(); err != nil { - return err - } - if err = p.parseFunctionsAndLocations(); err != nil { - return err - } - if !p.haveKnownSampleTypes() { - return nil - } - - p.newCache.Reset() - if err = p.parseSamples(); err != nil { - return err - } - return p.iterate(p.put) -} - -// step 1 -// - parse periodType -// - parse sampleType -// - count number of locations, functions, strings -func (p *VTStreamingParser) countStructs() error { - err := p.UnmarshalVTProfile(p.profile, opFlagCountStructs) - if err == nil { - p.functions = grow(p.arena, p.functions, p.nFunctions) - p.locations = grow(p.arena, p.locations, p.nLocations) - p.strings = grow(p.arena, p.strings, p.nStrings) - p.sampleTypes = grow(p.arena, p.sampleTypes, p.nSampleTypes) - p.profileIDLabelIndex = 0 - } - return err -} - -func (p *VTStreamingParser) parseFunctionsAndLocations() error { - p.lineRefs.reset(p.arena, p.nLocations) - err := p.UnmarshalVTProfile(p.profile, opFlagParseStructs) - if err == nil { - p.finder = newFinder(p.functions, p.locations) - for i := range p.sampleTypes { - p.sampleTypes[i].resolvedType = string(p.string(p.sampleTypes[i].Type)) - p.sampleTypes[i].resolvedUnit = string(p.string(p.sampleTypes[i].unit)) - } - p.periodType.resolvedType = string(p.string(p.periodType.Type)) - p.periodType.resolvedUnit = string(p.string(p.periodType.unit)) - } - return err -} - -func (p *VTStreamingParser) haveKnownSampleTypes() bool { - p.indexes = grow(p.arena, p.indexes, len(p.sampleTypes)) - p.types = grow(p.arena, p.types, len(p.sampleTypes)) - for i, s := range p.sampleTypes { - ssType := p.string(s.Type) - - st := string(ssType) - if p.sampleTypesFilter(st) { - if !p.cumulativeOnly || (p.cumulativeOnly && p.sampleTypesConfig[st].Cumulative) { - p.indexes = arenahelper.AppendA(p.indexes, i, p.arena) - p.types = arenahelper.AppendA(p.types, s.Type, p.arena) - } - } - } - if len(p.indexes) == 0 { - return false - } - return true -} - -func (p *VTStreamingParser) parseSamples() error { - return p.UnmarshalVTProfile(p.profile, opFlagParseSamples) -} - -func (p *VTStreamingParser) addStackLocation(lID uint64) error { - loc, ok := p.finder.FindLocation(lID) - if ok { - ref := loc.linesRef - lines := p.lineRefs.lines[(ref >> 32):(ref & 0xffffffff)] - for i := len(lines) - 1; i >= 0; i-- { - if err := p.addStackFrame(&lines[i]); err != nil { - return err - } - } - } - return nil -} - -func (p *VTStreamingParser) addStackFrame(l *line) error { - fID := l.functionID - f, ok := p.finder.FindFunction(fID) - if !ok { - return nil - } - var frame []byte - switch p.Formatter { - case StackFrameFormatterRuby: - pFuncName := p.strings[f.name] - pFileName := p.strings[f.filename] - frame = []byte(fmt.Sprintf("%s:%d - %s", - p.profile[(pFileName>>32):(pFileName&0xffffffff)], - l.line, - p.profile[(pFuncName>>32):(pFuncName&0xffffffff)])) - default: - case StackFrameFormatterGo: - pFuncName := p.strings[f.name] - frame = p.profile[(pFuncName >> 32):(pFuncName & 0xffffffff)] - } - pSample := &p.tmpSample - if len(pSample.tmpStack) < cap(pSample.tmpStack) { - pSample.tmpStack = append(pSample.tmpStack, frame) - } else { - pSample.tmpStack = arenahelper.AppendA(pSample.tmpStack, frame, p.arena) - } - return nil -} - -func (p *VTStreamingParser) string(i int64) []byte { - ps := p.strings[i] - return p.profile[(ps >> 32):(ps & 0xffffffff)] -} - -func (p *VTStreamingParser) resolveSampleType(v int64) (*valueType, bool) { - for i := range p.sampleTypes { - if p.sampleTypes[i].Type == v { - return &p.sampleTypes[i], true - } - } - return nil, false -} - -func (p *VTStreamingParser) iterate(fn func(stIndex int, st *valueType, l Labels, tr *tree.Tree) (keep bool, err error)) error { - err := p.newCache.iterate(func(stIndex int, l Labels, lh uint64, tr *tree.Tree) error { - t := &p.sampleTypes[stIndex] - keep, err := fn(stIndex, t, l, tr) - if err != nil { - return err - } - if !keep { - p.newCache.Remove(stIndex, lh) - } - return nil - }) - if err != nil { - return err - } - p.previousCache, p.newCache = p.newCache, p.previousCache - p.newCache.Reset() - return nil -} - -func (p *VTStreamingParser) createTrees() { - for _, vi := range p.indexes { - v := uint64(p.tmpSample.tmpValues[vi]) - if v == 0 { - continue - } - s := p.tmpSample.tmpStack - if j := findLabelIndex(p.tmpSample.tmpLabels, p.profileIDLabelIndex); j >= 0 { - p.newCache.GetOrCreateTree(vi, CutLabel(p.arena, p.tmpSample.tmpLabels, j)).InsertStackA(s, v) - } - p.newCache.GetOrCreateTree(vi, p.tmpSample.tmpLabels).InsertStackA(s, v) - } -} - -func (p *VTStreamingParser) put(stIndex int, st *valueType, l Labels, t *tree.Tree) (keep bool, err error) { - sampleTypeBytes := st.resolvedType - sampleType := sampleTypeBytes - sampleTypeConfig, ok := p.sampleTypesConfig[sampleType] - if !ok { - return false, fmt.Errorf("sample value type is unknown") - } - pi := storage.PutInput{ - StartTime: p.startTime, - EndTime: p.endTime, - SpyName: p.spyName, - Val: t, - } - // Cumulative profiles require two consecutive samples, - // therefore we have to cache this trie. - if sampleTypeConfig.Cumulative { - prev, found := p.previousCache.Get(stIndex, l.Hash()) - if !found { - // Keep the current entry in cache. - return true, nil - } - // Take diff with the previous tree. - // The result is written to prev, t is not changed. - pi.Val = prev.Diff(t) - } - pi.AggregationType = sampleTypeConfig.Aggregation - if sampleTypeConfig.Sampled { - pi.SampleRate = p.sampleRate() - } - if sampleTypeConfig.DisplayName != "" { - sampleType = sampleTypeConfig.DisplayName - } - if sampleTypeConfig.Units != "" { - pi.Units = sampleTypeConfig.Units - } else { - // TODO(petethepig): this conversion is questionable - unitsBytes := st.resolvedUnit - pi.Units = metadata.Units(unitsBytes) - if err != nil { - return false, err - } - } - pi.Key = p.buildName(sampleType, p.ResolveLabels(l)) - err = p.putter.Put(p.ctx, &pi) - return sampleTypeConfig.Cumulative, err -} - -var vtStreamingParserPool = sync.Pool{New: func() any { - return &VTStreamingParser{} -}} - -func VTStreamingParserFromPool(config ParserConfig) *VTStreamingParser { - res := vtStreamingParserPool.Get().(*VTStreamingParser) - res.Reset(config) - return res -} - -func (p *VTStreamingParser) ResetCache() { - p.previousCache.Reset() - p.newCache.Reset() -} - -func (p *VTStreamingParser) ReturnToPool() { - if p != nil { - vtStreamingParserPool.Put(p) - } -} - -func (p *VTStreamingParser) ResolveLabels(l Labels) map[string]string { - m := make(map[string]string, len(l)) - for _, label := range l { - k := label >> 32 - if k != 0 { - v := label & 0xffffffff - sk := p.string(int64(k)) - sv := p.string(int64(v)) - m[string(sk)] = string(sv) - } - } - return m -} - -func (p *VTStreamingParser) buildName(sampleTypeName string, labels map[string]string) *segment.Key { - for k, v := range p.labels { - labels[k] = v - } - labels["__name__"] += "." + sampleTypeName - return segment.NewKey(labels) -} - -func (p *VTStreamingParser) getAppMetadata(sampleTypeIndex int) (string, metadata.Metadata) { - st := &p.sampleTypes[sampleTypeIndex] - sampleType := st.resolvedType - sampleTypeConfig, ok := p.sampleTypesConfig[sampleType] - if !ok { - return "", metadata.Metadata{} - } - if sampleTypeConfig.DisplayName != "" { - sampleType = sampleTypeConfig.DisplayName - } - name := p.labels["__name__"] - if name == "" { - return "", metadata.Metadata{} - } - md := metadata.Metadata{SpyName: p.spyName} - if sampleTypeConfig.Sampled { - md.SampleRate = p.sampleRate() - } - if sampleTypeConfig.DisplayName != "" { - sampleType = sampleTypeConfig.DisplayName - } - if sampleTypeConfig.Units != "" { - md.Units = sampleTypeConfig.Units - } else { - // TODO(petethepig): this conversion is questionable - unitsBytes := st.resolvedUnit - md.Units = metadata.Units(unitsBytes) - } - md.AggregationType = sampleTypeConfig.Aggregation - return name + "." + sampleType, md -} - -func (p *VTStreamingParser) sampleRate() uint32 { - if p.period <= 0 || p.periodType.unit <= 0 { - return 0 - } - sampleUnit := time.Nanosecond - u := p.periodType.resolvedUnit - - switch u { - case "microseconds": - sampleUnit = time.Microsecond - case "milliseconds": - sampleUnit = time.Millisecond - case "seconds": - sampleUnit = time.Second - } - - return uint32(time.Second / (sampleUnit * time.Duration(p.period))) -} - -func (p *VTStreamingParser) Reset(config ParserConfig) { - p.putter = config.Putter - p.spyName = config.SpyName - p.labels = config.Labels - p.sampleTypesConfig = config.SampleTypes - p.previousCache.Reset() - p.newCache.Reset() - p.wbCache.reset() - - p.sampleTypesFilter = filterKnownSamples(config.SampleTypes) - p.Formatter = config.Formatter - p.ArenasEnabled = config.ArenasEnabled - if config.ArenasEnabled { - p.arena = arenahelper.NewArenaWrapper() - p.previousCache.arena = p.arena - p.newCache.arena = p.arena - } -} - -func filterKnownSamples(sampleTypes map[string]*tree.SampleTypeConfig) func(string) bool { - return func(s string) bool { - _, ok := sampleTypes[s] - return ok - } -} - -func findLabelIndex(tmpLabels []uint64, k int64) int { - for i, l := range tmpLabels { - lk := int64(l >> 32) - if lk == k { - return i - } - } - return -1 -} - -func grow[T any](a arenahelper.ArenaWrapper, it []T, n int) []T { - if it == nil || n > cap(it) { - return arenahelper.MakeSlice[T](a, 0, n) - } - return it[:0] -} - -func StackFrameFormatterForSpyName(spyName string) StackFormatter { - if spyName == "rbspy" || spyName == "pyspy" { - return StackFrameFormatterRuby - } - return StackFrameFormatterGo -} - -func decompress(bs []byte, f func([]byte) error) error { - var err error - if len(bs) < 2 { - err = fmt.Errorf("failed to read pprof profile header") - } else if bs[0] == 0x1f && bs[1] == 0x8b { - var gzipr *gzip.Reader - gzipr, err = gzip.NewReader(bytes.NewReader(bs)) - if err != nil { - err = fmt.Errorf("failed to create pprof profile zip reader: %w", err) - } else { - buf := PPROFBufPool.Get() - if _, err = io.Copy(buf, gzipr); err != nil { - err = fmt.Errorf("failed to decompress gzip: %w", err) - } else { - err = f(buf.Bytes()) - } - PPROFBufPool.Put(buf) - _ = gzipr.Close() - } - } else { - err = f(bs) - } - return err -} - -func stack2string(stack [][]byte, sep string) string { - sb := strings.Builder{} - for i, frame := range stack { - if i != 0 { - sb.WriteString(sep) - } - sb.Write(frame) - } - return sb.String() -} diff --git a/pkg/og/convert/pprof/streaming/parser_streaming_writebatch.go b/pkg/og/convert/pprof/streaming/parser_streaming_writebatch.go deleted file mode 100644 index f086900219..0000000000 --- a/pkg/og/convert/pprof/streaming/parser_streaming_writebatch.go +++ /dev/null @@ -1,288 +0,0 @@ -package streaming - -import ( - "context" - "fmt" - "github.com/cespare/xxhash/v2" - "github.com/grafana/pyroscope/pkg/og/stackbuilder" - "github.com/grafana/pyroscope/pkg/og/util/arenahelper" - "reflect" - "runtime/debug" - "time" - "unsafe" -) - -type ParseWriteBatchInput struct { - Context context.Context - StartTime, EndTime time.Time - Profile, Previous []byte - WriteBatchFactory stackbuilder.WriteBatchFactory -} - -func (p *VTStreamingParser) ParseWithWriteBatch(input ParseWriteBatchInput) (err error) { - defer func() { - if recover() != nil { - err = fmt.Errorf(fmt.Sprintf("parse panic %s", debug.Stack())) - } - }() - p.startTime = input.StartTime - p.endTime = input.EndTime - p.ctx = input.Context - p.wbf = input.WriteBatchFactory - p.cumulative = input.Previous != nil - p.cumulativeOnly = input.Previous != nil - p.wbCache.cumulative = p.cumulative - if input.Previous != nil { - err = p.parseWB(input.Previous, true) - } - if err == nil { - p.cumulativeOnly = false - err = p.parseWB(input.Profile, false) - } - p.ctx = nil - p.wbf = nil - return nil -} - -func (p *VTStreamingParser) parseWB(profile []byte, prev bool) (err error) { - return decompress(profile, func(profile []byte) error { - p.profile = profile - p.prev = prev - err := p.parseDecompressedWB() - p.profile = nil - return err - }) -} - -func (p *VTStreamingParser) parseDecompressedWB() (err error) { - if err = p.countStructs(); err != nil { - return err - } - if err = p.parseFunctionsAndLocations(); err != nil { - return err - } - if !p.haveKnownSampleTypes() { - return nil - } - err = p.UnmarshalVTProfile(p.profile, opFlagParseSamplesWriteBatch) - if !p.prev { - p.wbCache.flush() - } - return err -} - -func (p *VTStreamingParser) parseSampleWB(buffer []byte) error { - p.tmpSample.reset(p.arena) - err := p.tmpSample.UnmarshalSampleVT(buffer, p.arena) - if err != nil { - return err - } - - for i := len(p.tmpSample.tmpStackLoc) - 1; i >= 0; i-- { - err = p.addStackLocation(p.tmpSample.tmpStackLoc[i]) - if err != nil { - return err - } - } - - cont := p.mergeCumulativeWB() - if !cont { - return nil - } - p.appendWB() - return nil -} - -func (p *VTStreamingParser) appendWB() { - for _, vi := range p.indexes { - v := uint64(p.tmpSample.tmpValues[vi]) - if v == 0 { - continue - } - wb := p.wbCache.getWriteBatch(p, vi) - if wb == nil { - continue - } - - sb := wb.wb.StackBuilder() - sb.Reset() - for _, frame := range p.tmpSample.tmpStack { - sb.Push(frame) - } - stackID := sb.Build() - - wb.getAppender(p, p.tmpSample.tmpLabels).Append(stackID, v) - - if !p.cumulative { - if j := findLabelIndex(p.tmpSample.tmpLabels, p.profileIDLabelIndex); j >= 0 { - copy(p.tmpSample.tmpLabels[j:], p.tmpSample.tmpLabels[j+1:]) - p.tmpSample.tmpLabels = p.tmpSample.tmpLabels[:len(p.tmpSample.tmpLabels)-1] - wb.getAppender(p, p.tmpSample.tmpLabels).Append(stackID, v) - } - } - } -} - -func (p *VTStreamingParser) mergeCumulativeWB() (cont bool) { - if !p.cumulative { - return true - } - - h := p.tmpSample.hashStack(p.arena) - for _, vi := range p.indexes { - v := p.tmpSample.tmpValues[vi] - if v == 0 { - continue - } - wb := p.wbCache.getWriteBatch(p, vi) - if wb == nil { - continue - } - if p.prev { - wb.prev[h] += v - } else { - prevV, ok := wb.prev[h] - if ok { - if v > prevV { - wb.prev[h] = 0 - v -= prevV - } else { - wb.prev[h] = prevV - v - v = 0 - } - p.tmpSample.tmpValues[vi] = v - } - } - } - if p.prev { - return false - } - return true -} - -type writeBatchCache struct { - // sample type -> write batch - wbs []wbw - - cumulative bool -} - -type wbw struct { - wb stackbuilder.WriteBatch - appName string - appenders map[uint64]stackbuilder.SamplesAppender - - // stackHash -> value for cumulative prev - prev map[uint64]int64 -} - -func (c *writeBatchCache) reset() { - for i := range c.wbs { - c.wbs[i].wb = nil - c.wbs[i].appenders = nil - } -} - -func (c *writeBatchCache) getWriteBatch(parser *VTStreamingParser, sampleTypeIndex int) *wbw { - if sampleTypeIndex >= len(c.wbs) { - sz := sampleTypeIndex + 1 - if sz < 4 { - sz = 4 - } - newSampleTypes := arenahelper.MakeSlice[wbw](parser.arena, sz, sz) - copy(newSampleTypes, c.wbs) - c.wbs = newSampleTypes - } - p := &c.wbs[sampleTypeIndex] - if p.wb == nil { - appName, metadata := parser.getAppMetadata(sampleTypeIndex) - if appName == "" { - return nil - } - wb, err := parser.wbf.NewWriteBatch(appName, metadata) - if err != nil || wb == nil { - return nil - } - p.wb = wb - p.appName = appName - p.appenders = make(map[uint64]stackbuilder.SamplesAppender) - if c.cumulative { - p.prev = make(map[uint64]int64) - } - } - return p -} - -func (c *writeBatchCache) flush() { - for i := range c.wbs { - wb := c.wbs[i].wb - if wb != nil { - wb.Flush() - c.wbs[i].wb = nil - c.wbs[i].appenders = nil - } - } -} - -func (w *wbw) getAppender(parser *VTStreamingParser, labels Labels) stackbuilder.SamplesAppender { - h := labels.Hash() - e, found := w.appenders[h] - if found { - return e - } - allLabels := w.resolveLabels(parser, labels) - e = w.wb.SamplesAppender(parser.startTime.UnixNano(), parser.endTime.UnixNano(), allLabels) - w.appenders[h] = e - return e -} - -func (w *wbw) resolveLabels(parser *VTStreamingParser, labels Labels) []stackbuilder.Label { - labelsSize := len(parser.labels) + len(labels) - allLabels := arenahelper.MakeSlice[stackbuilder.Label](parser.arena, 0, labelsSize) - for k, v := range parser.labels { - if k == "__name__" { - v = w.appName - } - allLabels = append(allLabels, stackbuilder.Label{Key: k, Value: v}) - } - for _, label := range labels { - k := label >> 32 - if k != 0 { - v := label & 0xffffffff - bk := parser.string(int64(k)) - bv := parser.string(int64(v)) - //sk := "" - //if len(bk) != 0 { - // //sk = unsafe.String(&bk[0], len(bk)) - // sk = unsafe.String(&bk[0], len(bk)) - //} - //sv := "" - //if len(bv) != 0 { - // sv = unsafe.String(&bv[0], len(bv)) - //} - sk := string(bk) - sv := string(bv) - allLabels = append(allLabels, stackbuilder.Label{Key: sk, Value: sv}) - } - } - return allLabels -} - -func (s *sample) hashStack(a arenahelper.ArenaWrapper) uint64 { - if len(s.tmpStack) == 0 { - return zeroHash - } - s.stackHashes = grow(a, s.stackHashes, len(s.tmpStack)) - for _, frame := range s.tmpStack { - h := xxhash.Sum64(frame) - s.stackHashes = append(s.stackHashes, h) - } - - var raw []byte - sh := (*reflect.SliceHeader)(unsafe.Pointer(&raw)) - sh.Data = uintptr(unsafe.Pointer(&s.stackHashes[0])) - sh.Len = len(s.stackHashes) * 8 - sh.Cap = len(s.stackHashes) * 8 - res := xxhash.Sum64(raw) - return res -} diff --git a/pkg/og/convert/pprof/streaming/profile_finder.go b/pkg/og/convert/pprof/streaming/profile_finder.go deleted file mode 100644 index 503d84882c..0000000000 --- a/pkg/og/convert/pprof/streaming/profile_finder.go +++ /dev/null @@ -1,105 +0,0 @@ -package streaming - -import ( - "sort" -) - -func newFinder(functions []function, locations []location) finder { - res := finder{functions: functions, locations: locations} - if !locationSlice(locations) { - res.locationsMap = locationMap(locations) - } - if !functionSlice(functions) { - res.functionsMap = functionMap(functions) - } - return res -} - -type finder struct { - functions []function - locations []location - functionsMap map[uint64]*function - locationsMap map[uint64]*location -} - -func (f *finder) FindLocation(id uint64) (*location, bool) { - if f.locationsMap == nil { - idx := id - 1 - return &f.locations[idx], true - } - l, ok := f.locationsMap[id] - return l, ok -} - -func (f *finder) FindFunction(id uint64) (*function, bool) { - if f.functionsMap == nil { - idx := id - 1 - return &f.functions[idx], true - } - ff, ok := f.functionsMap[id] - return ff, ok -} - -func locationSlice(locations []location) (ok bool) { - // Check if it's already sorted first - max := uint64(0) - sorted := true - for i, l := range locations { - if l.id != uint64(i+1) { - sorted = false - if l.id > max { - max = l.id - } - } - } - if max > uint64(len(locations)) { - // IDs are not consecutive numbers starting at 1, a slice is not good enough - return false - } - if !sorted { - sort.Slice(locations, func(i, j int) bool { - return locations[i].id < locations[j].id - }) - } - return true -} - -func locationMap(locations []location) map[uint64]*location { - m := make(map[uint64]*location, len(locations)) - for i := range locations { - m[locations[i].id] = &locations[i] - } - return m -} - -func functionSlice(functions []function) (ok bool) { - // Check if it's already sorted first - max := uint64(0) - sorted := true - for i, f := range functions { - if f.id != uint64(i+1) { - sorted = false - if f.id > max { - max = f.id - } - } - } - if max > uint64(len(functions)) { - // IDs are not consecutive numbers starting at one, this won't work - return false - } - if !sorted { - sort.Slice(functions, func(i, j int) bool { - return functions[i].id < functions[j].id - }) - } - return true -} - -func functionMap(functions []function) map[uint64]*function { - m := make(map[uint64]*function, len(functions)) - for i := range functions { - m[functions[i].id] = &functions[i] - } - return m -} diff --git a/pkg/og/convert/pprof/streaming/structs.go b/pkg/og/convert/pprof/streaming/structs.go deleted file mode 100644 index e6a6cd3da6..0000000000 --- a/pkg/og/convert/pprof/streaming/structs.go +++ /dev/null @@ -1,63 +0,0 @@ -package streaming - -import ( - "github.com/grafana/pyroscope/pkg/og/storage/segment" - "github.com/grafana/pyroscope/pkg/og/util/arenahelper" -) - -var ( - profileIDLabel = []byte(segment.ProfileIDLabelName) -) - -type valueType struct { - Type int64 - unit int64 - - resolvedType string - resolvedUnit string -} -type function struct { - id uint64 - name int32 - filename int32 -} - -type location struct { - id uint64 - // packed from << 32 | to into values - linesRef uint64 -} - -type line struct { - functionID uint64 - line int64 -} - -// from,to into profile buffer -type istr uint64 - -type sample struct { - tmpValues []int64 - // k<<32|v - //type labelPacked uint64 - tmpLabels []uint64 - tmpStack [][]byte - stackHashes []uint64 - tmpStackLoc []uint64 - //todo rename - remove tmp prefix -} - -func (s *sample) reset(a arenahelper.ArenaWrapper) { - // 64 is max pc for golang + speculative number of inlines - if s.tmpStack == nil { - s.tmpStack = arenahelper.MakeSlice[[]byte](a, 0, 64+8) - s.tmpStackLoc = arenahelper.MakeSlice[uint64](a, 0, 64+8) - s.tmpValues = arenahelper.MakeSlice[int64](a, 0, 4) - s.tmpLabels = arenahelper.MakeSlice[uint64](a, 0, 4) - } else { - s.tmpStack = s.tmpStack[:0] - s.tmpStackLoc = s.tmpStackLoc[:0] - s.tmpValues = s.tmpValues[:0] - s.tmpLabels = s.tmpLabels[:0] - } -} diff --git a/pkg/og/convert/pprof/streaming/vt_function.go b/pkg/og/convert/pprof/streaming/vt_function.go deleted file mode 100644 index 566c121436..0000000000 --- a/pkg/og/convert/pprof/streaming/vt_function.go +++ /dev/null @@ -1,167 +0,0 @@ -package streaming - -import ( - "fmt" - "io" - "math" -) - -// revive:disable-next-line:cognitive-complexity,cyclomatic necessary complexity -func (m *function) UnmarshalVT(dAtA []byte) error { - m.id = 0 - m.name = 0 - m.filename = 0 - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - //preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Function: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Function: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) - } - m.id = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.id |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) - } - name := int64(0) - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - name |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - if name < 0 || name > math.MaxInt32 { - return fmt.Errorf("wromg mamae %d", name) - } - m.name = int32(name) - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field SystemName", wireType) - } - //m.SystemName = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - //m.SystemName |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Filename", wireType) - } - filename := int64(0) - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - filename |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - if filename < 0 || filename > math.MaxInt32 { - return fmt.Errorf("wromg filename %d", filename) - } - m.filename = int32(filename) - case 5: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field StartLine", wireType) - } - //m.StartLine = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - //m.StartLine |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - default: - return ErrUnknownField - //iNdEx = preIndex - //skippy, err := skip(dAtA[iNdEx:]) - //if err != nil { - // return err - //} - //if (skippy < 0) || (iNdEx+skippy) < 0 { - // return ErrInvalidLength - //} - //if (iNdEx + skippy) > l { - // return io.ErrUnexpectedEOF - //} - //m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - //iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} diff --git a/pkg/og/convert/pprof/streaming/vt_label.go b/pkg/og/convert/pprof/streaming/vt_label.go deleted file mode 100644 index be8c8dfcb4..0000000000 --- a/pkg/og/convert/pprof/streaming/vt_label.go +++ /dev/null @@ -1,126 +0,0 @@ -package streaming - -import ( - "fmt" - "io" -) - -// revive:disable-next-line:cognitive-complexity,cyclomatic necessary complexity -func UnmarshalVTLabel(dAtA []byte) (uint64, error) { - k := int64(0) - v := int64(0) - - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - //preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflow - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return 0, fmt.Errorf("proto: Label: wiretype end group for non-group") - } - if fieldNum <= 0 { - return 0, fmt.Errorf("proto: Label: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return 0, fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) - } - k = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflow - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - k |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 2: - if wireType != 0 { - return 0, fmt.Errorf("proto: wrong wireType = %d for field Str", wireType) - } - v = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflow - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 3: - if wireType != 0 { - return 0, fmt.Errorf("proto: wrong wireType = %d for field Num", wireType) - } - //m.Num = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflow - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - //m.Num |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 4: - if wireType != 0 { - return 0, fmt.Errorf("proto: wrong wireType = %d for field NumUnit", wireType) - } - //m.NumUnit = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflow - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - //m.NumUnit |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - default: - return 0, ErrUnknownField - } - } - - if iNdEx > l { - return 0, io.ErrUnexpectedEOF - } - return uint64(k<<32 | v), nil -} diff --git a/pkg/og/convert/pprof/streaming/vt_line.go b/pkg/og/convert/pprof/streaming/vt_line.go deleted file mode 100644 index a4a6dbf2c0..0000000000 --- a/pkg/og/convert/pprof/streaming/vt_line.go +++ /dev/null @@ -1,88 +0,0 @@ -package streaming - -import ( - "fmt" - "io" -) - -// revive:disable-next-line:cognitive-complexity,cyclomatic necessary complexity -func (m *line) UnmarshalVT(dAtA []byte) error { - m.functionID = 0 - m.line = 0 - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - //preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Line: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Line: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FunctionId", wireType) - } - m.functionID = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.functionID |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Line", wireType) - } - iline := int64(0) - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - iline |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.line = iline - default: - return ErrUnknownField - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} diff --git a/pkg/og/convert/pprof/streaming/vt_location.go b/pkg/og/convert/pprof/streaming/vt_location.go deleted file mode 100644 index 5d5b43bcdb..0000000000 --- a/pkg/og/convert/pprof/streaming/vt_location.go +++ /dev/null @@ -1,193 +0,0 @@ -package streaming - -import ( - "fmt" - "github.com/grafana/pyroscope/pkg/og/util/arenahelper" - "io" -) - -// location references into lines slice as packed uint64(from << 32 | to) -type locationFunctions struct { - lines []line -} - -func (l *locationFunctions) reset(a arenahelper.ArenaWrapper, nLocations int) { - l.lines = grow(a, l.lines, nLocations*2) -} - -// revive:disable-next-line:cognitive-complexity,cyclomatic necessary complexity -func (m *location) UnmarshalVT(dAtA []byte, functions *locationFunctions, a arenahelper.ArenaWrapper) error { - var tmpLine line - m.id = 0 - m.linesRef = 0 - refFrom := len(functions.lines) - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - //preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Location: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Location: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) - } - m.id = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.id |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field MappingId", wireType) - } - //m.MappingId = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - //m.MappingId |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType) - } - //m.Address = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - //m.Address |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Line", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - //m.Line = append(m.Line, &Line{}) - if err := tmpLine.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - if len(functions.lines) < cap(functions.lines) { - functions.lines = append(functions.lines, tmpLine) - } else { - functions.lines = arenahelper.AppendA(functions.lines, tmpLine, a) - } - iNdEx = postIndex - case 5: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field IsFolded", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - //m.IsFolded = bool(v != 0) - default: - return ErrUnknownField - //iNdEx = preIndex - //skippy, err := skip(dAtA[iNdEx:]) - //if err != nil { - // return err - //} - //if (skippy < 0) || (iNdEx+skippy) < 0 { - // return ErrInvalidLength - //} - //if (iNdEx + skippy) > l { - // return io.ErrUnexpectedEOF - //} - //m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - //iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - refTo := len(functions.lines) - ref := uint64(refFrom<<32 | refTo) - m.linesRef = ref - return nil -} diff --git a/pkg/og/convert/pprof/streaming/vt_profile.go b/pkg/og/convert/pprof/streaming/vt_profile.go deleted file mode 100644 index 035d0c1d68..0000000000 --- a/pkg/og/convert/pprof/streaming/vt_profile.go +++ /dev/null @@ -1,519 +0,0 @@ -package streaming - -import ( - "bytes" - "fmt" - "io" -) - -const ( - opFlagCountStructs = 1 << 0 - opFlagParseStructs = 1 << 1 - opFlagParseSamples = 1 << 2 - opFlagParseSamplesWriteBatch = 1 << 3 -) - -// revive:disable-next-line:cognitive-complexity,cyclomatic necessary complexity -func (p *VTStreamingParser) UnmarshalVTProfile(dAtA []byte, opFlag uint64) error { - l := len(dAtA) - iNdEx := 0 - countStructs := opFlag&opFlagCountStructs == opFlagCountStructs - parseStructs := opFlag&opFlagParseStructs == opFlagParseStructs - parseSamples := opFlag&opFlagParseSamples == opFlagParseSamples - parseSamplesWriteBatch := opFlag&opFlagParseSamplesWriteBatch == opFlagParseSamplesWriteBatch - strWriteIndex := 0 - locationWriteIndex := 0 - functionWriteIndex := 0 - sampleTypeWriteIndex := 0 - p.strings = p.strings[:p.nStrings] - p.locations = p.locations[:p.nLocations] - p.functions = p.functions[:p.nFunctions] - p.sampleTypes = p.sampleTypes[:p.nSampleTypes] - if countStructs { - p.period = 0 - p.nStrings = 0 - p.nFunctions = 0 - p.nLocations = 0 - p.nSampleTypes = 0 - } - for iNdEx < l { - //preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Profile: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Profile: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SampleType", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if countStructs { - p.nSampleTypes++ - } - if parseStructs { - //p.sampleTypes = append(p.sampleTypes, valueType{}) - if err := p.sampleTypes[sampleTypeWriteIndex].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - sampleTypeWriteIndex++ - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Sample", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if parseSamples { - if err := p.parseSampleVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - } - if parseSamplesWriteBatch { - if err := p.parseSampleWB(dAtA[iNdEx:postIndex]); err != nil { - return err - } - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Mapping", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Location", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if countStructs { - p.nLocations++ - } - if parseStructs { - //p.locations = append(p.locations, location{}) - if err := p.locations[locationWriteIndex].UnmarshalVT(dAtA[iNdEx:postIndex], &p.lineRefs, p.arena); err != nil { - return err - } - locationWriteIndex++ - } - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Function", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if countStructs { - p.nFunctions++ - } - if parseStructs { - //p.functions = append(p.functions, function{}) - if err := p.functions[functionWriteIndex].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - functionWriteIndex++ - } - iNdEx = postIndex - case 6: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field StringTable", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if countStructs { - p.nStrings++ - } - if parseStructs { - s := dAtA[iNdEx:postIndex] - if bytes.Equal(s, profileIDLabel) { - p.profileIDLabelIndex = int64(strWriteIndex) - } - ps := istr(iNdEx<<32 | postIndex) - p.strings[strWriteIndex] = ps - strWriteIndex++ - } - iNdEx = postIndex - case 7: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field DropFrames", wireType) - } - //p.DropFrames = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - //p.DropFrames |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 8: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field KeepFrames", wireType) - } - //p.KeepFrames = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - //p.KeepFrames |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 9: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field TimeNanos", wireType) - } - //p.TimeNanos = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - //p.TimeNanos |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 10: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field DurationNanos", wireType) - } - //p.DurationNanos = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - //p.DurationNanos |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 11: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field periodType", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if parseStructs { - if err := p.periodType.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - } - iNdEx = postIndex - case 12: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field period", wireType) - } - p.period = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - p.period |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 13: - if wireType == 0 { - var v int64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - } else if wireType == 2 { - var packedLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - packedLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if packedLen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + packedLen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - for iNdEx < postIndex { - var v int64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - } - } else { - return fmt.Errorf("proto: wrong wireType = %d for field Comment", wireType) - } - case 14: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field DefaultSampleType", wireType) - } - //p.DefaultSampleType = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - //p.DefaultSampleType |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - default: - return ErrUnknownField - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} diff --git a/pkg/og/convert/pprof/streaming/vt_sample.go b/pkg/og/convert/pprof/streaming/vt_sample.go deleted file mode 100644 index 959956f587..0000000000 --- a/pkg/og/convert/pprof/streaming/vt_sample.go +++ /dev/null @@ -1,316 +0,0 @@ -package streaming - -import ( - "fmt" - "github.com/grafana/pyroscope/pkg/og/util/arenahelper" - - "io" -) - -func (p *VTStreamingParser) parseSampleVT(buffer []byte) error { - p.tmpSample.reset(p.arena) - err := p.tmpSample.UnmarshalSampleVT(buffer, p.arena) - if err != nil { - return err - } - - for i := len(p.tmpSample.tmpStackLoc) - 1; i >= 0; i-- { - err = p.addStackLocation(p.tmpSample.tmpStackLoc[i]) - if err != nil { - return err - } - } - - p.createTrees() - - return nil -} - - - -// revive:disable-next-line:cognitive-complexity,cyclomatic necessary complexity -func (s *sample) UnmarshalSampleVT(dAtA []byte, a arenahelper.ArenaWrapper) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - //preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Sample: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Sample: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType == 0 { - var v uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - //err := p.addStackLocation(v) - //if err != nil { - // return err - //} - //m.LocationId = append(m.LocationId, v) - if len(s.tmpStackLoc) < cap(s.tmpStackLoc) { - s.tmpStackLoc = append(s.tmpStackLoc, v) - } else { - s.tmpStackLoc = arenahelper.AppendA(s.tmpStackLoc, v, a) - } - } else if wireType == 2 { - var packedLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - packedLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if packedLen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + packedLen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - //var elementCount int - //var count int - //for _, integer := range dAtA[iNdEx:postIndex] { - // if integer < 128 { - // count++ - // } - //} - //elementCount = count - //if elementCount != 0 && len(m.LocationId) == 0 { - // m.LocationId = make([]uint64, 0, elementCount) - //} - for iNdEx < postIndex { - var v uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - //m.LocationId = append(m.LocationId, v) - //err := p.addStackLocation(v) - //if err != nil { - // return err - //} - if len(s.tmpStackLoc) < cap(s.tmpStackLoc) { - s.tmpStackLoc = append(s.tmpStackLoc, v) - } else { - s.tmpStackLoc = arenahelper.AppendA(s.tmpStackLoc, v, a) - } - } - } else { - return fmt.Errorf("proto: wrong wireType = %d for field LocationId", wireType) - } - case 2: - if wireType == 0 { - var v int64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - //m.Value = append(m.Value, v) - if len(s.tmpValues) < cap(s.tmpValues) { - s.tmpValues = append(s.tmpValues, v) - } else { - s.tmpValues = arenahelper.AppendA(s.tmpValues, v, a) - } - } else if wireType == 2 { - var packedLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - packedLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if packedLen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + packedLen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - //var elementCount int - //var count int - //for _, integer := range dAtA[iNdEx:postIndex] { - // if integer < 128 { - // count++ - // } - //} - //elementCount = count - //if elementCount != 0 && len(m.Value) == 0 { - // m.Value = make([]int64, 0, elementCount) - //} - for iNdEx < postIndex { - var v int64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - //m.Value = append(m.Value, v) - if len(s.tmpValues) < cap(s.tmpValues) { - s.tmpValues = append(s.tmpValues, v) - } else { - s.tmpValues = arenahelper.AppendA(s.tmpValues, v, a) - } - } - } else { - return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) - } - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Label", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - //m.Label = append(m.Label, &Label{}) - //if err := m.Label[len(m.Label)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - // return err - //} - tmpLabel, err := UnmarshalVTLabel(dAtA[iNdEx:postIndex]) - if err != nil { - return err - } - v := tmpLabel & 0xffffffff - if v != 0 { - if len(s.tmpLabels) < cap(s.tmpLabels) { - s.tmpLabels = append(s.tmpLabels, tmpLabel) - } else { - s.tmpLabels = arenahelper.AppendA(s.tmpLabels, tmpLabel, a) - } - } - iNdEx = postIndex - default: - //iNdEx = preIndex - //skippy, err := skip(dAtA[iNdEx:]) - //if err != nil { - // return err - //} - //if (skippy < 0) || (iNdEx+skippy) < 0 { - // return ErrInvalidLength - //} - //if (iNdEx + skippy) > l { - // return io.ErrUnexpectedEOF - //} - //m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - //iNdEx += skippy - return ErrUnknownField - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -var ( - ErrInvalidLength = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflow = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroup = fmt.Errorf("proto: unexpected end of group") - ErrUnknownField = fmt.Errorf("proto: unknown field") -) diff --git a/pkg/og/convert/pprof/streaming/vt_value_type.go b/pkg/og/convert/pprof/streaming/vt_value_type.go deleted file mode 100644 index 0540440d8b..0000000000 --- a/pkg/og/convert/pprof/streaming/vt_value_type.go +++ /dev/null @@ -1,99 +0,0 @@ -package streaming - -import ( - "fmt" - "io" -) - -func (m *valueType) UnmarshalVT(dAtA []byte) error { - m.unit = 0 - m.Type = 0 - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - //preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ValueType: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ValueType: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) - } - m.Type = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Type |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Unit", wireType) - } - m.unit = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.unit |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - default: - return ErrUnknownField - //iNdEx = preIndex - //skippy, err := skip(dAtA[iNdEx:]) - //if err != nil { - // return err - //} - //if (skippy < 0) || (iNdEx+skippy) < 0 { - // return ErrInvalidLength - //} - //if (iNdEx + skippy) > l { - // return io.ErrUnexpectedEOF - //} - //m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - //iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} diff --git a/pkg/og/convert/pprof/testdata/pyspy-1.pb.gz b/pkg/og/convert/pprof/testdata/pyspy-1.pb.gz new file mode 100644 index 0000000000..e9ef3cd3eb Binary files /dev/null and b/pkg/og/convert/pprof/testdata/pyspy-1.pb.gz differ diff --git a/pkg/og/convert/pprof/testdata/req_2.pprof b/pkg/og/convert/pprof/testdata/req_2.pprof new file mode 100644 index 0000000000..26f25840a0 Binary files /dev/null and b/pkg/og/convert/pprof/testdata/req_2.pprof differ diff --git a/pkg/og/convert/pprof/testdata/req_2.st.json b/pkg/og/convert/pprof/testdata/req_2.st.json new file mode 100644 index 0000000000..3f1608e7fe --- /dev/null +++ b/pkg/og/convert/pprof/testdata/req_2.st.json @@ -0,0 +1 @@ +{"goroutine":{"units":"goroutines","aggregation":"average","display-name":"goroutines"}} \ No newline at end of file diff --git a/pkg/og/convert/pprof/testdata/req_3.pprof b/pkg/og/convert/pprof/testdata/req_3.pprof new file mode 100644 index 0000000000..c76a6b4467 Binary files /dev/null and b/pkg/og/convert/pprof/testdata/req_3.pprof differ diff --git a/pkg/og/convert/pprof/testdata/req_3.st.json b/pkg/og/convert/pprof/testdata/req_3.st.json new file mode 100644 index 0000000000..5e9e6ae136 --- /dev/null +++ b/pkg/og/convert/pprof/testdata/req_3.st.json @@ -0,0 +1 @@ +{"contentions":{"units":"lock_samples","display-name":"block_count"},"delay":{"units":"lock_nanoseconds","display-name":"block_duration"}} \ No newline at end of file diff --git a/pkg/og/convert/pprof/testdata/req_4.pprof b/pkg/og/convert/pprof/testdata/req_4.pprof new file mode 100644 index 0000000000..4adbcae759 Binary files /dev/null and b/pkg/og/convert/pprof/testdata/req_4.pprof differ diff --git a/pkg/og/convert/pprof/testdata/req_4.st.json b/pkg/og/convert/pprof/testdata/req_4.st.json new file mode 100644 index 0000000000..7e9736398b --- /dev/null +++ b/pkg/og/convert/pprof/testdata/req_4.st.json @@ -0,0 +1 @@ +{"contentions":{"units":"lock_samples","display-name":"mutex_count"},"delay":{"units":"lock_nanoseconds","display-name":"mutex_duration"}} \ No newline at end of file diff --git a/pkg/og/convert/pprof/testdata/req_5.pprof b/pkg/og/convert/pprof/testdata/req_5.pprof new file mode 100644 index 0000000000..e1b4638b6b Binary files /dev/null and b/pkg/og/convert/pprof/testdata/req_5.pprof differ diff --git a/pkg/og/convert/pprof/testdata/req_5.st.json b/pkg/og/convert/pprof/testdata/req_5.st.json new file mode 100644 index 0000000000..f045015de0 --- /dev/null +++ b/pkg/og/convert/pprof/testdata/req_5.st.json @@ -0,0 +1 @@ +{"alloc_objects":{"units":"objects"},"alloc_space":{"units":"bytes"},"inuse_objects":{"units":"objects","aggregation":"average"},"inuse_space":{"units":"bytes","aggregation":"average"}} \ No newline at end of file diff --git a/pkg/og/convert/profile_extra_bench_test.go b/pkg/og/convert/profile_extra_bench_test.go deleted file mode 100644 index 12c761754d..0000000000 --- a/pkg/og/convert/profile_extra_bench_test.go +++ /dev/null @@ -1,127 +0,0 @@ -package convert - -import ( - "bytes" - "compress/gzip" - "fmt" - "os" - "testing" - - "github.com/grafana/pyroscope/pkg/og/agent/spy" - "github.com/grafana/pyroscope/pkg/og/storage/tree" -) - -func BenchmarkProfile_Get(b *testing.B) { - buf, _ := os.ReadFile("testdata/cpu.pprof") - g, _ := gzip.NewReader(bytes.NewReader(buf)) - p, _ := ParsePprof(g) - noop := func(labels *spy.Labels, name []byte, val int) error { return nil } - b.ResetTimer() - - b.Run("ByteBufferPool", func(b *testing.B) { - for i := 0; i < b.N; i++ { - _ = p.Get("samples", noop) - } - }) -} - -// parse emulates the parsing work needed to write profiles, without the writing part. -func parse(p *tree.Profile) int { - var b bytes.Buffer - for _, s := range p.Sample { - for i := len(s.LocationId) - 1; i >= 0; i-- { - loc, ok := tree.FindLocation(p, s.LocationId[i]) - if !ok { - continue - } - for j := len(loc.Line) - 1; j >= 0; j-- { - fn, found := tree.FindFunction(p, loc.Line[j].FunctionId) - if !found { - continue - } - if b.Len() > 0 { - _ = b.WriteByte(';') - } - _, _ = b.WriteString(p.StringTable[fn.Name]) - } - } - } - return len(b.Bytes()) -} - -// parseWithCache is like parse, but locations and functions are tabled first. -func parseWithCache(p *tree.Profile) int { - finder := tree.NewFinder(p) - var b bytes.Buffer - for _, s := range p.Sample { - for i := len(s.LocationId) - 1; i >= 0; i-- { - loc, ok := finder.FindLocation(s.LocationId[i]) - if !ok { - continue - } - for j := len(loc.Line) - 1; j >= 0; j-- { - fn, ok := finder.FindFunction(loc.Line[j].FunctionId) - if !ok { - continue - } - if b.Len() > 0 { - _ = b.WriteByte(';') - } - _, _ = b.WriteString(p.StringTable[fn.Name]) - } - } - } - return len(b.Bytes()) -} - -func BenchmarkProfile_ParseNoCache(b *testing.B) { - buf, _ := os.ReadFile("testdata/cpu.pprof") - p, _ := ParsePprof(bytes.NewReader(buf)) - - b.ResetTimer() - - b.Run(fmt.Sprintf("Locations: %d, functions %d", len(p.Location), len(p.Function)), func(b *testing.B) { - for i := 0; i < b.N; i++ { - _ = parse(p) - } - }) -} - -func BenchmarkProfile_ParseWithCache(b *testing.B) { - buf, _ := os.ReadFile("testdata/cpu.pprof") - p, _ := ParsePprof(bytes.NewReader(buf)) - - b.ResetTimer() - - b.Run(fmt.Sprintf("Locations: %d, functions %d", len(p.Location), len(p.Function)), func(b *testing.B) { - for i := 0; i < b.N; i++ { - _ = parseWithCache(p) - } - }) -} - -func BenchmarkProfile_ParseNoCache_Big(b *testing.B) { - buf, _ := os.ReadFile("testdata/cpu-big.pprof") - p, _ := ParsePprof(bytes.NewReader(buf)) - - b.ResetTimer() - - b.Run(fmt.Sprintf("Locations: %d, functions %d", len(p.Location), len(p.Function)), func(b *testing.B) { - for i := 0; i < b.N; i++ { - _ = parse(p) - } - }) -} - -func BenchmarkProfile_ParseWithCache_Big(b *testing.B) { - buf, _ := os.ReadFile("testdata/cpu-big.pprof") - p, _ := ParsePprof(bytes.NewReader(buf)) - - b.ResetTimer() - - b.Run(fmt.Sprintf("Locations %d, functions %d", len(p.Location), len(p.Function)), func(b *testing.B) { - for i := 0; i < b.N; i++ { - _ = parseWithCache(p) - } - }) -} diff --git a/pkg/og/convert/testdata/cpu-big.pprof b/pkg/og/convert/testdata/cpu-big.pprof deleted file mode 100644 index cf5f35e948..0000000000 --- a/pkg/og/convert/testdata/cpu-big.pprof +++ /dev/null @@ -1,4690 +0,0 @@ - - - - - - -9 - - - -  - - -  -  -  -  - - - - - - - - - -  - - - -B - - - - - - -  -! -"# -"$ -%& -' -(  - -)  -  -*  -+   -,   -  -- [ - -. -/. -0 -1 -23 -45 -6 -7 -8 -9: -;< -=< -> -? -@ -A -= -B -C -D -E -F -G  -H -I -J -K -L -M -N -C -O -P -Q -R/ -S -TK -4 -C -UVW -X  -Y"# -Z"$ -["$ -Y"$ -\"$ -]"$ -H^' -_`' -4' -C' -ab - -cd - -eN  -f(   -g   -h  -*,  -i-  -j-  -k-  -l-  -m-  -n-  -o-  -p-  -q-  -r-  - -s- J -t- ) -u-  -v-   -w-   -x-  -y -z{ -|{ -}. -~.U -. -.H - -. -. -N.; -. -. -h/ -|/ -1 - - -6 - -7 - -7 -4 - -3 - -9: - -;< -  - -? - -A -eN - -D - -E  - -G - -J -eN - -P - - - - - -Q - -Q -Q - -RF - -*S - -+S - -,S - -S - - -S - --S> - - - -UVW  -UVW -! -S -"# - -Z"# -Y"# - -\"# -"$ - -Z"$ -"$) -"$ -Y"$  - -f(  - -f(  - -g  -n- 8 -n- h - -n-  - -n-  -s-  -s-  -s- + - -s-  -ps-  - -s-  -s- 1 - -s-  -s-  -y -zy -yJ -{ -h|{ - -|{ - -~. -z.P - -N. -h. -|. -. - -eN.5 -.6 - -B/ - -|/ -|/ -N|/ -h|/B - -|/ - -|/% - -|/ -/ -1 -1 - -I1 - -6 -6 -6 -7  -7 -7 -7  -7 -7 -7 - -3 -  -  -  -A - - -eN -  -Q -Qs -Q4 -Q: -BR -*S -+S -,ST -S --S -S - -i-S - -k-S -n-S -p-S - -q-S -r-S -s-S1 - -t-S - -u-S - -v-S  -w-S - -x-S - -UVW -UVWO -UVW -UVW -UVW -UVW# -UVW% -UVW -X  -! -! -!t -! -!  -R!. -! -! -*S  -+S  -,S - S - S/ --S -Z"# -Y"#  -Y"# -Y"# -Y"#s -Y"#; -Y"#l -Y"# -Y"#K -\"# -"$ -Z"$ -"$ -"$ -"$ -"$_ -"$a -"$ -"$, -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$v -Y"$ -Y"$ -Y"$j -\"$ -d - -d - -f(  -f(  -g   -g  -g  -g  -n-  -n-  -n-  -s-  -s-  -s-  - -eNs-  -s-   -  -y -y- -yP -y -y -y -y -y - -y -y -yʳ -y  -| -eN.b -|. -|. -|. - -|. -|. -h|.* -|. -|. -|.3 -. -eN.? -eN.) - -eN(/ -|/ -|/ -z|/5 -|/ -|/ -|/ -|/ -|/ -|/ -|/ -|/ -|/ -h|/1 -|/ -|/ -|/ -|/ -|/ -|/  -|/ -|/ -|/ -|/ -|/7 - -eN|/d -|/ -|/ -| -eN -  - - - 6 - 6 - 6 - 7 - 7! - 7 - - 7 -7 - 7 - 7 - - 3 -  -  - . -   -  -GD -4GD -  - Q - Q - Q - Q -k-S -n-S -p-S -r-S -s-S -u-S -v-S -w-SN --S -n-S& -n-SE -n-S -n-S -n-S -s-S - -s-S -s-S -s-S  -s-S -s-S  -s-S -s-S\ - - - - - - - -" - - - - - UVW - UVW - UVW -GUVW - UVW - UVW> - UVWX - UVW# - UVW - UVW - UVW6 -4UVW -BUVW - - UVW - UVW - UVW - UVW - UVW - UVW - UVW - UVW - UVW -4UVW - UVW -UVW -UVW -UVW -UVW -4UVWa - UVW} - UVW - UVW8 - UVW - UVW - UVW - X  - -! - -! - -!G - -!O - -! - - -! - -! - -! - -!C - -! - R! - R! - -! - -! - -! - -! -4! -  - *,S  - -*S - -+S - -,S - --S - i-S - k-S - l-S - n-S - p-S - r-S - s-S - t-S - v-S - w-S -  - -"# - Y"#< - Y"#1 - Y"# -Y"# - Y"#i - Y"# - Y"# - Y"# - -5Y"# - Y"# - Y"# - Y"# - Y"# - Y"# -4Y"#B - Y"# - Y"# - Y"# - Y"# - Y"# - \"# - -"$ - -"$ -IZ"$ - -"$ - 5"$ - -"$ - -"$ - -"$ - -"$ - -"$ - B"$ - -"$ - 4"$ - -"$ - -"$  - -"$ - Y"$o - Y"$R - Y"$ -Y"$" - Y"$ - Y"$ - Y"$ - Y"$ - Y"$ - Y"$ - Y"$  - Y"$ - Y"$ -tY"$ - Y"$ - Y"$V -4Y"$ - Y"$ - Y"$ - Y"$ - Y"$ -J\"$ - f(  - g - - g  - g  - -  -s-  -s-  -eNs-  -eNs-  -eNs-  -eNs-  - s-  - y - y - yo - y - y  - y  - y - y - y - y - y@ - y - y - yi - y - y  - y  -uy - y - y - yG - y -eN.7 - |. - |.m -z|." - |. -h|. - |. - |. - |. - |. - |. - |. - |.  - |.  -h|.% - |. - |. - |.  - |. - |. - |. - |. - |. - |. -eN|.$ - |. - |. -eN. - -. - |/ - |/ -eN|/ - |/ - |/ - |/ - |/ - |/ - |/O - |/  - |/' - |/ - |/ -eN|/ - |/ - |/ - |/ - -| - -| - eN - eN -  - 6 - 6 - 7 - 7 - -7 - 7 -  - 3 -  -  -  -  - Q - Q  - Q - Q - Q - Q - n-S - n-S - s-S - s-S  - s-S - s-S - s-S - n-S -s-S - s-S - s-S -eNs-S - s-S - s-S -  -  -  -  - F - H, -  -  -  -  -  - UVW - UVW - UVW - -ZUVW - -ZUVW - UVW - UVW - UVW - UVW - UVW  - -5UVW - UVW - UVW - UVW - UVW5 - -4UVW - UVW' - -UVW - UVW - UVW - UVW- - UVW - UVW- - UVW - UVW - UVW - UVW - UVW5 - UVW= - -UVW - -UVW - -UVW - -UVW - -UVW - -UVW3 - -UVW - -UVW - -UVW - -oUVW - UVW - UVW - UVW - X  - !q - !: - !M - ! - !  - H! - ! - ! - ! - ! - !  - ! - !  - ! - ! - ! - ! - ! - ! - 4! - -BR! - ! - j-S - k-S - n-S - p-S - r-SC - s-S - w-S - n-S - n-S! - s-S - s-S - s-S - s-S - s-S - s-S - s-S - 4 - T - "# - TKZ"# - Z"# - Y"#_ - Y"# - Y"# - Y"# - Y"# - Y"# - Y"# - - -4Y"#l - -5Y"#) - -Y"# - -Y"# - -Y"# - 4Y"# - Y"#% - Y"# - Y"# - - Y"#_ - Y"# - Y"#= - Y"# - Y"# - Y"# - Y"# - Y"# - "$ - "$ - Z"$ - -5Z"$ - Z"$ - Z"$ - 5"$ - -45"$ - "$ - "$ - "$ - "$ - "$^ - "$ - "$ - "$& - "$ - "$ - "$ - "$ - Y"$ - Y"$ - Y"$ - Y"$ - Y"$  - Y"$ - Y"$ - Y"$ - Y"$ - Y"$ - -4Y"$ - -5Y"$O - -Y"$ - -Y"$ - -Y"$ - 4Y"$B - -Y"$ - Y"$D - Y"$ - Y"$ - Y"$ - -5Y"$ - 45Y"$ - Y"$ - Y"$ - -BY"$ - Y"$ - Y"$ - Y"$ - Y"$ - Y"$ - Y"$ - Y"$ - Y"$ - Y"$ - Y"$ - -\"$ - 4\"$ - ' - g  - g  - g  - -s-  - -s- g - -s-  - -s-  -eNs-  - -s-  - eNs- 8 - s- & -  - y - eNy - -hy - y - y - y - y - y - y - y - -iy - -oy - y - y - y) - y  - y - y - y - y - y - y - y( -  - |. - |.4 - |. - eN|. - |. - |. - |. - |." - |. - |. - - eN|. - eN|.$ - |. - B/ - |/  - |/ - |/0 - eN|/ - |/ - |/ - |/1 - |/ - |/ - |/ - |/ - eN|/ - |/ -  - I1 - - 5 - 6 - 57 - 457 - 7 - 7 - 7 - 7  - 3 - - B - - - Q - Q - Q} - 4Q - n-S - -s-S - s-S - eNs-S - s-S - -s-S - -s-S - s-S - eNs-S - eNs-S - s-SZ - s-S - - H - - - - UVW - UVW - ZUVW - UVW - UVW - UVW - 4UVW - UVW - UVW - UVWa - BUVW - 5UVW - UVW - UVWx - UVW - 4UVW - UVW - UVW - UVW - UVWR - UVW  - UVW - UVW - UVW - wUVW - UVW - UVW - UVW - UVW - UVW - X  -!= -! -! -! -! -! -! -! -! -! -!  -! - t! -!( - B! -!  -! - -!f - !@ - 4!  -! -! - 5! - !7 -! -! - B! -! -! - !) -! -! -! -! -! - BR! - R! -! -! - 5! -! -! -! - - - n-S - n-S - s-S - s-S@ - s-S - s-S - s-S - s-S - n-S - n-S - s-S - s-S - eNs-S  - s-S - - - Y"# - Y"# - Y"# - Y"# - Y"# - 5Y"# - 5Y"# - 45Y"# - Y"# - Y"# - Y"# - Y"#b - Y"# - Y"# - Y"# - Y"# - Y"# - Y"# - Y"# - 4Y"#e - Y"# - Y"#5 - Y"# - Y"# - 5Y"# - BY"# - Y"# - Y"# - Y"# - Z"$ - Z"$ - Z"$ - Z"$ - Z"$ -"$ -"$ - B"$ -"$  - Y"$ - Y"$ - Y"$ - Y"$ - Y"$ - Y"$ - 5Y"$ - 5Y"$ - 5Y"$ - 5Y"$ - 45Y"$j - 5Y"$ - Y"$ - Y"$ - Y"$J - Y"$ - Y"$ - Y"$ - Y"$ - Y"$ - Y"$ - tY"$ - Y"$ - Y"$ - Y"$ - Y"$ - 4Y"$ - Y"$ - Y"$! - Y"$& - Y"$ - Y"$ - 5Y"$ - BY"$ - Y"$# - Y"$ - Y"$ - Y"$ - \"$ - ab - - g  - s-  - s- 6 - s-  - s-  - s-  - s- m - -eNs- 4 - -eNs-  - - - y - y - yz - y - y - y - eN|. - |. - |.S - |. - |. - eN|. - |.  - |. - |/ - |/  - |/ - |/- - |/ -| - I1 - -57  - 457 -7  -7 - -B - - - - - - - -Q -Q -Q - s-S - s-S - s-S? - s-S - s-S - -eNs-S - s-S - eNs-S - s-S< - - - - - - -UVW -UVW - 4UVW -ZUVW -ZUVW - ZUVW -ZUVW -UVW -UVW -UVW -UVW -5UVW -5UVW -5UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -jUVW -UVW -UVW -UVW -UVW -jUVW -lUVW -UVW -*UVW -+UVW -,UVW -UVW -UVW -UVW -UVW -X  -! -B! -!~ -! -! -!- -! -5! -B! -B! -! -! -! -! -!1 -! -! -! -! -! -! -! -! -4! -! -B! -B!  -5! -! -! -! -! -4! -! -B! -! -! -t! -! -! -! -! -4! -! -! -! -! -! -4!N -! -!6 -! -! -! -! -!$ -! - BR! -! -! -! -! -! -! -eN -s-S -s-S -s-S -s-S% -s-S -s-S - - - - -Y"#= -Y"#o -Y"# -Y"# -Y"# -Y"# -Y"#  -Y"# -Y"#  -Y"# -Y"# -Y"# -Y"# -Y"# -Y"# -Y"#  -Y"# -eN"$ -"$ -"$ - 4Y"$ -Y"$t -Y"$ -Y"$ -Y"$ -5Y"$ -5Y"$ -5Y"$ -Y"$  -Y"$ -Y"$ -Y"$ -Y"$  -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$% -Y"$ -Y"$ -lY"$ -Y"$ -Y"$ -$ -%& - -' -s- F -s-  - eNs- F -s- # - - -y -|.U -|. -|. -|/ -|/ -|/ -|/ -|/  - -57 -57 -57 -57 -7 -7 - -  -G - -5 - - - - -Q -Q -BR -s-S -s-S> -s-S - eNs-Sp -s-S, -s-S -s-S= -s-S -s-S -s-S= - eNs-S$ - eNs-S -I - - - - -UVW -UVWj -UVWh -UVW  -UVW -UVW -UVW -UVW -UVW -ZUVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -tUVW -UVW -UVW -UVWd -UVW -UVW -\UVW -\UVW -UVW -5UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW  -UVWw -UVW -sUVW& -UVW -UVW -wUVW -UVW -UVW -UVW -uUVWd -UVW -UVW -X  -! -! -! -! -4! -! -! -! -4!  -! -! -! -!3 -! -4! -! -! -w! -! -!  -! -!T -! -! -! -! -! -! -! -! -! -! -! -! -! -! -! -! -!  -!$ -! -! -! -! -! -! -! -!K -BR! -!Q -! -! -! -! -! -!3 -!k -!W -! -! -! -! -!E -!  -! -! -! -! -! -! -! -! -s-S -eNs-S -s-S -s-S! -s-S -s-S -s-ST -eNs-S -s-S` -eNs-S -s-S - - -"# -5Y"#; -Y"# -Y"# -,Y"# -5Y"# -Y"#  -Y"# -Y"#) -Y"# -Y"# -Y"# -Y"#) -"$ -PZ"$ -LZ"$ -Z"$ -"$ -"$ -"$ -"$ -Y"$ -BY"$ -5Y"$U -Y"$ -Y"$& -Y"$ -5Y"$ -Y"$ -Y"$ -Y"$ - -Y"$@ -Y"$ -Y"$ -5Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$3 -Y"$ -Y"$, -s-  -s-  -s-  -s-  -y -y -|. -|. -|/ -|/ -|/ -|/ -57 -57 -57 -76 -7 -7 -7 -z7 -3 - - -  - - - - - -Q -Q -Q% -s-S -s-S -s-S -eNs-S -eNs-S -s-S9 -s-S -s-S -s-S -eNs-S -s-S - - - - - -UVW -UVW" -UVW -UVWW -UVW -UVW% -5UVW -UVW - -UVW -UVW -OUVW -UVW -ZUVW -UVW -UVW -UVW -UVW -UVWC -UVW  -UVW -BUVWb -UVW -UVW -UVW -UVW -UVW  -4UVW -UVW& -UVW1 -UVW -UVW  -UVW\ -BUVW -BUVW -UVW -UVW -UVW -UVW -UVW -UVWW -4UVW -iUVW -oUVW -UVW -UVW -UVW -UVW - -UVW -UVW -UVW  -UVW -\UVW -J\UVW -\UVW -UVW -5UVW -BUVW -UVW -UVW -UVW" -UVW -sUVW -UVW  -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -X  -B! -! -!: -! -! -! -! -! -! -!3 -! -!= -!" -! -! -! -! -! -!3 -!% -j!n -l! -!J -*! -+! -,! -! -! -! -!  -! -! -!6 -! -w! -! -! -5! -!  -! -! -! -! -! -! -!$ -BR! -BR!  -uBR! -BR! -! -! -!  -! -!I -!! -! -! -! -!  -!7 -! -! -! -! -4! -B!l -! -!  -! -![ -4! -! -!% -! -!. -! -!" -! -! -!` -!H -! -! -! -!z -! -! -s-S -s-S -s-S4 -s-S -eNs-S -s-S! -s-S -s-S -s-S. -s-S -eNs-S -eNs-S - - -5 - - - - - -5Y"# -5Y"# -5Y"# -45Y"#5 -Y"# -l5Y"# -Y"# -Y"# -Y"#* -Y"# -lY"# -Y"# -Y"# -Y"# -Y"# -Y"#C -"$ -Z"$ -"$ - -4"$ -"$ -"$ -"$ -5Y"$ -5Y"$ -5Y"$ -5Y"$ -45Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$" -lY"$ -Y"$ -Y"$ -5Y"$ -5Y"$ -BY"$ -BY"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$] -Y"$ -' -' -y -y -y -Bf(/ -z|/ -|/ -|/ -|/ -|/ -|/ -| -57 -7" -7 -7 -7 - - - - -Q -Q -Q -Q -s-S -eNs-S -s-S -s-S -s-S -s-S - - - - - - -UVW -BUVW -UVW -UVW -UVW -UVW -UVW -UVW -4UVW -UVW -UVW -UVW  -UVW -UVW -5UVW -UVW -UVW -UVW -4UVW -UVW -eNZUVW -UVW -UVW -UVW -UVWU -UVW -BUVW -BUVW9 -UVW -UVW: -UVW -UVW -UVW  -UVW -4UVW -UVW -UVW -oUVW -UVW -UVW -UVW -UVW -oUVW -UVW -UVW -BUVWI -UVW -tUVW -UVW -UVW -4UVW -UVW -BUVW -BUVW -UVW -UVW -UVW -UVW -UVW -oUVW -UVWU -UVW -UVW -5UVW -UVW -UVW -UVW - -UVW -UVW -UVW -UVW -UVW -UVW -iUVW -oUVW -UVW -UVW -! - -! -! -! -! - -! -p! -!  -! -! -q! -s! -! -t! -w!A -! -! -! -! -! -! -! -!h -j! -l! -! -! -eN! -!  -B! -! -! -! -! -! -s! -! -BR! -BR! -BR! -BR! -BR! -BR! -BR! -!o -! -! -!v -!o -! -!  -! -!$ -! -!Q -! -!o -! -!_ -! -! -!  -! -!  -! -! -!j -! -! -! -! -! -4! -! -! -! -!@ -! -!  -! -! -! -! -!Y -! -! -! -s-S  -eNs-S -eNs-S -s-S -s-S -s-S -s-S -eNs-S -s-S - -5 - - - -5Y"# -5Y"# -Y"# -Y"# -Y"# -Y"# -Y"# -uY"# -Y"#v -Y"# -Y"# -Y"# -"$ -"$ -B"$ -"$% -5Y"$  -5Y"$ -5Y"$ -5Y"$ -5Y"$ -Y"$ -Y"$ -5Y"$ -5Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -sY"$ -Y"$ -Y"$c -Y"$ -uY"$ -Y"$ -Y"$` -BY"$ -Y"$ -Y"$ -eNY"$ -Y"$ -oY"$ -B\"$ -' -s -y -y -y -uy -|/ -|/ -z|/ -|/ -57 -57 -57 -7$ -7  -3 - - - - -s-S$ -s-S -5 - - -UVW -UVW -UVW -UVW -UVW -UVW -UVW  -UVW -UVW -5UVW -5UVW -5UVW4 -UVW -UVW -UVW% -UVW -UVW -ZUVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVWZ -UVW -5UVW -UVW - -4UVW -5UVW -UVW -UVW+ -UVW -UVW -UVW -UVW -UVW -UVW -UVWb -UVW -UVW -UVW3 -UVW -UVW - -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -BUVW -UVW -UVW -BUVW -UVW -UVW -UVW -UVW -UVW -X  -B! -B!  -! -!  -! -!( -j! -l! -! -B! -B! -uB! -!  -!  -! -! -! -! -s! -s! -s! -s! -! -! -eN! -! -! -! -uB! -! -!i -!N -!  -! -B! -u! -! -! -! -! -! -! -! -u!Z -! -BR! -oBR!> -BR!n -BR! -BR! -! -B! -!> -! -!" -!  -!H -! -4! -! -! -! -! -!  -! -!0 -! -B!3 -! -t! -!  -! -! -! -! -4! -!N -B!Q -! -! -! -! -4! -! -! -!R -! -! -!  -! -! -! -! -! -! -! -!K -! -! -! -! -!@ -! -! -5! -! -s-S -eNs-S -s-S -s-S -s-S -s-S -5P - - - - -  - - -  -o -B -"# -Z"# -5Y"# -Y"# -Y"#0 -Y"# -Y"# -sY"# -Y"# -Y"# -5Y"# -Y"# -"$ -Z"$ -B"$ -B"$ -"$ -5Y"$ -Y"$a -Y"$ -Y"$ -Y"$ -5Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$& -sY"$ -Y"$ -Y"$ -Y"$ -Y"$ -5Y"$ -BY"$ -Y"$ -' -y -|{ -|/  -|/ -57 -7 -7 -7  -3 -3 - - - - - -eN - -UVW -UVW  -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW - -UVW: -UVW -UVW -UVW8 -UVW -UVW -UVW -UVWf -UVW -5UVW -UVW -5UVW -5UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVWJ -jUVW -lUVW -,UVW -UVW -UVWS -UVW -UVW -UVW -BUVW -BUVW -UVW -UVW -UVW -UVW -UVW -UVW -5UVW -UVW -UVW -UVW -UVW -UVW -eNsUVW -UVW -UVW -B! - -! -! -! -! -! -!  -!  -B! -! -! -! -! -! -eNs! -s! -! -! -eN! -! -! -! - -! -B! - -! -! -B! -B! -! -! -! -s! -! -! -! -BR! -!  -! -! -!, -! -! -! -! -! -! -! -! -4! -! -! -!# -o! -! -! -! -! -! -! -! -! -! -B! -! -! -! -! -! -!, -!x -! -! -! -! -! -5! -! -!  -! -5! -5!4 -45! -s-S&! -! -! -! - -5 -! -+! -! -! -! - ! - -5Y"#  -Y"# -Y"# -Y"# -Y"# -Y"# -Y"#@ -Y"# -"$ -"$ -"$  -"$ -"$ -5Y"$ -5Y"$ -5Y"$  -l5Y"$ -+5Y"$ -5Y"$ -Y"$ -5Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -iY"$ -oY"$ -Y"$ -Y"$ -Y"$v -Y"$ -Y"$ -Y"$ -%& -|. -|/ -|/ -57 -7# -3" -! -! -! -! -! - -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -UVWk! -UVW! -UVW -UVW! -UVW -UVW! -UVW -5UVWQ -UVWh -UVW - -UVWl -=UVW! -UVW -UVW -UVW -UVW -UVW -UVW  -5UVW -UVW -UVW -UVW -UVW -UVWs -5UVW -UVW -5UVW -UVW' -5UVW -UVW -UVW -UVW -UVW -BUVW -BUVW -uBUVW -BUVWS -BUVW -UVW -UVW -UVW -UVW  -UVW -UVW -UVW -UVW -UVW -UVW -UVW -lUVW -UVW -UVW -UVW -uBUVW -BUVWl -BUVW -BUVW -uBUVW -BUVWc -UVWc -UVW -UVW -UVW -UVW -UVW -uUVW -UVW -UVW -UVW -UVW -UVW -UVW -UVW -eNUVW -UVW -UVW -! -B! -! -eNs! ! -! -! -! -! -! -o! -eN! -! -!! -! " -! -!! -! -! -!! -!! -!! -!" -!! -! -! -!! -!! -!! -!" -!" -!" -!" -!! -!! -!" -!! -!! -!! -!! -!! -! -l!! -! -B! -B!) -u! -!2! -!! -! ! -! -5! -5! -5!# -# -N# -*# -# -# -# -" -4" -o# -# -" -5" -" -! -4# -# - -# -# -# -# -# -# -# -# - -BY"# -5Y"# -Y"# -Y"# -Y"# -5Y"# -"$ -BY"$ -5Y"$ -5Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -Y"$ -5Y"$ -5Y"$ -Y"$ -Y"$ -y -|. -|.! -|/ -|/ -57 -57 -57 -57! -7! -7" -3# -" -UVW" -UVW" -UVW" -UVW! -UVW4 -BUVW! -UVW" -UVW" -UVW" -UVW" -UVW# -UVW" -UVW" -UVWe# -UVW# -UVW# -UVW" -UVW! -BUVW" -UVW# -UVW" -UVW# -UVW# -UVW" -4UVW" -UVW" -UVW" -UVWE" -UVW! -UVW(" -UVW# -UVW! -BUVW! -BUVW" -UVW" -UVW" -UVW# -UVW" -UVW" -4UVW" -UVW" -5UVW! -45UVW! -5UVW! -UVW! -UVW! -UVW" -UVW! -4UVW! -oUVW" -UVW " -UVWY" -UVW" -UVW# -UVW# -UVW" -UVW"" -UVW# -UVW" -UVW" -UVW" -UVW" -UVW ! -UVW! -BUVW" -UVW! -5UVW! -UVW -4UVW" -UVW" -UVW" -UVWs! -5UVW! -UVW! -5UVW ! -UVWB -4UVW! -UVW! -UVW! -UVW! -UVW! -UVW! -UVW -BUVW! -BUVW -BUVW -BUVW -uBUVW -BUVW5" -UVW" -UVW" -UVW" -UVW" -UVW" -UVW -uBUVW -BUVW1! -UVW! -UVW! -UVW! -UVW ! -UVW! -UVW -BUVW -BUVW! -BUVW -BUVW ! -BUVW -BUVW! -UVW" -UVW! -UVW" -UVW! -UVW^" -UVW" -UVW! -UVW! -UVW/! -UVW! -UVWr" -UVW4" -UVW -5UVW! -UVW" -UVW! -UVW  -UVW" -!# -!" -!" -!/" -! -eNs!" -s!(" -!f" -!" -![" -!# -!" -!! -!" -!" -!# -!# -!$" -l!# -! # -!0# -!" -w!# -!# -!Y$ -!" -l!# -!" -,!# -!# -!! -B!# -!# -!# -!# -!" -s!# -!! -B!! -B!5! -B!# -!" -!# -!# -!" -5!% - & - % - % - % - % - % - $ -5$ -5$ -$ -$ -% -  -% - % - % - % - % - ! -5Y"#! -5Y"#! -Y"#l! -Y"#! -Y"#! -Y"#" -"$" -"$! -5Y"$! -5Y"$! -Y"$! -Y"$" -Y"$! -Y"$! -Y"$ -! -Y"$! -Y"$! -Y"$z! -Y"$# -%&% - " -|.% - $ -UVW$ -UVW# -UVW# -UVW.$ -UVW$ -UVW$ -UVW$ -UVW -$ -UVW$ -UVW# -=UVW$ -UVW$ -UVW$ -UVW# -UVW% -UVW$ -UVWq% -UVW # -BUVW# -BUVW$ -UVW$ -UVW% -UVW$ -UVW$ -4UVW$ -UVW -# -tUVW$ -UVW$ -UVW$ -UVW$ -UVW $ -UVW# -BUVW# -UVW$ -UVW" -4UVW;# -UVW $ -UVW)$ -UVW$ -UVW$ -UVW$ -UVW# -tUVW$ -UVW$ -UVW.% -UVW $ -UVW?$ -UVW$ -UVW# -5UVW# -5UVW# -5UVW-# -5UVW# -5UVW# -UVW# -UVW# -UVW" -tUVW# -UVW# -UVW$ -UVW$ -UVW# -UVW# -UVWN$ -UVW$ -UVW$ -UVW$ -UVW$ -UVW2$ -UVW$ -UVW# -UVW# -UVW$ -UVW$ -UVW$ -UVW+$ -UVW$ -UVWr# -5UVW# -UVW# -5UVW# -UVW# -UVW;# -5UVW# -5UVW# -5UVW# -UVW# -UVW# -UVW" -BUVWK% -UVW" -BUVW" -BUVWk# -UVW" -BUVW" -BUVW" -BUVW! -oBUVW" -BUVW" -oUVW# -UVW -# -UVW% -UVW# -UVW$ -UVW" -iUVW# -oUVW$ -UVW# -UVW]# -UVW$ -UVW# -UVW" -5UVW# -UVW# -UVW# -UVW$ -!$ -!$ -!$ -!$ -!" -eN!$ -!% -!$ -!% -!# -!% - !$ -!$ -!$ -!$ -!K$ -!$ -!# -B! % - !% - !% - !% - !i% - !$ -,!# -B! % - !% - ! % - !5% - ! -$ -s!% - !% - !w" -uB!# -B!#% - !% - !% - !$ -s!% - !# -o!$ -!% - !% - !% - !% - !$ -5!& -!5' -"& -!' -"' -"' -"' -"' -"$ -"#&# -5Y"#3# -5Y"## -Y"#" -Y"## -Y"## -Y"## -Y"#% -"$$ -"$$ -5Y"$" -5Y"$# -5Y"$" -5Y"$# -Y"$# -Y"$" -Y"$" -Y"$# -Y"$# -Y"$# -Y"$# -Y"$# -Y"$# -Y"$! -eNY"$" -5Y"$# -Y"$! -eNY"$% - %&' -"$ -57' -"$ -oUVW& -!UVW $ -iUVW% - UVW# -BZUVW& -!UVWF% - UVW& -!UVW& -!UVW& -!UVW& -!UVW% - BUVW -% - UVW$ -tUVW& - UVW$ -4UVW(% - UVW' -!UVW' -!UVW& -!UVW& -!UVW& -!UVW!& -!UVW& -!UVW&& -!UVW& -!UVW& -!UVW% - UVW % - UVW% - UVW% - UVW& - UVW& - UVW& - UVW% - UVW}% - UVW& -!UVW -& -!UVW*& -!UVW& -!UVW% - 5UVW% - 5UVW% - UVW% - UVW% - UVW% - UVW& -!UVW&& -!UVW% - UVW% - UVW& -!UVWi% - 5UVW% - UVW% - 5UVW $ -45UVW% - UVW:% - 5UVW% - 5UVW% - 5UVW% - 5UVW% - UVW% - UVW% - UVW$ -BUVW$ -BUVW% - UVW$ -UVW" -eNUVW$ -UVW# -eNsUVW% - UVWj$ -5UVW$ -5UVW% - UVW' -!!& -!!& -!!% - !' -"!9& -!5!& -!!& -!5!& -!!& -!!$ -eN!& -!!% - B!% - B!' -"!' -"!' -"!& -!s!' -"!% - B!' -"! ' -"!"& -!s!& -!s!' -"!& -!!% - B!% - B!% - B!>% - B!' -"!( -"!' -"!' -"!' -"!' -"!& -!5!& -!5!& -!5!) -$) -$( -#) -$) -$% - Y"#t# -eNY"#$ -5Y"$$ -5Y"$$ -5Y"$& - Y"$% - Y"$% - Y"$) -$) -$) -$) -$& -!UVW' -"UVW& -!ZUVW( -#UVW ( -#UVW( -#UVW( -#UVW( -#UVW( -#UVW( -#UVW( -#UVW( -#UVW' -"5UVW' -"UVW' -"UVW& -!4UVW' -"UVW' -"UVW' -"UVW' -"UVW( -"UVW( -"UVW' -"UVWA' -"UVWR' -"UVW' -"UVW( -#UVW( -#UVW( -#UVW' -"lUVW' -"*UVW' -"UVW'' -"UVW' -"UVW' -"UVW& -!BUVW( -#UVW( -#UVW' -"jUVW' -"lUVW' -"*UVW' -"+UVW' -"5UVW' -"5UVW' -"UVW' -"UVW' -"UVW( -#UVW( -#UVW( -#UVWi' -"BUVW' -"5UVW' -"UVW& -!t5UVW' -"5UVW' -"UVW;& -!4UVW' -"5UVW' -"5UVW' -"5UVW' -"UVW' -"UVW' -"UVW' -"UVW$ -eNs5UVW' -"UVW' -"UVW' -"UVW' -"UVW3' -"UVW' -"UVW' -"UVW' -"UVW' -"UVW' -"!& -!eN!( -#!) -$!>( -#!( -#!( -#!2( -#!' -"B!) -$!) -$!( -#s!* -$!( -#!) -$!( -#!& -!iB!( -#!) -$!)) -$!) -$!) -$!) -$!) -$B* -%+ -&* -%* -%* -%* -%* -%& -!5Y"# -& -!5Y"$!& -!5Y"$% - eNY"$' -"Y"$+ -&+ -&, -'6) -$UVW) -$UVW) -$UVW) -$UVW* -%UVW* -%UVW* -%UVW* -%UVW* -%UVW) -$5UVW) -$UVW( -#BUVW* -%UVW) -$jUVW* -%UVW* -%UVW* -%UVW* -%UVW) -$sUVW* -%UVW) -$UVW) -$UVWN( -#lUVW( -#BUVW( -#BUVW( -#BUVW* -%UVW) -$oUVW* -%UVW* -%UVW) -$sUVW* -%UVW ) -$5UVW) -$UVW) -$UVW) -$UVW) -$UVW) -$UVW( -#uUVW* -$UVW* -%UVW( -#uUVW) -$UVW -+ -%UVW) -$5UVW) -$UVW ) -$5UVW3) -$UVWC( -#4UVW) -$5UVW) -$5UVW) -$UVW' -"uBUVW) -$5UVW) -$UVW) -$UVW* -%!+ -&!S* -%!* -%!* -%!+ -&!( -#eN!+ -&!]+ -&!* -%!* -%!* -%!* -%!j* -%!* -%!* -%!* -%!+ -&! + -&!) -$5!- -(, -', -', -'( -#5Y"$( -#5Y"$- -(+ -&|+ -&UVW* -%ZUVW* -%ZUVW, -'UVW, -'UVW+ -&UVW+ -&UVW* -%jUVW+ -&5UVW* -%BUVW* -%BUVW+ -&UVW+ -&UVW+ -&UVW+ -&UVW) -$oBUVW* -%BUVW* -%BUVW* -%BUVW, -'UVW, -'UVW+ -&5UVW+ -&UVW+ -&UVW, -&UVW+ -&UVW+ -&UVW, -'UVWp+ -&BUVW+ -&5UVW+ -&UVW+ -&5UVW* -%45UVW+ -&UVW<* -%4UVW+ -&5UVW+ -&5UVW+ -&5UVW+ -&UVW+ -&UVW + -&UVW+ -&UVW+ -&UVW+ -&5UVW+ -&UVW+ -&UVW+ -&UVW+ -&UVW- -(!S, -'!, -'!, -'!, -'!, -'!- -'!, -'!, -'!, -'!, -'!, -'!, -'!, -'!, -'!, -'!- -(!+ -&5!/ -*&/ -*- -(o. -). -)* -%5Y"#* -%5Y"$* -%5Y"$/ -*. -)0 -+. -). -)- -(UVW- -(UVW. -)UVW. -)UVW. -)UVW, -'BUVW, -'BUVW. -)UVW -, -'BUVW- -(UVW, -'BUVW/ -)UVW- -(UVW. -)UVW- -(UVW+ -&eNUVW- -(5UVW- -(UVW, -'oUVW- -(UVW- -(UVW. -)UVW. -)UVW`- -(5UVW- -(UVW - -(5UVW- -(UVW- -(UVW8- -(5UVW- -(UVW- -(UVW - -(UVW- -(UVW- -(UVW- -(UVW, -'BUVW, -'BUVW- -(UVW/ -*!i. -)5!- -(45!. -)!, -'eN!. -)!. -)!. -)!. -)!. -)!. -)!, -'eN!. -)!. -)!. -)!. -)!., -'eN!/ -*!/ -*!- -(5!- -(5!1 -,1 -,, -'5Y"$1 -,1 -,1 -,/ -*UVW/ -*UVW- -(uBUVW. -)BUVW. -)BUVW/ -*UVW/ -*UVW/ -*UVW0 -+UVW/ -*UVW0 -+UVW^/ -*5UVW/ -*UVW/ -*BUVW/ -*5UVW/ -*UVW9/ -*5UVW/ -*5UVW/ -*UVW/ -*UVW/ -*UVW/ -*UVW/ -*5UVW/ -*UVW1 -,!0 -+! -0 -+5!0 -+!0 -+!0 -+!0 -+!1 -+!0 -+!. -)eN!0 -+!1 -,!/ -*B!. -)uB!1 -,!/ -*5!3 -.2 --3 -.3 -.3 -.3 -.1 -,UVW2 --UVW2 --UVW1 -,UVW0 -+5UVW1 -,UVW1 -,UVW1 -,UVWN1 -,UVW1 -,UVW1 -,UVW1 -,UVW1 -,UVW0 -+5UVW0 -+UVW1 -,UVW1 -,UVW2 --UVWW1 -,5UVW1 -,UVW1 -,5UVW0 -+45UVW1 -,UVW,1 -,5UVW1 -,5UVW1 -,UVW1 -,UVW1 -,UVW1 -,UVW0 -+BUVW3 -.!t2 --5!2 --!2 --!2 --5!2 --!2 --!2 --5!1 -,l!2 --!2 --!2 --!2 --!2 --!0 -+eN!2 --!3 -.!4 -.!4 -.!5 -05 -05 -05 -065 -0 3 -.UVW3 -.UVW2 --UVW2 --UVW3 -.UVW3 -.UVW3 -.UVW3 -.UVW1 -,eNUVW4 -/UVWe3 -.UVW3 -.5UVW3 -.5UVW3 -.UVW83 -.5UVW3 -.5UVW3 -.UVW3 -.UVW3 -.UVW3 -.UVW3 -.UVW3 -.5UVW3 -.UVW6 -0!4 -/!4 -/!3 -.4!4 -/5!4 -/5!4 -/!4 -/!7 -27 -27 -2!7 -27 -27 -27 -27 -27 -27 -27 -25 -0UVW6 -1UVWI5 -0UVW5 -0UVW5 -0UVW5 -0UVW6 -1UVWS5 -0UVW5 -05UVW5 -0UVW'5 -05UVW5 -0UVW5 -0UVW5 -0UVW4 -/lUVW3 -.uBUVW8 -2!6 -15!6 -1!6 -15!6 -1!6 -15!6 -1!6 -1!5 -0B!5 -0B!7 -2!6 -1z!8 -3b9 -49 -49 -49 -49 -49 -4 9 -49 -49 -49 -49 -49 -49 -49 -49 -48 -2UVW8 -3UVW>8 -3UVW6 -1UVW6 -1UVW8 -3UVW\7 -2UVW7 -25UVW6 -145UVW7 -2UVW,7 -25UVW7 -25UVW7 -2UVW7 -2UVW7 -2UVW7 -2UVW: -4!8 -3!8 -3B!8 -3!8 -3!8 -3!7 -2B!; -6 ; -6; -6; -6; -6h; -6; -6; -6 -; -6 ; -6: -5UVW9 -4UVW: -5UVW]9 -4UVW9 -45UVW9 -4UVW/8 -34UVW9 -4UVW9 -4UVW -9 -4UVW9 -4UVW8 -3UVW; -6!: -55!: -5! 9 -4t!: -5!9 -4j!= -8= -8< -7= -8< -7= -8= -8= -8> -8= -8= -8 ; -65UVW< -7UVW^; -6UVW; -65UVW; -6UVW; -6UVW'; -65UVW; -65UVW; -6UVW; -6UVW= -8!x< -7! < -75!< -7!; -6B!? -:? -:? -:? -:> -9? -:? -:? -:? -:> -9? -:K? -:? -:? -:+> -9UVWR= -85UVW= -8UVW= -85UVW= -8UVW@= -85UVW= -85UVW= -8UVW= -8UVW= -8UVW= -8UVW= -8UVW= -8UVW< -7UVW< -7UVW? -:!k> -95!> -9!> -9!> -9!= -8B!> -9!A -<A -< @ -;A -<A -< A -<? -:BA -<A -<A -<A -<A -< @ -;5@ -;A -<@ -;=A -<A -< @ -;? -:u@ -;/A -<A -<A -<@ -;? -:5UVW@ -;UVWD? -:5UVW? -:UVW? -:5UVW ? -:UVW%? -:5UVW? -:UVW? -:UVW? -:UVW? -:UVW? -:UVWA -<!T@ -;!@ -;5!@ -;5!@ -;!@ -;!C ->C ->B -=C ->C ->#C ->D ->C ->C ->C ->C ->B -=4C ->B -=4B -=5B -=8A -<4C ->C ->C ->C ->A -<BB -=B -=xB -=C ->C ->C ->C ->B -=C ->C ->C ->A -!?B -=5!A -<45!B -=! B -=5!B -=!B -=!B -=!B -=!@ -;uB!A -<B!E -@D -?E -@(E -@@E -@D -?4E -@D -?"D -?E -@E -@8E -@D -?D -?E -@ C ->oD -?E -@E -@2E -@E -@E -@D -?4D -?E -@ -D -?E -@E -@E -@D -?D -?D -?,D -?D -?UVWQC ->5UVWC ->UVW C ->5UVWB -=45UVWC ->UVW!B -=4UVWC ->5UVWC ->UVWC ->UVWC ->UVWB -=BUVWE -@!5D -?5!D -?!D -?5!D -?!B -=uB!C ->B!G -BG -BF -AG -B2G -B"F -AF -AF -AG -BG -BF -AG -BG -BG -BF -A G -BG -BG -BG -BF -AUVWcE -@5UVWE -@UVWE -@5UVWE -@5UVWD -?45UVWE -@UVW E -@5UVWE -@5UVWE -@UVWE -@UVWE -@UVWD -?BUVWG -B!"F -A!F -A!F -A!E -@B!I -DH -CH -CH -CI -DH -CG -B4I -DI -DH -CI -DF -A5UVWH -CUVWaG -B5UVWG -BUVWG -B5UVWG -BUVW?G -B5UVWG -B5UVWG -B5UVWG -BUVWG -BUVWG -BUVWI -D!H -C5!H -C!H -C!H -C!K -FJ -E K -F -K -FJ -EUVWiI -D5UVWI -DUVWI -D5UVWI -DUVW.I -D5UVWI -D5UVWI -DUVWI -DUVWI -DUVWI -DUVWI -DUVWH -CBUVWK -F!J -E!J -E!J -E!I -DB!L -G5L -G M -HL -GL -GM -HL -GUVWjK -F5UVWK -FUVW K -F5UVWJ -E45UVWK -FUVW)K -F5UVWK -FUVWK -FUVWK -FUVWM -H!K -F45!L -G!O -JO -JM -HBO -JO -JN -IN -IO -JN -IUVWnM -H5UVWM -HUVWM -H5UVWM -HUVWAM -H5UVWM -H5UVWM -HUVWM -HUVWM -H5UVWM -HUVWM -HUVWK -FuBUVWM -HUVWO -J!N -IbbbQ -LQ -LP -KUVWvO -J5UVWO -JUVW -O -J5UVWO -JUVWO -JUVW1N -I4UVWO -J5UVWO -J5UVWO -JUVWO -JUVWO -JUVWN -IUVWQ -L! P -K5!P -K!O -JB!O -JB!R -MS -NS -NR -MUVWwQ -L5UVWQ -LUVW -Q -LBUVWQ -L5UVWQ -LUVW1P -K4UVWQ -L5UVWQ -LUVWQ -LUVWQ -LUVWQ -LUVWQ -LUVWQ -LUVWQ -LUVWP -KBUVWS -N! Q -L45!R -M!R -M5!Q -LB!U -PU -PU -PT -OU -PT -OUVWlS -NBUVWS -N5UVWS -NUVWS -N5UVW$R -M45UVWS -NUVWIS -N5UVWS -NUVWS -NUVWS -NUVWS -NUVWS -NUVWS -NUVWS -NUVWU -P!T -O!S -NB!W -RW -RV -QUVWcU -PBUVWU -P5UVWU -PUVWU -P5UVW U -PUVWLT -O4UVWU -P5UVWU -P5UVWU -PUVWU -PUVWU -PUVWU -PUVWU -PUVWW -R!Y -TY -TY -TY -TY -TX -SUVWW -RUVW W -R5UVWW -RUVWIV -Q4UVWW -RUVWW -R5UVWW -RUVWW -RUVWW -RUVWW -RUVWW -RUVWY -T!X -S5!W -R4!Y -TB[ -VZ -U[ -V[ -VY -TUVW4X -S4UVWY -T5UVWY -TUVWY -TUVWY -TUVWY -TUVWY -TUVWY -TUVWY -TUVW[ -V!Z -U!Z -U!Z -U!] -X] -X[ -VUVW[ -VUVW[ -VUVW[ -VUVW[ -V5UVW[ -VUVWZ -UBUVW[ -V5UVW[ -VUVW] -X!\ -W!_ -Z_ -Z\ -WBUVW] -XUVW_ -Z! -^ -Y!a -\_ -ZUVWa -\!` -[!` -[!` -[!` -[!c -^a -\BUVWc -^! b -]!b -]!e -`e -`! -d -_!g -bg -be -`UVWg -b! i -d i -di -d!g -bB!k -fk -f!m -hm -h!o -jn -io -j!n -i5!q -lq -l!s -n!u -pt -o! -! -~! -}! - - -""""""""""""""""" " " -" -" " " " " " """"""""""""""""""""""""""""""""""""" " "!"!"""""#"#"$"$"%"%"&"&"'"'"("(")")"*"*"+"+",","-"-"."."/"/"0"0"1"1"2"2"3"3"4"4"5"5"6"6"7"7"8"8"9"9":":";";"<"<"="=">">"?"?"@"@"A"A"B"B"C"C"D"D"E"E"F"F"G"G"H"H"I"I"J"J"K"K"L"L"M"M"N"N"O"O"P"P"Q"Q"R"R"S"S"T"T"U"U"V"V"W"W"X"X"Y"Y"Z"Z"["["\"\"]"]"^"^"_"_"`"`"a"a"b"b"c"c"d"d"e"e"f"f"g"g"h"h"i"i"j"j"k"k"l"l"m"m"n"n"o"o"p"p"q"q"r"r"s"s"t"t"u"u"v"v"w"w"x"x"y"y"z"z"{"{"|"|"}"}"~"~""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""*******  * - -*   * -  *   * * *****************  *!!* ""*!##*"$$*#%%*$&&*%''*&((*'))*(***)++**,,*+--*,..*-//*.00*/11*022*133*244*355*466*577*688*799*8::*9;;*:<<*;==*<>>*=??*>@@*?AA*@BB*ACC*BDD*CEE*DFF*EGG*FHH*GII*HJJ*IKK*JLL*KMM*LNN*MOO*NPP*OQQ*PRR*QSS*RTT*SUU*TVV*UWW*VXX*WYY*XZZ*Y[[*Z\\*[]]*\^^*]__*^``*_aa*`bb*acc*bdd*cee*dff*egg*fhh*gii*hjj*ikk*jll*kmm*lnn*moo*npp*oqq*prr*qss*rtt*suu*tvv*uww*vxx*wyy*xzz*y{{*z||*{}}*|~~*}*~** * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 222samples2-github.com/dgraph-io/badger/v2.(*DB).doWrites23github.com/dgraph-io/badger/v2.(*DB).doWrites.func12?github.com/dgraph-io/badger/v2.(*levelsController).runCompactor28github.com/dgraph-io/badger/v2/y.(*Closer).HasBeenClosed25github.com/dgraph-io/badger/v2/y.(*WaterMark).process2Ggithub.com/prometheus/client_golang/prometheus.(*Registry).Gather.func12Kgithub.com/grafana/pyroscope/pkg/og/agent.(*ProfileSession).takeSnapshots2>github.com/grafana/pyroscope/pkg/og/scrape.(*scrapeLoop).run2gogo2!net/http.(*persistConn).writeLoop2runtime.Gosched2runtime.bgscavenge2runtime.bgsweep2runtime.gcBgMarkWorker2 runtime.mcall2runtime.morestack2runtime.nanotime12runtime.systemstack2runtime/pprof.profileWriter2time.now2runtime.selectgo22github.com/dgraph-io/badger/v2.(*DB).writeRequests2Fgithub.com/dgraph-io/badger/v2.(*levelsController).isLevel0Compactable2Dgithub.com/dgraph-io/badger/v2.(*levelsController).pickCompactLevels2runtime.(*waitq).dequeueSudoG27github.com/dgraph-io/badger/v2/y.(*WaterMark).DoneUntil2;github.com/dgraph-io/badger/v2/y.(*WaterMark).process.func12runtime.(*waitq).enqueue2Cgithub.com/grafana/pyroscope/pkg/og/agent/gospy.(*GoSpy).Snapshot2runtime.newobject2?github.com/grafana/pyroscope/pkg/og/scrape.(*scrapePool).Sync2Dgithub.com/grafana/pyroscope/pkg/og/scrape.(*Manager).reload.func12=github.com/grafana/pyroscope/pkg/og/scrape.(*Target).report2Ggithub.com/grafana/pyroscope/pkg/og/storage/cache.(*Cache).saveToDisk2=github.com/grafana/pyroscope/pkg/og/storage/cache.New.func12=github.com/grafana/pyroscope/pkg/og/storage/cache.New.func22net/http.(*Transport).dialConn2!net/http.(*Transport).dialConnFor2 net/http.(*persistConn).readLoop2runtime.resettimer2runtime.scavengeSleep2&runtime.(*sweepLocker).blockCompletion2runtime.(*sweepLocker).dispose2!runtime.(*sweepLocker).tryAcquire2runtime.sweepone2runtime.gosched_m2runtime.park_m2gosave_systemstack_switch2*runtime/pprof.(*profileBuilder).addCPUData2sync.(*WaitGroup).state2sync.(*WaitGroup).Done2runtime.nextFreeFast2runtime.makeslice2/github.com/dgraph-io/badger/v2.(*DB).writeToLSM20github.com/dgraph-io/badger/v2.(*valueLog).write20github.com/dgraph-io/badger/v2/skl.(*Arena).size2*github.com/sirupsen/logrus.(*Entry).Debugf20github.com/dgraph-io/badger/v2.(*Options).Debugf2;github.com/dgraph-io/badger/v2.(*levelHandler).getTotalSize2github.com/grafana/pyroscope/pkg/og/storage/tree.getCacheKey2?github.com/grafana/pyroscope/pkg/og/storage/tree.FindFunction2?github.com/grafana/pyroscope/pkg/og/storage/tree.FindLocation2runtime.newSweepLocker2runtime.(*bucket).mp2runtime.mProf_PostSweep2runtime/pprof.writeHeapProto2runtime/pprof.writeHeapInternal2runtime/pprof.writeHeap2Bgithub.com/grafana/pyroscope/pkg/og/storage/labels.(*Labels).Put2Agithub.com/grafana/pyroscope/pkg/og/storage/tree.(*Tree).Insert2runtime.gcWriteBarrierR82runtime.growslice2runtime.roundupsize2runtime.typedslicecopy2Egithub.com/grafana/pyroscope/pkg/og/scrape/labels.(*Builder).Labels2;github.com/grafana/pyroscope/pkg/og/scrape.PopulateLabels2Cgithub.com/grafana/pyroscope/pkg/og/scrape.(*cache).writeProfiles25github.com/valyala/bytebufferpool.(*ByteBuffer).Bytes23github.com/valyala/bytebufferpool.(*ByteBuffer).Len29github.com/valyala/bytebufferpool.(*ByteBuffer).WriteByte2*google.golang.org/protobuf/proto.Unmarshal2'github.com/dgraph-io/badger/v2.NewEntry2bytes.(*Buffer).Bytes2encoding/binary.PutUvarint2>github.com/grafana/pyroscope/pkg/og/storage/dict.(*Dict).Put2>github.com/grafana/pyroscope/pkg/og/util/varint.Writer.Write25github.com/valyala/bytebufferpool.(*ByteBuffer).Write2runtime.mapaccess2_faststr23github.com/dgraph-io/badger/v2/y.(*WaterMark).Begin23github.com/dgraph-io/badger/v2.(*Txn).commitAndSend27github.com/grafana/pyroscope/pkg/og/util/varint.Write2Cgithub.com/grafana/pyroscope/pkg/og/storage/tree.(*Tree).minValue2;github.com/grafana/pyroscope/pkg/og/structs/cappedarr.New2Jgithub.com/grafana/pyroscope/pkg/og/storage/cache/lfu.(*Cache).increment2sync.(*Pool).Get2net/http.Header.sortedKeyValues2net/http.Header.writeSubset2strings.(*byteReplacer).Replace2strings.(*Replacer).Replace2runtime.siftupTimer2 runtime.write2 runtime.(*pageAlloc).scavengeOne2$runtime.(*pageAlloc).scavengeReserve2&runtime.(*pageAlloc).scavengeUnreserve2runtime.pallocSum.max2runtime.headTailIndex.head2runtime.headTailIndex.split2runtime.(*lfstack).push2!runtime.(*spanSetBlockAlloc).free2 runtime.(*headTailIndex).incTail2runtime.mProf_Free2runtime.mProf_FlushLocked2runtime.mProf_Flush2runtime.gcMarkTermination2runtime.gcMarkDone2runtime.(*gcWork).tryGet2 runtime.add12runtime.arenaIndex2runtime.gcFlushBgCredit2runtime.heapBits.bits2runtime.heapBits.next2runtime.markroot2runtime.pollWork2runtime.runqempty2runtime.scanobject2runtime.spanOfUnchecked2runtime.checkTimers2runtime.gopreempt_m2runtime.(*gQueue).pop2runtime.globrunqget2runtime.osyield2runtime.adjusttimers2runtime.runtimer2 runtime.gogo2runtime.(*randomEnum).next2runtime.(*randomEnum).position2runtime.(*randomOrder).start2runtime.checkRunqsNoP2runtime.checkTimersNoP2runtime.netpoll2runtime.nobarrierWakeTime2runtime.pMask.read2runtime.pMask.set2runtime.pidleget2runtime.pidleput2runtime.puintptr.ptr2runtime.releasep2runtime.stealWork2 runtime.stopm2 runtime.wakep2runtime.newstack2runtime.isSystemGoroutine2runtime.newproc12 bytes.Compare2,github.com/dgraph-io/badger/v2/y.CompareKeys22github.com/dgraph-io/badger/v2/skl.(*Arena).putKey2*github.com/dgraph-io/badger/v2/skl.newNode2bytes.(*Buffer).grow2hash/crc32.archUpdateCastagnoli2hash/crc32.castagnoliShift2os.(*File).Write2os.(*File).write2runtime.semrelease129github.com/dgraph-io/badger/v2/table.(*Builder).addHelper2;github.com/dgraph-io/badger/v2/table.(*Builder).finishBlock27github.com/dgraph-io/badger/v2/table.(*Builder).keyDiff26github.com/dgraph-io/badger/v2/y.(*ValueStruct).Decode22github.com/dgraph-io/badger/v2/skl.(*Arena).getVal2Jgithub.com/grafana/pyroscope/pkg/og/structs/transporttrie.(*Trie).Insert2 sort.Search2runtime.sweepClass.split2!runtime.(*MemProfileRecord).Stack2runtime.FuncForPC2runtime.duffcopy22runtime/pprof.(*profileBuilder).appendLocsForStack2(runtime/pprof.(*profileBuilder).pbSample2runtime/pprof.scaleHeapSample2@github.com/grafana/pyroscope/pkg/og/storage/cache.(*Cache).get2Hgithub.com/grafana/pyroscope/pkg/og/storage/cache.(*Cache).GetOrCreate2Fgithub.com/grafana/pyroscope/pkg/og/storage/segment.(*streeNode).put2Dgithub.com/grafana/pyroscope/pkg/og/storage/segment.(*Segment).Put2.github.com/sirupsen/logrus.(*Entry).WithFields2/github.com/sirupsen/logrus.(*Logger).WithFields2runtime.alignUp2=github.com/grafana/pyroscope/pkg/og/flameql.ValidateAppName2Ngithub.com/grafana/pyroscope/pkg/og/storage/segment.(*parser).nameParserCase2>github.com/grafana/pyroscope/pkg/og/storage/segment.ParseKey2bytes.genSplit2 bytes.Split2Egithub.com/grafana/pyroscope/pkg/og/storage/tree.(*treeNode).insert2:github.com/grafana/pyroscope/pkg/og/storage/tree.newNode2runtime.acquirem2runtime.(*wbBuf).putFast2"runtime.bulkBarrierPreWriteSrcOnly2runtime.getMCache2runtime.makeSpanClass2runtime.memclrNoHeapPointers2runtime.releasem2runtime.morestack_noctxt2runtime.bulkBarrierPreWrite2runtime.spanOf2runtime.concatstrings2runtime.concatstring228github.com/grafana/pyroscope/pkg/og/scrape.buildConfig2 -time.Until2context.WithDeadline2context.WithTimeout2>github.com/grafana/pyroscope/pkg/og/scrape.cache.getOrCreate2runtime.asyncPreempt2 bytes.Equal2;google.golang.org/protobuf/proto.UnmarshalOptions.unmarshal2time.(*Timer).Stop2context.WithDeadline.func32Ggithub.com/grafana/pyroscope/pkg/og/scrape.(*scrapeLoop).scrape.func12sync.(*Pool).pin2-github.com/valyala/bytebufferpool.(*Pool).Get28github.com/valyala/bytebufferpool.(*ByteBuffer).ReadFrom2 io.copyBuffer2io.Copy2net/http.(*Request).WithContext2Ggithub.com/grafana/pyroscope/pkg/og/storage/cache/lfu.(*Cache).delete2Fgithub.com/grafana/pyroscope/pkg/og/storage/cache/lfu.(*Cache).Evict2Bgithub.com/grafana/pyroscope/pkg/og/storage/cache.(*Cache).Evict2Jgithub.com/grafana/pyroscope/pkg/og/storage/cache/lfu.(*Cache).writeBack2Jgithub.com/grafana/pyroscope/pkg/og/storage/cache/lfu.(*Cache).WriteBack2Fgithub.com/grafana/pyroscope/pkg/og/storage/cache.(*Cache).WriteBack2Fgithub.com/grafana/pyroscope/pkg/og/storage.(*Storage).writeBackTask2runtime.chansend12Igithub.com/grafana/pyroscope/pkg/og/storage/dict.(*trieNode).findNodeAt2;github.com/grafana/pyroscope/pkg/og/util/varint.NewWriter2sync.(*Mutex).Unlock2sync.(*RWMutex).Lock2sync.(*RWMutex).Unlock2Kgithub.com/grafana/pyroscope/pkg/og/storage/tree.(*Tree).iterateWithTotal2time.Now2/github.com/dgraph-io/badger/v2.(*oracle).readTs2,github.com/dgraph-io/badger/v2.(*Txn).modify2.github.com/dgraph-io/badger/v2.(*Txn).SetEntry2Ngithub.com/grafana/pyroscope/pkg/og/storage/dimension.(*Dimension).Serialize2Fgithub.com/grafana/pyroscope/pkg/og/storage.dimensionCodec.Serialize2Igithub.com/grafana/pyroscope/pkg/og/storage/cache/lfu.(*Cache).remEntry2runtime.write12(runtime.(*pageAlloc).scavengeRangeLocked2+runtime.(*pallocData).findScavengeCandidate2 runtime.(*addrRanges).removeLast2 runtime.futex2runtime.futexwakeup2runtime.(*mheap).freeSpan.func12runtime.futexsleep2runtime.newArenaMayUnlock2runtime.(*lfstack).pop2runtime.markrootSpans2runtime.(*gcBits).bitp2runtime.(*gcBits).bytep2runtime.(*gcWork).putFast2!runtime.(*mspan).divideByElemSize2runtime.(*mspan).objIndex2runtime.findObject2runtime.greyobject2runtime.markBits.setMarked2runtime.pageIndexOf2runtime.(*timeHistogram).record2runtime.guintptr.ptr2!runtime.(*mcache).prepareForSweep2runtime.acquirep2runtime.epollwait2 runtime.read2runtime.runqsteal2 runtime.mPark2 runtime.mget2runtime.startm2runtime.findfunc2cmpbody2runtime.convT3223github.com/dgraph-io/badger/v2/skl.(*Arena).putNode2bytes.makeSlice2 hash/crc32.castagnoliSSE42Triple2internal/poll.ignoringEINTRIO2runtime.(*mspan).nextFreeIndex2runtime.(*mcache).nextFree2runtime.readyWithTime2 os.direntIno2os.(*File).readdir2os.(*File).Readdirnames21github.com/prometheus/procfs.Proc.fileDescriptors24github.com/prometheus/procfs.Proc.FileDescriptorsLen2Jgithub.com/prometheus/client_golang/prometheus.(*processCollector).Collect2 -io.ReadAll2io/ioutil.ReadAll2@github.com/grafana/pyroscope/pkg/og/agent/gospy.getHeapProfile2Ggithub.com/grafana/pyroscope/pkg/og/structs/transporttrie.newTrieNode2Egithub.com/grafana/pyroscope/pkg/og/storage/tree.FindFunction.func12Egithub.com/grafana/pyroscope/pkg/og/storage/tree.FindLocation.func12runtime.record2runtime.MemProfile2 math.archExp2math.Exp2runtime.findmoduledatap2runtime.CallersFrames2,runtime/pprof.(*profileBuilder).emitLocation2runtime/pprof.allFrames2 runtime/pprof.(*protobuf).int64s2!runtime/pprof.(*protobuf).uint64s2 runtime/pprof.(*protobuf).varint2Igithub.com/grafana/pyroscope/pkg/og/storage/cache/lfu.(*Cache).GetOrSet2Qgithub.com/grafana/pyroscope/pkg/og/storage/dimension.(*Dimension).Insert.func12Kgithub.com/grafana/pyroscope/pkg/og/storage/dimension.(*Dimension).Insert2Hgithub.com/grafana/pyroscope/pkg/og/storage/labels.(*Labels).Put.func12Jgithub.com/grafana/pyroscope/pkg/og/storage/segment.(*Segment).Put.func12Ogithub.com/grafana/pyroscope/pkg/og/storage/segment.(*streeNode).relationship2time.Time.AppendFormat2time.Time.Format2time.Time.String2 bytes.Index2bytes.IndexByte2 memeqbody2runtime.gcmarknewobject2runtime.wbBufFlush2github.com/grafana/pyroscope/pkg/og/scrape.(*scraper).scrape2'compress/flate.(*decompressor).moreBits2'compress/flate.(*dictDecoder).writeCopy2!net/http.(*Request).requiresHTTP12-net/http.(*Transport).connectMethodForRequest2runtime.(*gList).empty23github.com/dgraph-io/badger/v2.(*header).DecodeFrom26github.com/dgraph-io/badger/v2.(*levelsController).get2(github.com/dgraph-io/badger/v2.(*DB).get21github.com/dgraph-io/badger/v2.(*hashReader).Read2Tgithub.com/grafana/pyroscope/pkg/og/storage/segment.(*streeNode).deleteNodesBefore2@github.com/dgraph-io/badger/v2/table.(*MergeIterator).setCurrent24github.com/dgraph-io/badger/v2.(*Iterator).parseItem2/github.com/dgraph-io/badger/v2.(*Iterator).Next2Ngithub.com/grafana/pyroscope/pkg/og/storage/labels.(*Labels).GetValues.func12Hgithub.com/grafana/pyroscope/pkg/og/storage/labels.(*Labels).GetValues2runtime.bool2int2runtime.typeBitsBulkBarrier2runtime.sendDirect2runtime.convTslice2encoding/json.mapEncoder.encode2)encoding/json.(*encodeState).reflectValue2$encoding/json.(*encodeState).marshal2encoding/json.Marshal2Fgithub.com/grafana/pyroscope/pkg/og/util/serialization.WriteMetadata2runtime.makeHeadTailIndex2runtime.adjustframe2internal/poll.(*FD).Read2runtime.funcspdelta2#runtime.(*stackScanState).addObject2runtime.scanblock2runtime.injectglist2runtime.selectnbsend2runtime.exitsyscallfast25github.com/dgraph-io/badger/v2/table.(*Iterator).Next2;github.com/dgraph-io/badger/v2/table.(*ConcatIterator).Next2,compress/flate.(*huffmanBitWriter).writeBits2"compress/flate.(*compressor).close2compress/flate.(*Writer).Close2compress/flate.emitLiteral2compress/flate.token.length2 time.Time.Add2time.Time.Equal2bufio.(*Reader).ReadByte2strings.(*Builder).WriteString2"runtime.heapBits.forwardOrBoundary2runtime.addspecial2runtime.setprofilebucket2runtime.mProf_Malloc.func12fmt.(*pp).doPrintf2 fmt.Sprintf27github.com/sirupsen/logrus.(*TextFormatter).appendValue2:github.com/sirupsen/logrus.(*TextFormatter).appendKeyValue2runtime.eqslice2reflect.resolveTypeOff2math/bits.Reverse162&net/http.(*Transport).queueForIdleConn2net/http.(*Transport).getConn2runtime.(*pageBits).clearRange2runtime.(*pallocBits).free25github.com/dgraph-io/badger/v2.(*hashReader).ReadByte2Tgithub.com/grafana/pyroscope/pkg/og/storage/segment.(*streeNode).walkNodesToDelete2Rgithub.com/grafana/pyroscope/pkg/og/storage/segment.(*Segment).WalkNodesToDelete2encoding/json.stringEncoder2encoding/json.interfaceEncoder2runtime.(*fixalloc).alloc2container/list.(*List).remove2runtime.getempty2runtime.(*mheap).alloc.func12runtime.getStackMap25github.com/dgraph-io/badger/v2/table.(*Iterator).next2,compress/flate.(*huffmanBitWriter).writeCode2&encoding/json.(*decodeState).unmarshal2encoding/json.Unmarshal2Egithub.com/grafana/pyroscope/pkg/og/util/serialization.ReadMetadata2encoding/json.checkValid2 errors.Is2Cgithub.com/prometheus/client_golang/prometheus.(*histogram).Observe2Cgithub.com/grafana/pyroscope/pkg/og/storage.treeCodec.Deserialize2runtime.convTstring2!runtime.(*mspan).markBitsForIndex2runtime.heapBits.forward2runtime.(*mspan).ensureSwept2sort.medianOfThree2"runtime.(*spanSetBlockAlloc).alloc2runtime.resolveTypeOff2runtime.(*_type).typeOff2runtime.pallocSum.unpack2time.Time.After2Jgithub.com/grafana/pyroscope/pkg/og/storage/segment.(*streeNode).isAfter2 runtime.(*stackScanState).putPtr2runtime.(*mspan).init2runtime.pcdatavalue2github.com/grafana/pyroscope/pkg/og/storage/tree.Deserialize2strings.IndexAny2Ggithub.com/grafana/pyroscope/pkg/og/storage/segment.FromTreeToDictKey2runtime.(*mheap).allocSpan2runtime.(*mheap).reclaim2runtime.(*mheap).tryAllocMSpan2runtime.spanAllocType.manual2runtime.funcdata2runtime.spanHasSpecials2runtime.mapaccess1_fast322"compress/flate.(*dictDecoder).init2net/http.(*bodyEOFSignal).Read2runtime.(*pageAlloc).find2runtime.(*pageAlloc).alloc2runtime.(*itabTableType).find2runtime.getitab2runtime.assertE2I22fmt.(*pp).handleMethods2)golang.org/x/net/trace.(*eventLog).printf2)golang.org/x/net/trace.(*eventLog).Printf23github.com/dgraph-io/badger/v2/table.(*Table).block2*compress/flate.(*huffmanEncoder).bitCounts2)compress/flate.(*huffmanEncoder).generate2.compress/flate.(*huffmanBitWriter).indexTokens2#encoding/json.(*decodeState).object2"encoding/json.(*decodeState).value2bytes.(*Reader).Read2)github.com/dgraph-io/badger/v2.(*Txn).Get2Cgithub.com/grafana/pyroscope/pkg/og/storage/dict.(*Dict).GetValue2!runtime.(*mheap).allocMSpanLocked2runtime.(*mheap).allocNeedsZero2runtime.(*mheap).setSpans2runtime.(*pageCache).alloc2runtime.pcdatastart2net/http.(*body).Read29github.com/dgraph-io/badger/v2/table.(*Table).NewIterator22github.com/dgraph-io/badger/v2.(*levelHandler).get2Ngithub.com/grafana/pyroscope/pkg/og/storage/segment.RetentionPolicy.isBefore2Rgithub.com/grafana/pyroscope/pkg/og/storage/segment.RetentionPolicy.levelMaxTime2time.(*Time).nsec2time.(*Time).sec2Kgithub.com/grafana/pyroscope/pkg/og/storage/segment.(*streeNode).isBefore2runtime.isSweepDone2runtime.pallocSum.start2runtime.(*pageCache).allocN2runtime.newAllocBits2 -os.newFile2os.openFileNolog2 os.OpenFile2os.Open2&github.com/prometheus/procfs.Proc.Stat2Qgithub.com/prometheus/client_golang/prometheus.(*processCollector).processCollect27github.com/dgraph-io/badger/v2/skl.(*Skiplist).findNear22github.com/dgraph-io/badger/v2/skl.(*Skiplist).Get2encoding/json.unquoteBytes2reflect.(*rtype).Implements2bytes.NewReader2Dgithub.com/grafana/pyroscope/pkg/og/storage/dict.(*Dict).readValue2runtime.(*wbBuf).reset2runtime.fastrand2runtime.pcvalueCacheKey2runtime.chunkIdx.l22net/http.(*body).readLocked2?github.com/dgraph-io/badger/v2/table.(*Iterator).seekFrom.func129github.com/dgraph-io/badger/v2/table.(*Iterator).seekFrom25github.com/dgraph-io/badger/v2/table.(*Iterator).seek2syscall.RawSyscall2syscall.getsockname2syscall.Getsockname2net.(*netFD).dial2 -net.socket2net.internetSocket2net.(*sysDialer).doDialTCP2net.(*sysDialer).dialTCP2net.(*sysDialer).dialSingle2net.(*sysDialer).dialSerial2net.(*Dialer).DialContext2Hgithub.com/grafana/pyroscope/pkg/og/storage/tree.(*Tree).IterateStacks2@github.com/grafana/pyroscope/pkg/og/storage/tree.(*Tree).Pprof2Hgithub.com/grafana/pyroscope/pkg/og/server.(*Controller).renderHandler2net/http.HandlerFunc.ServeHTTP2>github.com/slok/go-http-metrics/middleware/std.Handler.func1.12=github.com/slok/go-http-metrics/middleware.Middleware.Measure2github.com/grafana/pyroscope/pkg/og/storage/dict.Deserialize2Igithub.com/grafana/pyroscope/pkg/og/storage.dictionaryCodec.Deserialize2reflect.cvtBytesString2reflect.Value.Convert2runtime.mapassign2reflect.mapassign2Bgithub.com/grafana/pyroscope/pkg/og/storage.(*Storage).Get.func12Jgithub.com/grafana/pyroscope/pkg/og/storage/segment.(*Segment).Get.func125google.golang.org/protobuf/internal/impl.pointer.Elem2Cgoogle.golang.org/protobuf/internal/impl.(*MessageInfo).sizePointer2=google.golang.org/protobuf/internal/impl.sizeMessageSliceInfo2Ggoogle.golang.org/protobuf/internal/impl.(*MessageInfo).sizePointerSlow2 0 { - sampleRate = uint32(time.Second / time.Duration(p.Period)) - } - } - t := tree.New() - p.Get(stype, func(_labels *spy.Labels, name []byte, val int) error { - t.Insert(name, uint64(val)) - return nil - }) - fb := flamebearer.NewProfile(flamebearer.ProfileConfig{ - Tree: t, - Name: name, - MaxNodes: maxNodes, - Metadata: metadata.Metadata{ - SpyName: "unknown", - SampleRate: sampleRate, - Units: units, - }, - }) - return &fb, nil - } - return nil, errors.New("no supported sample type found") -} - -func CollapsedToProfile(b []byte, name string, maxNodes int) (*flamebearer.FlamebearerProfile, error) { - t := tree.New() - for _, line := range bytes.Split(b, []byte("\n")) { - if len(line) == 0 { - continue - } - i := bytes.LastIndexByte(line, ' ') - if i < 0 { - return nil, errors.New("unable to find stacktrace and value separator") - } - value, err := strconv.ParseUint(string(line[i+1:]), 10, 64) - if err != nil { - return nil, fmt.Errorf("unable to parse sample value: %w", err) - } - t.Insert(line[:i], value) - } - fb := flamebearer.NewProfile(flamebearer.ProfileConfig{ - Name: name, - Tree: t, - MaxNodes: maxNodes, - Metadata: metadata.Metadata{ - SpyName: "unknown", - SampleRate: 100, // We don't have this information, use the default - }, - }) - return &fb, nil -} - -func PerfScriptToProfile(b []byte, name string, maxNodes int) (*flamebearer.FlamebearerProfile, error) { - t := tree.New() - p := perf.NewScriptParser(b) - events, err := p.ParseEvents() - if err != nil { - return nil, err - } - for _, e := range events { - t.InsertStack(e, 1) - } - fb := flamebearer.NewProfile(flamebearer.ProfileConfig{ - Name: name, - Tree: t, - MaxNodes: maxNodes, - Metadata: metadata.Metadata{ - SpyName: "unknown", - SampleRate: 100, // We don't have this information, use the default - }, - }) - return &fb, nil -} diff --git a/pkg/og/structs/flamebearer/convert/convert_suite_test.go b/pkg/og/structs/flamebearer/convert/convert_suite_test.go deleted file mode 100644 index 7d73600270..0000000000 --- a/pkg/og/structs/flamebearer/convert/convert_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package convert_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestServer(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Flamebearer Convert Suite") -} diff --git a/pkg/og/structs/flamebearer/convert/convert_test.go b/pkg/og/structs/flamebearer/convert/convert_test.go deleted file mode 100644 index bb77586cb3..0000000000 --- a/pkg/og/structs/flamebearer/convert/convert_test.go +++ /dev/null @@ -1,704 +0,0 @@ -package convert - -import ( - "io/ioutil" - "reflect" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/grafana/pyroscope/pkg/og/structs/flamebearer" -) - -var _ = Describe("Server", func() { - Describe("Detecting format", func() { - Context("with a valid pprof type", func() { - When("there's only type", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Type: "pprof", - } - }) - It("should return pprof as type is be enough to detect the type", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(PprofToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - When("there's pprof type and json filename", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Name: "profile.json", - Type: "pprof", - } - }) - It("should return pprof as type takes precedence over filename", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(PprofToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - - When("there's pprof type and json profile contents", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Data: []byte(`{"flamebearer":""}`), - Type: "pprof", - } - }) - It("should return pprof as type takes precedence over profile contents", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(PprofToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - }) - - Context("with no (valid) type and a valid pprof filename", func() { - When("there's pprof filename and json profile contents", func() { - var m ProfileFile - BeforeEach(func() { - m = ProfileFile{ - Name: "profile.pprof", - Data: []byte(`{"flamebearer":""}`), - } - }) - - It("should return pprof as filename takes precedence over profile contents", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(PprofToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - When("there's pprof filename and an unsupported type", func() { - var m ProfileFile - BeforeEach(func() { - m = ProfileFile{ - Name: "profile.pprof", - Type: "unsupported", - } - }) - - It("should return pprof as unsupported type is ignored", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(PprofToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - }) - - Context("with no (valid) type and filename, a valid pprof profile", func() { - When("there's a profile with uncompressed pprof content", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Data: []byte{0x0a, 0x04}, - } - }) - - It("should return pprof", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(PprofToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - - When("there's a profile with compressed pprof content", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Data: []byte{0x1f, 0x8b}, - } - }) - - It("should return pprof", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(PprofToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - - When("there's a profile with compressed pprof content and an unsupported type", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Data: []byte{0x1f, 0x8b}, - Type: "unsupported", - } - }) - - It("should return pprof as unsupported types are ignored", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(PprofToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - - When("there's a profile with compressed pprof content and unsupported filename", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Name: "profile.unsupported", - Data: []byte{0x1f, 0x8b}, - } - }) - - It("should return pprof as unsupported filenames are ignored", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(PprofToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - }) - - Context("with a valid json type", func() { - When("there's only type", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Type: "json", - } - }) - It("should return json as type is be enough to detect the type", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(JSONToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - When("there's json type and pprof filename", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Name: "profile.pprof", - Type: "json", - } - }) - It("should return json as type takes precedence over filename", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(JSONToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - - When("there's json type and pprof profile contents", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Data: []byte{0x1f, 0x8b}, - Type: "json", - } - }) - It("should return json as type takes precedence over profile contents", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(JSONToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - }) - - Context("with no (valid) type and a valid json filename", func() { - When("there's json filename and pprof profile contents", func() { - var m ProfileFile - BeforeEach(func() { - m = ProfileFile{ - Name: "profile.json", - Data: []byte{0x1f, 0x8b}, - } - }) - - It("should return json as filename takes precedence over profile contents", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(JSONToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - When("there's json filename and an unsupported type", func() { - var m ProfileFile - BeforeEach(func() { - m = ProfileFile{ - Name: "profile.json", - Type: "unsupported", - } - }) - - It("should return json as unsupported type is ignored", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(JSONToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - }) - - Context("with no (valid) type and filename, a valid json profile", func() { - When("there's a profile with json content", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Data: []byte(`{"flamebearer":""}`), - } - }) - - It("should return json", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(JSONToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - - When("there's a profile with json content and an unsupported type", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Data: []byte(`{"flamebearer":""}`), - Type: "unsupported", - } - }) - - It("should return json as unsupported types are ignored", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(JSONToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - - When("there's a profile with json content and unsupported filename", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Name: "profile.unsupported", - Data: []byte(`{"flamebearer":""}`), - } - }) - - It("should return json as unsupported filenames are ignored", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(JSONToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - }) - - Context("with a valid collapsed type", func() { - When("there's only type", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Type: "collapsed", - } - }) - It("should return collapsed as type is be enough to detect the type", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(CollapsedToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - When("there's collapsed type and pprof filename", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Name: "profile.pprof", - Type: "collapsed", - } - }) - It("should return collapsed as type takes precedence over filename", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(CollapsedToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - - When("there's json type and pprof profile contents", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Data: []byte{0x1f, 0x8b}, - Type: "collapsed", - } - }) - It("should return collapsed as type takes precedence over profile contents", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(CollapsedToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - }) - - Context("with no (valid) type and a valid collapsed filename", func() { - When("there's collapsed filename and pprof profile contents", func() { - var m ProfileFile - BeforeEach(func() { - m = ProfileFile{ - Name: "profile.collapsed", - Data: []byte{0x1f, 0x8b}, - } - }) - - It("should return collapsed as filename takes precedence over profile contents", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(CollapsedToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - When("there's collapsed filename and an unsupported type", func() { - var m ProfileFile - BeforeEach(func() { - m = ProfileFile{ - Name: "profile.collapsed", - Type: "unsupported", - } - }) - - It("should return collapsed as unsupported type is ignored", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(CollapsedToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - - When("there's collapsed text filename and an unsupported type", func() { - var m ProfileFile - BeforeEach(func() { - m = ProfileFile{ - Name: "profile.collapsed.txt", - Type: "unsupported", - } - }) - - It("should return collapsed as unsupported type is ignored", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(CollapsedToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - }) - - Context("with no (valid) type and filename, a valid collapsed profile", func() { - When("there's a profile with collapsed content", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Data: []byte("fn1 1\nfn2 2"), - } - }) - - It("should return collapsed", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(CollapsedToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - - When("there's a profile with collapsed content and an unsupported type", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Data: []byte("fn1 1\nfn2 2"), - Type: "unsupported", - } - }) - - It("should return collapsed as unsupported types are ignored", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(CollapsedToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - - When("there's a profile with collapsed content and unsupported filename", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Name: "profile.unsupported", - Data: []byte("fn1 1\nfn2 2"), - } - }) - - It("should return collapsed as unsupported filenames are ignored", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(CollapsedToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - }) - - Context("perf script", func() { - When("detect by content", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Data: []byte("java 12688 [002] 6544038.708352: cpu-clock:\n\n"), - } - }) - - It("should return perf_script", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(PerfScriptToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - When("detect by .txt extension and content", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Name: "foo.txt", - Data: []byte("java 12688 [002] 6544038.708352: cpu-clock:\n\n"), - } - }) - - It("should return perf_script", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(PerfScriptToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - When("detect by .perf_script extension", func() { - var m ProfileFile - - BeforeEach(func() { - m = ProfileFile{ - Name: "foo.perf_script", - Data: []byte("foo;bar 239"), - } - }) - - It("should return perf_script", func() { - // We want to compare functions, which is not ideal. - expected := reflect.ValueOf(PerfScriptToProfile).Pointer() - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - Expect(reflect.ValueOf(f).Pointer()).To(Equal(expected)) - }) - }) - }) - - Context("with an empty ProfileFile", func() { - var m ProfileFile - It("should return an error", func() { - _, err := converter(m) - Expect(err).ToNot(Succeed()) - }) - }) - }) - - Describe("Calling DiffV1", func() { - Context("with v1 profiles", func() { - var base, diff *flamebearer.FlamebearerProfile - - When("Diff is called with valid and equal base and diff profiles", func() { - BeforeEach(func() { - base = &flamebearer.FlamebearerProfile{ - Version: 1, - FlamebearerProfileV1: flamebearer.FlamebearerProfileV1{ - Metadata: flamebearer.FlamebearerMetadataV1{ - Format: "single", - }, - // Taken from flamebearer test - Flamebearer: flamebearer.FlamebearerV1{ - Names: []string{"total", "a", "c", "b"}, - Levels: [][]int{ - {0, 3, 0, 0}, - {0, 3, 0, 1}, - {0, 1, 1, 3, 0, 2, 2, 2}, - }, - NumTicks: 3, - MaxSelf: 2, - }, - }, - } - - diff = &flamebearer.FlamebearerProfile{ - Version: 1, - FlamebearerProfileV1: flamebearer.FlamebearerProfileV1{ - Metadata: flamebearer.FlamebearerMetadataV1{ - Format: "single", - }, - // Taken from flamebearer test - Flamebearer: flamebearer.FlamebearerV1{ - Names: []string{"total", "a", "c", "b"}, - Levels: [][]int{ - {0, 3, 0, 0}, - {0, 3, 0, 1}, - {0, 1, 1, 3, 0, 2, 2, 2}, - }, - NumTicks: 3, - MaxSelf: 2, - }, - }, - } - }) - - It("returns the diff profile", func() { - fb, err := flamebearer.Diff("name", base, diff, 1024) - Expect(err).To(Succeed()) - Expect(fb.Version).To(Equal(uint(1))) - Expect(fb.Metadata.Name).To(Equal("name")) - Expect(fb.Metadata.Format).To(Equal("double")) - Expect(fb.Flamebearer.Names).To(Equal([]string{"total", "a", "c", "b"})) - Expect(fb.Flamebearer.Levels).To(Equal([][]int{ - {0, 3, 0, 0, 3, 0, 0}, - {0, 3, 0, 0, 3, 0, 1}, - {0, 1, 1, 0, 1, 1, 3, 0, 2, 2, 0, 2, 2, 2}, - })) - Expect(fb.Flamebearer.NumTicks).To(Equal(6)) - Expect(fb.Flamebearer.MaxSelf).To(Equal(2)) - Expect(fb.LeftTicks).To(Equal(uint64(3))) - Expect(fb.RightTicks).To(Equal(uint64(3))) - }) - }) - }) - }) -}) - -var _ = Describe("Convert", func() { - It("converts malformed pprof", func() { - m := ProfileFile{ - Type: "pprof", - Data: readFile("./testdata/cpu-unknown.pb.gz"), - } - - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - - b, err := f(m.Data, "appname", 1024) - Expect(err).To(BeNil()) - Expect(b).ToNot(BeNil()) - }) - - Describe("JSON", func() { - It("prunes tree", func() { - m := ProfileFile{ - Type: "json", - Data: readFile("./testdata/profile.json"), - } - - f, err := converter(m) - Expect(err).To(BeNil()) - Expect(f).ToNot(BeNil()) - - b, err := f(m.Data, "appname", 1) - Expect(err).To(BeNil()) - Expect(b).ToNot(BeNil()) - - // 1 + total - Expect(len(b.FlamebearerProfileV1.Flamebearer.Levels)).To(Equal(2)) - }) - }) -}) - -func readFile(path string) []byte { - f, err := ioutil.ReadFile(path) - if err != nil { - panic(err) - } - return f -}