fast-prometheus 0.1.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 +7 -0
- data/AGENTS.md +38 -0
- data/CHANGELOG.md +59 -0
- data/README.md +109 -0
- data/lib/fast/prometheus/counter.rb +22 -0
- data/lib/fast/prometheus/errors.rb +11 -0
- data/lib/fast/prometheus/exposition.rb +46 -0
- data/lib/fast/prometheus/formats/metrics_pb.rb +33 -0
- data/lib/fast/prometheus/formats/protobuf.rb +154 -0
- data/lib/fast/prometheus/formats/text.rb +90 -0
- data/lib/fast/prometheus/gauge.rb +37 -0
- data/lib/fast/prometheus/histogram.rb +112 -0
- data/lib/fast/prometheus/metric.rb +144 -0
- data/lib/fast/prometheus/middleware/exporter.rb +39 -0
- data/lib/fast/prometheus/middleware/instrumentation.rb +34 -0
- data/lib/fast/prometheus/native_histogram.rb +145 -0
- data/lib/fast/prometheus/otlp/grpc_exporter.rb +39 -0
- data/lib/fast/prometheus/otlp/http_exporter.rb +43 -0
- data/lib/fast/prometheus/otlp/mapper.rb +207 -0
- data/lib/fast/prometheus/otlp/pb/opentelemetry/proto/collector/metrics/v1/metrics_service_pb.rb +27 -0
- data/lib/fast/prometheus/otlp/pb/opentelemetry/proto/common/v1/common_pb.rb +26 -0
- data/lib/fast/prometheus/otlp/pb/opentelemetry/proto/metrics/v1/metrics_pb.rb +41 -0
- data/lib/fast/prometheus/otlp/pb/opentelemetry/proto/resource/v1/resource_pb.rb +23 -0
- data/lib/fast/prometheus/otlp/push.rb +39 -0
- data/lib/fast/prometheus/otlp/service_interface.rb +18 -0
- data/lib/fast/prometheus/rack/exporter.rb +32 -0
- data/lib/fast/prometheus/rack/instrumentation.rb +33 -0
- data/lib/fast/prometheus/registry.rb +111 -0
- data/lib/fast/prometheus/request_metrics.rb +45 -0
- data/lib/fast/prometheus/snapshot.rb +78 -0
- data/lib/fast/prometheus/store.rb +37 -0
- data/lib/fast/prometheus/summary.rb +50 -0
- data/lib/fast/prometheus/version.rb +7 -0
- data/lib/fast/prometheus.rb +17 -0
- metadata +158 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
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"
|
|
8
|
+
|
|
9
|
+
module Fast
|
|
10
|
+
module Prometheus
|
|
11
|
+
module OTLP
|
|
12
|
+
# Maps a Snapshot to an ExportMetricsServiceRequest.
|
|
13
|
+
class Mapper
|
|
14
|
+
CUMULATIVE = Opentelemetry::Proto::Metrics::V1::AggregationTemporality::AGGREGATION_TEMPORALITY_CUMULATIVE
|
|
15
|
+
private_constant :CUMULATIVE
|
|
16
|
+
|
|
17
|
+
def initialize(resource_attributes: {}, start_time: Time.now)
|
|
18
|
+
@resource_attributes = resource_attributes.map { |k, v| kv(k, v) }
|
|
19
|
+
@start_time_unix_nano = (start_time.to_f * 1_000_000_000).to_i
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def request(snapshot)
|
|
23
|
+
Opentelemetry::Proto::Collector::Metrics::V1::ExportMetricsServiceRequest.new(
|
|
24
|
+
resource_metrics: [
|
|
25
|
+
Opentelemetry::Proto::Metrics::V1::ResourceMetrics.new(
|
|
26
|
+
resource: Opentelemetry::Proto::Resource::V1::Resource.new(
|
|
27
|
+
attributes: @resource_attributes
|
|
28
|
+
),
|
|
29
|
+
scope_metrics: [
|
|
30
|
+
Opentelemetry::Proto::Metrics::V1::ScopeMetrics.new(
|
|
31
|
+
scope: Opentelemetry::Proto::Common::V1::InstrumentationScope.new(
|
|
32
|
+
name: "fast-prometheus",
|
|
33
|
+
version: Fast::Prometheus::VERSION
|
|
34
|
+
),
|
|
35
|
+
metrics: snapshot.metrics.map { |ms| build_metric(ms, snapshot.taken_at) }
|
|
36
|
+
)
|
|
37
|
+
]
|
|
38
|
+
)
|
|
39
|
+
]
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def build_metric(metric_snapshot, taken_at)
|
|
46
|
+
time_unix_nano = (taken_at.to_f * 1_000_000_000).to_i
|
|
47
|
+
|
|
48
|
+
Opentelemetry::Proto::Metrics::V1::Metric.new(
|
|
49
|
+
name: metric_snapshot.name.to_s,
|
|
50
|
+
description: metric_snapshot.docstring,
|
|
51
|
+
**build_data(metric_snapshot, time_unix_nano)
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def build_data(metric_snapshot, time_unix_nano)
|
|
56
|
+
case metric_snapshot.type
|
|
57
|
+
when :counter
|
|
58
|
+
{
|
|
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)
|
|
62
|
+
end,
|
|
63
|
+
aggregation_temporality: CUMULATIVE,
|
|
64
|
+
is_monotonic: true
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
when :gauge
|
|
68
|
+
{
|
|
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)
|
|
72
|
+
end
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
when :histogram
|
|
76
|
+
{
|
|
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)
|
|
80
|
+
end,
|
|
81
|
+
aggregation_temporality: CUMULATIVE
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
when :summary
|
|
85
|
+
{
|
|
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)
|
|
89
|
+
end
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
when :native_histogram
|
|
93
|
+
{
|
|
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)
|
|
97
|
+
end,
|
|
98
|
+
aggregation_temporality: CUMULATIVE
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def number_data_point(series, time_unix_nano)
|
|
105
|
+
Opentelemetry::Proto::Metrics::V1::NumberDataPoint.new(
|
|
106
|
+
attributes: build_attributes(series.labels),
|
|
107
|
+
start_time_unix_nano: @start_time_unix_nano,
|
|
108
|
+
time_unix_nano: time_unix_nano,
|
|
109
|
+
as_double: series.value.to_f
|
|
110
|
+
)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def histogram_data_point(series, time_unix_nano)
|
|
114
|
+
nv = series.value
|
|
115
|
+
bounds, counts = non_cumulative_buckets(nv.cumulative_buckets)
|
|
116
|
+
|
|
117
|
+
Opentelemetry::Proto::Metrics::V1::HistogramDataPoint.new(
|
|
118
|
+
attributes: build_attributes(series.labels),
|
|
119
|
+
start_time_unix_nano: @start_time_unix_nano,
|
|
120
|
+
time_unix_nano: time_unix_nano,
|
|
121
|
+
count: nv.count,
|
|
122
|
+
sum: nv.sum,
|
|
123
|
+
bucket_counts: counts,
|
|
124
|
+
explicit_bounds: bounds
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def non_cumulative_buckets(cumulative_buckets)
|
|
129
|
+
# cumulative_buckets: [[bound1, cum1], [bound2, cum2], ..., [+Inf, cumN]]
|
|
130
|
+
# bounds = all boundaries except +Inf
|
|
131
|
+
# counts = non-cumulative per-bucket counts + overflow
|
|
132
|
+
bounds = []
|
|
133
|
+
counts = Array.new(cumulative_buckets.size)
|
|
134
|
+
prev = 0
|
|
135
|
+
|
|
136
|
+
cumulative_buckets.each_with_index do |(boundary, cumulative), i|
|
|
137
|
+
counts[i] = cumulative - prev
|
|
138
|
+
prev = cumulative
|
|
139
|
+
bounds << boundary unless boundary.infinite?
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
[bounds, counts]
|
|
143
|
+
end
|
|
144
|
+
|
|
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),
|
|
150
|
+
start_time_unix_nano: @start_time_unix_nano,
|
|
151
|
+
time_unix_nano: time_unix_nano,
|
|
152
|
+
count: sv.count,
|
|
153
|
+
sum: sv.sum
|
|
154
|
+
)
|
|
155
|
+
end
|
|
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),
|
|
162
|
+
start_time_unix_nano: @start_time_unix_nano,
|
|
163
|
+
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)
|
|
171
|
+
)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Convert sparse [[prom_idx, count], ...] to OTLP Buckets{offset, bucket_counts}.
|
|
175
|
+
# OTLP index = prom index - 1. Offset = min OTLP index.
|
|
176
|
+
# Buckets are always sorted (from NativeHistogram::Slot#positive_buckets / #negative_buckets).
|
|
177
|
+
def dense_buckets(buckets)
|
|
178
|
+
return Opentelemetry::Proto::Metrics::V1::ExponentialHistogramDataPoint::Buckets.new if buckets.empty?
|
|
179
|
+
|
|
180
|
+
first_idx, = buckets.first
|
|
181
|
+
last_idx, = buckets.last
|
|
182
|
+
offset = first_idx - 1
|
|
183
|
+
length = last_idx - offset
|
|
184
|
+
|
|
185
|
+
counts = Array.new(length, 0)
|
|
186
|
+
buckets.each { |prom_idx, count| counts[prom_idx - offset - 1] = count }
|
|
187
|
+
|
|
188
|
+
Opentelemetry::Proto::Metrics::V1::ExponentialHistogramDataPoint::Buckets.new(
|
|
189
|
+
offset: offset,
|
|
190
|
+
bucket_counts: counts
|
|
191
|
+
)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def build_attributes(labels)
|
|
195
|
+
labels.map { |k, v| kv(k.to_s, v.to_s) }
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def kv(key, value)
|
|
199
|
+
Opentelemetry::Proto::Common::V1::KeyValue.new(
|
|
200
|
+
key: key,
|
|
201
|
+
value: Opentelemetry::Proto::Common::V1::AnyValue.new(string_value: value)
|
|
202
|
+
)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
data/lib/fast/prometheus/otlp/pb/opentelemetry/proto/collector/metrics/v1/metrics_service_pb.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# source: opentelemetry/proto/collector/metrics/v1/metrics_service.proto
|
|
4
|
+
|
|
5
|
+
require 'google/protobuf'
|
|
6
|
+
|
|
7
|
+
require 'opentelemetry/proto/metrics/v1/metrics_pb'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
descriptor_data = "\n>opentelemetry/proto/collector/metrics/v1/metrics_service.proto\x12(opentelemetry.proto.collector.metrics.v1\x1a,opentelemetry/proto/metrics/v1/metrics.proto\"h\n\x1b\x45xportMetricsServiceRequest\x12I\n\x10resource_metrics\x18\x01 \x03(\x0b\x32/.opentelemetry.proto.metrics.v1.ResourceMetrics\"~\n\x1c\x45xportMetricsServiceResponse\x12^\n\x0fpartial_success\x18\x01 \x01(\x0b\x32\x45.opentelemetry.proto.collector.metrics.v1.ExportMetricsPartialSuccess\"R\n\x1b\x45xportMetricsPartialSuccess\x12\x1c\n\x14rejected_data_points\x18\x01 \x01(\x03\x12\x15\n\rerror_message\x18\x02 \x01(\t2\xac\x01\n\x0eMetricsService\x12\x99\x01\n\x06\x45xport\x12\x45.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest\x1a\x46.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse\"\x00\x42\xa4\x01\n+io.opentelemetry.proto.collector.metrics.v1B\x13MetricsServiceProtoP\x01Z3go.opentelemetry.io/proto/otlp/collector/metrics/v1\xaa\x02(OpenTelemetry.Proto.Collector.Metrics.V1b\x06proto3"
|
|
11
|
+
|
|
12
|
+
pool = ::Google::Protobuf::DescriptorPool.generated_pool
|
|
13
|
+
pool.add_serialized_file(descriptor_data)
|
|
14
|
+
|
|
15
|
+
module Opentelemetry
|
|
16
|
+
module Proto
|
|
17
|
+
module Collector
|
|
18
|
+
module Metrics
|
|
19
|
+
module V1
|
|
20
|
+
ExportMetricsServiceRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest").msgclass
|
|
21
|
+
ExportMetricsServiceResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse").msgclass
|
|
22
|
+
ExportMetricsPartialSuccess = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.collector.metrics.v1.ExportMetricsPartialSuccess").msgclass
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# source: opentelemetry/proto/common/v1/common.proto
|
|
4
|
+
|
|
5
|
+
require 'google/protobuf'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
descriptor_data = "\n*opentelemetry/proto/common/v1/common.proto\x12\x1dopentelemetry.proto.common.v1\"\xad\x02\n\x08\x41nyValue\x12\x16\n\x0cstring_value\x18\x01 \x01(\tH\x00\x12\x14\n\nbool_value\x18\x02 \x01(\x08H\x00\x12\x13\n\tint_value\x18\x03 \x01(\x03H\x00\x12\x16\n\x0c\x64ouble_value\x18\x04 \x01(\x01H\x00\x12@\n\x0b\x61rray_value\x18\x05 \x01(\x0b\x32).opentelemetry.proto.common.v1.ArrayValueH\x00\x12\x43\n\x0ckvlist_value\x18\x06 \x01(\x0b\x32+.opentelemetry.proto.common.v1.KeyValueListH\x00\x12\x15\n\x0b\x62ytes_value\x18\x07 \x01(\x0cH\x00\x12\x1f\n\x15string_value_strindex\x18\x08 \x01(\x05H\x00\x42\x07\n\x05value\"E\n\nArrayValue\x12\x37\n\x06values\x18\x01 \x03(\x0b\x32\'.opentelemetry.proto.common.v1.AnyValue\"G\n\x0cKeyValueList\x12\x37\n\x06values\x18\x01 \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValue\"e\n\x08KeyValue\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x36\n\x05value\x18\x02 \x01(\x0b\x32\'.opentelemetry.proto.common.v1.AnyValue\x12\x14\n\x0ckey_strindex\x18\x03 \x01(\x05\"\x94\x01\n\x14InstrumentationScope\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\x12;\n\nattributes\x18\x03 \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValue\x12 \n\x18\x64ropped_attributes_count\x18\x04 \x01(\r\"X\n\tEntityRef\x12\x12\n\nschema_url\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\x0f\n\x07id_keys\x18\x03 \x03(\t\x12\x18\n\x10\x64\x65scription_keys\x18\x04 \x03(\tB{\n io.opentelemetry.proto.common.v1B\x0b\x43ommonProtoP\x01Z(go.opentelemetry.io/proto/otlp/common/v1\xaa\x02\x1dOpenTelemetry.Proto.Common.V1b\x06proto3"
|
|
9
|
+
|
|
10
|
+
pool = ::Google::Protobuf::DescriptorPool.generated_pool
|
|
11
|
+
pool.add_serialized_file(descriptor_data)
|
|
12
|
+
|
|
13
|
+
module Opentelemetry
|
|
14
|
+
module Proto
|
|
15
|
+
module Common
|
|
16
|
+
module V1
|
|
17
|
+
AnyValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.common.v1.AnyValue").msgclass
|
|
18
|
+
ArrayValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.common.v1.ArrayValue").msgclass
|
|
19
|
+
KeyValueList = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.common.v1.KeyValueList").msgclass
|
|
20
|
+
KeyValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.common.v1.KeyValue").msgclass
|
|
21
|
+
InstrumentationScope = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.common.v1.InstrumentationScope").msgclass
|
|
22
|
+
EntityRef = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.common.v1.EntityRef").msgclass
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# source: opentelemetry/proto/metrics/v1/metrics.proto
|
|
4
|
+
|
|
5
|
+
require 'google/protobuf'
|
|
6
|
+
|
|
7
|
+
require 'opentelemetry/proto/common/v1/common_pb'
|
|
8
|
+
require 'opentelemetry/proto/resource/v1/resource_pb'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
descriptor_data = "\n,opentelemetry/proto/metrics/v1/metrics.proto\x12\x1eopentelemetry.proto.metrics.v1\x1a*opentelemetry/proto/common/v1/common.proto\x1a.opentelemetry/proto/resource/v1/resource.proto\"X\n\x0bMetricsData\x12I\n\x10resource_metrics\x18\x01 \x03(\x0b\x32/.opentelemetry.proto.metrics.v1.ResourceMetrics\"\xaf\x01\n\x0fResourceMetrics\x12;\n\x08resource\x18\x01 \x01(\x0b\x32).opentelemetry.proto.resource.v1.Resource\x12\x43\n\rscope_metrics\x18\x02 \x03(\x0b\x32,.opentelemetry.proto.metrics.v1.ScopeMetrics\x12\x12\n\nschema_url\x18\x03 \x01(\tJ\x06\x08\xe8\x07\x10\xe9\x07\"\x9f\x01\n\x0cScopeMetrics\x12\x42\n\x05scope\x18\x01 \x01(\x0b\x32\x33.opentelemetry.proto.common.v1.InstrumentationScope\x12\x37\n\x07metrics\x18\x02 \x03(\x0b\x32&.opentelemetry.proto.metrics.v1.Metric\x12\x12\n\nschema_url\x18\x03 \x01(\t\"\xcd\x03\n\x06Metric\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x0c\n\x04unit\x18\x03 \x01(\t\x12\x36\n\x05gauge\x18\x05 \x01(\x0b\x32%.opentelemetry.proto.metrics.v1.GaugeH\x00\x12\x32\n\x03sum\x18\x07 \x01(\x0b\x32#.opentelemetry.proto.metrics.v1.SumH\x00\x12>\n\thistogram\x18\t \x01(\x0b\x32).opentelemetry.proto.metrics.v1.HistogramH\x00\x12U\n\x15\x65xponential_histogram\x18\n \x01(\x0b\x32\x34.opentelemetry.proto.metrics.v1.ExponentialHistogramH\x00\x12:\n\x07summary\x18\x0b \x01(\x0b\x32\'.opentelemetry.proto.metrics.v1.SummaryH\x00\x12\x39\n\x08metadata\x18\x0c \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValueB\x06\n\x04\x64\x61taJ\x04\x08\x04\x10\x05J\x04\x08\x06\x10\x07J\x04\x08\x08\x10\t\"M\n\x05Gauge\x12\x44\n\x0b\x64\x61ta_points\x18\x01 \x03(\x0b\x32/.opentelemetry.proto.metrics.v1.NumberDataPoint\"\xba\x01\n\x03Sum\x12\x44\n\x0b\x64\x61ta_points\x18\x01 \x03(\x0b\x32/.opentelemetry.proto.metrics.v1.NumberDataPoint\x12W\n\x17\x61ggregation_temporality\x18\x02 \x01(\x0e\x32\x36.opentelemetry.proto.metrics.v1.AggregationTemporality\x12\x14\n\x0cis_monotonic\x18\x03 \x01(\x08\"\xad\x01\n\tHistogram\x12G\n\x0b\x64\x61ta_points\x18\x01 \x03(\x0b\x32\x32.opentelemetry.proto.metrics.v1.HistogramDataPoint\x12W\n\x17\x61ggregation_temporality\x18\x02 \x01(\x0e\x32\x36.opentelemetry.proto.metrics.v1.AggregationTemporality\"\xc3\x01\n\x14\x45xponentialHistogram\x12R\n\x0b\x64\x61ta_points\x18\x01 \x03(\x0b\x32=.opentelemetry.proto.metrics.v1.ExponentialHistogramDataPoint\x12W\n\x17\x61ggregation_temporality\x18\x02 \x01(\x0e\x32\x36.opentelemetry.proto.metrics.v1.AggregationTemporality\"P\n\x07Summary\x12\x45\n\x0b\x64\x61ta_points\x18\x01 \x03(\x0b\x32\x30.opentelemetry.proto.metrics.v1.SummaryDataPoint\"\x86\x02\n\x0fNumberDataPoint\x12;\n\nattributes\x18\x07 \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValue\x12\x1c\n\x14start_time_unix_nano\x18\x02 \x01(\x06\x12\x16\n\x0etime_unix_nano\x18\x03 \x01(\x06\x12\x13\n\tas_double\x18\x04 \x01(\x01H\x00\x12\x10\n\x06\x61s_int\x18\x06 \x01(\x10H\x00\x12;\n\texemplars\x18\x05 \x03(\x0b\x32(.opentelemetry.proto.metrics.v1.Exemplar\x12\r\n\x05\x66lags\x18\x08 \x01(\rB\x07\n\x05valueJ\x04\x08\x01\x10\x02\"\xe6\x02\n\x12HistogramDataPoint\x12;\n\nattributes\x18\t \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValue\x12\x1c\n\x14start_time_unix_nano\x18\x02 \x01(\x06\x12\x16\n\x0etime_unix_nano\x18\x03 \x01(\x06\x12\r\n\x05\x63ount\x18\x04 \x01(\x06\x12\x10\n\x03sum\x18\x05 \x01(\x01H\x00\x88\x01\x01\x12\x15\n\rbucket_counts\x18\x06 \x03(\x06\x12\x17\n\x0f\x65xplicit_bounds\x18\x07 \x03(\x01\x12;\n\texemplars\x18\x08 \x03(\x0b\x32(.opentelemetry.proto.metrics.v1.Exemplar\x12\r\n\x05\x66lags\x18\n \x01(\r\x12\x10\n\x03min\x18\x0b \x01(\x01H\x01\x88\x01\x01\x12\x10\n\x03max\x18\x0c \x01(\x01H\x02\x88\x01\x01\x42\x06\n\x04_sumB\x06\n\x04_minB\x06\n\x04_maxJ\x04\x08\x01\x10\x02\"\xda\x04\n\x1d\x45xponentialHistogramDataPoint\x12;\n\nattributes\x18\x01 \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValue\x12\x1c\n\x14start_time_unix_nano\x18\x02 \x01(\x06\x12\x16\n\x0etime_unix_nano\x18\x03 \x01(\x06\x12\r\n\x05\x63ount\x18\x04 \x01(\x06\x12\x10\n\x03sum\x18\x05 \x01(\x01H\x00\x88\x01\x01\x12\r\n\x05scale\x18\x06 \x01(\x11\x12\x12\n\nzero_count\x18\x07 \x01(\x06\x12W\n\x08positive\x18\x08 \x01(\x0b\x32\x45.opentelemetry.proto.metrics.v1.ExponentialHistogramDataPoint.Buckets\x12W\n\x08negative\x18\t \x01(\x0b\x32\x45.opentelemetry.proto.metrics.v1.ExponentialHistogramDataPoint.Buckets\x12\r\n\x05\x66lags\x18\n \x01(\r\x12;\n\texemplars\x18\x0b \x03(\x0b\x32(.opentelemetry.proto.metrics.v1.Exemplar\x12\x10\n\x03min\x18\x0c \x01(\x01H\x01\x88\x01\x01\x12\x10\n\x03max\x18\r \x01(\x01H\x02\x88\x01\x01\x12\x16\n\x0ezero_threshold\x18\x0e \x01(\x01\x1a\x30\n\x07\x42uckets\x12\x0e\n\x06offset\x18\x01 \x01(\x11\x12\x15\n\rbucket_counts\x18\x02 \x03(\x04\x42\x06\n\x04_sumB\x06\n\x04_minB\x06\n\x04_max\"\xc5\x02\n\x10SummaryDataPoint\x12;\n\nattributes\x18\x07 \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValue\x12\x1c\n\x14start_time_unix_nano\x18\x02 \x01(\x06\x12\x16\n\x0etime_unix_nano\x18\x03 \x01(\x06\x12\r\n\x05\x63ount\x18\x04 \x01(\x06\x12\x0b\n\x03sum\x18\x05 \x01(\x01\x12Y\n\x0fquantile_values\x18\x06 \x03(\x0b\x32@.opentelemetry.proto.metrics.v1.SummaryDataPoint.ValueAtQuantile\x12\r\n\x05\x66lags\x18\x08 \x01(\r\x1a\x32\n\x0fValueAtQuantile\x12\x10\n\x08quantile\x18\x01 \x01(\x01\x12\r\n\x05value\x18\x02 \x01(\x01J\x04\x08\x01\x10\x02\"\xc1\x01\n\x08\x45xemplar\x12\x44\n\x13\x66iltered_attributes\x18\x07 \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValue\x12\x16\n\x0etime_unix_nano\x18\x02 \x01(\x06\x12\x13\n\tas_double\x18\x03 \x01(\x01H\x00\x12\x10\n\x06\x61s_int\x18\x06 \x01(\x10H\x00\x12\x0f\n\x07span_id\x18\x04 \x01(\x0c\x12\x10\n\x08trace_id\x18\x05 \x01(\x0c\x42\x07\n\x05valueJ\x04\x08\x01\x10\x02*\x8c\x01\n\x16\x41ggregationTemporality\x12\'\n#AGGREGATION_TEMPORALITY_UNSPECIFIED\x10\x00\x12!\n\x1d\x41GGREGATION_TEMPORALITY_DELTA\x10\x01\x12&\n\"AGGREGATION_TEMPORALITY_CUMULATIVE\x10\x02*^\n\x0e\x44\x61taPointFlags\x12\x1f\n\x1b\x44\x41TA_POINT_FLAGS_DO_NOT_USE\x10\x00\x12+\n\'DATA_POINT_FLAGS_NO_RECORDED_VALUE_MASK\x10\x01\x42\x7f\n!io.opentelemetry.proto.metrics.v1B\x0cMetricsProtoP\x01Z)go.opentelemetry.io/proto/otlp/metrics/v1\xaa\x02\x1eOpenTelemetry.Proto.Metrics.V1b\x06proto3"
|
|
12
|
+
|
|
13
|
+
pool = ::Google::Protobuf::DescriptorPool.generated_pool
|
|
14
|
+
pool.add_serialized_file(descriptor_data)
|
|
15
|
+
|
|
16
|
+
module Opentelemetry
|
|
17
|
+
module Proto
|
|
18
|
+
module Metrics
|
|
19
|
+
module V1
|
|
20
|
+
MetricsData = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.metrics.v1.MetricsData").msgclass
|
|
21
|
+
ResourceMetrics = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.metrics.v1.ResourceMetrics").msgclass
|
|
22
|
+
ScopeMetrics = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.metrics.v1.ScopeMetrics").msgclass
|
|
23
|
+
Metric = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.metrics.v1.Metric").msgclass
|
|
24
|
+
Gauge = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.metrics.v1.Gauge").msgclass
|
|
25
|
+
Sum = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.metrics.v1.Sum").msgclass
|
|
26
|
+
Histogram = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.metrics.v1.Histogram").msgclass
|
|
27
|
+
ExponentialHistogram = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.metrics.v1.ExponentialHistogram").msgclass
|
|
28
|
+
Summary = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.metrics.v1.Summary").msgclass
|
|
29
|
+
NumberDataPoint = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.metrics.v1.NumberDataPoint").msgclass
|
|
30
|
+
HistogramDataPoint = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.metrics.v1.HistogramDataPoint").msgclass
|
|
31
|
+
ExponentialHistogramDataPoint = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.metrics.v1.ExponentialHistogramDataPoint").msgclass
|
|
32
|
+
ExponentialHistogramDataPoint::Buckets = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.metrics.v1.ExponentialHistogramDataPoint.Buckets").msgclass
|
|
33
|
+
SummaryDataPoint = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.metrics.v1.SummaryDataPoint").msgclass
|
|
34
|
+
SummaryDataPoint::ValueAtQuantile = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.metrics.v1.SummaryDataPoint.ValueAtQuantile").msgclass
|
|
35
|
+
Exemplar = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.metrics.v1.Exemplar").msgclass
|
|
36
|
+
AggregationTemporality = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.metrics.v1.AggregationTemporality").enummodule
|
|
37
|
+
DataPointFlags = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.metrics.v1.DataPointFlags").enummodule
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# source: opentelemetry/proto/resource/v1/resource.proto
|
|
4
|
+
|
|
5
|
+
require 'google/protobuf'
|
|
6
|
+
|
|
7
|
+
require 'opentelemetry/proto/common/v1/common_pb'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
descriptor_data = "\n.opentelemetry/proto/resource/v1/resource.proto\x12\x1fopentelemetry.proto.resource.v1\x1a*opentelemetry/proto/common/v1/common.proto\"\xa8\x01\n\x08Resource\x12;\n\nattributes\x18\x01 \x03(\x0b\x32\'.opentelemetry.proto.common.v1.KeyValue\x12 \n\x18\x64ropped_attributes_count\x18\x02 \x01(\r\x12=\n\x0b\x65ntity_refs\x18\x03 \x03(\x0b\x32(.opentelemetry.proto.common.v1.EntityRefB\x83\x01\n\"io.opentelemetry.proto.resource.v1B\rResourceProtoP\x01Z*go.opentelemetry.io/proto/otlp/resource/v1\xaa\x02\x1fOpenTelemetry.Proto.Resource.V1b\x06proto3"
|
|
11
|
+
|
|
12
|
+
pool = ::Google::Protobuf::DescriptorPool.generated_pool
|
|
13
|
+
pool.add_serialized_file(descriptor_data)
|
|
14
|
+
|
|
15
|
+
module Opentelemetry
|
|
16
|
+
module Proto
|
|
17
|
+
module Resource
|
|
18
|
+
module V1
|
|
19
|
+
Resource = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.resource.v1.Resource").msgclass
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fast/prometheus"
|
|
4
|
+
require "async"
|
|
5
|
+
require "console"
|
|
6
|
+
|
|
7
|
+
module Fast
|
|
8
|
+
module Prometheus
|
|
9
|
+
module OTLP
|
|
10
|
+
# Periodically pushes metrics using any exporter that responds to #export.
|
|
11
|
+
class Push
|
|
12
|
+
def initialize(exporter:, interval: 15)
|
|
13
|
+
@exporter = exporter
|
|
14
|
+
@interval = interval
|
|
15
|
+
@task = nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Start the push loop. Returns the Async::Task running it.
|
|
19
|
+
def run(parent: Async::Task.current)
|
|
20
|
+
@task = parent.async do |task|
|
|
21
|
+
task.annotate("OTLP push loop")
|
|
22
|
+
loop do
|
|
23
|
+
sleep(@interval)
|
|
24
|
+
@exporter.export
|
|
25
|
+
rescue StandardError => e
|
|
26
|
+
Console.warn("OTLP push error: #{e}")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Stop the push loop.
|
|
32
|
+
def stop
|
|
33
|
+
@task&.stop
|
|
34
|
+
@task = nil
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fast/prometheus"
|
|
4
|
+
require "protocol/grpc/interface"
|
|
5
|
+
require_relative "mapper"
|
|
6
|
+
|
|
7
|
+
module Fast
|
|
8
|
+
module Prometheus
|
|
9
|
+
module OTLP
|
|
10
|
+
# Protocol::GRPC::Interface for opentelemetry.proto.collector.metrics.v1.MetricsService.
|
|
11
|
+
class MetricsServiceInterface < Protocol::GRPC::Interface
|
|
12
|
+
rpc :Export,
|
|
13
|
+
request_class: Opentelemetry::Proto::Collector::Metrics::V1::ExportMetricsServiceRequest,
|
|
14
|
+
response_class: Opentelemetry::Proto::Collector::Metrics::V1::ExportMetricsServiceResponse
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fast/prometheus"
|
|
4
|
+
require "fast/prometheus/exposition"
|
|
5
|
+
|
|
6
|
+
module Fast
|
|
7
|
+
module Prometheus
|
|
8
|
+
module Rack
|
|
9
|
+
# Rack middleware that serves metrics at a configurable path.
|
|
10
|
+
# Handles content negotiation (text vs protobuf) and gzip compression.
|
|
11
|
+
class Exporter
|
|
12
|
+
def initialize(app, registry: Fast::Prometheus.registry, path: "/metrics")
|
|
13
|
+
@app = app
|
|
14
|
+
@registry = registry
|
|
15
|
+
@path = path
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call(env)
|
|
19
|
+
return @app.call(env) unless env["REQUEST_METHOD"] == "GET" && env["PATH_INFO"] == @path
|
|
20
|
+
|
|
21
|
+
body, headers = Exposition.render(
|
|
22
|
+
@registry,
|
|
23
|
+
accept: env["HTTP_ACCEPT"],
|
|
24
|
+
accept_encoding: env["HTTP_ACCEPT_ENCODING"]
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
[200, headers, [body]]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fast/prometheus"
|
|
4
|
+
require "fast/prometheus/request_metrics"
|
|
5
|
+
|
|
6
|
+
module Fast
|
|
7
|
+
module Prometheus
|
|
8
|
+
module Rack
|
|
9
|
+
# Rack middleware that records RED metrics for every request.
|
|
10
|
+
# Drop one line into config.ru and get request counts and durations.
|
|
11
|
+
class Instrumentation
|
|
12
|
+
def initialize(app, registry: Fast::Prometheus.registry, native: false, prefix: "http_server")
|
|
13
|
+
@app = app
|
|
14
|
+
@metrics = RequestMetrics.new(registry: registry, native: native, prefix: prefix)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call(env)
|
|
18
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
19
|
+
status, headers, body = @app.call(env)
|
|
20
|
+
@metrics.record(env["REQUEST_METHOD"], status.to_s, start)
|
|
21
|
+
[status, headers, body]
|
|
22
|
+
rescue StandardError => e
|
|
23
|
+
begin
|
|
24
|
+
@metrics.record(env["REQUEST_METHOD"], "500", start)
|
|
25
|
+
rescue StandardError
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
raise e
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "errors"
|
|
4
|
+
require_relative "counter"
|
|
5
|
+
require_relative "gauge"
|
|
6
|
+
require_relative "histogram"
|
|
7
|
+
require_relative "summary"
|
|
8
|
+
require_relative "native_histogram"
|
|
9
|
+
require_relative "snapshot"
|
|
10
|
+
|
|
11
|
+
module Fast
|
|
12
|
+
module Prometheus
|
|
13
|
+
# Registry holds a collection of metrics and produces immutable snapshots.
|
|
14
|
+
class Registry
|
|
15
|
+
def initialize
|
|
16
|
+
@by_name = {}
|
|
17
|
+
@lock = Mutex.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Register a metric. Raises DuplicateMetric if a metric with the same name
|
|
21
|
+
# is already registered. Returns the metric.
|
|
22
|
+
def register(metric)
|
|
23
|
+
@lock.synchronize do
|
|
24
|
+
raise DuplicateMetric, "metric #{metric.name} already registered" if @by_name.key?(metric.name)
|
|
25
|
+
|
|
26
|
+
@by_name[metric.name] = metric
|
|
27
|
+
metric
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Remove a metric by name.
|
|
32
|
+
def unregister(name)
|
|
33
|
+
@lock.synchronize { @by_name.delete(name) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Look up a metric by name, or nil.
|
|
37
|
+
def get(name)
|
|
38
|
+
@lock.synchronize { @by_name[name] }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Atomic fetch-or-register: returns the metric already registered under
|
|
42
|
+
# +name+, else registers and returns the block's metric. The block runs
|
|
43
|
+
# only when +name+ is absent, inside the same lock hold as the lookup,
|
|
44
|
+
# so concurrent callers for one absent name never race each other into
|
|
45
|
+
# DuplicateMetric. Raises ArgumentError if the block's metric is not
|
|
46
|
+
# named +name+.
|
|
47
|
+
def fetch_or_register(name)
|
|
48
|
+
@lock.synchronize do
|
|
49
|
+
next @by_name[name] if @by_name.key?(name)
|
|
50
|
+
|
|
51
|
+
metric = yield
|
|
52
|
+
unless metric.name == name
|
|
53
|
+
raise ArgumentError, "fetch_or_register(#{name.inspect}) block built #{metric.name.inspect}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
@by_name[name] = metric
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Return the registered metrics, insertion order.
|
|
61
|
+
def metrics
|
|
62
|
+
@lock.synchronize { @by_name.values }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Convenience: build, register, and return a Counter.
|
|
66
|
+
def counter(name, **kwargs)
|
|
67
|
+
register(Counter.new(name, **kwargs))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Convenience: build, register, and return a Gauge.
|
|
71
|
+
def gauge(name, **kwargs)
|
|
72
|
+
register(Gauge.new(name, **kwargs))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Convenience: build, register, and return a Histogram.
|
|
76
|
+
def histogram(name, **kwargs)
|
|
77
|
+
register(Histogram.new(name, **kwargs))
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Convenience: build, register, and return a Summary.
|
|
81
|
+
def summary(name, **kwargs)
|
|
82
|
+
register(Summary.new(name, **kwargs))
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Convenience: build, register, and return a NativeHistogram.
|
|
86
|
+
def native_histogram(name, **kwargs)
|
|
87
|
+
register(NativeHistogram.new(name, **kwargs))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Collect an immutable snapshot of all registered metrics. The registry
|
|
91
|
+
# lock only covers copying the metric list; each metric's own snapshot
|
|
92
|
+
# is built under its own store lock (see Snapshot.of).
|
|
93
|
+
def collect
|
|
94
|
+
Snapshot.of(metrics)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
@registry_lock = Mutex.new
|
|
99
|
+
|
|
100
|
+
# Module-level default registry. Constructed at most once across threads:
|
|
101
|
+
# the fast path is an unsynchronized read (safe under the GVL), and only
|
|
102
|
+
# the first caller pays for the lock.
|
|
103
|
+
def self.registry
|
|
104
|
+
@registry || @registry_lock.synchronize { @registry ||= Registry.new }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def self.registry=(registry)
|
|
108
|
+
@registry = registry
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fast/prometheus"
|
|
4
|
+
|
|
5
|
+
module Fast
|
|
6
|
+
module Prometheus
|
|
7
|
+
# Owns the RED metrics (request count + duration) recorded for every HTTP
|
|
8
|
+
# request. Shared by Middleware::Instrumentation and Rack::Instrumentation
|
|
9
|
+
# so registration and recording exist exactly once.
|
|
10
|
+
class RequestMetrics
|
|
11
|
+
def initialize(registry:, native: false, prefix: "http_server")
|
|
12
|
+
@registry = registry
|
|
13
|
+
@counter = ensure_counter(prefix)
|
|
14
|
+
@histogram = ensure_histogram(prefix, native)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def record(method, status, started_at)
|
|
18
|
+
labels = { method: method, status: status }
|
|
19
|
+
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
|
|
20
|
+
@counter.increment(labels: labels)
|
|
21
|
+
@histogram.observe(duration, labels: labels)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def ensure_counter(prefix)
|
|
27
|
+
name = :"#{prefix}_requests_total"
|
|
28
|
+
@registry.fetch_or_register(name) do
|
|
29
|
+
Counter.new(name, docstring: "Total HTTP requests", labels: %i[method status])
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def ensure_histogram(prefix, native)
|
|
34
|
+
name = :"#{prefix}_request_duration_seconds"
|
|
35
|
+
@registry.fetch_or_register(name) do
|
|
36
|
+
if native
|
|
37
|
+
NativeHistogram.new(name, docstring: "HTTP request duration in seconds", labels: %i[method status])
|
|
38
|
+
else
|
|
39
|
+
Histogram.new(name, docstring: "HTTP request duration in seconds", labels: %i[method status])
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|