///|
priv struct Group {
labels : @model.LabelSet
points : Array[@model.Point]
}
///|
fn grouping_labels(
labels : @model.LabelSet,
without : Bool,
names : Array[String],
) -> @model.LabelSet {
if without {
let excluded : Array[String] = ["__name__"]
for name in names {
excluded.push(name)
}
labels.without(excluded)
} else {
labels.only(names)
}
}
///|
fn aggregate_value(
operator : @ast.AggOp,
group : Group,
) -> Double raise @model.PromqlError {
if group.points.is_empty() {
raise @model.EvalError("aggregation received an empty group")
}
match operator {
@ast.AggOp::Sum => {
let mut result = 0.0
for point in group.points {
result += point.value
}
result
}
@ast.AggOp::Avg => {
let mut result = 0.0
for point in group.points {
result += point.value
}
result / group.points.length().to_double()
}
@ast.AggOp::Min => {
let mut result = group.points[0].value
for point in group.points {
result = result.min(point.value)
}
result
}
@ast.AggOp::Max => {
let mut result = group.points[0].value
for point in group.points {
result = result.max(point.value)
}
result
}
@ast.AggOp::Count => group.points.length().to_double()
_ => raise @model.EvalError("topk and bottomk need a scalar parameter")
}
}
///|
fn evaluate_aggregate(
operator : @ast.AggOp,
args : Array[@ast.Expr],
without : Bool,
names : Array[String],
context : EvalContext,
) -> @model.Value raise @model.PromqlError {
if operator is @ast.AggOp::TopK || operator is @ast.AggOp::BottomK {
if args.length() != 2 {
raise @model.EvalError("topk and bottomk expect a scalar and a vector")
}
let count_value = evaluate(args[0], context)
let count = match count_value {
@model.Scalar(value) => value
_ => raise @model.EvalError("topk and bottomk count must be a scalar")
}
let points = match evaluate(args[1], context) {
@model.Instant(points) => points
value =>
raise @model.EvalError(
"topk and bottomk expect an instant vector, got \{value.kind()}",
)
}
let groups : Array[Group] = []
for point in points {
let labels = grouping_labels(point.labels, without, names)
let mut found = false
for group in groups {
if group.labels == labels {
group.points.push(point)
found = true
break
}
}
if !found {
groups.push({ labels, points: [point], })
}
}
let output : Array[@model.Point] = []
for group in groups {
group.points.sort_by((left, right) => {
let order = if operator is @ast.AggOp::TopK {
right.value.compare(left.value)
} else {
left.value.compare(right.value)
}
if order == 0 {
left.labels.to_string().compare(right.labels.to_string())
} else {
order
}
})
let limit = if count <= 0.0 || count != count {
0
} else if count >= group.points.length().to_double() {
group.points.length()
} else {
count.to_int()
}
for index in 0.. points
value =>
raise @model.EvalError(
"aggregation expects an instant vector, got \{value.kind()}",
)
}
let groups : Array[Group] = []
for point in input {
let labels = grouping_labels(point.labels, without, names)
let mut found = false
for group in groups {
if group.labels == labels {
group.points.push(point)
found = true
break
}
}
if !found {
groups.push({ labels, points: [point], })
}
}
let output : Array[@model.Point] = []
for group in groups {
output.push({
labels: group.labels,
value: aggregate_value(operator, group),
})
}
output.sort_by((left, right) => {
left.labels.to_string().compare(right.labels.to_string())
})
@model.Instant(output)
}