github.com/grafana/pyroscope@v1.18.0/pkg/test/integration/testdata/README.md (about) 1 ## Data generation 2 3 OTLP ingest handler has been updated to be compatible with OTLP protocol 1.8. Unfortunately, otel-collector does not yet have full support for 1.8.0, which prevents us from using otelcollector-contrib compiled image. Below procedure should be revisited once otel-collector is fully updated to 1.8. 4 5 To generate data for this fixture, use following procedure: 6 7 1. Patch ingest_handler.go to dump the received data: 8 9 ``` 10 diff --git a/pkg/ingester/otlp/ingest_handler.go b/pkg/ingester/otlp/ingest_handler.go 11 index bf7a6612f..1a6619243 100644 12 --- a/pkg/ingester/otlp/ingest_handler.go 13 +++ b/pkg/ingester/otlp/ingest_handler.go 14 @@ -2,13 +2,18 @@ package otlp 15 16 import ( 17 "context" 18 + "encoding/json" 19 "fmt" 20 "net/http" 21 + "os" 22 + "path/filepath" 23 "strings" 24 + "time" 25 26 "google.golang.org/grpc" 27 "google.golang.org/grpc/codes" 28 "google.golang.org/grpc/keepalive" 29 + "google.golang.org/protobuf/encoding/protojson" 30 "google.golang.org/protobuf/proto" 31 32 "github.com/go-kit/log" 33 @@ -103,6 +108,20 @@ func (h *ingestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 34 } 35 36 func (h *ingestHandler) Export(ctx context.Context, er *pprofileotlp.ExportProfilesServiceRequest) (*pprofileotlp.ExportProfilesServiceResponse, error) { 37 + // Debug: dump request to files & optionally corrupt data 38 + //for _, rp := range er.GetResourceProfiles() { 39 + // for _, sp := range rp.GetScopeProfiles() { 40 + // for _, p := range sp.GetProfiles() { 41 + // for _, s := range p.Sample { 42 + // s.StackIndex = 1000000000 43 + // } 44 + // } 45 + // } 46 + //} 47 + if err := h.dumpRequestToFiles(er); err != nil { 48 + level.Warn(h.log).Log("msg", "failed to dump request to debug files", "err", err) 49 + } 50 + 51 // TODO: @petethepig This logic is copied from util.AuthenticateUser and should be refactored into a common function 52 // Extracts user ID from the request metadata and returns and injects the user ID in the context 53 if !h.multitenancyEnabled { 54 @@ -243,3 +262,55 @@ func appendAttributesUnique(labels []*typesv1.LabelPair, attrs []*v1.KeyValue, p 55 } 56 return labels 57 } 58 + 59 +// dumpRequestToFiles dumps the received request to both protobuf and JSON formats for debugging 60 +func (h *ingestHandler) dumpRequestToFiles(er *pprofileotlp.ExportProfilesServiceRequest) error { 61 + captureDir := "/tmp/capture" 62 + 63 + // Ensure capture directory exists 64 + if err := os.MkdirAll(captureDir, 0755); err != nil { 65 + return fmt.Errorf("failed to create capture directory: %w", err) 66 + } 67 + 68 + // Generate timestamp-based filename 69 + timestamp := time.Now().UnixNano() 70 + 71 + // Dump as protobuf binary 72 + pbData, err := proto.Marshal(er) 73 + if err != nil { 74 + return fmt.Errorf("failed to marshal protobuf: %w", err) 75 + } 76 + 77 + pbFilename := filepath.Join(captureDir, fmt.Sprintf("%d.pb.bin", timestamp)) 78 + if err := os.WriteFile(pbFilename, pbData, 0644); err != nil { 79 + return fmt.Errorf("failed to write protobuf file: %w", err) 80 + } 81 + 82 + // Dump as formatted JSON 83 + jsonData, err := protojson.MarshalOptions{ 84 + Multiline: true, 85 + Indent: " ", 86 + }.Marshal(er) 87 + if err != nil { 88 + return fmt.Errorf("failed to marshal JSON: %w", err) 89 + } 90 + 91 + // Pretty print JSON 92 + var prettyJSON map[string]interface{} 93 + if err := json.Unmarshal(jsonData, &prettyJSON); err != nil { 94 + return fmt.Errorf("failed to unmarshal JSON for pretty printing: %w", err) 95 + } 96 + 97 + formattedJSON, err := json.MarshalIndent(prettyJSON, "", " ") 98 + if err != nil { 99 + return fmt.Errorf("failed to marshal pretty JSON: %w", err) 100 + } 101 + 102 + jsonFilename := filepath.Join(captureDir, fmt.Sprintf("%d.pb.json", timestamp)) 103 + if err := os.WriteFile(jsonFilename, formattedJSON, 0644); err != nil { 104 + return fmt.Errorf("failed to write JSON file: %w", err) 105 + } 106 + 107 + level.Debug(h.log).Log("msg", "dumped request to debug files", "pb_file", pbFilename, "json_file", jsonFilename) 108 + return nil 109 +} 110 ``` 111 2.Launch local pyroscope instance 112 3.Compile & start ebpf profiler with following parameters: 113 114 ``` 115 ./ebpf-profiler -collection-agent <pyroscope:4040> -off-cpu-threshold 1 -disable-tls 116 ``` 117 118 Example command to start profiler under Docker/Podman on Mac (from a source directory with compiled profiler): 119 ``` 120 podman run -v "$PWD":/agent --mount type=bind,source=/sys/kernel/tracing,target=/sys/kernel/tracing --mount type=bind,source=/sys/kernel/debug,target=/sys/kernel/debug -it --privileged --replace --pid=host --name ebpf --user 0:0 otel/opentelemetry-ebpf-profiler-dev:latest /agent/ebpf-profiler -collection-agent <pyroscope:4040> -off-cpu-threshold 1 -disable-tls 121 ``` 122 Note that this will capture live data from all processes on the machine it runs on 123 124 4.Allow some profiles to be gathered, explore /tmp/capture dir