Skip to content

Commit 2fcaf51

Browse files
committed
Optimization attempt.
Signed-off-by: Bartlomiej Plotka <[email protected]>
1 parent a1c9be4 commit 2fcaf51

File tree

3 files changed

+78
-64
lines changed

3 files changed

+78
-64
lines changed

prometheus/cache/cache.go

Lines changed: 71 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -43,25 +43,61 @@ var separatorByteSlice = []byte{model.SeparatorByte} // For convenient use with
4343
// Use CachedTGatherer with classic Registry using NewMultiTRegistry and ToTransactionalGatherer helpers.
4444
// NOTE(bwplotka): Experimental, API and behaviour can change.
4545
type CachedTGatherer struct {
46-
metrics map[uint64]*dto.Metric
47-
metricFamilyByName map[string]*dto.MetricFamily
46+
metricFamilyByName map[string]*family
4847
mMu sync.RWMutex
4948
}
5049

5150
func NewCachedTGatherer() *CachedTGatherer {
5251
return &CachedTGatherer{
53-
metrics: make(map[uint64]*dto.Metric),
54-
metricFamilyByName: map[string]*dto.MetricFamily{},
52+
metricFamilyByName: map[string]*family{},
5553
}
5654
}
5755

56+
type family struct {
57+
*dto.MetricFamily
58+
59+
metricsByHash map[uint64]*dto.Metric
60+
}
61+
62+
// normalizeMetricFamilies returns a MetricFamily slice with empty
63+
// MetricFamilies pruned and the remaining MetricFamilies sorted by name within
64+
// the slice, with the contained Metrics sorted within each MetricFamily.
65+
func normalizeMetricFamilies(metricFamiliesByName map[string]*family) []*dto.MetricFamily {
66+
for _, mf := range metricFamiliesByName {
67+
if cap(mf.Metric) < len(mf.metricsByHash) {
68+
mf.Metric = make([]*dto.Metric, 0, len(mf.metricsByHash))
69+
}
70+
mf.Metric = mf.Metric[:0]
71+
for _, m := range mf.metricsByHash {
72+
mf.Metric = append(mf.Metric, m)
73+
}
74+
sort.Sort(internal.MetricSorter(mf.Metric))
75+
}
76+
77+
for _, mf := range metricFamiliesByName {
78+
sort.Sort(internal.MetricSorter(mf.Metric))
79+
}
80+
names := make([]string, 0, len(metricFamiliesByName))
81+
for name, mf := range metricFamiliesByName {
82+
if len(mf.Metric) > 0 {
83+
names = append(names, name)
84+
}
85+
}
86+
sort.Strings(names)
87+
result := make([]*dto.MetricFamily, 0, len(names))
88+
for _, name := range names {
89+
result = append(result, metricFamiliesByName[name].MetricFamily)
90+
}
91+
return result
92+
}
93+
5894
// Gather implements TransactionalGatherer interface.
5995
func (c *CachedTGatherer) Gather() (_ []*dto.MetricFamily, done func(), err error) {
6096
c.mMu.RLock()
6197

62-
// BenchmarkCachedTGatherer_Update shows, even for 1 million metrics with 1000 families
98+
// BenchmarkCachedTGatherer_Update shows, even for 1 million metrics among 1000 families
6399
// this is efficient enough (~300µs and ~50 kB per op), no need to cache it for now.
64-
return internal.NormalizeMetricFamilies(c.metricFamilyByName), c.mMu.RUnlock, nil
100+
return normalizeMetricFamilies(c.metricFamilyByName), c.mMu.RUnlock, nil
65101
}
66102

67103
type Key struct {
@@ -123,11 +159,9 @@ func (c *CachedTGatherer) Update(reset bool, inserts []Insert, deletions []Key)
123159
c.mMu.Lock()
124160
defer c.mMu.Unlock()
125161

126-
currMetrics := c.metrics
127-
currMetricFamilies := c.metricFamilyByName
162+
currMetricFamilyByName := c.metricFamilyByName
128163
if reset {
129-
currMetrics = make(map[uint64]*dto.Metric, len(c.metrics))
130-
currMetricFamilies = make(map[string]*dto.MetricFamily, len(c.metricFamilyByName))
164+
currMetricFamilyByName = make(map[string]*family, len(c.metricFamilyByName))
131165
}
132166

133167
errs := prometheus.MultiError{}
@@ -139,22 +173,35 @@ func (c *CachedTGatherer) Update(reset bool, inserts []Insert, deletions []Key)
139173
}
140174

141175
// Update metric family.
142-
mf, ok := c.metricFamilyByName[inserts[i].FQName]
176+
mf, ok := currMetricFamilyByName[inserts[i].FQName]
177+
oldMf, oldOk := c.metricFamilyByName[inserts[i].FQName]
143178
if !ok {
144-
mf = &dto.MetricFamily{}
145-
mf.Name = &inserts[i].FQName
146-
} else if reset {
147-
// Reset metric slice, since we want to start from scratch.
148-
mf.Metric = mf.Metric[:0]
179+
if !oldOk {
180+
mf = &family{
181+
MetricFamily: &dto.MetricFamily{},
182+
metricsByHash: map[uint64]*dto.Metric{},
183+
}
184+
mf.Name = &inserts[i].FQName
185+
} else if reset {
186+
mf = &family{
187+
MetricFamily: oldMf.MetricFamily,
188+
metricsByHash: make(map[uint64]*dto.Metric, len(oldMf.metricsByHash)),
189+
}
190+
}
149191
}
192+
150193
mf.Type = inserts[i].ValueType.ToDTO()
151194
mf.Help = &inserts[i].Help
152195

