mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -08:00
161 lines
5 KiB
TypeScript
161 lines
5 KiB
TypeScript
|
import { formatPrometheusDuration } from "../lib/formatTime";
|
||
|
import ASTNode, {
|
||
|
VectorSelector,
|
||
|
matchType,
|
||
|
vectorMatchCardinality,
|
||
|
nodeType,
|
||
|
StartOrEnd,
|
||
|
MatrixSelector,
|
||
|
} from "./ast";
|
||
|
import {
|
||
|
aggregatorsWithParam,
|
||
|
maybeParenthesizeBinopChild,
|
||
|
escapeString,
|
||
|
} from "./utils";
|
||
|
|
||
|
const serializeAtAndOffset = (
|
||
|
timestamp: number | null,
|
||
|
startOrEnd: StartOrEnd,
|
||
|
offset: number
|
||
|
): string =>
|
||
|
`${timestamp !== null ? ` @ ${(timestamp / 1000).toFixed(3)}` : startOrEnd !== null ? ` @ ${startOrEnd}()` : ""}${
|
||
|
offset === 0
|
||
|
? ""
|
||
|
: offset > 0
|
||
|
? ` offset ${formatPrometheusDuration(offset)}`
|
||
|
: ` offset -${formatPrometheusDuration(-offset)}`
|
||
|
}`;
|
||
|
|
||
|
const serializeSelector = (node: VectorSelector | MatrixSelector): string => {
|
||
|
const matchers = node.matchers
|
||
|
.filter(
|
||
|
(m) =>
|
||
|
!(
|
||
|
m.name === "__name__" &&
|
||
|
m.type === matchType.equal &&
|
||
|
m.value === node.name
|
||
|
)
|
||
|
)
|
||
|
.map((m) => `${m.name}${m.type}"${escapeString(m.value)}"`);
|
||
|
|
||
|
const range =
|
||
|
node.type === nodeType.matrixSelector
|
||
|
? `[${formatPrometheusDuration(node.range)}]`
|
||
|
: "";
|
||
|
const atAndOffset = serializeAtAndOffset(
|
||
|
node.timestamp,
|
||
|
node.startOrEnd,
|
||
|
node.offset
|
||
|
);
|
||
|
|
||
|
return `${node.name}${matchers.length > 0 ? `{${matchers.join(",")}}` : ""}${range}${atAndOffset}`;
|
||
|
};
|
||
|
|
||
|
const serializeNode = (
|
||
|
node: ASTNode,
|
||
|
indent = 0,
|
||
|
pretty = false,
|
||
|
initialIndent = true
|
||
|
): string => {
|
||
|
const childListSeparator = pretty ? "\n" : "";
|
||
|
const childSeparator = pretty ? "\n" : " ";
|
||
|
const childIndent = indent + 2;
|
||
|
const ind = pretty ? " ".repeat(indent) : "";
|
||
|
// Needed for unary operators.
|
||
|
const initialInd = initialIndent ? ind : "";
|
||
|
|
||
|
switch (node.type) {
|
||
|
case nodeType.aggregation:
|
||
|
return `${initialInd}${node.op}${
|
||
|
node.without
|
||
|
? ` without(${node.grouping.join(", ")}) `
|
||
|
: node.grouping.length > 0
|
||
|
? ` by(${node.grouping.join(", ")}) `
|
||
|
: ""
|
||
|
}(${childListSeparator}${
|
||
|
aggregatorsWithParam.includes(node.op) && node.param !== null
|
||
|
? `${serializeNode(node.param, childIndent, pretty)},${childSeparator}`
|
||
|
: ""
|
||
|
}${serializeNode(node.expr, childIndent, pretty)}${childListSeparator}${ind})`;
|
||
|
|
||
|
case nodeType.subquery:
|
||
|
return `${initialInd}${serializeNode(node.expr, indent, pretty)}[${formatPrometheusDuration(node.range)}:${
|
||
|
node.step !== 0 ? formatPrometheusDuration(node.step) : ""
|
||
|
}]${serializeAtAndOffset(node.timestamp, node.startOrEnd, node.offset)}`;
|
||
|
|
||
|
case nodeType.parenExpr:
|
||
|
return `${initialInd}(${childListSeparator}${serializeNode(
|
||
|
node.expr,
|
||
|
childIndent,
|
||
|
pretty
|
||
|
)}${childListSeparator}${ind})`;
|
||
|
|
||
|
case nodeType.call: {
|
||
|
const sep = node.args.length > 0 ? childListSeparator : "";
|
||
|
|
||
|
return `${initialInd}${node.func.name}(${sep}${node.args
|
||
|
.map((arg) => serializeNode(arg, childIndent, pretty))
|
||
|
.join("," + childSeparator)}${sep}${node.args.length > 0 ? ind : ""})`;
|
||
|
}
|
||
|
|
||
|
case nodeType.matrixSelector:
|
||
|
return `${initialInd}${serializeSelector(node)}`;
|
||
|
|
||
|
case nodeType.vectorSelector:
|
||
|
return `${initialInd}${serializeSelector(node)}`;
|
||
|
|
||
|
case nodeType.numberLiteral:
|
||
|
return `${initialInd}${node.val}`;
|
||
|
|
||
|
case nodeType.stringLiteral:
|
||
|
return `${initialInd}"${escapeString(node.val)}"`;
|
||
|
|
||
|
case nodeType.unaryExpr:
|
||
|
return `${initialInd}${node.op}${serializeNode(node.expr, indent, pretty, false)}`;
|
||
|
|
||
|
case nodeType.binaryExpr: {
|
||
|
let matching = "";
|
||
|
let grouping = "";
|
||
|
const vm = node.matching;
|
||
|
if (vm !== null && (vm.labels.length > 0 || vm.on)) {
|
||
|
if (vm.on) {
|
||
|
matching = ` on(${vm.labels.join(", ")})`;
|
||
|
} else {
|
||
|
matching = ` ignoring(${vm.labels.join(", ")})`;
|
||
|
}
|
||
|
|
||
|
if (
|
||
|
vm.card === vectorMatchCardinality.manyToOne ||
|
||
|
vm.card === vectorMatchCardinality.oneToMany
|
||
|
) {
|
||
|
grouping = ` group_${vm.card === vectorMatchCardinality.manyToOne ? "left" : "right"}(${vm.include.join(",")})`;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return `${serializeNode(maybeParenthesizeBinopChild(node.op, node.lhs), childIndent, pretty)}${childSeparator}${ind}${
|
||
|
node.op
|
||
|
}${node.bool ? " bool" : ""}${matching}${grouping}${childSeparator}${serializeNode(
|
||
|
maybeParenthesizeBinopChild(node.op, node.rhs),
|
||
|
childIndent,
|
||
|
pretty
|
||
|
)}`;
|
||
|
}
|
||
|
|
||
|
case nodeType.placeholder:
|
||
|
// TODO: Should we just throw an error when trying to serialize an AST containing a placeholder node?
|
||
|
// (that would currently break editing-as-text of ASTs that contain placeholders)
|
||
|
return `${initialInd}…${
|
||
|
node.children.length > 0
|
||
|
? `(${childListSeparator}${node.children
|
||
|
.map((child) => serializeNode(child, childIndent, pretty))
|
||
|
.join("," + childSeparator)}${childListSeparator}${ind})`
|
||
|
: ""
|
||
|
}`;
|
||
|
|
||
|
default:
|
||
|
throw new Error("unsupported node type");
|
||
|
}
|
||
|
};
|
||
|
|
||
|
export default serializeNode;
|