From e5ac91222b4bc55121fc7b54564460f05c64136f Mon Sep 17 00:00:00 2001 From: "Matt T. Proud" Date: Mon, 20 May 2013 19:10:26 +0200 Subject: [PATCH 1/2] Benchmark memory arena; simplify map generation. The one-off keys have been replaced with ``model.LabelPair``, which is indexable. The performance impact is negligible, but it represents a cognitive simplification. --- storage/metric/interface.go | 2 +- storage/metric/memory.go | 87 +++++++++++++++----------------- storage/metric/memory_test.go | 95 +++++++++++++++++++++++++++++++++++ storage/metric/tiered.go | 4 +- 4 files changed, 139 insertions(+), 49 deletions(-) create mode 100644 storage/metric/memory_test.go diff --git a/storage/metric/interface.go b/storage/metric/interface.go index 1743a3334..f4de63c1d 100644 --- a/storage/metric/interface.go +++ b/storage/metric/interface.go @@ -78,5 +78,5 @@ type Series interface { } type IteratorsForFingerprintBuilder interface { - ForStream(stream stream) (storage.RecordDecoder, storage.RecordFilter, storage.RecordOperator) + ForStream(stream *stream) (storage.RecordDecoder, storage.RecordFilter, storage.RecordOperator) } diff --git a/storage/metric/memory.go b/storage/metric/memory.go index 5ad54c276..3cb8c3564 100644 --- a/storage/metric/memory.go +++ b/storage/metric/memory.go @@ -14,7 +14,6 @@ package metric import ( - "fmt" "github.com/prometheus/prometheus/model" "github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/utility" @@ -22,12 +21,6 @@ import ( "time" ) -const ( - // Used as a separator in the format string for generating the internal label - // value pair set fingerprints. - reservedDelimiter = `"` -) - // Models a given sample entry stored in the in-memory arena. type value interface { // Gets the given value. @@ -53,13 +46,13 @@ type stream struct { values *skiplist.SkipList } -func (s stream) add(timestamp time.Time, value model.SampleValue) { +func (s *stream) add(timestamp time.Time, value model.SampleValue) { s.values.Set(skipListTime(timestamp), singletonValue(value)) } -func (s stream) forEach(decoder storage.RecordDecoder, filter storage.RecordFilter, operator storage.RecordOperator) (scannedEntireCorpus bool, err error) { +func (s *stream) forEach(decoder storage.RecordDecoder, filter storage.RecordFilter, operator storage.RecordOperator) (scannedEntireCorpus bool, err error) { if s.values.Len() == 0 { - return + return false, nil } iterator := s.values.SeekToLast() @@ -77,7 +70,7 @@ func (s stream) forEach(decoder storage.RecordDecoder, filter storage.RecordFilt switch filter.Filter(decodedKey, decodedValue) { case storage.STOP: - return + return false, nil case storage.SKIP: continue case storage.ACCEPT: @@ -93,20 +86,20 @@ func (s stream) forEach(decoder storage.RecordDecoder, filter storage.RecordFilt break } } - scannedEntireCorpus = true - return + + return true, nil } -func newStream(metric model.Metric) stream { - return stream{ +func newStream(metric model.Metric) *stream { + return &stream{ values: skiplist.New(), metric: metric, } } type memorySeriesStorage struct { - fingerprintToSeries map[model.Fingerprint]stream - labelPairToFingerprints map[string]model.Fingerprints + fingerprintToSeries map[model.Fingerprint]*stream + labelPairToFingerprints map[model.LabelPair]model.Fingerprints labelNameToFingerprints map[model.LabelName]model.Fingerprints } @@ -128,8 +121,10 @@ func (s *memorySeriesStorage) AppendSample(sample model.Sample) error { s.fingerprintToSeries[fingerprint] = series for k, v := range metric { - labelPair := fmt.Sprintf("%s%s%s", k, reservedDelimiter, v) - + labelPair := model.LabelPair{ + Name: k, + Value: v, + } labelPairValues := s.labelPairToFingerprints[labelPair] labelPairValues = append(labelPairValues, fingerprint) s.labelPairToFingerprints[labelPair] = labelPairValues @@ -163,8 +158,10 @@ func (s *memorySeriesStorage) GetFingerprintsForLabelSet(l model.LabelSet) (fing sets := []utility.Set{} for k, v := range l { - signature := fmt.Sprintf("%s%s%s", k, reservedDelimiter, v) - values := s.labelPairToFingerprints[signature] + values := s.labelPairToFingerprints[model.LabelPair{ + Name: k, + Value: v, + }] set := utility.Set{} for _, fingerprint := range values { set.Add(fingerprint) @@ -174,7 +171,7 @@ func (s *memorySeriesStorage) GetFingerprintsForLabelSet(l model.LabelSet) (fing setCount := len(sets) if setCount == 0 { - return + return fingerprints, nil } base := sets[0] @@ -186,7 +183,7 @@ func (s *memorySeriesStorage) GetFingerprintsForLabelSet(l model.LabelSet) (fing fingerprints = append(fingerprints, fingerprint) } - return + return fingerprints, nil } func (s *memorySeriesStorage) GetFingerprintsForLabelName(l model.LabelName) (fingerprints model.Fingerprints, err error) { @@ -194,27 +191,27 @@ func (s *memorySeriesStorage) GetFingerprintsForLabelName(l model.LabelName) (fi fingerprints = append(fingerprints, values...) - return + return fingerprints, nil } -func (s *memorySeriesStorage) GetMetricForFingerprint(f model.Fingerprint) (metric model.Metric, err error) { +func (s memorySeriesStorage) GetMetricForFingerprint(f model.Fingerprint) (model.Metric, error) { series, ok := s.fingerprintToSeries[f] if !ok { - return + return nil, nil } - metric = model.Metric{} + metric := model.Metric{} for label, value := range series.metric { metric[label] = value } - return + return metric, nil } func (s *memorySeriesStorage) GetValueAtTime(f model.Fingerprint, t time.Time) (samples model.Values) { series, ok := s.fingerprintToSeries[f] if !ok { - return + return samples } iterator := series.values.Seek(skipListTime(t)) @@ -226,14 +223,14 @@ func (s *memorySeriesStorage) GetValueAtTime(f model.Fingerprint, t time.Time) ( iterator = series.values.SeekToLast() if iterator == nil { // The list is empty. - return + return samples } } defer iterator.Close() if iterator.Key() == nil || iterator.Value() == nil { - return + return samples } foundTime := time.Time(iterator.Key().(skipListTime)) @@ -249,19 +246,17 @@ func (s *memorySeriesStorage) GetValueAtTime(f model.Fingerprint, t time.Time) ( }) } - return + return samples } -func (s *memorySeriesStorage) GetBoundaryValues(f model.Fingerprint, i model.Interval) (first model.Values, second model.Values) { - first = s.GetValueAtTime(f, i.OldestInclusive) - second = s.GetValueAtTime(f, i.NewestInclusive) - return +func (s memorySeriesStorage) GetBoundaryValues(f model.Fingerprint, i model.Interval) (model.Values, model.Values) { + return s.GetValueAtTime(f, i.OldestInclusive), s.GetValueAtTime(f, i.NewestInclusive) } func (s *memorySeriesStorage) GetRangeValues(f model.Fingerprint, i model.Interval) (samples model.Values) { series, ok := s.fingerprintToSeries[f] if !ok { - return + return samples } iterator := series.values.Seek(skipListTime(i.OldestInclusive)) @@ -273,7 +268,7 @@ func (s *memorySeriesStorage) GetRangeValues(f model.Fingerprint, i model.Interv iterator = series.values.SeekToLast() if iterator == nil { // The list is empty. - return + return samples } } @@ -297,12 +292,12 @@ func (s *memorySeriesStorage) GetRangeValues(f model.Fingerprint, i model.Interv } } - return + return samples } -func (s *memorySeriesStorage) Close() { - s.fingerprintToSeries = map[model.Fingerprint]stream{} - s.labelPairToFingerprints = map[string]model.Fingerprints{} +func (s memorySeriesStorage) Close() { + s.fingerprintToSeries = map[model.Fingerprint]*stream{} + s.labelPairToFingerprints = map[model.LabelPair]model.Fingerprints{} s.labelNameToFingerprints = map[model.LabelName]model.Fingerprints{} } @@ -329,10 +324,10 @@ func (s *memorySeriesStorage) ForEachSample(builder IteratorsForFingerprintBuild return } -func NewMemorySeriesStorage() *memorySeriesStorage { - return &memorySeriesStorage{ - fingerprintToSeries: make(map[model.Fingerprint]stream), - labelPairToFingerprints: make(map[string]model.Fingerprints), +func NewMemorySeriesStorage() memorySeriesStorage { + return memorySeriesStorage{ + fingerprintToSeries: make(map[model.Fingerprint]*stream), + labelPairToFingerprints: make(map[model.LabelPair]model.Fingerprints), labelNameToFingerprints: make(map[model.LabelName]model.Fingerprints), } } diff --git a/storage/metric/memory_test.go b/storage/metric/memory_test.go new file mode 100644 index 000000000..34514826d --- /dev/null +++ b/storage/metric/memory_test.go @@ -0,0 +1,95 @@ +// Copyright 2013 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metric + +import ( + "fmt" + "github.com/prometheus/prometheus/model" + "runtime" + "testing" + "time" +) + +func BenchmarkStreamAdd(b *testing.B) { + b.StopTimer() + s := newStream(model.Metric{}) + times := make([]time.Time, 0, b.N) + samples := make([]model.SampleValue, 0, b.N) + for i := 0; i < b.N; i++ { + times = append(times, time.Date(i, 0, 0, 0, 0, 0, 0, time.UTC)) + samples = append(samples, model.SampleValue(i)) + } + + b.StartTimer() + + var pre runtime.MemStats + runtime.ReadMemStats(&pre) + + for i := 0; i < b.N; i++ { + s.add(times[i], samples[i]) + } + + var post runtime.MemStats + runtime.ReadMemStats(&post) + + b.Logf("%d cycles with %f bytes per cycle, totalling %d", b.N, float32(post.TotalAlloc-pre.TotalAlloc)/float32(b.N), post.TotalAlloc-pre.TotalAlloc) +} + +func benchmarkAppendSample(b *testing.B, labels int) { + b.StopTimer() + s := NewMemorySeriesStorage() + + metric := model.Metric{} + + for i := 0; i < labels; i++ { + metric[model.LabelName(fmt.Sprintf("label_%d", i))] = model.LabelValue(fmt.Sprintf("value_%d", i)) + } + samples := make(model.Samples, 0, b.N) + for i := 0; i < b.N; i++ { + samples = append(samples, model.Sample{ + Metric: metric, + Value: model.SampleValue(i), + Timestamp: time.Date(i, 0, 0, 0, 0, 0, 0, time.UTC), + }) + } + + b.StartTimer() + var pre runtime.MemStats + runtime.ReadMemStats(&pre) + + for i := 0; i < b.N; i++ { + s.AppendSample(samples[i]) + } + + var post runtime.MemStats + runtime.ReadMemStats(&post) + + b.Logf("%d cycles with %f bytes per cycle, totalling %d", b.N, float32(post.TotalAlloc-pre.TotalAlloc)/float32(b.N), post.TotalAlloc-pre.TotalAlloc) +} + +func BenchmarkAppendSample1(b *testing.B) { + benchmarkAppendSample(b, 1) +} + +func BenchmarkAppendSample10(b *testing.B) { + benchmarkAppendSample(b, 10) +} + +func BenchmarkAppendSample100(b *testing.B) { + benchmarkAppendSample(b, 100) +} + +func BenchmarkAppendSample1000(b *testing.B) { + benchmarkAppendSample(b, 1000) +} diff --git a/storage/metric/tiered.go b/storage/metric/tiered.go index ea801f97d..b4b5ccccc 100644 --- a/storage/metric/tiered.go +++ b/storage/metric/tiered.go @@ -239,7 +239,7 @@ type memoryToDiskFlusher struct { } type memoryToDiskFlusherVisitor struct { - stream stream + stream *stream flusher *memoryToDiskFlusher memoryDeleteMutex *sync.RWMutex } @@ -293,7 +293,7 @@ func (f memoryToDiskFlusherVisitor) Operate(key, value interface{}) (err *storag return } -func (f *memoryToDiskFlusher) ForStream(stream stream) (decoder storage.RecordDecoder, filter storage.RecordFilter, operator storage.RecordOperator) { +func (f *memoryToDiskFlusher) ForStream(stream *stream) (decoder storage.RecordDecoder, filter storage.RecordFilter, operator storage.RecordOperator) { visitor := memoryToDiskFlusherVisitor{ stream: stream, flusher: f, From ec5b5bae287131e3498950c642dc5d0917045068 Mon Sep 17 00:00:00 2001 From: "Matt T. Proud" Date: Mon, 20 May 2013 20:31:58 +0200 Subject: [PATCH 2/2] Fuck you, Travis. --- .travis.yml | 14 ++++++++++---- storage/metric/memory.go | 12 ++++++------ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7a9d985ec..487339e28 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,11 +2,17 @@ language: go -# Detective work is required to ascertain why this is required BOTH in the -# before_script directive and in the shell library. +# Explicitly stop before_script from doing anything by giving 'em nil work. before_script: - - gvm install go1.1 || true - - gvm use go1.1 || true + - echo "Before Script" + +# Explicitly stop install from doing anything by giving 'em nil work. +install: + - echo "Install" script: + - echo "Script" + - cd ${TRAVIS_BUILD_DIR} + - gvm install go1.1 || true + - gvm use go1.1 || true - bash -l ./tests-for-die-in-a-fire-travis.sh diff --git a/storage/metric/memory.go b/storage/metric/memory.go index 3cb8c3564..c4c3639e2 100644 --- a/storage/metric/memory.go +++ b/storage/metric/memory.go @@ -186,7 +186,7 @@ func (s *memorySeriesStorage) GetFingerprintsForLabelSet(l model.LabelSet) (fing return fingerprints, nil } -func (s *memorySeriesStorage) GetFingerprintsForLabelName(l model.LabelName) (fingerprints model.Fingerprints, err error) { +func (s *memorySeriesStorage) GetFingerprintsForLabelName(l model.LabelName) (fingerprints model.Fingerprints, _ error) { values := s.labelNameToFingerprints[l] fingerprints = append(fingerprints, values...) @@ -194,7 +194,7 @@ func (s *memorySeriesStorage) GetFingerprintsForLabelName(l model.LabelName) (fi return fingerprints, nil } -func (s memorySeriesStorage) GetMetricForFingerprint(f model.Fingerprint) (model.Metric, error) { +func (s *memorySeriesStorage) GetMetricForFingerprint(f model.Fingerprint) (model.Metric, error) { series, ok := s.fingerprintToSeries[f] if !ok { return nil, nil @@ -249,7 +249,7 @@ func (s *memorySeriesStorage) GetValueAtTime(f model.Fingerprint, t time.Time) ( return samples } -func (s memorySeriesStorage) GetBoundaryValues(f model.Fingerprint, i model.Interval) (model.Values, model.Values) { +func (s *memorySeriesStorage) GetBoundaryValues(f model.Fingerprint, i model.Interval) (model.Values, model.Values) { return s.GetValueAtTime(f, i.OldestInclusive), s.GetValueAtTime(f, i.NewestInclusive) } @@ -295,7 +295,7 @@ func (s *memorySeriesStorage) GetRangeValues(f model.Fingerprint, i model.Interv return samples } -func (s memorySeriesStorage) Close() { +func (s *memorySeriesStorage) Close() { s.fingerprintToSeries = map[model.Fingerprint]*stream{} s.labelPairToFingerprints = map[model.LabelPair]model.Fingerprints{} s.labelNameToFingerprints = map[model.LabelName]model.Fingerprints{} @@ -324,8 +324,8 @@ func (s *memorySeriesStorage) ForEachSample(builder IteratorsForFingerprintBuild return } -func NewMemorySeriesStorage() memorySeriesStorage { - return memorySeriesStorage{ +func NewMemorySeriesStorage() *memorySeriesStorage { + return &memorySeriesStorage{ fingerprintToSeries: make(map[model.Fingerprint]*stream), labelPairToFingerprints: make(map[model.LabelPair]model.Fingerprints), labelNameToFingerprints: make(map[model.LabelName]model.Fingerprints),