-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
intern.go
135 lines (116 loc) · 4.5 KB
/
intern.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.
package server
import (
"fmt"
"github.com/DataDog/datadog-agent/pkg/config"
"github.com/DataDog/datadog-agent/pkg/config/utils"
"github.com/DataDog/datadog-agent/pkg/telemetry"
)
var (
// There are multiple instances of the interner, one per worker (depends on # of virtual CPUs).
// Most metrics are tagged with the instance ID, however some are left as global
// Note `New` vs `NewSimple`
tlmSIResets = telemetry.NewCounter("dogstatsd", "string_interner_resets", []string{"interner_id"},
"Amount of resets of the string interner used in dogstatsd")
tlmSIRSize = telemetry.NewGauge("dogstatsd", "string_interner_entries", []string{"interner_id"},
"Number of entries in the string interner")
tlmSIRBytes = telemetry.NewGauge("dogstatsd", "string_interner_bytes", []string{"interner_id"},
"Number of bytes stored in the string interner")
tlmSIRHits = telemetry.NewCounter("dogstatsd", "string_interner_hits", []string{"interner_id"},
"Number of times string interner returned an existing string")
tlmSIRMiss = telemetry.NewCounter("dogstatsd", "string_interner_miss", []string{"interner_id"},
"Number of times string interner created a new string object")
//nolint:unused // TODO(AML) Fix unused linter
tlmSIRNew = telemetry.NewSimpleCounter("dogstatsd", "string_interner_new",
"Number of times string interner was created")
tlmSIRStrBytes = telemetry.NewSimpleHistogram("dogstatsd", "string_interner_str_bytes",
"Number of times string with specific length were added",
[]float64{1, 2, 4, 8, 16, 32, 64, 128})
)
// stringInterner is a string cache providing a longer life for strings,
// helping to avoid GC runs because they're re-used many times instead of
// created every time.
//
// The current interning strategy is fairly simple, but can require manual
// adjustments of the `maxSize` to improve performance, which is not ideal.
// However the current strategy works well enough, and there is an
// accepted go proposal to offer an "interning" mechanism from the
// go runtime directly.
// Once this is available, the interner design should be re-visited to
// take advantage of the new "Unique" api that is proposed below.
// ref: https://github.com/golang/go/issues/62483
type stringInterner struct {
strings map[string]string
maxSize int
id string
telemetry siTelemetry
}
type siTelemetry struct {
enabled bool
curBytes int
resets telemetry.SimpleCounter
size telemetry.SimpleGauge
bytes telemetry.SimpleGauge
hits telemetry.SimpleCounter
miss telemetry.SimpleCounter
}
func newStringInterner(maxSize int, internerID int) *stringInterner {
i := &stringInterner{
strings: make(map[string]string),
id: fmt.Sprintf("interner_%d", internerID),
maxSize: maxSize,
telemetry: siTelemetry{
enabled: utils.IsTelemetryEnabled(config.Datadog),
},
}
if i.telemetry.enabled {
i.prepareTelemetry()
}
return i
}
func (i *stringInterner) prepareTelemetry() {
i.telemetry.resets = tlmSIResets.WithValues(i.id)
i.telemetry.size = tlmSIRSize.WithValues(i.id)
i.telemetry.bytes = tlmSIRBytes.WithValues(i.id)
i.telemetry.hits = tlmSIRHits.WithValues(i.id)
i.telemetry.miss = tlmSIRMiss.WithValues(i.id)
}
// LoadOrStore always returns the string from the cache, adding it into the
// cache if needed.
// If we need to store a new entry and the cache is at its maximum capacity,
// it is reset.
func (i *stringInterner) LoadOrStore(key []byte) string {
// here is the string interner trick: the map lookup using
// string(key) doesn't actually allocate a string, but is
// returning the string value -> no new heap allocation
// for this string.
// See https://github.com/golang/go/commit/f5f5a8b6209f84961687d993b93ea0d397f5d5bf
if s, found := i.strings[string(key)]; found {
if i.telemetry.enabled {
i.telemetry.hits.Inc()
}
return s
}
if len(i.strings) >= i.maxSize {
if i.telemetry.enabled {
i.telemetry.resets.Inc()
i.telemetry.bytes.Sub(float64(i.telemetry.curBytes))
i.telemetry.size.Sub(float64(len(i.strings)))
i.telemetry.curBytes = 0
}
i.strings = make(map[string]string)
}
s := string(key)
i.strings[s] = s
if i.telemetry.enabled {
i.telemetry.miss.Inc()
i.telemetry.size.Inc()
i.telemetry.bytes.Add(float64(len(s)))
tlmSIRStrBytes.Observe(float64(len(s)))
i.telemetry.curBytes += len(s)
}
return s
}