fast-prometheus 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +52 -0
- data/lib/fast/prometheus/gauge.rb +4 -0
- data/lib/fast/prometheus/histogram.rb +44 -8
- data/lib/fast/prometheus/metric.rb +55 -12
- data/lib/fast/prometheus/native_histogram.rb +23 -2
- data/lib/fast/prometheus/registry.rb +21 -9
- data/lib/fast/prometheus/snapshot.rb +3 -31
- data/lib/fast/prometheus/summary.rb +20 -1
- data/lib/fast/prometheus/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d60578f0bebf141e385a3313c32792c5310dd048a242e8f7b901585b71ab43cb
|
|
4
|
+
data.tar.gz: cd38790e53ff5680b72e7773b79226fbb25768bf03e4eeeba14fccb280acee6d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: efb05ec4182f3564031b9997bc2ffe840a9846ac0d652a29d6d1f118188de58d6156c49e4db703a8ca65305041bd758406b6bfe56728d767c9850f44e9382d77
|
|
7
|
+
data.tar.gz: 1063c21987bc816e1e41821e21ca831a8576c69d07c823fc2da1488c5800712ac75aa4360bf69cf04cb0b0c1af708a05468b8ca9b17aec1e31e94e2bdeeb2186
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,58 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.0] - 2026-09-15
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `Registry#exist?(name)` — true iff a metric is registered under `name`.
|
|
15
|
+
- `Metric#init_label_set(labels)` is now public: it resolves `labels` like any
|
|
16
|
+
mutator and creates that series at the type's zero value under the store
|
|
17
|
+
lock, only if it's absent — it never resets a live series.
|
|
18
|
+
- Every metric now seeds a zero-valued series at construction when it's fully
|
|
19
|
+
bound (no declared labels, or `preset_labels:` covers every declared
|
|
20
|
+
label); a fully-bound `with_labels` child seeds its own series the same
|
|
21
|
+
way. A seeded series shows up in `values`, `Registry#collect`, and text/
|
|
22
|
+
protobuf exposition at zero — for example a freshly registered counter now
|
|
23
|
+
renders `requests_total 0.0` instead of being absent from scrapes until
|
|
24
|
+
first incremented. Partially-bound metrics and zero-valued
|
|
25
|
+
`increment`/`decrement` still seed nothing; `get` still never creates a
|
|
26
|
+
series.
|
|
27
|
+
- `Gauge#set_to_current_time(labels: {})` — `set(Time.now.to_f, labels:
|
|
28
|
+
labels)`.
|
|
29
|
+
- `Metric#snapshot_values` — every series' value in its frozen snapshot shape
|
|
30
|
+
(`HistogramValue`, `SummaryValue`, `NativeHistogramValue`, or the
|
|
31
|
+
already-frozen `Float` for `Counter`/`Gauge`), keyed by label hash, built
|
|
32
|
+
under the store lock.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- **Breaking:** `Metric` names may now be given as a `String` or a `Symbol`
|
|
37
|
+
and are normalized to a `Symbol` on construction; `Metric#name` always
|
|
38
|
+
returns a `Symbol` where it previously echoed back whatever was passed in.
|
|
39
|
+
`Registry#unregister`, `#get`, `#exist?`, and `#fetch_or_register` accept
|
|
40
|
+
either form and symbolize it before lookup; `DuplicateMetric` now fires
|
|
41
|
+
across forms (registering `"requests_total"` after `:requests_total` is
|
|
42
|
+
already registered raises).
|
|
43
|
+
- **Breaking:** `Metric#label_names` is renamed to `Metric#labels`; the old
|
|
44
|
+
name no longer exists (no alias). `MetricSnapshot#label_names` is
|
|
45
|
+
unaffected.
|
|
46
|
+
- **Breaking:** `Histogram#get`/`#values` and `Summary#get`/`#values` now
|
|
47
|
+
return prometheus-client-shaped hashes instead of the internal
|
|
48
|
+
`HistogramSlot`/`Summary::Value` storage objects. `Histogram#get` returns a
|
|
49
|
+
fresh `Hash` keyed by each bucket boundary's `to_s` (ascending), then
|
|
50
|
+
`"+Inf"`, then `"sum"`, with cumulative `Integer` bucket counts and a
|
|
51
|
+
`Float` sum; `Summary#get` returns `{ "count" => Integer, "sum" => Float }`.
|
|
52
|
+
`Histogram#cumulative_buckets`, `#sum`, and `#count` now return zero-valued
|
|
53
|
+
results for an unobserved series instead of `nil`. `NativeHistogram#get`/
|
|
54
|
+
`#values` now return the frozen `NativeHistogramValue` snapshot type
|
|
55
|
+
(previously the internal `NativeHistogram::Slot`), including a zero-valued
|
|
56
|
+
value for an unobserved series. All of these are fresh copies per call;
|
|
57
|
+
mutating a returned `Hash` no longer affects the metric.
|
|
58
|
+
- `Fast::Prometheus.registry=` now assigns under the same lock the reader
|
|
59
|
+
uses, closing a race between a writer and a concurrent reader/memoizing
|
|
60
|
+
first read.
|
|
61
|
+
|
|
10
62
|
## [0.1.0] - 2026-09-11
|
|
11
63
|
|
|
12
64
|
### Added
|
|
@@ -17,6 +17,10 @@ module Fast
|
|
|
17
17
|
store.synchronize { store[key] = value.to_f }
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
def set_to_current_time(labels: {})
|
|
21
|
+
set(Time.now.to_f, labels: labels)
|
|
22
|
+
end
|
|
23
|
+
|
|
20
24
|
def increment(by: 1, labels: {})
|
|
21
25
|
raise ArgumentError, "by must be a numeric" unless by.is_a?(Numeric)
|
|
22
26
|
return if by.zero?
|
|
@@ -69,25 +69,35 @@ module Fast
|
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
-
# Cumulative bucket counts for a specific series
|
|
72
|
+
# Cumulative bucket counts for a specific series, ending with
|
|
73
|
+
# [Float::INFINITY, count]. Zero-valued pairs for an unobserved series.
|
|
73
74
|
def cumulative_buckets(labels: {})
|
|
74
|
-
|
|
75
|
+
key = resolve(labels)
|
|
76
|
+
store.synchronize { slot_or_zero(key).cumulative_buckets }
|
|
75
77
|
end
|
|
76
78
|
|
|
77
|
-
#
|
|
79
|
+
# Prometheus-client-shaped Hash: each bucket boundary's to_s => cumulative
|
|
80
|
+
# count, then "+Inf" => cumulative count, then "sum" => Float sum. A
|
|
81
|
+
# fresh Hash per call; zero-valued for an unobserved series.
|
|
78
82
|
def get(labels: {})
|
|
79
83
|
key = resolve(labels)
|
|
80
|
-
store.synchronize {
|
|
84
|
+
store.synchronize { hash_shape(slot_or_zero(key)) }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def values
|
|
88
|
+
series_map { |slot| hash_shape(slot) }
|
|
81
89
|
end
|
|
82
90
|
|
|
83
|
-
# Reader for sum of a specific series.
|
|
91
|
+
# Reader for sum of a specific series; 0.0 when unobserved.
|
|
84
92
|
def sum(labels: {})
|
|
85
|
-
|
|
93
|
+
key = resolve(labels)
|
|
94
|
+
store.synchronize { slot_or_zero(key).sum }
|
|
86
95
|
end
|
|
87
96
|
|
|
88
|
-
# Reader for count of a specific series.
|
|
97
|
+
# Reader for count of a specific series; 0 when unobserved.
|
|
89
98
|
def count(labels: {})
|
|
90
|
-
|
|
99
|
+
key = resolve(labels)
|
|
100
|
+
store.synchronize { slot_or_zero(key).count }
|
|
91
101
|
end
|
|
92
102
|
|
|
93
103
|
def self.linear_buckets(start:, width:, count:)
|
|
@@ -107,6 +117,32 @@ module Fast
|
|
|
107
117
|
def construction_options
|
|
108
118
|
{ buckets: @buckets }
|
|
109
119
|
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
def zero_value
|
|
124
|
+
HistogramSlot.new(buckets: @buckets)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def slot_or_zero(key)
|
|
128
|
+
store[key] || zero_value
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def snapshot_value(slot)
|
|
132
|
+
HistogramValue.new(
|
|
133
|
+
sum: slot.sum,
|
|
134
|
+
count: slot.count,
|
|
135
|
+
cumulative_buckets: slot.cumulative_buckets.map { |pair| pair.dup.freeze }.freeze
|
|
136
|
+
)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def hash_shape(slot)
|
|
140
|
+
pairs = slot.cumulative_buckets
|
|
141
|
+
hash = pairs[0..-2].to_h { |boundary, count| [boundary.to_s, count] }
|
|
142
|
+
hash["+Inf"] = pairs.last.last
|
|
143
|
+
hash["sum"] = slot.sum
|
|
144
|
+
hash
|
|
145
|
+
end
|
|
110
146
|
end
|
|
111
147
|
end
|
|
112
148
|
end
|
|
@@ -21,27 +21,35 @@ module Fast
|
|
|
21
21
|
validate_label_names(labels)
|
|
22
22
|
validate_preset_labels(labels, preset_labels)
|
|
23
23
|
|
|
24
|
-
@name = name
|
|
24
|
+
@name = name.to_s.to_sym
|
|
25
25
|
@docstring = docstring
|
|
26
|
-
@
|
|
26
|
+
@labels = labels
|
|
27
27
|
@preset_labels = preset_labels.transform_values { |v| v.to_s.freeze }
|
|
28
28
|
@store = store || Store.new
|
|
29
29
|
|
|
30
30
|
return unless fully_bound?
|
|
31
31
|
|
|
32
32
|
@resolved_key = resolve_internal(@preset_labels)
|
|
33
|
+
seed(@resolved_key)
|
|
33
34
|
end
|
|
34
35
|
|
|
35
|
-
attr_reader :name, :docstring, :
|
|
36
|
+
attr_reader :name, :docstring, :labels, :preset_labels
|
|
36
37
|
|
|
37
|
-
# Returns the current value for the given label set.
|
|
38
|
-
#
|
|
39
|
-
#
|
|
38
|
+
# Returns the current value for the given label set. Scalar types
|
|
39
|
+
# (Counter, Gauge) return 0.0 for unobserved series. Histogram, Summary,
|
|
40
|
+
# and NativeHistogram override this with their own zero-valued shapes.
|
|
40
41
|
def get(labels: {})
|
|
41
42
|
key = resolve(labels)
|
|
42
43
|
store.synchronize { store[key] || 0.0 }
|
|
43
44
|
end
|
|
44
45
|
|
|
46
|
+
# Creates the series for +labels+ at its zero value if absent. Never
|
|
47
|
+
# resets a series that already exists. Raises InvalidLabelSet on an
|
|
48
|
+
# unknown or incomplete label set, same as any mutator.
|
|
49
|
+
def init_label_set(labels = {})
|
|
50
|
+
seed(resolve(labels))
|
|
51
|
+
end
|
|
52
|
+
|
|
45
53
|
def type
|
|
46
54
|
raise NotImplementedError, "subclasses must implement #type"
|
|
47
55
|
end
|
|
@@ -52,7 +60,7 @@ module Fast
|
|
|
52
60
|
self.class.new(
|
|
53
61
|
@name,
|
|
54
62
|
docstring: @docstring,
|
|
55
|
-
labels: @
|
|
63
|
+
labels: @labels,
|
|
56
64
|
preset_labels: merged,
|
|
57
65
|
store: @store,
|
|
58
66
|
**construction_options
|
|
@@ -60,7 +68,15 @@ module Fast
|
|
|
60
68
|
end
|
|
61
69
|
|
|
62
70
|
def values
|
|
63
|
-
|
|
71
|
+
snapshot_values
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Every series' value in its frozen snapshot shape (see HistogramValue,
|
|
75
|
+
# SummaryValue, NativeHistogramValue; Counter/Gauge use the already-
|
|
76
|
+
# frozen Float), keyed by label hash, built under the store lock. The
|
|
77
|
+
# seam MetricSnapshot uses to build a consistent view of every series.
|
|
78
|
+
def snapshot_values
|
|
79
|
+
series_map { |slot| snapshot_value(slot) }
|
|
64
80
|
end
|
|
65
81
|
|
|
66
82
|
# Runs +block+ exclusively with respect to every other mutation or read
|
|
@@ -86,7 +102,7 @@ module Fast
|
|
|
86
102
|
|
|
87
103
|
validate_label_keys(labels)
|
|
88
104
|
|
|
89
|
-
@
|
|
105
|
+
@labels.map do |n|
|
|
90
106
|
if labels.key?(n)
|
|
91
107
|
labels[n].to_s
|
|
92
108
|
else
|
|
@@ -100,8 +116,35 @@ module Fast
|
|
|
100
116
|
|
|
101
117
|
private
|
|
102
118
|
|
|
119
|
+
# Zero value for a fresh series. Counter/Gauge share this Float
|
|
120
|
+
# default; Histogram, Summary, and NativeHistogram override it with
|
|
121
|
+
# their own empty slot.
|
|
122
|
+
def zero_value
|
|
123
|
+
0.0
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Turns one slot into its frozen snapshot value. Counter/Gauge slots
|
|
127
|
+
# are already-frozen Floats; Histogram, Summary, and NativeHistogram
|
|
128
|
+
# override this to build their own snapshot value type.
|
|
129
|
+
def snapshot_value(slot)
|
|
130
|
+
slot
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Shared shape behind #values and #snapshot_values: every series'
|
|
134
|
+
# storage transformed by +block+, keyed by label hash, under the lock.
|
|
135
|
+
def series_map(&block)
|
|
136
|
+
store.synchronize { store.to_h.transform_keys { |key| @labels.zip(key).to_h }.transform_values(&block) }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Creates the series at +key+ at its zero value if absent. Never resets
|
|
140
|
+
# a series that already exists. Shared by #initialize (auto-seeding a
|
|
141
|
+
# fully-bound metric) and #init_label_set.
|
|
142
|
+
def seed(key)
|
|
143
|
+
store.synchronize { store[key] ||= zero_value }
|
|
144
|
+
end
|
|
145
|
+
|
|
103
146
|
def fully_bound?
|
|
104
|
-
@preset_labels.size == @
|
|
147
|
+
@preset_labels.size == @labels.size
|
|
105
148
|
end
|
|
106
149
|
|
|
107
150
|
def validate_metric_name(name)
|
|
@@ -132,12 +175,12 @@ module Fast
|
|
|
132
175
|
|
|
133
176
|
def validate_label_keys(labels)
|
|
134
177
|
labels.each_key do |key|
|
|
135
|
-
raise InvalidLabelSet, "unknown label name: #{key.inspect}" unless @
|
|
178
|
+
raise InvalidLabelSet, "unknown label name: #{key.inspect}" unless @labels.include?(key)
|
|
136
179
|
end
|
|
137
180
|
end
|
|
138
181
|
|
|
139
182
|
def resolve_internal(merged)
|
|
140
|
-
@
|
|
183
|
+
@labels.map { |n| merged.fetch(n) }.freeze
|
|
141
184
|
end
|
|
142
185
|
end
|
|
143
186
|
end
|
|
@@ -107,10 +107,15 @@ module Fast
|
|
|
107
107
|
end
|
|
108
108
|
end
|
|
109
109
|
|
|
110
|
-
#
|
|
110
|
+
# A frozen NativeHistogramValue for a label set; zero-valued at this
|
|
111
|
+
# metric's schema/zero_threshold for an unobserved series.
|
|
111
112
|
def get(labels: {})
|
|
112
113
|
key = resolve(labels)
|
|
113
|
-
store.synchronize { store[key] }
|
|
114
|
+
store.synchronize { snapshot_value(store[key] || zero_value) }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def values
|
|
118
|
+
series_map { |slot| snapshot_value(slot) }
|
|
114
119
|
end
|
|
115
120
|
|
|
116
121
|
protected
|
|
@@ -121,6 +126,22 @@ module Fast
|
|
|
121
126
|
|
|
122
127
|
private
|
|
123
128
|
|
|
129
|
+
def zero_value
|
|
130
|
+
Slot.new(schema: @schema, zero_threshold: @zero_threshold)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def snapshot_value(slot)
|
|
134
|
+
NativeHistogramValue.new(
|
|
135
|
+
schema: slot.schema,
|
|
136
|
+
zero_threshold: slot.zero_threshold,
|
|
137
|
+
zero_count: slot.zero_count,
|
|
138
|
+
sum: slot.sum,
|
|
139
|
+
count: slot.count,
|
|
140
|
+
positive_buckets: slot.positive_buckets.map { |pair| pair.dup.freeze }.freeze,
|
|
141
|
+
negative_buckets: slot.negative_buckets.map { |pair| pair.dup.freeze }.freeze
|
|
142
|
+
).freeze
|
|
143
|
+
end
|
|
144
|
+
|
|
124
145
|
def index_for(value, schema)
|
|
125
146
|
factor = 2.0**schema
|
|
126
147
|
idx = (Math.log2(value) * factor).ceil
|
|
@@ -28,14 +28,19 @@ module Fast
|
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
# Remove a metric by name.
|
|
31
|
+
# Remove a metric by name (String or Symbol).
|
|
32
32
|
def unregister(name)
|
|
33
|
-
@lock.synchronize { @by_name.delete(name) }
|
|
33
|
+
@lock.synchronize { @by_name.delete(normalize(name)) }
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
# Look up a metric by name, or nil.
|
|
36
|
+
# Look up a metric by name (String or Symbol), or nil.
|
|
37
37
|
def get(name)
|
|
38
|
-
@lock.synchronize { @by_name[name] }
|
|
38
|
+
@lock.synchronize { @by_name[normalize(name)] }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# True iff a metric is registered under +name+ (String or Symbol).
|
|
42
|
+
def exist?(name)
|
|
43
|
+
@lock.synchronize { @by_name.key?(normalize(name)) }
|
|
39
44
|
end
|
|
40
45
|
|
|
41
46
|
# Atomic fetch-or-register: returns the metric already registered under
|
|
@@ -45,15 +50,16 @@ module Fast
|
|
|
45
50
|
# DuplicateMetric. Raises ArgumentError if the block's metric is not
|
|
46
51
|
# named +name+.
|
|
47
52
|
def fetch_or_register(name)
|
|
53
|
+
key = normalize(name)
|
|
48
54
|
@lock.synchronize do
|
|
49
|
-
next @by_name[
|
|
55
|
+
next @by_name[key] if @by_name.key?(key)
|
|
50
56
|
|
|
51
57
|
metric = yield
|
|
52
|
-
unless metric.name ==
|
|
53
|
-
raise ArgumentError, "fetch_or_register(#{
|
|
58
|
+
unless metric.name == key
|
|
59
|
+
raise ArgumentError, "fetch_or_register(#{key.inspect}) block built #{metric.name.inspect}"
|
|
54
60
|
end
|
|
55
61
|
|
|
56
|
-
@by_name[
|
|
62
|
+
@by_name[key] = metric
|
|
57
63
|
end
|
|
58
64
|
end
|
|
59
65
|
|
|
@@ -93,6 +99,12 @@ module Fast
|
|
|
93
99
|
def collect
|
|
94
100
|
Snapshot.of(metrics)
|
|
95
101
|
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
def normalize(name)
|
|
106
|
+
name.to_s.to_sym
|
|
107
|
+
end
|
|
96
108
|
end
|
|
97
109
|
|
|
98
110
|
@registry_lock = Mutex.new
|
|
@@ -105,7 +117,7 @@ module Fast
|
|
|
105
117
|
end
|
|
106
118
|
|
|
107
119
|
def self.registry=(registry)
|
|
108
|
-
@registry = registry
|
|
120
|
+
@registry_lock.synchronize { @registry = registry }
|
|
109
121
|
end
|
|
110
122
|
end
|
|
111
123
|
end
|
|
@@ -24,43 +24,15 @@ module Fast
|
|
|
24
24
|
metric.name,
|
|
25
25
|
metric.docstring,
|
|
26
26
|
metric.type,
|
|
27
|
-
metric.
|
|
27
|
+
metric.labels.dup.freeze,
|
|
28
28
|
build_series(metric).freeze
|
|
29
29
|
)
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def self.build_series(metric)
|
|
34
|
-
metric.
|
|
35
|
-
Series.new(
|
|
36
|
-
labels: labels.dup.freeze,
|
|
37
|
-
value: build_value(metric.type, value)
|
|
38
|
-
)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
private_class_method def self.build_value(type, value)
|
|
43
|
-
case type
|
|
44
|
-
when :counter, :gauge
|
|
45
|
-
value
|
|
46
|
-
when :histogram
|
|
47
|
-
HistogramValue.new(
|
|
48
|
-
sum: value.sum,
|
|
49
|
-
count: value.count,
|
|
50
|
-
cumulative_buckets: value.cumulative_buckets.map { |pair| pair.dup.freeze }.freeze
|
|
51
|
-
)
|
|
52
|
-
when :summary
|
|
53
|
-
SummaryValue.new(sum: value.sum, count: value.count)
|
|
54
|
-
when :native_histogram
|
|
55
|
-
NativeHistogramValue.new(
|
|
56
|
-
schema: value.schema,
|
|
57
|
-
zero_threshold: value.zero_threshold,
|
|
58
|
-
zero_count: value.zero_count,
|
|
59
|
-
sum: value.sum,
|
|
60
|
-
count: value.count,
|
|
61
|
-
positive_buckets: value.positive_buckets.map { |pair| pair.dup.freeze }.freeze,
|
|
62
|
-
negative_buckets: value.negative_buckets.map { |pair| pair.dup.freeze }.freeze
|
|
63
|
-
)
|
|
34
|
+
metric.snapshot_values.map do |labels, value|
|
|
35
|
+
Series.new(labels: labels.dup.freeze, value: value)
|
|
64
36
|
end
|
|
65
37
|
end
|
|
66
38
|
end
|
|
@@ -30,9 +30,14 @@ module Fast
|
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
# {"count" => Integer, "sum" => Float}. Zero-valued for an unobserved series.
|
|
33
34
|
def get(labels: {})
|
|
34
35
|
key = resolve(labels)
|
|
35
|
-
store.synchronize { store[key] }
|
|
36
|
+
store.synchronize { hash_shape(store[key] || zero_value) }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def values
|
|
40
|
+
series_map { |slot| hash_shape(slot) }
|
|
36
41
|
end
|
|
37
42
|
|
|
38
43
|
protected
|
|
@@ -45,6 +50,20 @@ module Fast
|
|
|
45
50
|
|
|
46
51
|
super
|
|
47
52
|
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def zero_value
|
|
57
|
+
Value.new
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def snapshot_value(slot)
|
|
61
|
+
SummaryValue.new(sum: slot.sum, count: slot.count)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def hash_shape(slot)
|
|
65
|
+
{ "count" => slot.count, "sum" => slot.sum }
|
|
66
|
+
end
|
|
48
67
|
end
|
|
49
68
|
end
|
|
50
69
|
end
|