Skip to content

Commit 8a1cb56

Browse files
committed
Optimise histograms
Two things: when fetching for the buckets, lookup (and therefore only copy) the bucket config instead of the entire record). Second, keep buckets as a big tuple instead of a big list, which is smaller and therefore cheaper to copy, and also can apply binary search during the bucket lookup instead of linear search over a list.
1 parent e04f104 commit 8a1cb56

File tree

2 files changed

+58
-44
lines changed

2 files changed

+58
-44
lines changed

src/metrics/prometheus_histogram.erl

Lines changed: 32 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -241,27 +241,24 @@ Raises:
241241
Count :: integer().
242242
observe_n(Registry, Name, LabelValues, Value, Count) when is_integer(Value), is_integer(Count) ->
243243
Key = key(Registry, Name, LabelValues),
244-
case ets:lookup(?TABLE, Key) of
245-
[Metric] ->
246-
BucketPosition = calculate_histogram_bucket_position(Metric, Value),
247-
ets:update_counter(
248-
?TABLE,
249-
Key,
250-
[
251-
{?ISUM_POS, Value * Count},
252-
{?BUCKETS_START + BucketPosition, Count}
253-
]
254-
);
255-
[] ->
244+
try ets:lookup_element(?TABLE, Key, ?BOUNDS_POS) of
245+
Buckets ->
246+
BucketPosition = prometheus_buckets:position(Buckets, Value),
247+
Spec = [{?ISUM_POS, Value * Count}, {?BUCKETS_START + BucketPosition, Count}],
248+
ets:update_counter(?TABLE, Key, Spec)
249+
catch
250+
error:badarg ->
256251
insert_metric(Registry, Name, LabelValues, Value, Count, fun observe_n/5)
257252
end,
258253
ok;
259254
observe_n(Registry, Name, LabelValues, Value, Count) when is_float(Value), is_integer(Count) ->
260255
Key = key(Registry, Name, LabelValues),
261-
case ets:lookup(?TABLE, Key) of
262-
[Metric] ->
263-
fobserve_impl(Key, Metric, Value, Count);
264-
[] ->
256+
try ets:lookup_element(?TABLE, Key, ?BOUNDS_POS) of
257+
Buckets ->
258+
BucketPosition = prometheus_buckets:position(Buckets, Value),
259+
fobserve_impl(Key, Buckets, BucketPosition, Value, Count)
260+
catch
261+
error:badarg ->
265262
insert_metric(Registry, Name, LabelValues, Value, Count, fun observe_n/5)
266263
end;
267264
observe_n(_Registry, _Name, _LabelValues, Value, Count) when is_number(Value) ->
@@ -445,7 +442,6 @@ Raises:
445442
LabelValues :: prometheus_metric:label_values().
446443
value(Registry, Name, LabelValues) ->
447444
MF = prometheus_metric:check_mf_exists(?TABLE, Registry, Name, LabelValues),
448-
449445
RawValues = [
450446
ets:lookup(?TABLE, {Registry, Name, LabelValues, Scheduler})
451447
|| Scheduler <- schedulers_seq()
@@ -542,11 +538,6 @@ insert_metric(Registry, Name, LabelValues, Value, Count, CB) ->
542538
insert_placeholders(Registry, Name, LabelValues),
543539
CB(Registry, Name, LabelValues, Value, Count).
544540

545-
fobserve_impl(Key, Metric, Value, Count) ->
546-
Buckets = metric_buckets(Metric),
547-
BucketPos = calculate_histogram_bucket_position(Metric, Value),
548-
fobserve_impl(Key, Buckets, BucketPos, Value, Count).
549-
550541
fobserve_impl(Key, Buckets, BucketPos, Value, Count) ->
551542
ets:select_replace(?TABLE, generate_select_replace(Key, Buckets, BucketPos, Value, Count)).
552543

@@ -558,33 +549,33 @@ insert_placeholders(Registry, Name, LabelValues) ->
558549
prometheus_time:maybe_convert_to_native(DU, Bucket)
559550
end,
560551
BoundCounters = lists:duplicate(length(MFBuckets), 0),
552+
Buckets = list_to_tuple(lists:map(Fun, MFBuckets)),
561553
MetricSpec =
562-
[key(Registry, Name, LabelValues), lists:map(Fun, MFBuckets), 0, 0] ++
563-
BoundCounters,
554+
[key(Registry, Name, LabelValues), Buckets, 0, 0 | BoundCounters],
564555
ets:insert_new(?TABLE, list_to_tuple(MetricSpec)).
565556

566-
calculate_histogram_bucket_position(Metric, Value) ->
567-
Buckets = metric_buckets(Metric),
568-
prometheus_buckets:position(Buckets, Value).
569-
570557
generate_select_replace(Key, Bounds, BucketPos, Value, Count) ->
571558
BoundPlaceholders = gen_query_bound_placeholders(Bounds),
572-
HistMatch = list_to_tuple([Key, '$2', '$3', '$4'] ++ BoundPlaceholders),
559+
HistMatch = list_to_tuple([Key, '$2', '$3', '$4' | BoundPlaceholders]),
573560
BucketUpdate =
574561
lists:sublist(BoundPlaceholders, BucketPos) ++
575-
[{'+', gen_query_placeholder(?BUCKETS_START + BucketPos), Count}] ++
576-
lists:nthtail(BucketPos + 1, BoundPlaceholders),
577-
HistUpdate = list_to_tuple([{Key}, '$2', '$3', {'+', '$4', Value * Count}] ++ BucketUpdate),
562+
[
563+
{'+', gen_query_placeholder(?BUCKETS_START + BucketPos), Count}
564+
| lists:nthtail(BucketPos + 1, BoundPlaceholders)
565+
],
566+
HistUpdate = list_to_tuple([{Key}, '$2', '$3', {'+', '$4', Value * Count} | BucketUpdate]),
578567
[{HistMatch, [], [{HistUpdate}]}].
579568

580-
buckets_seq(Buckets) ->
581-
lists:seq(?BUCKETS_START, ?BUCKETS_START + length(Buckets) - 1).
569+
buckets_seq(Buckets) when is_list(Buckets) ->
570+
lists:seq(?BUCKETS_START, ?BUCKETS_START + length(Buckets) - 1);
571+
buckets_seq(Buckets) when is_tuple(Buckets) ->
572+
lists:seq(?BUCKETS_START, ?BUCKETS_START + tuple_size(Buckets) - 1).
582573

583574
generate_update_spec(Buckets) ->
584575
[{Index, 0} || Index <- buckets_seq(Buckets)].
585576

586577
gen_query_placeholder(Index) ->
587-
list_to_atom("$" ++ integer_to_list(Index)).
578+
list_to_atom([$$ | integer_to_list(Index)]).
588579

589580
gen_query_bound_placeholders(Buckets) ->
590581
[gen_query_placeholder(Index) || Index <- buckets_seq(Buckets)].
@@ -593,9 +584,9 @@ augment_counters([Start | Counters]) ->
593584
augment_counters(Counters, [Start], Start).
594585

595586
augment_counters([], LAcc, _CAcc) ->
596-
LAcc;
587+
lists:reverse(LAcc);
597588
augment_counters([Counter | Counters], LAcc, CAcc) ->
598-
augment_counters(Counters, LAcc ++ [CAcc + Counter], CAcc + Counter).
589+
augment_counters(Counters, [CAcc + Counter | LAcc], CAcc + Counter).
599590

600591
metric_buckets(Metric) ->
601592
element(?BOUNDS_POS, Metric).
@@ -606,7 +597,7 @@ reduce_buckets_counters(Metrics) ->
606597
sub_tuple_to_list(
607598
Metric,
608599
?BUCKETS_START,
609-
?BUCKETS_START + length(metric_buckets(Metric))
600+
?BUCKETS_START + tuple_size(metric_buckets(Metric))
610601
)
611602
|| Metric <- Metrics
612603
],
@@ -636,17 +627,17 @@ create_histogram_metric(CLabels, Labels, DU, Bounds, LabelValues, [ISum, FSum |
636627

637628
load_all_values(Registry, Name, Bounds) ->
638629
BoundPlaceholders = gen_query_bound_placeholders(Bounds),
639-
QuerySpec = [{Registry, Name, '$1', '_'}, '_', '$3', '$4'] ++ BoundPlaceholders,
630+
QuerySpec = [{Registry, Name, '$1', '_'}, '_', '$3', '$4' | BoundPlaceholders],
640631
ets:match(?TABLE, list_to_tuple(QuerySpec)).
641632

642633
deregister_select(Registry, Name, Buckets) ->
643634
BoundCounters = lists:duplicate(length(Buckets), '_'),
644-
MetricSpec = [{Registry, Name, '_', '_'}, '_', '_', '_'] ++ BoundCounters,
635+
MetricSpec = [{Registry, Name, '_', '_'}, '_', '_', '_' | BoundCounters],
645636
[{list_to_tuple(MetricSpec), [], [true]}].
646637

647638
delete_metrics(Registry, Buckets) ->
648639
BoundCounters = lists:duplicate(length(Buckets), '_'),
649-
MetricSpec = [{Registry, '_', '_', '_'}, '_', '_', '_'] ++ BoundCounters,
640+
MetricSpec = [{Registry, '_', '_', '_'}, '_', '_', '_' | BoundCounters],
650641
ets:match_delete(?TABLE, list_to_tuple(MetricSpec)).
651642

652643
sub_tuple_to_list(Tuple, Pos, Size) when Pos < Size ->

src/prometheus_buckets.erl

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,14 @@ linear(_Start, _Step, Count) when Count < 1 ->
130130
linear(Start, Step, Count) ->
131131
linear(Start, Step, Count, []).
132132

133-
-spec position(buckets(), number()) -> pos_integer().
133+
?DOC("""
134+
Find the first index that is greater than or equal to the given value.
135+
""").
136+
-spec position(buckets() | tuple(), number()) -> pos_integer().
134137
position(Buckets, Value) when is_list(Buckets), is_number(Value) ->
135-
find_position(Buckets, Value, 0).
138+
find_position(Buckets, Value, 0);
139+
position(Buckets, Value) when is_tuple(Buckets), 1 < tuple_size(Buckets), is_number(Value) ->
140+
find_position_in_tuple(Buckets, Value, 1, tuple_size(Buckets)).
136141

137142
linear(_Current, _Step, 0, Acc) ->
138143
lists:reverse(Acc);
@@ -155,7 +160,8 @@ try_to_maintain_integer_bounds(Bound) when is_float(Bound) ->
155160
false -> Bound
156161
end.
157162

158-
-spec find_position(buckets(), number(), non_neg_integer()) -> pos_integer().
163+
%% Find the first index that is greater than or equal to the given value.
164+
-spec find_position(buckets(), number(), non_neg_integer()) -> non_neg_integer().
159165
find_position([], _Value, _Pos) ->
160166
0;
161167
find_position([Bound | L], Value, Pos) ->
@@ -165,3 +171,20 @@ find_position([Bound | L], Value, Pos) ->
165171
false ->
166172
find_position(L, Value, Pos + 1)
167173
end.
174+
175+
%% Find the first index that is greater than or equal to the given value.
176+
-spec find_position_in_tuple(tuple(), number(), non_neg_integer(), pos_integer()) ->
177+
non_neg_integer().
178+
find_position_in_tuple(Tuple, Value, Low, High) when Low =< High ->
179+
Mid = Low + (High - Low) div 2,
180+
case element(Mid, Tuple) of
181+
Element when Element < Value ->
182+
find_position_in_tuple(Tuple, Value, Mid + 1, High);
183+
Element when Value =< Element ->
184+
find_position_in_tuple(Tuple, Value, Low, Mid - 1)
185+
end;
186+
find_position_in_tuple(Tuple, _Value, Low, _High) ->
187+
case tuple_size(Tuple) < Low of
188+
true -> 0;
189+
false -> Low - 1
190+
end.

0 commit comments

Comments
 (0)