fast-prometheus 0.1.0 → 0.3.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.
@@ -1,17 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fast/prometheus"
4
-
5
- $LOAD_PATH.unshift(File.expand_path("pb", __dir__)) unless $LOAD_PATH.include?(File.expand_path("pb", __dir__))
6
-
7
- require "opentelemetry/proto/collector/metrics/v1/metrics_service_pb"
4
+ require_relative "proto"
8
5
 
9
6
  module Fast
10
7
  module Prometheus
11
8
  module OTLP
12
- # Maps a Snapshot to an ExportMetricsServiceRequest.
9
+ # Maps a Snapshot to a Proto::ExportMetricsServiceRequest.
13
10
  class Mapper
14
- CUMULATIVE = Opentelemetry::Proto::Metrics::V1::AggregationTemporality::AGGREGATION_TEMPORALITY_CUMULATIVE
11
+ CUMULATIVE = :AGGREGATION_TEMPORALITY_CUMULATIVE
15
12
  private_constant :CUMULATIVE
16
13
 
17
14
  def initialize(resource_attributes: {}, start_time: Time.now)
@@ -20,15 +17,15 @@ module Fast
20
17
  end
21
18
 
22
19
  def request(snapshot)
23
- Opentelemetry::Proto::Collector::Metrics::V1::ExportMetricsServiceRequest.new(
20
+ Proto::ExportMetricsServiceRequest.new(
24
21
  resource_metrics: [
25
- Opentelemetry::Proto::Metrics::V1::ResourceMetrics.new(
26
- resource: Opentelemetry::Proto::Resource::V1::Resource.new(
22
+ Proto::ResourceMetrics.new(
23
+ resource: Proto::Resource.new(
27
24
  attributes: @resource_attributes
28
25
  ),
29
26
  scope_metrics: [
30
- Opentelemetry::Proto::Metrics::V1::ScopeMetrics.new(
31
- scope: Opentelemetry::Proto::Common::V1::InstrumentationScope.new(
27
+ Proto::ScopeMetrics.new(
28
+ scope: Proto::InstrumentationScope.new(
32
29
  name: "fast-prometheus",
33
30
  version: Fast::Prometheus::VERSION
34
31
  ),
@@ -45,7 +42,7 @@ module Fast
45
42
  def build_metric(metric_snapshot, taken_at)
46
43
  time_unix_nano = (taken_at.to_f * 1_000_000_000).to_i
47
44
 
48
- Opentelemetry::Proto::Metrics::V1::Metric.new(
45
+ Proto::Metric.new(
49
46
  name: metric_snapshot.name.to_s,
50
47
  description: metric_snapshot.docstring,
51
48
  **build_data(metric_snapshot, time_unix_nano)
@@ -56,9 +53,9 @@ module Fast
56
53
  case metric_snapshot.type
57
54
  when :counter
58
55
  {
59
- sum: Opentelemetry::Proto::Metrics::V1::Sum.new(
60
- data_points: metric_snapshot.series.map do |series|
61
- number_data_point(series, time_unix_nano)
56
+ sum: Proto::Sum.new(
57
+ data_points: data_points(metric_snapshot) do |attributes, value|
58
+ number_data_point(attributes, value, time_unix_nano)
62
59
  end,
63
60
  aggregation_temporality: CUMULATIVE,
64
61
  is_monotonic: true
@@ -66,34 +63,34 @@ module Fast
66
63
  }
67
64
  when :gauge
68
65
  {
69
- gauge: Opentelemetry::Proto::Metrics::V1::Gauge.new(
70
- data_points: metric_snapshot.series.map do |series|
71
- number_data_point(series, time_unix_nano)
66
+ gauge: Proto::Gauge.new(
67
+ data_points: data_points(metric_snapshot) do |attributes, value|
68
+ number_data_point(attributes, value, time_unix_nano)
72
69
  end
73
70
  )
74
71
  }
75
72
  when :histogram
76
73
  {
77
- histogram: Opentelemetry::Proto::Metrics::V1::Histogram.new(
78
- data_points: metric_snapshot.series.map do |series|
79
- histogram_data_point(series, time_unix_nano)
74
+ histogram: Proto::Histogram.new(
75
+ data_points: data_points(metric_snapshot) do |attributes, value|
76
+ histogram_data_point(attributes, value, time_unix_nano)
80
77
  end,
81
78
  aggregation_temporality: CUMULATIVE
82
79
  )
83
80
  }
84
81
  when :summary
85
82
  {
86
- summary: Opentelemetry::Proto::Metrics::V1::Summary.new(
87
- data_points: metric_snapshot.series.map do |series|
88
- summary_data_point(series, time_unix_nano)
83
+ summary: Proto::Summary.new(
84
+ data_points: data_points(metric_snapshot) do |attributes, value|
85
+ summary_data_point(attributes, value, time_unix_nano)
89
86
  end
90
87
  )
91
88
  }
92
89
  when :native_histogram
93
90
  {
94
- exponential_histogram: Opentelemetry::Proto::Metrics::V1::ExponentialHistogram.new(
95
- data_points: metric_snapshot.series.map do |series|
96
- exponential_histogram_data_point(series, time_unix_nano)
91
+ exponential_histogram: Proto::ExponentialHistogram.new(
92
+ data_points: data_points(metric_snapshot) do |attributes, value|
93
+ exponential_histogram_data_point(attributes, value, time_unix_nano)
97
94
  end,
98
95
  aggregation_temporality: CUMULATIVE
99
96
  )
@@ -101,25 +98,30 @@ module Fast
101
98
  end
102
99
  end
103
100
 
104
- def number_data_point(series, time_unix_nano)
105
- Opentelemetry::Proto::Metrics::V1::NumberDataPoint.new(
106
- attributes: build_attributes(series.labels),
101
+ # One data point per series: the block gets the series' OTLP
102
+ # attributes and its snapshot value.
103
+ def data_points(metric_snapshot)
104
+ metric_snapshot.series.map { |values, value| yield build_attributes(metric_snapshot.labels(values)), value }
105
+ end
106
+
107
+ def number_data_point(attributes, value, time_unix_nano)
108
+ Proto::NumberDataPoint.new(
109
+ attributes: attributes,
107
110
  start_time_unix_nano: @start_time_unix_nano,
108
111
  time_unix_nano: time_unix_nano,
109
- as_double: series.value.to_f
112
+ as_double: value.to_f
110
113
  )
111
114
  end
112
115
 
113
- def histogram_data_point(series, time_unix_nano)
114
- nv = series.value
115
- bounds, counts = non_cumulative_buckets(nv.cumulative_buckets)
116
+ def histogram_data_point(attributes, value, time_unix_nano)
117
+ bounds, counts = non_cumulative_buckets(value.cumulative_buckets)
116
118
 
117
- Opentelemetry::Proto::Metrics::V1::HistogramDataPoint.new(
118
- attributes: build_attributes(series.labels),
119
+ Proto::HistogramDataPoint.new(
120
+ attributes: attributes,
119
121
  start_time_unix_nano: @start_time_unix_nano,
120
122
  time_unix_nano: time_unix_nano,
121
- count: nv.count,
122
- sum: nv.sum,
123
+ count: value.count,
124
+ sum: value.sum,
123
125
  bucket_counts: counts,
124
126
  explicit_bounds: bounds
125
127
  )
@@ -142,32 +144,28 @@ module Fast
142
144
  [bounds, counts]
143
145
  end
144
146
 
145
- def summary_data_point(series, time_unix_nano)
146
- sv = series.value
147
-
148
- Opentelemetry::Proto::Metrics::V1::SummaryDataPoint.new(
149
- attributes: build_attributes(series.labels),
147
+ def summary_data_point(attributes, value, time_unix_nano)
148
+ Proto::SummaryDataPoint.new(
149
+ attributes: attributes,
150
150
  start_time_unix_nano: @start_time_unix_nano,
151
151
  time_unix_nano: time_unix_nano,
152
- count: sv.count,
153
- sum: sv.sum
152
+ count: value.count,
153
+ sum: value.sum
154
154
  )
155
155
  end
156
156
 
157
- def exponential_histogram_data_point(series, time_unix_nano)
158
- nv = series.value
159
-
160
- Opentelemetry::Proto::Metrics::V1::ExponentialHistogramDataPoint.new(
161
- attributes: build_attributes(series.labels),
157
+ def exponential_histogram_data_point(attributes, value, time_unix_nano)
158
+ Proto::ExponentialHistogramDataPoint.new(
159
+ attributes: attributes,
162
160
  start_time_unix_nano: @start_time_unix_nano,
163
161
  time_unix_nano: time_unix_nano,
164
- count: nv.count,
165
- sum: nv.sum,
166
- scale: nv.schema,
167
- zero_count: nv.zero_count,
168
- zero_threshold: nv.zero_threshold,
169
- positive: dense_buckets(nv.positive_buckets),
170
- negative: dense_buckets(nv.negative_buckets)
162
+ count: value.count,
163
+ sum: value.sum,
164
+ scale: value.schema,
165
+ zero_count: value.zero_count,
166
+ zero_threshold: value.zero_threshold,
167
+ positive: dense_buckets(value.positive_buckets),
168
+ negative: dense_buckets(value.negative_buckets)
171
169
  )
172
170
  end
173
171
 
@@ -175,7 +173,7 @@ module Fast
175
173
  # OTLP index = prom index - 1. Offset = min OTLP index.
176
174
  # Buckets are always sorted (from NativeHistogram::Slot#positive_buckets / #negative_buckets).
177
175
  def dense_buckets(buckets)
178
- return Opentelemetry::Proto::Metrics::V1::ExponentialHistogramDataPoint::Buckets.new if buckets.empty?
176
+ return Proto::ExponentialHistogramDataPoint::Buckets.new if buckets.empty?
179
177
 
180
178
  first_idx, = buckets.first
181
179
  last_idx, = buckets.last
@@ -185,7 +183,7 @@ module Fast
185
183
  counts = Array.new(length, 0)
186
184
  buckets.each { |prom_idx, count| counts[prom_idx - offset - 1] = count }
187
185
 
188
- Opentelemetry::Proto::Metrics::V1::ExponentialHistogramDataPoint::Buckets.new(
186
+ Proto::ExponentialHistogramDataPoint::Buckets.new(
189
187
  offset: offset,
190
188
  bucket_counts: counts
191
189
  )
@@ -196,9 +194,9 @@ module Fast
196
194
  end
197
195
 
198
196
  def kv(key, value)
199
- Opentelemetry::Proto::Common::V1::KeyValue.new(
197
+ Proto::KeyValue.new(
200
198
  key: key,
201
- value: Opentelemetry::Proto::Common::V1::AnyValue.new(string_value: value)
199
+ value: Proto::AnyValue.new(string_value: value)
202
200
  )
203
201
  end
204
202
  end
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fast/protowire"
4
+
5
+ module Fast
6
+ module Prometheus
7
+ module OTLP
8
+ # The OTLP metrics data model (proto/opentelemetry, proto3), declared
9
+ # for the wire: what the exporters send and the one response they read.
10
+ module Proto
11
+ class AnyValue < Fast::Protowire::Message
12
+ oneof :value do
13
+ field :string_value, :string, 1
14
+ field :bool_value, :bool, 2
15
+ field :int_value, :int64, 3
16
+ field :double_value, :double, 4
17
+ field :bytes_value, :bytes, 7
18
+ end
19
+ end
20
+
21
+ class KeyValue < Fast::Protowire::Message
22
+ field :key, :string, 1
23
+ field :value, AnyValue, 2
24
+ end
25
+
26
+ class InstrumentationScope < Fast::Protowire::Message
27
+ field :name, :string, 1
28
+ field :version, :string, 2
29
+ repeated :attributes, KeyValue, 3
30
+ field :dropped_attributes_count, :uint32, 4
31
+ end
32
+
33
+ class Resource < Fast::Protowire::Message
34
+ repeated :attributes, KeyValue, 1
35
+ field :dropped_attributes_count, :uint32, 2
36
+ end
37
+
38
+ AggregationTemporality = Fast::Protowire::Enum.define(
39
+ AGGREGATION_TEMPORALITY_UNSPECIFIED: 0,
40
+ AGGREGATION_TEMPORALITY_DELTA: 1,
41
+ AGGREGATION_TEMPORALITY_CUMULATIVE: 2
42
+ )
43
+
44
+ class NumberDataPoint < Fast::Protowire::Message
45
+ field :start_time_unix_nano, :fixed64, 2
46
+ field :time_unix_nano, :fixed64, 3
47
+ oneof :value do
48
+ field :as_double, :double, 4
49
+ field :as_int, :sfixed64, 6
50
+ end
51
+ repeated :attributes, KeyValue, 7
52
+ field :flags, :uint32, 8
53
+ end
54
+
55
+ class HistogramDataPoint < Fast::Protowire::Message
56
+ field :start_time_unix_nano, :fixed64, 2
57
+ field :time_unix_nano, :fixed64, 3
58
+ field :count, :fixed64, 4
59
+ optional :sum, :double, 5
60
+ repeated :bucket_counts, :fixed64, 6
61
+ repeated :explicit_bounds, :double, 7
62
+ repeated :attributes, KeyValue, 9
63
+ field :flags, :uint32, 10
64
+ optional :min, :double, 11
65
+ optional :max, :double, 12
66
+ end
67
+
68
+ class SummaryDataPoint < Fast::Protowire::Message
69
+ class ValueAtQuantile < Fast::Protowire::Message
70
+ field :quantile, :double, 1
71
+ field :value, :double, 2
72
+ end
73
+
74
+ field :start_time_unix_nano, :fixed64, 2
75
+ field :time_unix_nano, :fixed64, 3
76
+ field :count, :fixed64, 4
77
+ field :sum, :double, 5
78
+ repeated :quantile_values, ValueAtQuantile, 6
79
+ repeated :attributes, KeyValue, 7
80
+ field :flags, :uint32, 8
81
+ end
82
+
83
+ class ExponentialHistogramDataPoint < Fast::Protowire::Message
84
+ class Buckets < Fast::Protowire::Message
85
+ field :offset, :sint32, 1
86
+ repeated :bucket_counts, :uint64, 2
87
+ end
88
+
89
+ repeated :attributes, KeyValue, 1
90
+ field :start_time_unix_nano, :fixed64, 2
91
+ field :time_unix_nano, :fixed64, 3
92
+ field :count, :fixed64, 4
93
+ optional :sum, :double, 5
94
+ field :scale, :sint32, 6
95
+ field :zero_count, :fixed64, 7
96
+ field :positive, Buckets, 8
97
+ field :negative, Buckets, 9
98
+ field :flags, :uint32, 10
99
+ optional :min, :double, 12
100
+ optional :max, :double, 13
101
+ field :zero_threshold, :double, 14
102
+ end
103
+
104
+ class Gauge < Fast::Protowire::Message
105
+ repeated :data_points, NumberDataPoint, 1
106
+ end
107
+
108
+ class Sum < Fast::Protowire::Message
109
+ repeated :data_points, NumberDataPoint, 1
110
+ field :aggregation_temporality, AggregationTemporality, 2
111
+ field :is_monotonic, :bool, 3
112
+ end
113
+
114
+ class Histogram < Fast::Protowire::Message
115
+ repeated :data_points, HistogramDataPoint, 1
116
+ field :aggregation_temporality, AggregationTemporality, 2
117
+ end
118
+
119
+ class ExponentialHistogram < Fast::Protowire::Message
120
+ repeated :data_points, ExponentialHistogramDataPoint, 1
121
+ field :aggregation_temporality, AggregationTemporality, 2
122
+ end
123
+
124
+ class Summary < Fast::Protowire::Message
125
+ repeated :data_points, SummaryDataPoint, 1
126
+ end
127
+
128
+ class Metric < Fast::Protowire::Message
129
+ field :name, :string, 1
130
+ field :description, :string, 2
131
+ field :unit, :string, 3
132
+ oneof :data do
133
+ field :gauge, Gauge, 5
134
+ field :sum, Sum, 7
135
+ field :histogram, Histogram, 9
136
+ field :exponential_histogram, ExponentialHistogram, 10
137
+ field :summary, Summary, 11
138
+ end
139
+ repeated :metadata, KeyValue, 12
140
+ end
141
+
142
+ class ScopeMetrics < Fast::Protowire::Message
143
+ field :scope, InstrumentationScope, 1
144
+ repeated :metrics, Metric, 2
145
+ field :schema_url, :string, 3
146
+ end
147
+
148
+ class ResourceMetrics < Fast::Protowire::Message
149
+ field :resource, Resource, 1
150
+ repeated :scope_metrics, ScopeMetrics, 2
151
+ field :schema_url, :string, 3
152
+ end
153
+
154
+ class ExportMetricsServiceRequest < Fast::Protowire::Message
155
+ repeated :resource_metrics, ResourceMetrics, 1
156
+ end
157
+
158
+ class ExportMetricsPartialSuccess < Fast::Protowire::Message
159
+ field :rejected_data_points, :int64, 1
160
+ field :error_message, :string, 2
161
+ end
162
+
163
+ class ExportMetricsServiceResponse < Fast::Protowire::Message
164
+ field :partial_success, ExportMetricsPartialSuccess, 1
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "fast/prometheus"
4
4
  require "protocol/grpc/interface"
5
- require_relative "mapper"
5
+ require_relative "proto"
6
6
 
7
7
  module Fast
8
8
  module Prometheus
@@ -10,8 +10,8 @@ module Fast
10
10
  # Protocol::GRPC::Interface for opentelemetry.proto.collector.metrics.v1.MetricsService.
11
11
  class MetricsServiceInterface < Protocol::GRPC::Interface
12
12
  rpc :Export,
13
- request_class: Opentelemetry::Proto::Collector::Metrics::V1::ExportMetricsServiceRequest,
14
- response_class: Opentelemetry::Proto::Collector::Metrics::V1::ExportMetricsServiceResponse
13
+ request_class: Proto::ExportMetricsServiceRequest,
14
+ response_class: Proto::ExportMetricsServiceResponse
15
15
  end
16
16
  end
17
17
  end
@@ -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[name] if @by_name.key?(name)
55
+ next @by_name[key] if @by_name.key?(key)
50
56
 
51
57
  metric = yield
52
- unless metric.name == name
53
- raise ArgumentError, "fetch_or_register(#{name.inspect}) block built #{metric.name.inspect}"
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[name] = metric
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
@@ -2,66 +2,40 @@
2
2
 
3
3
  module Fast
4
4
  module Prometheus
5
- # Immutable value types for snapshots. Deep-frozen Data types.
6
-
7
- HistogramValue = Data.define(:sum, :count, :cumulative_buckets)
8
-
9
- NativeHistogramValue = Data.define(:schema, :zero_threshold, :zero_count, :sum, :count,
10
- :positive_buckets, :negative_buckets)
11
-
12
- Series = Data.define(:labels, :value)
13
-
14
- SummaryValue = Data.define(:sum, :count)
5
+ # Immutable value types for snapshots.
6
+ #
7
+ # A MetricSnapshot's +series+ is the metric's store as it stood at one
8
+ # instant: a frozen Hash from each series' label values (an Array in
9
+ # +label_names+ order, the store's own key) to its value. Counter and
10
+ # gauge values are Floats; the other types' values are the frozen
11
+ # Structs below, built from the live slot while the store was locked.
12
+ # Nothing per series is copied beyond that, so a snapshot of any size
13
+ # costs one Hash per metric.
14
+ #
15
+ # The value shapes are Structs frozen where they are built, not Data:
16
+ # Data.new allocates two objects besides the instance, and a scrape
17
+ # builds one of these per histogram, summary or native histogram series.
18
+ # (sum and count shadow Enumerable's on the Structs, as they would on Data.)
19
+
20
+ HistogramValue = Struct.new(:sum, :count, :cumulative_buckets) # rubocop:disable Lint/StructNewOverride
21
+
22
+ NativeHistogramValue = Struct.new(:schema, :zero_threshold, :zero_count, :sum, :count, # rubocop:disable Lint/StructNewOverride
23
+ :positive_buckets, :negative_buckets)
24
+
25
+ SummaryValue = Struct.new(:sum, :count) # rubocop:disable Lint/StructNewOverride
15
26
 
16
27
  MetricSnapshot = Data.define(:name, :docstring, :type, :label_names, :series) do
17
- # Build a MetricSnapshot from a live metric. Reads only public readers
18
- # and copies all mutable structures. The whole build runs under the
19
- # metric's lock so no series is observed mid-mutation and no writer
20
- # interleaves between series.
28
+ # Build a MetricSnapshot from a live metric. +series+ is the metric's
29
+ # store copied and its slots frozen under the metric's lock (see
30
+ # Metric#snapshot_values), so no series is observed mid-mutation and
31
+ # no writer interleaves between series.
21
32
  def self.of(metric)
22
- metric.synchronize do
23
- new(
24
- metric.name,
25
- metric.docstring,
26
- metric.type,
27
- metric.label_names.dup.freeze,
28
- build_series(metric).freeze
29
- )
30
- end
31
- end
32
-
33
- def self.build_series(metric)
34
- metric.values.map do |labels, value|
35
- Series.new(
36
- labels: labels.dup.freeze,
37
- value: build_value(metric.type, value)
38
- )
39
- end
33
+ new(metric.name, metric.docstring, metric.type, metric.labels.dup.freeze, metric.snapshot_values.freeze)
40
34
  end
41
35
 
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
- )
64
- end
36
+ # The {name => value} Hash for one series' label values.
37
+ def labels(values)
38
+ label_names.zip(values).to_h
65
39
  end
66
40
  end
67
41
 
@@ -17,8 +17,10 @@ module Fast
17
17
  @monitor = Monitor.new
18
18
  end
19
19
 
20
- def synchronize(&block)
21
- @monitor.synchronize(&block)
20
+ # Written with yield rather than a captured &block, so taking the lock
21
+ # allocates nothing.
22
+ def synchronize
23
+ @monitor.synchronize { yield } # rubocop:disable Style/ExplicitBlockArgument
22
24
  end
23
25
 
24
26
  def [](key)
@@ -21,7 +21,7 @@ module Fast
21
21
  :summary
22
22
  end
23
23
 
24
- def observe(value, labels: {})
24
+ def observe(value, labels: NO_LABELS)
25
25
  key = resolve(labels)
26
26
  store.synchronize do
27
27
  slot = store[key] ||= Value.new
@@ -30,9 +30,14 @@ module Fast
30
30
  end
31
31
  end
32
32
 
33
- def get(labels: {})
33
+ # {"count" => Integer, "sum" => Float}. Zero-valued for an unobserved series.
34
+ def get(labels: NO_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(slot.sum, slot.count).freeze
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Fast
4
4
  module Prometheus
5
- VERSION = "0.1.0"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end