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