Skip to content

Commit 183be78

Browse files
committed
refactor(stacktrace): remove unused prefixTrie implementation
Deleted character-based prefixTrie implementation (~130 lines) as segmentPrefixTrie is the only trie used in production code. segmentPrefixTrie provides better performance for path-based prefix matching and is the sole implementation used by the stacktrace filtering system.
1 parent ed45a45 commit 183be78

File tree

3 files changed

+0
-577
lines changed

3 files changed

+0
-577
lines changed

internal/stacktrace/trie.go

Lines changed: 0 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -5,143 +5,6 @@
55

66
package stacktrace
77

8-
import (
9-
"sync"
10-
)
11-
12-
// prefixTrie is a thread-safe trie data structure optimized for prefix matching.
13-
// It's designed for high-performance concurrent read operations with occasional writes.
14-
//
15-
// Memory vs Performance Trade-offs:
16-
// - Slightly higher initial memory overhead for data structure vs slice
17-
// - Zero allocations during lookups (same as slice)
18-
// - O(m) lookup time where m=string length (vs O(n) where n=prefix count)
19-
// - Performance varies: slower for early matches, much faster for no-match scenarios
20-
type prefixTrie struct {
21-
root *trieNode
22-
mu sync.RWMutex
23-
}
24-
25-
// trieNode represents a single node in the trie
26-
type trieNode struct {
27-
children map[rune]*trieNode
28-
isEnd bool // true if this node represents the end of a prefix
29-
}
30-
31-
// newPrefixTrie creates a new empty PrefixTrie
32-
func newPrefixTrie() *prefixTrie {
33-
return &prefixTrie{
34-
root: &trieNode{
35-
children: make(map[rune]*trieNode),
36-
},
37-
}
38-
}
39-
40-
// Insert adds a prefix to the trie
41-
func (t *prefixTrie) Insert(prefix string) {
42-
if prefix == "" {
43-
return
44-
}
45-
46-
t.mu.Lock()
47-
defer t.mu.Unlock()
48-
49-
node := t.root
50-
for _, ch := range prefix {
51-
if node.children[ch] == nil {
52-
node.children[ch] = &trieNode{
53-
children: make(map[rune]*trieNode),
54-
}
55-
}
56-
node = node.children[ch]
57-
}
58-
node.isEnd = true
59-
}
60-
61-
// HasPrefix checks if the given string has any of the prefixes stored in the trie.
62-
// Returns true if any prefix in the trie is a prefix of the input string.
63-
func (t *prefixTrie) HasPrefix(s string) (found bool) {
64-
if s == "" {
65-
return false
66-
}
67-
68-
t.mu.RLock()
69-
defer t.mu.RUnlock()
70-
71-
node := t.root
72-
for _, ch := range s {
73-
if node.isEnd {
74-
return true
75-
}
76-
77-
node = node.children[ch]
78-
if node == nil {
79-
return false
80-
}
81-
}
82-
83-
return node.isEnd
84-
}
85-
86-
// InsertAll adds multiple prefixes to the trie in a single operation
87-
func (t *prefixTrie) InsertAll(prefixes []string) {
88-
t.mu.Lock()
89-
defer t.mu.Unlock()
90-
91-
for _, prefix := range prefixes {
92-
if prefix == "" {
93-
continue
94-
}
95-
96-
node := t.root
97-
for _, ch := range prefix {
98-
if node.children[ch] == nil {
99-
node.children[ch] = &trieNode{
100-
children: make(map[rune]*trieNode),
101-
}
102-
}
103-
node = node.children[ch]
104-
}
105-
node.isEnd = true
106-
}
107-
}
108-
109-
// Size returns the number of prefixes stored in the trie
110-
func (t *prefixTrie) Size() int {
111-
t.mu.RLock()
112-
defer t.mu.RUnlock()
113-
114-
return t.countPrefixes(t.root)
115-
}
116-
117-
// countPrefixes recursively counts the number of complete prefixes in the trie
118-
func (t *prefixTrie) countPrefixes(node *trieNode) int {
119-
if node == nil {
120-
return 0
121-
}
122-
123-
count := 0
124-
if node.isEnd {
125-
count = 1
126-
}
127-
128-
for _, child := range node.children {
129-
count += t.countPrefixes(child)
130-
}
131-
132-
return count
133-
}
134-
135-
// Clear removes all prefixes from the trie
136-
func (t *prefixTrie) Clear() {
137-
t.mu.Lock()
138-
defer t.mu.Unlock()
139-
140-
t.root = &trieNode{
141-
children: make(map[rune]*trieNode),
142-
}
143-
}
144-
1458
// segmentPrefixTrie is a path segment-based trie optimized for "/" delimited paths.
1469
// It stores path segments (e.g., "github.com", "DataDog") as nodes instead of individual characters,
14710
// providing better memory efficiency and potentially faster lookups for module paths.

