Skip to content

Commit f86374a

Browse files
carabasdanielactions-user
authored andcommitted
Add memory helpers to tracing package (#2703)
Moved memory attribute helpers into tracing package and added tracing for memory usage during blast radius calculation recursion GitOrigin-RevId: d9770471a5f32fcc5fd00bbd447e33eb3d332335
1 parent 03afc74 commit f86374a

File tree

2 files changed

+144
-0
lines changed

2 files changed

+144
-0
lines changed

tracing/memory.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package tracing
2+
3+
import (
4+
"runtime"
5+
6+
"go.opentelemetry.io/otel/attribute"
7+
"go.opentelemetry.io/otel/trace"
8+
)
9+
10+
// safeUint64ToInt64 safely converts uint64 to int64 for OpenTelemetry attributes
11+
// Returns int64 max value if the uint64 exceeds int64 maximum to prevent overflow
12+
func safeUint64ToInt64(val uint64) int64 {
13+
const maxInt64 = 9223372036854775807 // 2^63 - 1
14+
if val > maxInt64 { // Check if val exceeds int64 max
15+
return maxInt64 // Return int64 max value
16+
}
17+
return int64(val)
18+
}
19+
20+
// MemoryStats represents memory statistics at a point in time, converted to int64 for safe use
21+
type MemoryStats struct {
22+
Alloc int64 // bytes allocated and not yet freed
23+
HeapAlloc int64 // bytes allocated and not yet freed (same as Alloc above but specifically for heap objects)
24+
Sys int64 // total bytes of memory obtained from the OS
25+
NumGC int64 // number of completed GC cycles
26+
PauseTotal int64 // cumulative nanoseconds in GC stop-the-world pauses
27+
}
28+
29+
// ReadMemoryStats captures current memory statistics and converts them to int64
30+
func ReadMemoryStats() MemoryStats {
31+
var memStats runtime.MemStats
32+
runtime.ReadMemStats(&memStats)
33+
return MemoryStats{
34+
Alloc: safeUint64ToInt64(memStats.Alloc),
35+
HeapAlloc: safeUint64ToInt64(memStats.HeapAlloc),
36+
Sys: safeUint64ToInt64(memStats.Sys),
37+
NumGC: int64(memStats.NumGC),
38+
PauseTotal: safeUint64ToInt64(memStats.PauseTotalNs),
39+
}
40+
}
41+
42+
// SetMemoryAttributes sets memory-related attributes on a span with the given prefix
43+
func SetMemoryAttributes(span trace.Span, prefix string, memStats MemoryStats) {
44+
span.SetAttributes(
45+
attribute.Int64(prefix+".memoryBytes", memStats.Alloc),
46+
attribute.Int64(prefix+".memoryHeapBytes", memStats.HeapAlloc),
47+
attribute.Int64(prefix+".memorySysBytes", memStats.Sys),
48+
attribute.Int64(prefix+".memoryNumGC", memStats.NumGC),
49+
attribute.Int64(prefix+".memoryPauseTotalNs", memStats.PauseTotal),
50+
)
51+
}
52+
53+
// SetMemoryDeltaAttributes sets memory delta attributes on a span with the given prefix
54+
// It calculates the difference between before and after memory stats
55+
func SetMemoryDeltaAttributes(span trace.Span, prefix string, before, after MemoryStats) {
56+
deltaAlloc := after.Alloc - before.Alloc
57+
deltaHeapAlloc := after.HeapAlloc - before.HeapAlloc
58+
deltaSys := after.Sys - before.Sys
59+
deltaNumGC := after.NumGC - before.NumGC
60+
deltaPauseTotal := after.PauseTotal - before.PauseTotal
61+
62+
span.SetAttributes(
63+
attribute.Int64(prefix+".memoryDeltaBytes", deltaAlloc),
64+
attribute.Int64(prefix+".memoryDeltaHeapBytes", deltaHeapAlloc),
65+
attribute.Int64(prefix+".memoryDeltaSysBytes", deltaSys),
66+
attribute.Int64(prefix+".memoryDeltaNumGC", deltaNumGC),
67+
attribute.Int64(prefix+".memoryDeltaPauseTotalNs", deltaPauseTotal),
68+
)
69+
}

tracing/memory_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package tracing
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestSafeUint64ToInt64(t *testing.T) {
8+
t.Parallel()
9+
10+
tests := []struct {
11+
name string
12+
input uint64
13+
expected int64
14+
}{
15+
{
16+
name: "small value",
17+
input: 1000,
18+
expected: 1000,
19+
},
20+
{
21+
name: "int64 max value",
22+
input: 9223372036854775807, // 2^63 - 1
23+
expected: 9223372036854775807,
24+
},
25+
{
26+
name: "int64 max + 1",
27+
input: 9223372036854775808, // 2^63
28+
expected: 9223372036854775807, // Should be clamped to int64 max
29+
},
30+
{
31+
name: "very large value",
32+
input: 18446744073709551615, // uint64 max
33+
expected: 9223372036854775807, // Should be clamped to int64 max
34+
},
35+
}
36+
37+
for _, tt := range tests {
38+
test := tt // capture loop variable
39+
t.Run(test.name, func(t *testing.T) {
40+
t.Parallel()
41+
result := safeUint64ToInt64(test.input)
42+
if result != test.expected {
43+
t.Errorf("safeUint64ToInt64(%d) = %d, expected %d", test.input, result, test.expected)
44+
}
45+
})
46+
}
47+
}
48+
49+
func TestReadMemoryStats(t *testing.T) {
50+
t.Parallel()
51+
52+
stats := ReadMemoryStats()
53+
54+
// Basic sanity checks - these values should be reasonable
55+
if stats.Alloc <= 0 {
56+
t.Errorf("Alloc should be greater than 0, got %d", stats.Alloc)
57+
}
58+
if stats.HeapAlloc <= 0 {
59+
t.Errorf("HeapAlloc should be greater than 0, got %d", stats.HeapAlloc)
60+
}
61+
if stats.Sys <= 0 {
62+
t.Errorf("Sys should be greater than 0, got %d", stats.Sys)
63+
}
64+
65+
// Verify that values are within int64 range (they should be since we convert them)
66+
if stats.Alloc < 0 {
67+
t.Errorf("Alloc should not be negative, got %d", stats.Alloc)
68+
}
69+
if stats.HeapAlloc < 0 {
70+
t.Errorf("HeapAlloc should not be negative, got %d", stats.HeapAlloc)
71+
}
72+
if stats.Sys < 0 {
73+
t.Errorf("Sys should not be negative, got %d", stats.Sys)
74+
}
75+
}

0 commit comments

Comments
 (0)