diff --git a/interpreter/apmint/apmint.go b/interpreter/apmint/apmint.go index 5e9967ffa..ddfe142a8 100644 --- a/interpreter/apmint/apmint.go +++ b/interpreter/apmint/apmint.go @@ -8,11 +8,10 @@ package apmint // import "go.opentelemetry.io/ebpf-profiler/interpreter/apmint" import ( + "encoding/hex" "errors" "fmt" - "hash/fnv" "regexp" - "strconv" "unsafe" log "github.com/sirupsen/logrus" @@ -145,46 +144,31 @@ type Instance struct { var _ interpreter.Instance = &Instance{} -// hashTrace calculates the hash of a trace and returns it. -// Be aware that changes to this calculation will break the ability to -// look backwards for the same TraceHash in our backend. -func hashTrace(trace *libpf.Trace) (hash [16]byte) { - var buf [24]byte - h := fnv.New128a() - for _, uniqueFrame := range trace.Frames { - frame := uniqueFrame.Value() - _, _ = h.Write(frame.FileID.Bytes()) - // Using FormatUint() or putting AppendUint() into a function leads - // to escaping to heap (allocation). - _, _ = h.Write(strconv.AppendUint(buf[:0], uint64(frame.AddressOrLineno), 10)) - } - h.Sum(hash[:0]) - return -} - // Detach implements the interpreter.Instance interface. func (i *Instance) Detach(ebpf interpreter.EbpfHandler, pid libpf.PID) error { return ebpf.DeleteProcData(libpf.APMInt, pid) } // NotifyAPMAgent sends out collected traces to the connected APM agent. -func (i *Instance) NotifyAPMAgent(pid libpf.PID, rawTrace *host.Trace, umTrace *libpf.Trace) { +func (i *Instance) NotifyAPMAgent( + pid libpf.PID, rawTrace *host.Trace, umTraceHash libpf.TraceHash, count uint16) { if rawTrace.APMTransactionID == libpf.InvalidAPMSpanID || i.socket == nil { return } + log.Debugf("Reporting %dx trace hash %s -> TX %s for PID %d", + count, umTraceHash.StringNoQuotes(), + hex.EncodeToString(rawTrace.APMTransactionID[:]), pid) + msg := traceCorrMsg{ MessageType: 1, MinorVersion: 1, APMTraceID: rawTrace.APMTraceID, APMTransactionID: rawTrace.APMTransactionID, - StackTraceID: hashTrace(umTrace), - Count: 1, + StackTraceID: umTraceHash, + Count: count, } - log.Debugf("Reporting a trace hash %x -> TX %x for PID %d", - msg.StackTraceID[:], rawTrace.APMTransactionID[:], pid) - if err := i.socket.SendMessage(msg.Serialize()); err != nil { log.Debugf("Failed to send trace mappings to APM agent: %v", err) } diff --git a/interpreter/apmint/socket.go b/interpreter/apmint/socket.go index 8f8c4eb29..6172c4370 100644 --- a/interpreter/apmint/socket.go +++ b/interpreter/apmint/socket.go @@ -103,7 +103,7 @@ type traceCorrMsg struct { MinorVersion uint16 APMTraceID libpf.APMTraceID APMTransactionID libpf.APMTransactionID - StackTraceID [16]byte + StackTraceID libpf.TraceHash Count uint16 } @@ -113,7 +113,7 @@ func (m *traceCorrMsg) Serialize() []byte { _ = binary.Write(&buf, binary.LittleEndian, m.MinorVersion) _, _ = buf.Write(m.APMTraceID[:]) _, _ = buf.Write(m.APMTransactionID[:]) - _, _ = buf.Write(m.StackTraceID[:]) + _, _ = buf.Write(m.StackTraceID.Bytes()) _ = binary.Write(&buf, binary.LittleEndian, m.Count) return buf.Bytes() } diff --git a/libpf/trace.go b/libpf/trace.go index fda4b6221..bbae7c8d5 100644 --- a/libpf/trace.go +++ b/libpf/trace.go @@ -41,6 +41,7 @@ func (frames *Frames) Append(frame *Frame) { // Trace represents a stack trace. type Trace struct { Frames Frames + Hash TraceHash CustomLabels map[string]string } diff --git a/libpf/tracehash.go b/libpf/tracehash.go new file mode 100644 index 000000000..be2bee3b5 --- /dev/null +++ b/libpf/tracehash.go @@ -0,0 +1,60 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package libpf // import "go.opentelemetry.io/ebpf-profiler/libpf" + +import ( + "encoding" + "encoding/base64" + + "go.opentelemetry.io/ebpf-profiler/libpf/basehash" +) + +// TraceHash represents the unique hash of a trace +type TraceHash struct { + basehash.Hash128 +} + +func NewTraceHash(hi, lo uint64) TraceHash { + return TraceHash{basehash.New128(hi, lo)} +} + +// TraceHashFromBytes parses a byte slice of a trace hash into the internal data representation. +func TraceHashFromBytes(b []byte) (TraceHash, error) { + h, err := basehash.New128FromBytes(b) + if err != nil { + return TraceHash{}, err + } + return TraceHash{h}, nil +} + +func (h TraceHash) Equal(other TraceHash) bool { + return h.Hash128.Equal(other.Hash128) +} + +func (h TraceHash) Less(other TraceHash) bool { + return h.Hash128.Less(other.Hash128) +} + +// EncodeTo encodes the hash into the base64 encoded representation +// and stores it in the provided destination byte array. +// The length of the destination must be at least EncodedLen(). +func (h TraceHash) EncodeTo(dst []byte) { + base64.RawURLEncoding.Encode(dst, h.Bytes()) +} + +// EncodedLen returns the length of the hash's base64 representation. +func (TraceHash) EncodedLen() int { + // TraceHash is 16 bytes long, the base64 representation is one base64 byte per 6 bits. + return ((16)*8)/6 + 1 +} + +// Hash32 returns a 32 bits hash of the input. +// It's main purpose is to be used for LRU caching. +func (h TraceHash) Hash32() uint32 { + return uint32(h.Lo()) +} + +// Compile-time interface checks +var _ encoding.TextUnmarshaler = (*TraceHash)(nil) +var _ encoding.TextMarshaler = (*TraceHash)(nil) diff --git a/libpf/tracehash_test.go b/libpf/tracehash_test.go new file mode 100644 index 000000000..ed26df687 --- /dev/null +++ b/libpf/tracehash_test.go @@ -0,0 +1,83 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package libpf + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTraceHashSprintf(t *testing.T) { + origHash := NewTraceHash(0x0001C03F8D6B8520, 0xEDEAEEA9460BEEBB) + + marshaled := fmt.Sprintf("%d", origHash) + //nolint:goconst + expected := "{492854164817184 17143777342331285179}" + assert.Equal(t, expected, marshaled) + + marshaled = fmt.Sprintf("%s", origHash) + expected = "{%!s(uint64=492854164817184) %!s(uint64=17143777342331285179)}" + assert.Equal(t, expected, marshaled) + + marshaled = fmt.Sprintf("%v", origHash) + //nolint:goconst + expected = "{492854164817184 17143777342331285179}" + assert.Equal(t, expected, marshaled) + + marshaled = fmt.Sprintf("%#v", origHash) + expected = "0x1c03f8d6b8520edeaeea9460beebb" + assert.Equal(t, expected, marshaled) + + // Values were chosen to test non-zero-padded output + traceHash := NewTraceHash(42, 100) + + marshaled = fmt.Sprintf("%x", traceHash) + expected = "2a0000000000000064" + assert.Equal(t, expected, marshaled) + + marshaled = fmt.Sprintf("%X", traceHash) + expected = "2A0000000000000064" + assert.Equal(t, expected, marshaled) + + marshaled = fmt.Sprintf("%#x", traceHash) + expected = "0x2a0000000000000064" + assert.Equal(t, expected, marshaled) + + marshaled = fmt.Sprintf("%#X", traceHash) + expected = "0x2A0000000000000064" + assert.Equal(t, expected, marshaled) +} + +func TestTraceHashMarshal(t *testing.T) { + origHash := NewTraceHash(0x600DF00D, 0xF00D600D) + + // Test (Un)MarshalJSON + data, err := origHash.MarshalJSON() + require.NoError(t, err) + + marshaled := string(data) + expected := "\"00000000600df00d00000000f00d600d\"" + assert.Equal(t, expected, marshaled) + + var jsonHash TraceHash + err = jsonHash.UnmarshalJSON(data) + require.NoError(t, err) + assert.Equal(t, origHash, jsonHash) + + // Test (Un)MarshalText + data, err = origHash.MarshalText() + require.NoError(t, err) + + marshaled = string(data) + expected = "00000000600df00d00000000f00d600d" + assert.Equal(t, expected, marshaled) + + var textHash TraceHash + err = textHash.UnmarshalText(data) + require.NoError(t, err) + assert.Equal(t, origHash, textHash) +} diff --git a/processmanager/manager.go b/processmanager/manager.go index dd46dec15..2cc103db4 100644 --- a/processmanager/manager.go +++ b/processmanager/manager.go @@ -26,6 +26,7 @@ import ( "go.opentelemetry.io/ebpf-profiler/reporter" "go.opentelemetry.io/ebpf-profiler/times" "go.opentelemetry.io/ebpf-profiler/tracer/types" + "go.opentelemetry.io/ebpf-profiler/traceutil" "go.opentelemetry.io/ebpf-profiler/util" ) @@ -295,10 +296,12 @@ func (pm *ProcessManager) ConvertTrace(trace *host.Trace) (newTrace *libpf.Trace } } } + newTrace.Hash = traceutil.HashTrace(newTrace) return newTrace } -func (pm *ProcessManager) MaybeNotifyAPMAgent(rawTrace *host.Trace, umTrace *libpf.Trace) string { +func (pm *ProcessManager) MaybeNotifyAPMAgent( + rawTrace *host.Trace, umTraceHash libpf.TraceHash, count uint16) string { pm.mu.RLock() pidInterp, ok := pm.interpreters[rawTrace.PID] pm.mu.RUnlock() @@ -309,7 +312,7 @@ func (pm *ProcessManager) MaybeNotifyAPMAgent(rawTrace *host.Trace, umTrace *lib var serviceName string for _, mapping := range pidInterp { if apm, ok := mapping.(*apmint.Instance); ok { - apm.NotifyAPMAgent(rawTrace.PID, rawTrace, umTrace) + apm.NotifyAPMAgent(rawTrace.PID, rawTrace, umTraceHash, count) if serviceName != "" { log.Warnf("Overwriting APM service name from '%s' to '%s' for PID %d", diff --git a/processmanager/manager_test.go b/processmanager/manager_test.go index 3f7c967fa..6ada3571b 100644 --- a/processmanager/manager_test.go +++ b/processmanager/manager_test.go @@ -29,6 +29,7 @@ import ( "go.opentelemetry.io/ebpf-profiler/reporter" "go.opentelemetry.io/ebpf-profiler/support" tracertypes "go.opentelemetry.io/ebpf-profiler/tracer/types" + "go.opentelemetry.io/ebpf-profiler/traceutil" "go.opentelemetry.io/ebpf-profiler/util" "github.com/stretchr/testify/assert" @@ -262,6 +263,87 @@ func (s *symbolReporterMockup) ExecutableMetadata(_ *reporter.ExecutableMetadata var _ reporter.SymbolReporter = (*symbolReporterMockup)(nil) +func TestInterpreterConvertTrace(t *testing.T) { + partialNativeFrameFileID := uint64(0xabcdbeef) + nativeFrameLineno := libpf.AddressOrLineno(0x1234) + + pythonAndNativeTrace := &host.Trace{ + Frames: []host.Frame{{ + // This represents a native frame + File: host.FileID(partialNativeFrameFileID), + Lineno: nativeFrameLineno, + Type: libpf.NativeFrame, + }, { + File: host.FileID(42), + Lineno: libpf.AddressOrLineno(0x13e1bb8e), // same as runForeverTrace + Type: libpf.PythonFrame, + }}, + } + + tests := map[string]struct { + trace *host.Trace + expect *libpf.Trace + }{ + "Convert Trace": { + trace: pythonAndNativeTrace, + expect: getExpectedTrace(pythonAndNativeTrace, + []libpf.AddressOrLineno{0, 1}), + }, + } + + for name, testcase := range tests { + t.Run(name, func(t *testing.T) { + mapper := NewMapFileIDMapper() + for i, f := range testcase.trace.Frames { + mapper.Set(f.File, testcase.expect.Frames[i].Value().FileID) + } + + // For this test do not include interpreters. + noIinterpreters, _ := tracertypes.Parse("") + + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + + // To test ConvertTrace we do not require all parts of processmanager. + manager, err := New(ctx, + noIinterpreters, + 1*time.Second, + nil, + nil, + &symbolReporterMockup{}, + nil, + true, + libpf.Set[string]{}) + require.NoError(t, err) + + newTrace := manager.ConvertTrace(testcase.trace) + + testcase.expect.Hash = traceutil.HashTrace(testcase.expect) + if testcase.expect.Hash == newTrace.Hash { + assert.Equal(t, testcase.expect, newTrace) + } + }) + } +} + +// getExpectedTrace returns a new libpf trace that is based on the provided host trace, but +// with the linenos replaced by the provided values. This function is for generating an expected +// trace for tests below. +func getExpectedTrace(origTrace *host.Trace, linenos []libpf.AddressOrLineno) *libpf.Trace { + newTrace := &libpf.Trace{ + Hash: libpf.NewTraceHash(uint64(origTrace.Hash), uint64(origTrace.Hash)), + } + + for i, frame := range origTrace.Frames { + lineno := frame.Lineno + if linenos != nil { + lineno = linenos[i] + } + newTrace.AppendFrame(frame.Type, libpf.NewFileID(uint64(frame.File), 0), lineno) + } + return newTrace +} + func TestNewMapping(t *testing.T) { tests := map[string]struct { // newMapping holds the arguments that are passed to NewMapping() in the test. diff --git a/reporter/base_reporter.go b/reporter/base_reporter.go index 9ba6c52d0..8a49f26d2 100644 --- a/reporter/base_reporter.go +++ b/reporter/base_reporter.go @@ -69,6 +69,7 @@ func (b *baseReporter) ReportTraceEvent(trace *libpf.Trace, meta *samples.TraceE containerID := meta.ContainerID key := samples.TraceAndMetaKey{ + Hash: trace.Hash, Comm: meta.Comm, ProcessName: meta.ProcessName, ExecutablePath: meta.ExecutablePath, diff --git a/reporter/samples/attrmgr_test.go b/reporter/samples/attrmgr_test.go index c02706e1b..5b76ebb59 100644 --- a/reporter/samples/attrmgr_test.go +++ b/reporter/samples/attrmgr_test.go @@ -11,6 +11,7 @@ import ( "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/pprofile" + "go.opentelemetry.io/ebpf-profiler/libpf" semconv "go.opentelemetry.io/otel/semconv/v1.34.0" ) @@ -28,6 +29,7 @@ func TestAttrTableManager(t *testing.T) { "empty": { k: []TraceAndMetaKey{ { + Hash: libpf.TraceHash{}, Comm: "", ApmServiceName: "", ContainerID: "", @@ -42,12 +44,14 @@ func TestAttrTableManager(t *testing.T) { "duplicate": { k: []TraceAndMetaKey{ { + Hash: libpf.TraceHash{}, Comm: "comm1", ApmServiceName: "apmServiceName1", ContainerID: "containerID1", Pid: 1234, }, { + Hash: libpf.TraceHash{}, Comm: "comm1", ApmServiceName: "apmServiceName1", ContainerID: "containerID1", @@ -65,12 +69,14 @@ func TestAttrTableManager(t *testing.T) { "different": { k: []TraceAndMetaKey{ { + Hash: libpf.TraceHash{}, Comm: "comm1", ApmServiceName: "apmServiceName1", ContainerID: "containerID1", Pid: 1234, }, { + Hash: libpf.TraceHash{}, Comm: "comm2", ApmServiceName: "apmServiceName2", ContainerID: "containerID2", diff --git a/reporter/samples/samples.go b/reporter/samples/samples.go index 838783cd4..0f24e8597 100644 --- a/reporter/samples/samples.go +++ b/reporter/samples/samples.go @@ -34,6 +34,9 @@ type TraceEvents struct { // contain all trace fields that aren't already part of the trace hash to ensure // that we don't accidentally merge traces with different fields. type TraceAndMetaKey struct { + // Hash is not sent forward, but it is used as the primary key + // to not aggregate difference traces. + Hash libpf.TraceHash // comm and apmServiceName are provided by the eBPF programs Comm string ApmServiceName string diff --git a/tracehandler/tracehandler.go b/tracehandler/tracehandler.go index 97230b584..e89ae1f54 100644 --- a/tracehandler/tracehandler.go +++ b/tracehandler/tracehandler.go @@ -41,7 +41,7 @@ type TraceProcessor interface { // MaybeNotifyAPMAgent notifies a potentially existing connected APM agent // that a stack trace was collected in their process. If an APM agent is // listening, the service name is returned. - MaybeNotifyAPMAgent(rawTrace *host.Trace, umTrace *libpf.Trace) string + MaybeNotifyAPMAgent(rawTrace *host.Trace, umTraceHash libpf.TraceHash, count uint16) string // ConvertTrace converts a trace from eBPF into the form we want to send to // the collection agent. Depending on the frame type it will attempt to symbolize @@ -135,7 +135,7 @@ func (m *traceHandler) HandleTrace(bpfTrace *host.Trace) { traceCacheLifetime); exists { m.traceCacheHit++ // Fast path - meta.APMServiceName = m.traceProcessor.MaybeNotifyAPMAgent(bpfTrace, &trace) + meta.APMServiceName = m.traceProcessor.MaybeNotifyAPMAgent(bpfTrace, trace.Hash, 1) if err := m.reporter.ReportTraceEvent(&trace, meta); err != nil { log.Errorf("Failed to report trace event: %v", err) } @@ -147,7 +147,7 @@ func (m *traceHandler) HandleTrace(bpfTrace *host.Trace) { umTrace := m.traceProcessor.ConvertTrace(bpfTrace) m.traceCache.Add(bpfTrace.Hash, *umTrace) - meta.APMServiceName = m.traceProcessor.MaybeNotifyAPMAgent(bpfTrace, umTrace) + meta.APMServiceName = m.traceProcessor.MaybeNotifyAPMAgent(bpfTrace, umTrace.Hash, 1) if err := m.reporter.ReportTraceEvent(umTrace, meta); err != nil { log.Errorf("Failed to report trace event: %v", err) } diff --git a/tracehandler/tracehandler_test.go b/tracehandler/tracehandler_test.go index 1a88d3d27..752a81c03 100644 --- a/tracehandler/tracehandler_test.go +++ b/tracehandler/tracehandler_test.go @@ -35,14 +35,14 @@ type fakeTraceProcessor struct{} var _ tracehandler.TraceProcessor = (*fakeTraceProcessor)(nil) func (f *fakeTraceProcessor) ConvertTrace(trace *host.Trace) *libpf.Trace { - converted := &libpf.Trace{} - converted.Frames.Append(&libpf.Frame{AddressOrLineno: libpf.AddressOrLineno(trace.Hash)}) - return converted + var newTrace libpf.Trace + newTrace.Hash = libpf.NewTraceHash(uint64(trace.Hash), uint64(trace.Hash)) + return &newTrace } func (f *fakeTraceProcessor) ProcessedUntil(times.KTime) {} -func (f *fakeTraceProcessor) MaybeNotifyAPMAgent(*host.Trace, *libpf.Trace) string { +func (f *fakeTraceProcessor) MaybeNotifyAPMAgent(*host.Trace, libpf.TraceHash, uint16) string { return "" } @@ -54,16 +54,15 @@ type arguments struct { type mockReporter struct { t *testing.T - reports map[host.TraceHash]uint16 + reports map[libpf.TraceHash]uint16 } func (m *mockReporter) ReportTraceEvent(trace *libpf.Trace, _ *samples.TraceEventMeta) error { - hash := host.TraceHash(trace.Frames[0].Value().AddressOrLineno) - if _, exists := m.reports[hash]; exists { - m.reports[hash]++ + if _, exists := m.reports[trace.Hash]; exists { + m.reports[trace.Hash]++ return nil } - m.reports[hash] = 1 + m.reports[trace.Hash] = 1 return nil } @@ -72,7 +71,7 @@ func TestTraceHandler(t *testing.T) { tests := map[string]struct { input []arguments expireTimeout time.Duration - expectedEvents map[host.TraceHash]uint16 + expectedEvents map[libpf.TraceHash]uint16 }{ // no input simulates a case where no data is provided as input // to the functions of traceHandler. @@ -82,8 +81,8 @@ func TestTraceHandler(t *testing.T) { "single trace": {input: []arguments{ {trace: &host.Trace{Hash: host.TraceHash(0x1234)}}, }, - expectedEvents: map[host.TraceHash]uint16{ - 0x1234: 1, + expectedEvents: map[libpf.TraceHash]uint16{ + libpf.NewTraceHash(0x1234, 0x1234): 1, }, }, @@ -92,8 +91,8 @@ func TestTraceHandler(t *testing.T) { {trace: &host.Trace{Hash: host.TraceHash(4)}}, {trace: &host.Trace{Hash: host.TraceHash(4)}}, }, - expectedEvents: map[host.TraceHash]uint16{ - 4: 2, + expectedEvents: map[libpf.TraceHash]uint16{ + libpf.NewTraceHash(4, 4): 2, }, }, } @@ -102,7 +101,7 @@ func TestTraceHandler(t *testing.T) { t.Run(name, func(t *testing.T) { r := &mockReporter{ t: t, - reports: make(map[host.TraceHash]uint16), + reports: make(map[libpf.TraceHash]uint16), } traceChan := make(chan *host.Trace) diff --git a/traceutil/traceutil.go b/traceutil/traceutil.go new file mode 100644 index 000000000..fead3a987 --- /dev/null +++ b/traceutil/traceutil.go @@ -0,0 +1,29 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package traceutil // import "go.opentelemetry.io/ebpf-profiler/traceutil" + +import ( + "hash/fnv" + "strconv" + + "go.opentelemetry.io/ebpf-profiler/libpf" +) + +// HashTrace calculates the hash of a trace and returns it. +// Be aware that changes to this calculation will break the ability to +// look backwards for the same TraceHash in our backend. +func HashTrace(trace *libpf.Trace) libpf.TraceHash { + var buf [24]byte + h := fnv.New128a() + for _, uniqueFrame := range trace.Frames { + frame := uniqueFrame.Value() + _, _ = h.Write(frame.FileID.Bytes()) + // Using FormatUint() or putting AppendUint() into a function leads + // to escaping to heap (allocation). + _, _ = h.Write(strconv.AppendUint(buf[:0], uint64(frame.AddressOrLineno), 10)) + } + // make instead of nil avoids a heap allocation + traceHash, _ := libpf.TraceHashFromBytes(h.Sum(make([]byte, 0, 16))) + return traceHash +} diff --git a/interpreter/apmint/hash_test.go b/traceutil/traceutil_test.go similarity index 68% rename from interpreter/apmint/hash_test.go rename to traceutil/traceutil_test.go index fd3e2a258..c1bc5d08c 100644 --- a/interpreter/apmint/hash_test.go +++ b/traceutil/traceutil_test.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package apmint // import "go.opentelemetry.io/ebpf-profiler/interpreter/apmint" +package traceutil import ( "testing" @@ -30,23 +30,19 @@ func newPythonTrace() *libpf.Trace { func TestHashTrace(t *testing.T) { tests := map[string]struct { trace *libpf.Trace - result [16]byte + result libpf.TraceHash }{ "empty trace": { - trace: &libpf.Trace{}, - result: [16]uint8{0x6c, 0x62, 0x27, 0x2e, 0x7, 0xbb, 0x1, 0x42, - 0x62, 0xb8, 0x21, 0x75, 0x62, 0x95, 0xc5, 0x8d}, - }, + trace: &libpf.Trace{}, + result: libpf.NewTraceHash(0x6c62272e07bb0142, 0x62b821756295c58d)}, "python trace": { - trace: newPythonTrace(), - result: [16]byte{0x21, 0xc6, 0xfe, 0x4c, 0x62, 0x86, 0x88, 0x56, - 0xcf, 0x51, 0x5, 0x96, 0xea, 0xb6, 0x8d, 0xc8}, - }, + trace: newPythonTrace(), + result: libpf.NewTraceHash(0x21c6fe4c62868856, 0xcf510596eab68dc8)}, } for name, testcase := range tests { t.Run(name, func(t *testing.T) { - assert.Equal(t, testcase.result, hashTrace(testcase.trace)) + assert.Equal(t, testcase.result, HashTrace(testcase.trace)) }) } }