153-
currMetricFamilies[inserts[i].FQName] = mf
196+
currMetricFamilyByName[inserts[i].FQName] = mf
154197

155198
// Update metric pointer.
156199
hSum := inserts[i].hash()
157-
m, ok := c.metrics[hSum]
200+
m, ok := mf.metricsByHash[hSum]
201+
if !ok && reset && oldOk {
202+
m, ok = oldMf.metricsByHash[hSum]
203+
}
204+
158205
if !ok {
159206
m = &dto.Metric{Label: make([]*dto.LabelPair, 0, len(inserts[i].LabelNames))}
160207
for j := range inserts[i].LabelNames {
@@ -202,16 +249,7 @@ func (c *CachedTGatherer) Update(reset bool, inserts []Insert, deletions []Key)
202249
if inserts[i].Timestamp != nil {
203250
m.TimestampMs = proto.Int64(inserts[i].Timestamp.Unix()*1000 + int64(inserts[i].Timestamp.Nanosecond()/1000000))
204251
}
205-
currMetrics[hSum] = m
206-
207-
if !reset && ok {
208-
// If we did update without reset and we found metric in previous
209-
// map, we know metric pointer exists in metric family map, so just continue.
210-
continue
211-
}
212-
213-
// Will be sorted later anyway, so just append.
214-
mf.Metric = append(mf.Metric, m)
252+
mf.metricsByHash[hSum] = m
215253
}
216254

217255
for _, del := range deletions {
@@ -220,42 +258,18 @@ func (c *CachedTGatherer) Update(reset bool, inserts []Insert, deletions []Key)
220258
continue
221259
}
222260

223-
hSum := del.hash()
224-
m, ok := currMetrics[hSum]
225-
if !ok {
226-
continue
227-
}
228-
delete(currMetrics, hSum)
229-
230-
mf, ok := currMetricFamilies[del.FQName]
261+
mf, ok := currMetricFamilyByName[del.FQName]
231262
if !ok {
232-
// Impossible, but well...
233-
errs.Append(fmt.Errorf("could not remove metric %s(%s) from metric family, metric family does not exists", del.FQName, del.LabelValues))
234263
continue
235264
}
236265

237-
toDel := -1
238-
for i := range mf.Metric {
239-
if mf.Metric[i] == m {
240-
toDel = i
241-
break
242-
}
243-
}
244-
245-
if toDel == -1 {
246-
errs.Append(fmt.Errorf("could not remove metric %s(%s) from metric family, metric family does not have such metric", del.FQName, del.LabelValues))
247-
continue
248-
}
249-
250-
if len(mf.Metric) == 1 {
251-
delete(currMetricFamilies, del.FQName)
266+
hSum := del.hash()
267+
if _, ok := mf.metricsByHash[hSum]; !ok {
252268
continue
253269
}
254-
255-
mf.Metric = append(mf.Metric[:toDel], mf.Metric[toDel+1:]...)
270+
delete(mf.metricsByHash, hSum)
256271
}
257272

258-
c.metrics = currMetrics
259-
c.metricFamilyByName = currMetricFamilies
273+
c.metricFamilyByName = currMetricFamilyByName
260274
return errs.MaybeUnwrap()
261275
}

prometheus/cache/cache_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ func BenchmarkCachedTGatherer_Update(b *testing.B) {
220220
b.Error("update:", err)
221221
}
222222

223-
if len(c.metricFamilyByName) != 1e3 || len(c.metrics) != 1e6 {
223+
if len(c.metricFamilyByName) != 1e3 || len(c.metricFamilyByName["realistic_longer_name_123"].metricsByHash) != 1e3 {
224224
// Ensure we did not generate duplicates.
225225
panic("generated data set gave wrong numbers")
226226
}

prometheus/internal/metric.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,18 @@ func (s LabelPairSorter) Less(i, j int) bool {
3535
return s[i].GetName() < s[j].GetName()
3636
}
3737

38-
// metricSorter is a sortable slice of *dto.Metric.
39-
type metricSorter []*dto.Metric
38+
// MetricSorter is a sortable slice of *dto.Metric.
39+
type MetricSorter []*dto.Metric
4040

41-
func (s metricSorter) Len() int {
41+
func (s MetricSorter) Len() int {
4242
return len(s)
4343
}
4444

45-
func (s metricSorter) Swap(i, j int) {
45+
func (s MetricSorter) Swap(i, j int) {
4646
s[i], s[j] = s[j], s[i]
4747
}
4848

49-
func (s metricSorter) Less(i, j int) bool {
49+
func (s MetricSorter) Less(i, j int) bool {
5050
if len(s[i].Label) != len(s[j].Label) {
5151
// This should not happen. The metrics are
5252
// inconsistent. However, we have to deal with the fact, as
@@ -84,7 +84,7 @@ func (s metricSorter) Less(i, j int) bool {
8484
// the slice, with the contained Metrics sorted within each MetricFamily.
8585
func NormalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily) []*dto.MetricFamily {
8686
for _, mf := range metricFamiliesByName {
87-
sort.Sort(metricSorter(mf.Metric))
87+
sort.Sort(MetricSorter(mf.Metric))
8888
}
8989
names := make([]string, 0, len(metricFamiliesByName))
9090
for name, mf := range metricFamiliesByName {

0 commit comments

Comments
 (0)