(ctx: &EvalContext, args: &[Expr])
| 332 | } |
| 333 | |
| 334 | fn eval_histogram_quantile(ctx: &EvalContext, args: &[Expr]) -> Result<Value, PromqlError> { |
| 335 | if args.len() < 2 { |
| 336 | return Err(PromqlError::WrongArgCount { |
| 337 | func: "histogram_quantile".to_string(), |
| 338 | expected: 2, |
| 339 | got: args.len(), |
| 340 | }); |
| 341 | } |
| 342 | let Value::Scalar(phi, _) = eval(ctx, &args[0])? else { |
| 343 | return Err(PromqlError::TypeError { |
| 344 | context: "histogram_quantile".to_string(), |
| 345 | detail: "first arg must be scalar".to_string(), |
| 346 | }); |
| 347 | }; |
| 348 | let val = eval(ctx, &args[1])?; |
| 349 | let Value::Vector(samples) = val else { |
| 350 | return Err(PromqlError::TypeError { |
| 351 | context: "histogram_quantile".to_string(), |
| 352 | detail: "second arg must be instant vector".to_string(), |
| 353 | }); |
| 354 | }; |
| 355 | |
| 356 | // Group by labels excluding "le". |
| 357 | let mut groups: std::collections::BTreeMap<String, Vec<(f64, f64)>> = |
| 358 | std::collections::BTreeMap::new(); |
| 359 | let mut group_labels_map: std::collections::BTreeMap<String, Labels> = |
| 360 | std::collections::BTreeMap::new(); |
| 361 | |
| 362 | for s in &samples { |
| 363 | let le_str = s.labels.get("le").cloned().unwrap_or_default(); |
| 364 | let le: f64 = le_str.parse().unwrap_or(f64::INFINITY); |
| 365 | let mut key_labels = s.labels.clone(); |
| 366 | key_labels.remove("le"); |
| 367 | key_labels.remove("__name__"); |
| 368 | let key = super::helpers::labels_key(&key_labels); |
| 369 | group_labels_map.entry(key.clone()).or_insert(key_labels); |
| 370 | groups.entry(key).or_default().push((le, s.value)); |
| 371 | } |
| 372 | |
| 373 | let mut result = Vec::new(); |
| 374 | for (key, mut buckets) in groups { |
| 375 | buckets.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal)); |
| 376 | let quantile_val = histogram_quantile_from_buckets(phi, &buckets); |
| 377 | if let Some(labels) = group_labels_map.get(&key) { |
| 378 | result.push(InstantSample { |
| 379 | labels: labels.clone(), |
| 380 | value: quantile_val, |
| 381 | timestamp_ms: ctx.timestamp_ms, |
| 382 | }); |
| 383 | } |
| 384 | } |
| 385 | Ok(Value::Vector(result)) |
| 386 | } |
| 387 | |
| 388 | /// Compute quantile from sorted histogram buckets (le, count) pairs. |
| 389 | fn histogram_quantile_from_buckets(phi: f64, buckets: &[(f64, f64)]) -> f64 { |
no test coverage detected