diff --git a/web/api/v1/translate_ast.go b/web/api/v1/translate_ast.go index b3f5eda212..dc2e7e2901 100644 --- a/web/api/v1/translate_ast.go +++ b/web/api/v1/translate_ast.go @@ -84,6 +84,8 @@ func translateAST(node parser.Expr) any { "matchers": translateMatchers(vs.LabelMatchers), "timestamp": vs.Timestamp, "startOrEnd": getStartOrEnd(vs.StartOrEnd), + "anchored": vs.Anchored, + "smoothed": vs.Smoothed, } case *parser.SubqueryExpr: return map[string]any{ @@ -124,6 +126,8 @@ func translateAST(node parser.Expr) any { "matchers": translateMatchers(n.LabelMatchers), "timestamp": n.Timestamp, "startOrEnd": getStartOrEnd(n.StartOrEnd), + "anchored": n.Anchored, + "smoothed": n.Smoothed, } } panic("unsupported node type") diff --git a/web/ui/mantine-ui/src/pages/query/ExplainViews/Selector.tsx b/web/ui/mantine-ui/src/pages/query/ExplainViews/Selector.tsx index ca575ca2f4..2c564d3a4a 100644 --- a/web/ui/mantine-ui/src/pages/query/ExplainViews/Selector.tsx +++ b/web/ui/mantine-ui/src/pages/query/ExplainViews/Selector.tsx @@ -171,9 +171,22 @@ const SelectorExplainView: FC = ({ node }) => { {node.type === nodeType.vectorSelector ? ( <> - This node selects the latest (non-stale) sample value within the - last{" "} - {lookbackDelta} + This node {node.smoothed ? "smooths the value" : "selects the latest"}{" "} + {node.anchored || node.smoothed ? "" : "non-stale "} + {!node.smoothed && ( + <> + sample value within the last{" "} + {lookbackDelta} + + )} + {node.smoothed && ( + <> + using smoothed mode (linear interpolation with nearest + points within{" "} + {lookbackDelta}{" "} + before and after execution timestamp, ignoring staleness markers) + + )} ) : ( <> @@ -182,6 +195,21 @@ const SelectorExplainView: FC = ({ node }) => { {formatPrometheusDuration(node.range)} {" "} of data going backward from the evaluation timestamp + {node.anchored && ( + <> + {" "} + using anchored mode (includes first sample before or at + start boundary, and last sample of the range at end boundary, ignoring staleness markers) + + )} + {node.smoothed && ( + <> + {" "} + using smoothed mode (applies linear + interpolation at the boundaries using nearest samples + before and after boundaries, ignoring staleness markers) + + )} )} {node.timestamp !== null ? ( diff --git a/web/ui/mantine-ui/src/pages/query/Graph.tsx b/web/ui/mantine-ui/src/pages/query/Graph.tsx index bd92e28b23..a30d52c822 100644 --- a/web/ui/mantine-ui/src/pages/query/Graph.tsx +++ b/web/ui/mantine-ui/src/pages/query/Graph.tsx @@ -56,6 +56,8 @@ const Graph: FC = ({ offset: node.offset, timestamp: node.timestamp, startOrEnd: node.startOrEnd, + anchored: node.anchored, + smoothed: node.smoothed, } : node ); diff --git a/web/ui/mantine-ui/src/pages/query/MetricsExplorer/LabelsExplorer.tsx b/web/ui/mantine-ui/src/pages/query/MetricsExplorer/LabelsExplorer.tsx index d18c017b18..16b19ad9d3 100644 --- a/web/ui/mantine-ui/src/pages/query/MetricsExplorer/LabelsExplorer.tsx +++ b/web/ui/mantine-ui/src/pages/query/MetricsExplorer/LabelsExplorer.tsx @@ -94,6 +94,8 @@ const LabelsExplorer: FC = ({ offset: 0, timestamp: null, startOrEnd: null, + anchored: false, + smoothed: false, }; // Based on the selected pool (if any), load the list of targets. diff --git a/web/ui/mantine-ui/src/pages/query/TreeNode.tsx b/web/ui/mantine-ui/src/pages/query/TreeNode.tsx index 25ffdce650..b0ba1f68aa 100644 --- a/web/ui/mantine-ui/src/pages/query/TreeNode.tsx +++ b/web/ui/mantine-ui/src/pages/query/TreeNode.tsx @@ -130,12 +130,16 @@ const TreeNode: FC<{ // more cheaply. E.g. 'foo[7w]' can be expensive to fully fetch, but wrapping it // in 'last_over_time(foo[7w])' is cheaper and also gives us all the info we // need (number of series and labels). + // Strip anchored/smoothed modifiers when wrapping, as last_over_time doesn't support them. let queryNode = node; if (queryNode.type === nodeType.matrixSelector) { + const matrixNode = { ...queryNode }; + matrixNode.anchored = false; + matrixNode.smoothed = false; queryNode = { type: nodeType.call, func: functionSignatures["last_over_time"], - args: [node], + args: [matrixNode], }; } diff --git a/web/ui/mantine-ui/src/promql/ast.ts b/web/ui/mantine-ui/src/promql/ast.ts index 3819c961b2..94872c6db0 100644 --- a/web/ui/mantine-ui/src/promql/ast.ts +++ b/web/ui/mantine-ui/src/promql/ast.ts @@ -147,6 +147,8 @@ export interface MatrixSelector { offset: number; timestamp: number | null; startOrEnd: StartOrEnd; + anchored: boolean; + smoothed: boolean; } export interface Subquery { @@ -187,6 +189,8 @@ export interface VectorSelector { offset: number; timestamp: number | null; startOrEnd: StartOrEnd; + anchored: boolean; + smoothed: boolean; } export interface Placeholder { diff --git a/web/ui/mantine-ui/src/promql/format.tsx b/web/ui/mantine-ui/src/promql/format.tsx index 47cd259bd8..f4b883f678 100644 --- a/web/ui/mantine-ui/src/promql/format.tsx +++ b/web/ui/mantine-ui/src/promql/format.tsx @@ -131,6 +131,18 @@ const formatSelector = ( ] )} + {node.anchored && ( + <> + {" "} + anchored + + )} + {node.smoothed && ( + <> + {" "} + smoothed + + )} {formatAtAndOffset(node.timestamp, node.startOrEnd, node.offset)} ); diff --git a/web/ui/mantine-ui/src/promql/serialize.ts b/web/ui/mantine-ui/src/promql/serialize.ts index 1d2c63f4fc..bbccede708 100644 --- a/web/ui/mantine-ui/src/promql/serialize.ts +++ b/web/ui/mantine-ui/src/promql/serialize.ts @@ -56,13 +56,18 @@ const serializeSelector = (node: VectorSelector | MatrixSelector): string => { node.type === nodeType.matrixSelector ? `[${formatPrometheusDuration(node.range)}]` : ""; + const extendedAttribute = node.anchored + ? " anchored" + : node.smoothed + ? " smoothed" + : ""; const atAndOffset = serializeAtAndOffset( node.timestamp, node.startOrEnd, node.offset ); - return `${!metricExtendedCharset ? metricName : ""}${matchers.length > 0 ? `{${matchers.join(",")}}` : ""}${range}${atAndOffset}`; + return `${!metricExtendedCharset ? metricName : ""}${matchers.length > 0 ? `{${matchers.join(",")}}` : ""}${range}${extendedAttribute}${atAndOffset}`; }; const serializeNode = ( diff --git a/web/ui/mantine-ui/src/promql/serializeAndFormat.test.ts b/web/ui/mantine-ui/src/promql/serializeAndFormat.test.ts index da4be7ced1..62b10cd781 100644 --- a/web/ui/mantine-ui/src/promql/serializeAndFormat.test.ts +++ b/web/ui/mantine-ui/src/promql/serializeAndFormat.test.ts @@ -24,6 +24,8 @@ describe("serializeNode and formatNode", () => { offset: 0, timestamp: null, startOrEnd: null, + anchored: false, + smoothed: false, }, output: "metric_name", }, @@ -40,6 +42,8 @@ describe("serializeNode and formatNode", () => { offset: 0, timestamp: null, startOrEnd: null, + anchored: false, + smoothed: false, }, output: 'metric_name{label1="value1",label2!="value2",label3=~"value3",label4!~"value4"}', @@ -52,6 +56,8 @@ describe("serializeNode and formatNode", () => { offset: 60000, timestamp: null, startOrEnd: null, + anchored: false, + smoothed: false, }, output: "metric_name offset 1m", }, @@ -63,6 +69,8 @@ describe("serializeNode and formatNode", () => { offset: -60000, timestamp: null, startOrEnd: "start", + anchored: false, + smoothed: false, }, output: "metric_name @ start() offset -1m", }, @@ -74,6 +82,8 @@ describe("serializeNode and formatNode", () => { offset: -60000, timestamp: null, startOrEnd: "end", + anchored: false, + smoothed: false, }, output: "metric_name @ end() offset -1m", }, @@ -85,6 +95,8 @@ describe("serializeNode and formatNode", () => { offset: -60000, timestamp: 123000, startOrEnd: null, + anchored: false, + smoothed: false, }, output: "metric_name @ 123.000 offset -1m", }, @@ -98,6 +110,8 @@ describe("serializeNode and formatNode", () => { offset: 60000, timestamp: null, startOrEnd: null, + anchored: false, + smoothed: false, }, output: "metric_name offset 1m", }, @@ -110,6 +124,8 @@ describe("serializeNode and formatNode", () => { offset: 0, timestamp: null, startOrEnd: null, + anchored: false, + smoothed: false, }, output: 'metric_name{label1="\\"\\"\\""}', }, @@ -129,6 +145,8 @@ describe("serializeNode and formatNode", () => { offset: 600000, timestamp: null, startOrEnd: null, + anchored: false, + smoothed: false, }, output: 'metric_name{label1="value1",label2!="value2",label3=~"value3",label4!~"value4"}[5m] offset 10m', @@ -142,6 +160,8 @@ describe("serializeNode and formatNode", () => { offset: -600000, timestamp: 123000, startOrEnd: null, + anchored: false, + smoothed: false, }, output: "metric_name[5m] @ 123.000 offset -10m", }, @@ -154,6 +174,8 @@ describe("serializeNode and formatNode", () => { offset: -600000, timestamp: null, startOrEnd: "start", + anchored: false, + smoothed: false, }, output: "metric_name[5m] @ start() offset -10m", }, @@ -167,11 +189,164 @@ describe("serializeNode and formatNode", () => { offset: 0, timestamp: null, startOrEnd: null, + anchored: false, + smoothed: false, }, output: '{label1="value1"}', }, + // Anchored and smoothed modifiers. + { + node: { + type: nodeType.vectorSelector, + name: "metric_name", + matchers: [], + offset: 0, + timestamp: null, + startOrEnd: null, + anchored: true, + smoothed: false, + }, + output: "metric_name anchored", + }, + { + node: { + type: nodeType.vectorSelector, + name: "metric_name", + matchers: [], + offset: 0, + timestamp: null, + startOrEnd: null, + anchored: false, + smoothed: true, + }, + output: "metric_name smoothed", + }, + { + node: { + type: nodeType.vectorSelector, + name: "metric_name", + matchers: [], + offset: 60000, + timestamp: null, + startOrEnd: null, + anchored: true, + smoothed: false, + }, + output: "metric_name anchored offset 1m", + }, + { + node: { + type: nodeType.vectorSelector, + name: "metric_name", + matchers: [], + offset: 60000, + timestamp: null, + startOrEnd: null, + anchored: false, + smoothed: true, + }, + output: "metric_name smoothed offset 1m", + }, + { + node: { + type: nodeType.vectorSelector, + name: "metric_name", + matchers: [], + offset: 0, + timestamp: 123000, + startOrEnd: null, + anchored: true, + smoothed: false, + }, + output: "metric_name anchored @ 123.000", + }, + { + node: { + type: nodeType.matrixSelector, + name: "metric_name", + matchers: [], + range: 300000, + offset: 0, + timestamp: null, + startOrEnd: null, + anchored: true, + smoothed: false, + }, + output: "metric_name[5m] anchored", + }, + { + node: { + type: nodeType.matrixSelector, + name: "metric_name", + matchers: [], + range: 300000, + offset: 0, + timestamp: null, + startOrEnd: null, + anchored: false, + smoothed: true, + }, + output: "metric_name[5m] smoothed", + }, + { + node: { + type: nodeType.matrixSelector, + name: "metric_name", + matchers: [], + range: 300000, + offset: 600000, + timestamp: null, + startOrEnd: null, + anchored: true, + smoothed: false, + }, + output: "metric_name[5m] anchored offset 10m", + }, + { + node: { + type: nodeType.matrixSelector, + name: "metric_name", + matchers: [], + range: 300000, + offset: 600000, + timestamp: null, + startOrEnd: null, + anchored: false, + smoothed: true, + }, + output: "metric_name[5m] smoothed offset 10m", + }, + { + node: { + type: nodeType.matrixSelector, + name: "metric_name", + matchers: [], + range: 300000, + offset: 0, + timestamp: 123000, + startOrEnd: null, + anchored: true, + smoothed: false, + }, + output: "metric_name[5m] anchored @ 123.000", + }, + { + node: { + type: nodeType.matrixSelector, + name: "metric_name", + matchers: [], + range: 300000, + offset: 600000, + timestamp: 123000, + startOrEnd: null, + anchored: false, + smoothed: true, + }, + output: "metric_name[5m] smoothed @ 123.000 offset 10m", + }, + // Aggregations. { node: { @@ -309,6 +484,8 @@ describe("serializeNode and formatNode", () => { offset: 0, timestamp: null, startOrEnd: null, + anchored: false, + smoothed: false, }, ], }, @@ -681,6 +858,8 @@ describe("serializeNode and formatNode", () => { startOrEnd: null, timestamp: null, type: nodeType.vectorSelector, + anchored: false, + smoothed: false, }, grouping: ["a", "ä"], op: aggregationType.sum, @@ -714,6 +893,8 @@ describe("serializeNode and formatNode", () => { startOrEnd: null, timestamp: null, type: nodeType.vectorSelector, + anchored: false, + smoothed: false, }, grouping: ["d", "ä"], op: aggregationType.sum, @@ -744,6 +925,8 @@ describe("serializeNode and formatNode", () => { startOrEnd: null, timestamp: null, type: nodeType.vectorSelector, + anchored: false, + smoothed: false, }, type: nodeType.parenExpr, }, diff --git a/web/ui/mantine-ui/src/promql/utils.test.ts b/web/ui/mantine-ui/src/promql/utils.test.ts index 04e28a78f1..5166a6ffbf 100644 --- a/web/ui/mantine-ui/src/promql/utils.test.ts +++ b/web/ui/mantine-ui/src/promql/utils.test.ts @@ -118,6 +118,8 @@ describe("nodeValueType", () => { offset: 0, timestamp: null, startOrEnd: null, + anchored: false, + smoothed: false, }, rhs: { type: nodeType.numberLiteral, val: "1" }, matching: null, @@ -138,6 +140,8 @@ describe("nodeValueType", () => { offset: 0, timestamp: null, startOrEnd: null, + anchored: false, + smoothed: false, }, rhs: { type: nodeType.vectorSelector, @@ -146,6 +150,8 @@ describe("nodeValueType", () => { offset: 0, timestamp: null, startOrEnd: null, + anchored: false, + smoothed: false, }, matching: null, bool: false,