internal/stacktrace/trie_benchmark_test.go

Lines changed: 0 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -84,18 +84,6 @@ func BenchmarkLinearSearch_BulkLookups(b *testing.B) {
8484
}
8585
}
8686

87-
func BenchmarkTrie_BulkLookups(b *testing.B) {
88-
trie := newPrefixTrie()
89-
trie.InsertAll(benchmarkPrefixes)
90-
b.ResetTimer()
91-
92-
for i := 0; i < b.N; i++ {
93-
for _, testStr := range benchmarkTestStrings {
94-
_ = trie.HasPrefix(testStr)
95-
}
96-
}
97-
}
98-
9987
func BenchmarkSegmentTrie_BulkLookups(b *testing.B) {
10088
trie := newSegmentPrefixTrie()
10189
trie.InsertAll(benchmarkPrefixes)
@@ -121,17 +109,6 @@ func BenchmarkLinearSearch_SingleLookup_EarlyMatch(b *testing.B) {
121109
}
122110
}
123111

124-
func BenchmarkTrie_SingleLookup_EarlyMatch(b *testing.B) {
125-
trie := newPrefixTrie()
126-
trie.InsertAll(benchmarkPrefixes)
127-
testStr := "cloud.google.com/go/storage/internal"
128-
b.ResetTimer()
129-
130-
for i := 0; i < b.N; i++ {
131-
_ = trie.HasPrefix(testStr)
132-
}
133-
}
134-
135112
func BenchmarkSegmentTrie_SingleLookup_EarlyMatch(b *testing.B) {
136113
trie := newSegmentPrefixTrie()
137114
trie.InsertAll(benchmarkPrefixes)
@@ -156,17 +133,6 @@ func BenchmarkLinearSearch_SingleLookup_NoMatch(b *testing.B) {
156133
}
157134
}
158135

159-
func BenchmarkTrie_SingleLookup_NoMatch(b *testing.B) {
160-
trie := newPrefixTrie()
161-
trie.InsertAll(benchmarkPrefixes)
162-
testStr := "example.com/mycompany/internal/service"
163-
b.ResetTimer()
164-
165-
for i := 0; i < b.N; i++ {
166-
_ = trie.HasPrefix(testStr)
167-
}
168-
}
169-
170136
func BenchmarkSegmentTrie_SingleLookup_NoMatch(b *testing.B) {
171137
trie := newSegmentPrefixTrie()
172138
trie.InsertAll(benchmarkPrefixes)
@@ -187,14 +153,6 @@ func BenchmarkLinearSearch_Construction(b *testing.B) {
187153
}
188154
}
189155

