Skip to content

Commit 4ffc47b

Browse files
committed
Add API to set default value of labeled metrics
1 parent 16d242f commit 4ffc47b

15 files changed

Lines changed: 396 additions & 44 deletions

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,36 @@ Common options are:
164164
165165
Histogram also accepts `buckets` option. Please refer to respective modules docs for the more information.
166166
167+
#### Default series
168+
169+
Metrics with no labels automatically get a zero-value series created at declaration time.
170+
Metrics **with** labels do **not** create any series automatically — a series only appears after the
171+
first write (e.g. `inc`, `observe`, `set`).
172+
173+
If you need a labeled series to be present in the output before the first write, call
174+
`prometheus_metric:set_default(MetricModule, Registry, Name, LabelValues)` explicitly:
175+
176+
```erlang
177+
%% Declare a labeled counter
178+
prometheus_counter:declare([{name, http_requests_total}, {labels, [method]}, {help, ""}]),
179+
%% Pre-seed specific label combinations so they appear with value 0 immediately
180+
prometheus_metric:set_default(prometheus_counter, default, http_requests_total, [get]),
181+
prometheus_metric:set_default(prometheus_counter, default, http_requests_total, [post]).
182+
```
183+
184+
If you are using the default registry, you can use the shorter
185+
`prometheus_metric:set_default(MetricModule, Name, LabelValues)` form:
186+
187+
```erlang
188+
%% Equivalent to the previous example, but defaults Registry to `default`
189+
prometheus_metric:set_default(prometheus_counter, http_requests_total, [get]),
190+
prometheus_metric:set_default(prometheus_counter, http_requests_total, [post]).
191+
```
192+
193+
Both `set_default/3` and `set_default/4` are idempotent: calling them again for an already-created series is a no-op.
194+
It raises `{unknown_metric, Registry, Name}` if the metric has not been declared, and
195+
`{invalid_metric_arity, Present, Expected}` if the label value count does not match.
196+
167197
### Exposition Formats
168198
169199
- [`prometheus_text_format`](https://github.com/deadtrickster/prometheus.erl/blob/master/doc/prometheus_text_format.md) - renders metrics for a given registry (default is `default`) in text format;

src/metrics/prometheus_boolean.erl

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ fuse_event(Fuse, Event) ->
4141
declare/1,
4242
deregister/1,
4343
deregister/2,
44-
set_default/2,
44+
set_default/3,
4545
set/2,
4646
set/3,
4747
set/4,
@@ -127,10 +127,22 @@ deregister(Registry, Name) ->
127127
NumDeleted = ets:select_delete(?TABLE, deregister_select(Registry, Name)),
128128
{MFR, NumDeleted > 0}.
129129

130-
?DOC(false).
131-
-spec set_default(prometheus_registry:registry(), prometheus_metric:name()) -> ok.
132-
set_default(Registry, Name) ->
133-
set(Registry, Name, [], undefined).
130+
?DOC("""
131+
Pre-seeds a boolean series for `Registry`, `Name` and `LabelValues` with the value `undefined`,
132+
if the series does not yet exist.
133+
134+
Raises:
135+
136+
* `{unknown_metric, Registry, Name}` error if boolean with name `Name` can't be found in `Registry`.
137+
* `{invalid_metric_arity, Present, Expected}` error if labels count mismatch.
138+
""").
139+
-spec set_default(
140+
Registry :: prometheus_registry:registry(),
141+
Name :: prometheus_metric:name(),
142+
LabelValues :: prometheus_metric:label_values()
143+
) -> ok.
144+
set_default(Registry, Name, LabelValues) ->
145+
set(Registry, Name, LabelValues, undefined).
134146

135147
?DOC(#{equiv => set(default, Name, [], Value)}).
136148
-spec set(prometheus_metric:name(), prometheus:prometheus_boolean()) -> ok.

src/metrics/prometheus_counter.erl

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ inc(Caller) ->
5858
declare/1,
5959
deregister/1,
6060
deregister/2,
61-
set_default/2,
61+
set_default/3,
6262
inc/1,
6363
inc/2,
6464
inc/3,
@@ -146,10 +146,25 @@ deregister(Registry, Name) ->
146146
NumDeleted = ets:select_delete(?TABLE, deregister_select(Registry, Name)),
147147
{MFR, NumDeleted > 0}.
148148

149-
?DOC(false).
150-
-spec set_default(prometheus_registry:registry(), prometheus_metric:name()) -> boolean().
151-
set_default(Registry, Name) ->
152-
ets:insert_new(?TABLE, {key(Registry, Name, []), 0, 0}).
149+
?DOC("""
150+
Pre-seeds a counter series for `Registry`, `Name` and `LabelValues` with an initial value of 0,
151+
if the series does not yet exist.
152+
153+
Useful for ensuring a labeled series is present in output before any increments happen.
154+
155+
Raises:
156+
157+
* `{unknown_metric, Registry, Name}` error if counter with name `Name` can't be found in `Registry`.
158+
* `{invalid_metric_arity, Present, Expected}` error if labels count mismatch.
159+
""").
160+
-spec set_default(
161+
Registry :: prometheus_registry:registry(),
162+
Name :: prometheus_metric:name(),
163+
LabelValues :: prometheus_metric:label_values()
164+
) -> boolean().
165+
set_default(Registry, Name, LabelValues) ->
166+
prometheus_metric:check_mf_exists(?TABLE, Registry, Name, LabelValues),
167+
ets:insert_new(?TABLE, {key(Registry, Name, LabelValues), 0, 0}).
153168

154169
?DOC(#{equiv => inc(default, Name, [], 1)}).
155170
-spec inc(prometheus_metric:name()) -> ok.

src/metrics/prometheus_gauge.erl

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ track_checked_out_sockets(CheckoutFun) ->
5454
declare/1,
5555
deregister/1,
5656
deregister/2,
57-
set_default/2,
57+
set_default/3,
5858
set/2,
5959
set/3,
6060
set/4,
@@ -161,10 +161,25 @@ deregister(Registry, Name) ->
161161
NumDeleted = ets:select_delete(?TABLE, deregister_select(Registry, Name)),
162162
{MFR, NumDeleted > 0}.
163163

164-
?DOC(false).
165-
-spec set_default(prometheus_registry:registry(), prometheus_metric:name()) -> boolean().
166-
set_default(Registry, Name) ->
167-
ets:insert_new(?TABLE, {{Registry, Name, []}, 0, 0}).
164+
?DOC("""
165+
Pre-seeds a gauge series for `Registry`, `Name` and `LabelValues` with an initial value of 0,
166+
if the series does not yet exist.
167+
168+
Useful for ensuring a labeled series is present in output before any updates happen.
169+
170+
Raises:
171+
172+
* `{unknown_metric, Registry, Name}` error if gauge with name `Name` can't be found in `Registry`.
173+
* `{invalid_metric_arity, Present, Expected}` error if labels count mismatch.
174+
""").
175+
-spec set_default(
176+
Registry :: prometheus_registry:registry(),
177+
Name :: prometheus_metric:name(),
178+
LabelValues :: prometheus_metric:label_values()
179+
) -> boolean().
180+
set_default(Registry, Name, LabelValues) ->
181+
prometheus_metric:check_mf_exists(?TABLE, Registry, Name, LabelValues),
182+
ets:insert_new(?TABLE, {{Registry, Name, LabelValues}, 0, 0}).
168183

169184
?DOC(#{equiv => set(default, Name, [], Value)}).
170185
-spec set(prometheus_metric:name(), number()) -> ok.

src/metrics/prometheus_histogram.erl

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ the number of time ticks when the recent value was observed).
5252
declare/1,
5353
deregister/1,
5454
deregister/2,
55-
set_default/2,
55+
set_default/3,
5656
observe/2,
5757
observe/3,
5858
observe/4,
@@ -178,10 +178,24 @@ deregister(Registry, Name) ->
178178
_:_ -> {false, false}
179179
end.
180180

181-
?DOC(false).
182-
-spec set_default(prometheus_registry:registry(), prometheus_metric:name()) -> boolean().
183-
set_default(Registry, Name) ->
184-
insert_placeholders(Registry, Name, []).
181+
?DOC("""
182+
Pre-seeds a histogram series for `Registry`, `Name` and `LabelValues` with zero-count buckets
183+
and zero sum, if the series does not yet exist.
184+
185+
Useful for ensuring a labeled series is present in output before any observations happen.
186+
187+
Raises:
188+
189+
* `{unknown_metric, Registry, Name}` error if histogram with name `Name` can't be found in `Registry`.
190+
* `{invalid_metric_arity, Present, Expected}` error if labels count mismatch.
191+
""").
192+
-spec set_default(
193+
Registry :: prometheus_registry:registry(),
194+
Name :: prometheus_metric:name(),
195+
LabelValues :: prometheus_metric:label_values()
196+
) -> boolean().
197+
set_default(Registry, Name, LabelValues) ->
198+
insert_placeholders(Registry, Name, LabelValues).
185199

186200
?DOC(#{equiv => observe(default, Name, [], Value)}).
187201
-spec observe(prometheus_metric:name(), number()) -> ok.

src/metrics/prometheus_quantile_summary.erl

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ It takes `error` and `bound` as in `t:ddskerl_ets:opts/0`.
5454
-export([
5555
new/1,
5656
declare/1,
57-
set_default/2,
57+
set_default/3,
5858
deregister/1,
5959
deregister/2,
6060
observe/2,
@@ -122,11 +122,23 @@ declare(Spec) ->
122122
Spec1 = validate_summary_spec(Spec),
123123
prometheus_metric:insert_mf(?TABLE, ?MODULE, Spec1).
124124

125-
?DOC(false).
126-
-spec set_default(prometheus_registry:registry(), prometheus_metric:name()) -> boolean().
127-
set_default(Registry, Name) ->
128-
#{error := Error, bound := Bound} = get_configuration(Registry, Name),
129-
Key = key(Registry, Name, []),
125+
?DOC("""
126+
Pre-seeds a quantile summary series for `Registry`, `Name` and `LabelValues` with an empty
127+
initial state, if the series does not yet exist.
128+
129+
Raises:
130+
131+
* `{unknown_metric, Registry, Name}` error if summary with name `Name` can't be found in `Registry`.
132+
* `{invalid_metric_arity, Present, Expected}` error if labels count mismatch.
133+
""").
134+
-spec set_default(
135+
Registry :: prometheus_registry:registry(),
136+
Name :: prometheus_metric:name(),
137+
LabelValues :: prometheus_metric:label_values()
138+
) -> boolean().
139+
set_default(Registry, Name, LabelValues) ->
140+
#{error := Error, bound := Bound} = get_configuration(Registry, Name, LabelValues),
141+
Key = key(Registry, Name, LabelValues),
130142
ddskerl_ets:new(?TABLE, Key, Error, Bound).
131143

132144
?DOC(#{equiv => deregister(default, Name)}).
@@ -472,8 +484,8 @@ insert_metric(Registry, Name, LabelValues, Key) ->
472484
Configuration = Configuration0#{name => Key},
473485
ddskerl_ets:new(Configuration).
474486

475-
get_configuration(Registry, Name) ->
476-
MF = prometheus_metric:check_mf_exists(?TABLE, Registry, Name),
487+
get_configuration(Registry, Name, LabelValues) ->
488+
MF = prometheus_metric:check_mf_exists(?TABLE, Registry, Name, LabelValues),
477489
prometheus_metric:mf_data(MF).
478490

479491
key(Registry, Name, LabelValues) ->

src/metrics/prometheus_summary.erl

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ observe_response(Size) ->
4444
declare/1,
4545
deregister/1,
4646
deregister/2,
47-
set_default/2,
47+
set_default/3,
4848
observe/2,
4949
observe/3,
5050
observe/4,
@@ -133,10 +133,25 @@ deregister(Registry, Name) ->
133133
NumDeleted = ets:select_delete(?TABLE, deregister_select(Registry, Name)),
134134
{MFR, NumDeleted > 0}.
135135

136-
?DOC(false).
137-
-spec set_default(prometheus_registry:registry(), prometheus_metric:name()) -> boolean().
138-
set_default(Registry, Name) ->
139-
ets:insert_new(?TABLE, {key(Registry, Name, []), 0, 0, 0}).
136+
?DOC("""
137+
Pre-seeds a summary series for `Registry`, `Name` and `LabelValues` with zero count and zero sum,
138+
if the series does not yet exist.
139+
140+
Useful for ensuring a labeled series is present in output before any observations happen.
141+
142+
Raises:
143+
144+
* `{unknown_metric, Registry, Name}` error if summary with name `Name` can't be found in `Registry`.
145+
* `{invalid_metric_arity, Present, Expected}` error if labels count mismatch.
146+
""").
147+
-spec set_default(
148+
Registry :: prometheus_registry:registry(),
149+
Name :: prometheus_metric:name(),
150+
LabelValues :: prometheus_metric:label_values()
151+
) -> boolean().
152+
set_default(Registry, Name, LabelValues) ->
153+
prometheus_metric:check_mf_exists(?TABLE, Registry, Name, LabelValues),
154+
ets:insert_new(?TABLE, {key(Registry, Name, LabelValues), 0, 0, 0}).
140155

141156
?DOC(#{equiv => observe(default, Name, [], Value)}).
142157
-spec observe(prometheus_metric:name(), number()) -> ok.

src/prometheus_metric.erl

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ as well as handling metric labels and data.
1717
-export([
1818
insert_new_mf/3,
1919
insert_mf/3,
20+
set_default/3,
21+
set_default/4,
2022
deregister_mf/2,
2123
deregister_mf/3,
2224
check_mf_exists/3,
@@ -85,10 +87,11 @@ as well as handling metric labels and data.
8587
?DOC("Inserts a new metric function into the table.").
8688
-callback declare(Spec :: spec()) -> boolean().
8789

88-
?DOC("Sets the default metric function for the module.").
89-
-callback set_default(Registry, Name) -> dynamic() when
90-
Registry :: prometheus_registry:registry(),
91-
Name :: name().
90+
?DOC("Sets the default (zero/initial) state for the metric series identified by Registry, Name and LabelValues.").
91+
-callback set_default(Registry :: prometheus_registry:registry(),
92+
Name :: name(),
93+
LabelValues :: label_values()) ->
94+
dynamic().
9295

9396
?DOC("Removes a metric function by name.").
9497
-callback remove(Name :: name()) -> boolean() | no_return().
@@ -159,6 +162,20 @@ insert_mf(Table, Module, Spec) ->
159162
false
160163
end.
161164

165+
?DOC("Calls Module:set_default(default, Name, LabelValues). ").
166+
-spec set_default(Module :: module(), Name :: name(), LabelValues :: label_values()) -> dynamic().
167+
set_default(Module, Name, LabelValues) ->
168+
set_default(Module, default, Name, LabelValues).
169+
170+
?DOC("Calls Module:set_default(Registry, Name, LabelValues). ").
171+
-spec set_default(Module :: module(),
172+
Registry :: prometheus_registry:registry(),
173+
Name :: name(),
174+
LabelValues :: label_values()) ->
175+
dynamic().
176+
set_default(Module, Registry, Name, LabelValues) ->
177+
Module:set_default(Registry, Name, LabelValues).
178+
162179
?DOC(false).
163180
-spec deregister_mf(Table, Registry) -> boolean() | no_return() when
164181
Table :: atom(),
@@ -264,7 +281,7 @@ normalize_mf_row([Name, {Labels, Help}, C, D, Data]) ->
264281
Name :: name(),
265282
Labels :: list().
266283
maybe_set_default(Module, Registry, Name, []) ->
267-
Module:set_default(Registry, Name);
284+
set_default(Module, Registry, Name, []);
268285
maybe_set_default(_, _, _, _) ->
269286
ok.
270287

test/eunit/metric/prometheus_boolean_tests.erl

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ prometheus_format_test_() ->
1616
fun test_collector1/1,
1717
fun test_collector2/1,
1818
fun test_collector3/1,
19-
fun test_values/1
19+
fun test_values/1,
20+
fun test_set_default_with_labels/1
2021
]}.
2122

2223
test_registration(_) ->
@@ -227,6 +228,31 @@ test_values(_) ->
227228
)
228229
].
229230

231+
test_set_default_with_labels(_) ->
232+
prometheus_boolean:new([
233+
{name, fuse_state},
234+
{labels, [name]},
235+
{help, "Fuse state"}
236+
]),
237+
%% Before seeding: labeled series is undefined
238+
Undef = prometheus_boolean:value(fuse_state, [myapp]),
239+
%% Seed the labeled series
240+
prometheus_boolean:set_default(default, fuse_state, [myapp]),
241+
%% After seeding: labeled series returns undefined (boolean default)
242+
Seeded = prometheus_boolean:value(fuse_state, [myapp]),
243+
[
244+
?_assertEqual(undefined, Undef),
245+
?_assertEqual(undefined, Seeded),
246+
?_assertError(
247+
{unknown_metric, default, unknown_boolean},
248+
prometheus_boolean:set_default(default, unknown_boolean, [myapp])
249+
),
250+
?_assertError(
251+
{invalid_metric_arity, 2, 1},
252+
prometheus_boolean:set_default(default, fuse_state, [myapp, extra])
253+
)
254+
].
255+
230256
test_collector1(_) ->
231257
prometheus_boolean:new([
232258
{name, simple_boolean},

0 commit comments

Comments
 (0)