Skip to content

Commit 78a988e

Browse files
committed
Add testcase for promql blog
1 parent a6a764b commit 78a988e

2 files changed

Lines changed: 222 additions & 0 deletions

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Run this with:
2+
#
3+
# git clone https://github.com/prometheus/prometheus
4+
# cd prometheus
5+
#
6+
# then copy it into ./promql/promqltest/testdata/blog_example_1.test
7+
#
8+
# and run:
9+
#
10+
# go test -v -run 'TestEvaluations/testdata/blog_example_1.test' --count=1 ./promql
11+
12+
13+
load 1m
14+
requests_total{instance="foo", endpoint="/api/v1/status"} 1000x5
15+
requests_total{instance="foo", endpoint="/api/v1/query"} 2000x5
16+
requests_total{instance="bar", endpoint="/api/v1/status"} 2500x5
17+
requests_total{instance="ownermissing", endpoint="/api/v1/status"} 3000x5
18+
component_price_multiplier{instance="foo", owner="internaluser1"} 1x5
19+
component_price_multiplier{instance="bar", owner="ushealthcare"} 20x5
20+
component_price_multiplier{instance="baz", owner="idleandunused"} 0x5
21+
22+
# 1:1 label matching; fails because requests_total{instance="foo"} has 2 matching series on LHS
23+
eval instant at 1m requests_total{} * on (instance) (component_price_multiplier)
24+
expect fail msg:multiple matches for labels: many-to-one matching must be explicit (group_left/group_right)
25+
26+
# 1:m matching; fails because requests_total{instance="foo"} has 2 matching series on LHS
27+
#
28+
# Fails with
29+
#
30+
# found duplicate series for the match group {instance="foo"} on the left hand-side of the operation: [{__name__="requests_total", endpoint="/api/v1/query", instance="foo"}, {__name__="requests_total", endpoint="/api/v1/status", instance="foo"}];many-to-many matching not allowed: matching labels must be unique on one side
31+
#
32+
eval instant at 1m requests_total{} * on (instance) group_right(owner) (component_price_multiplier)
33+
expect fail regex:found duplicate series for the match group \{instance="foo"\} on the left hand-side of the operation: \[.*\];many-to-many matching not allowed: matching labels must be unique on one side
34+
35+
# n:1 matching
36+
#
37+
# Works; acts like an inner join, dropping the unmatched requests_total{instance="ownermissing"} series
38+
#
39+
eval instant at 1m requests_total{} * on (instance) group_left(owner) (component_price_multiplier)
40+
{endpoint="/api/v1/status", instance="foo", owner="internaluser1"} 1000
41+
{endpoint="/api/v1/query", instance="foo", owner="internaluser1"} 2000
42+
{endpoint="/api/v1/status", instance="bar", owner="ushealthcare"} 50000
43+
44+
# n:1 matching with unmatched values kept (emulated LEFT JOIN)
45+
#
46+
# Works. The * 1 is just there to drop the __name__ label.
47+
#
48+
eval instant at 1m requests_total{} * on (instance) group_left(owner) (component_price_multiplier) or on (instance) (requests_total{} * 1)
49+
{endpoint="/api/v1/status", instance="foo", owner="internaluser1"} 1000
50+
{endpoint="/api/v1/query", instance="foo", owner="internaluser1"} 2000
51+
{endpoint="/api/v1/status", instance="bar", owner="ushealthcare"} 50000
52+
{endpoint="/api/v1/status", instance="ownermissing"} 3000
53+
54+
# -------------------
55+
# Some extras
56+
# -------------------
57+
58+
# Now what if we introduce duplication into the RHS? Now there are two different
59+
# series matching component_price_multiplier{instance="foo"}, one with a subcategory set
60+
load 1m
61+
component_price_multiplier{instance="foo", subcategory="discount", owner="internaluser1"} 0.9x5
62+
63+
# Fails with an error because the RHS is non-unique on (instance) now
64+
# found duplicate series for the match group {instance="foo"} on the right hand-side of the operation: [{__name__="component_price_multiplier", instance="foo", owner="internaluser1", subcategory="discount"}, {__name__="component_price_multiplier", instance="foo", owner="internaluser1"}];many-to-many matching not allowed: matching labels must be unique on one side
65+
#
66+
eval instant at 1m requests_total{} * on (instance) group_left(owner) (component_price_multiplier)
67+
expect fail regex:found duplicate series for the match group \{instance="foo"\} on the right hand-side of the operation: \[.*\];many-to-many matching not allowed: matching labels must be unique on one side
68+
69+
# Adding subcategory to the join condition worked; a missing label is treated as "" so
70+
# the RHS with subcategory didn't match any LHS rows and was filtered out.
71+
eval instant at 1m requests_total{} * on (instance, subcategory) group_left(owner) (component_price_multiplier)
72+
{endpoint="/api/v1/status", instance="foo", owner="internaluser1"} 1000
73+
{endpoint="/api/v1/query", instance="foo", owner="internaluser1"} 2000
74+
{endpoint="/api/v1/status", instance="bar", owner="ushealthcare"} 50000
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
-- PostgreSQL-flavoured SQL
2+
3+
CREATE TABLE metrics(labels jsonb, value float8);
4+
5+
INSERT INTO metrics(labels, value)
6+
VALUES
7+
('{"__name__": "requests_total", "instance": "foo", "endpoint": "/api/v1/status"}', 1000),
8+
('{"__name__": "requests_total", "instance": "foo", "endpoint": "/api/v1/query"}', 2000),
9+
('{"__name__": "requests_total", "instance": "bar", "endpoint": "/api/v1/status"}', 2500),
10+
('{"__name__": "requests_total", "instance": "ownermissing", "endpoint": "/api/v1/status"}', 3000);
11+
12+
INSERT INTO metrics(labels, value)
13+
VALUES
14+
('{"__name__": "component_price_multiplier", "instance": "foo", "owner": "internaluser1"}', 1),
15+
('{"__name__": "component_price_multiplier", "instance": "bar", "owner": "ushealthcare"}', 20),
16+
('{"__name__": "component_price_multiplier", "instance":"baz", "owner":"idleandunused"}', 0);
17+
18+
-- For convenient reference to specific metric series
19+
CREATE VIEW requests_total AS
20+
SELECT * FROM metrics WHERE labels ->> '__name__' = 'requests_total';
21+
22+
CREATE VIEW component_price_multiplier AS
23+
SELECT * FROM metrics WHERE labels ->> '__name__' = 'component_price_multiplier';
24+
25+
-- Like:
26+
--
27+
-- requests_total{}
28+
-- * on (instance)
29+
-- group_left(owner)
30+
-- component_price_multiplier{}
31+
32+
SELECT
33+
jsonb_insert(
34+
-- take all LHS labels
35+
requests_total.labels,
36+
-- add owner label from RHS
37+
ARRAY['owner'], component_price_multiplier.labels -> 'owner'
38+
) AS labels,
39+
-- apply arithmetic on value
40+
requests_total.value * component_price_multiplier.value AS value
41+
FROM requests_total
42+
-- keep rows for which a LHS and RHS match exists
43+
INNER JOIN component_price_multiplier ON (
44+
requests_total.labels ->> 'instance' = component_price_multiplier.labels ->> 'instance'
45+
);
46+
47+
-- Result:
48+
-- labels | value
49+
-- -----------------------------------------------------------------------------------------------------------+-------
50+
-- {"owner": "internaluser1", "__name__": "requests_total", "endpoint": "/api/v1/status", "instance": "foo"} | 1000
51+
-- {"owner": "internaluser1", "__name__": "requests_total", "endpoint": "/api/v1/query", "instance": "foo"} | 2000
52+
-- {"owner": "ushealthcare", "__name__": "requests_total", "endpoint": "/api/v1/status", "instance": "bar"} | 50000
53+
-- (3 rows)
54+
55+
56+
57+
-- Now as a left join so we keep the unmatched LHS. Note the coalesce(...) for the value, because
58+
-- lhs.value * rhs.value is null if the rhs is null in a left join.
59+
SELECT
60+
coalesce(
61+
-- if RHS matched, we can return a result with the owner label
62+
jsonb_insert(
63+
-- take all LHS labels
64+
requests_total.labels,
65+
-- add owner label from RHS
66+
ARRAY['owner'], component_price_multiplier.labels -> 'owner'
67+
),
68+
-- otherwise, we return the LHS labels unchanged
69+
requests_total.labels
70+
) AS labels,
71+
coalesce(
72+
-- if lhs and rhs did not match the rhs here is null, so
73+
requests_total.value * component_price_multiplier.value,
74+
-- pick the lhs if so
75+
requests_total.value
76+
) AS value
77+
FROM requests_total
78+
-- keep all LHS rows, add RHS fields only where LHS and RHS match, otherwise
79+
-- RHS fields will be null
80+
LEFT JOIN component_price_multiplier ON (
81+
requests_total.labels ->> 'instance' = component_price_multiplier.labels ->> 'instance'
82+
);
83+
84+
-- Result:
85+
--
86+
-- labels | value
87+
-- -----------------------------------------------------------------------------------------------------------+-------
88+
-- {"owner": "internaluser1", "__name__": "requests_total", "endpoint": "/api/v1/status", "instance": "foo"} | 1000
89+
-- {"owner": "internaluser1", "__name__": "requests_total", "endpoint": "/api/v1/query", "instance": "foo"} | 2000
90+
-- {"owner": "ushealthcare", "__name__": "requests_total", "endpoint": "/api/v1/status", "instance": "bar"} | 50000
91+
-- {"__name__": "requests_total", "endpoint": "/api/v1/status", "instance": "ownermissing"} | 3000
92+
-- (4 rows)
93+
94+
95+
-- Add the extra component_price_multiplier{instance="foo"} row with extra subcategory="discount" label
96+
INSERT INTO metrics(labels, value)
97+
VALUES
98+
('{"__name__": "component_price_multiplier", "instance": "foo", "subcategory": "discount", "owner": "internaluser1"}', 0.9);
99+
100+
101+
-- and repeat the first query
102+
103+
SELECT
104+
-- take all LHS labels, add 'owner' label from RHS
105+
jsonb_insert(requests_total.labels, ARRAY['owner'], component_price_multiplier.labels -> 'owner') AS labels,
106+
-- apply arithmetic on value
107+
requests_total.value * component_price_multiplier.value AS value
108+
FROM requests_total
109+
-- keep rows for which a LHS and RHS match exists
110+
INNER JOIN component_price_multiplier ON (
111+
requests_total.labels ->> 'instance' = component_price_multiplier.labels ->> 'instance'
112+
);
113+
114+
-- result: duplicate series (which would not be allowed in PromQL, resulting in an error)
115+
--
116+
-- labels | value
117+
-- -----------------------------------------------------------------------------------------------------------+-------
118+
-- {"owner": "internaluser1", "__name__": "requests_total", "endpoint": "/api/v1/status", "instance": "foo"} | 900
119+
-- {"owner": "internaluser1", "__name__": "requests_total", "endpoint": "/api/v1/status", "instance": "foo"} | 1000
120+
-- {"owner": "internaluser1", "__name__": "requests_total", "endpoint": "/api/v1/query", "instance": "foo"} | 1800
121+
-- {"owner": "internaluser1", "__name__": "requests_total", "endpoint": "/api/v1/query", "instance": "foo"} | 2000
122+
-- {"owner": "ushealthcare", "__name__": "requests_total", "endpoint": "/api/v1/status", "instance": "bar"} | 50000
123+
-- (5 rows)
124+
125+
-- We can correct this by adding subcategory to the join conditions
126+
SELECT
127+
-- take all LHS labels, add 'owner' label from RHS
128+
jsonb_insert(requests_total.labels, ARRAY['owner'], component_price_multiplier.labels -> 'owner') AS labels,
129+
-- apply arithmetic on value
130+
requests_total.value * component_price_multiplier.value AS value
131+
FROM requests_total
132+
-- keep rows for which a LHS and RHS match exists
133+
INNER JOIN component_price_multiplier ON (
134+
requests_total.labels ->> 'instance' = component_price_multiplier.labels ->> 'instance'
135+
AND
136+
-- Here we need to treat NULL = 'non-null' as false, not null, hence IS DISTINCT FROM instead of =
137+
requests_total.labels ->> 'subcategory' IS NOT DISTINCT FROM component_price_multiplier.labels ->> 'subcategory'
138+
);
139+
140+
-- result:
141+
-- labels | value
142+
-- -----------------------------------------------------------------------------------------------------------+-------
143+
-- {"owner": "internaluser1", "__name__": "requests_total", "endpoint": "/api/v1/status", "instance": "foo"} | 1000
144+
-- {"owner": "internaluser1", "__name__": "requests_total", "endpoint": "/api/v1/query", "instance": "foo"} | 2000
145+
-- {"owner": "ushealthcare", "__name__": "requests_total", "endpoint": "/api/v1/status", "instance": "bar"} | 50000
146+
-- (3 rows)
147+
148+

0 commit comments

Comments
 (0)