190-
func BenchmarkTrie_Construction(b *testing.B) {
191-
b.ResetTimer()
192-
for i := 0; i < b.N; i++ {
193-
trie := newPrefixTrie()
194-
trie.InsertAll(benchmarkPrefixes)
195-
}
196-
}
197-
198156
func BenchmarkSegmentTrie_Construction(b *testing.B) {
199157
b.ResetTimer()
200158
for i := 0; i < b.N; i++ {
@@ -216,18 +174,6 @@ func BenchmarkLinearSearch_LookupAllocations(b *testing.B) {
216174
}
217175
}
218176

219-
func BenchmarkTrie_LookupAllocations(b *testing.B) {
220-
trie := newPrefixTrie()
221-
trie.InsertAll(benchmarkPrefixes)
222-
testStr := "github.com/test/package/internal"
223-
b.ResetTimer()
224-
b.ReportAllocs()
225-
226-
for i := 0; i < b.N; i++ {
227-
_ = trie.HasPrefix(testStr)
228-
}
229-
}
230-
231177
func BenchmarkSegmentTrie_LookupAllocations(b *testing.B) {
232178
trie := newSegmentPrefixTrie()
233179
trie.InsertAll(benchmarkPrefixes)
@@ -240,21 +186,6 @@ func BenchmarkSegmentTrie_LookupAllocations(b *testing.B) {
240186
}
241187
}
242188

243-
// Concurrent access benchmark
244-
245-
func BenchmarkTrie_Parallel(b *testing.B) {
246-
trie := newPrefixTrie()
247-
trie.InsertAll(benchmarkPrefixes)
248-
testStr := "cloud.google.com/go/storage/internal"
249-
b.ResetTimer()
250-
251-
b.RunParallel(func(pb *testing.PB) {
252-
for pb.Next() {
253-
_ = trie.HasPrefix(testStr)
254-
}
255-
})
256-
}
257-
258189
// Data structure memory overhead comparison
259190
// This measures the memory cost of the data structure itself, not lookup allocations
260191

@@ -274,21 +205,6 @@ func BenchmarkDataStructureMemoryOverhead(b *testing.B) {
274205
m2.Alloc-m1.Alloc, len(benchmarkPrefixes))
275206
})
276207

277-
b.Run("Trie_DataStructure", func(b *testing.B) {
278-
var m1, m2 runtime.MemStats
279-
runtime.GC()
280-
runtime.ReadMemStats(&m1)
281-
282-
trie := newPrefixTrie()
283-
trie.InsertAll(benchmarkPrefixes)
284-
285-
runtime.GC()
286-
runtime.ReadMemStats(&m2)
287-
b.ReportMetric(float64(m2.Alloc-m1.Alloc), "bytes_overhead")
288-
b.Logf("Character trie data structure overhead: %d bytes for %d prefixes",
289-
m2.Alloc-m1.Alloc, len(benchmarkPrefixes))
290-
})
291-
292208
b.Run("SegmentTrie_DataStructure", func(b *testing.B) {
293209
var m1, m2 runtime.MemStats
294210
runtime.GC()
@@ -308,21 +224,13 @@ func BenchmarkDataStructureMemoryOverhead(b *testing.B) {
308224
// Test correctness - ensure all implementations return the same results
309225
func TestImplementationConsistency(t *testing.T) {
310226
linear := newLinearPrefixMatcher(benchmarkPrefixes)
311-
charTrie := newPrefixTrie()
312-
charTrie.InsertAll(benchmarkPrefixes)
313227
segmentTrie := newSegmentPrefixTrie()
314228
segmentTrie.InsertAll(benchmarkPrefixes)
315229

316230
for _, testStr := range benchmarkTestStrings {
317231
linearResult := linear.HasPrefix(testStr)
318-
charTrieResult := charTrie.HasPrefix(testStr)
319232
segmentTrieResult := segmentTrie.HasPrefix(testStr)
320233

321-
if linearResult != charTrieResult {
322-
t.Errorf("Linear vs Character Trie mismatch for %q: linear=%v, charTrie=%v",
323-
testStr, linearResult, charTrieResult)
324-
}
325-
326234
if linearResult != segmentTrieResult {
327235
t.Errorf("Linear vs Segment Trie mismatch for %q: linear=%v, segmentTrie=%v",
328236
testStr, linearResult, segmentTrieResult)

0 commit comments

Comments
 (0)