///|
/// Aggregation function for a production query window.
pub(all) enum ProductionWindowFunction {
QueryMean
QuerySum
QueryMinimum
QueryMaximum
QueryMedian
QueryP95
QueryCount
QueryRate
QueryChange
}
///|
pub fn production_window_function_name(
kind : ProductionWindowFunction,
) -> String {
match kind {
QueryMean => "mean"
QuerySum => "sum"
QueryMinimum => "minimum"
QueryMaximum => "maximum"
QueryMedian => "median"
QueryP95 => "p95"
QueryCount => "count"
QueryRate => "rate"
QueryChange => "change"
}
}
///|
pub struct ProductionQueryWindow {
start : Int64
end : Int64
}
///|
pub fn ProductionQueryWindow::new(
start : Int64,
end : Int64,
) -> ProductionQueryWindow {
{ start, end: if end < start { start } else { end } }
}
///|
pub fn ProductionQueryWindow::start(self : ProductionQueryWindow) -> Int64 {
self.start
}
///|
pub fn ProductionQueryWindow::end(self : ProductionQueryWindow) -> Int64 {
self.end
}
///|
pub fn ProductionQueryWindow::contains(
self : ProductionQueryWindow,
timestamp : Int64,
) -> Bool {
timestamp >= self.start && timestamp < self.end
}
///|
pub fn ProductionQueryWindow::duration(self : ProductionQueryWindow) -> Int64 {
self.end - self.start
}
///|
pub struct ProductionQueryResult {
metric : String
window : ProductionQueryWindow
function : ProductionWindowFunction
value : Double
count : Int
quality : Double
}
///|
pub fn ProductionQueryResult::metric(self : ProductionQueryResult) -> String {
self.metric
}
///|
pub fn ProductionQueryResult::window(
self : ProductionQueryResult,
) -> ProductionQueryWindow {
self.window
}
///|
pub fn ProductionQueryResult::function(
self : ProductionQueryResult,
) -> ProductionWindowFunction {
self.function
}
///|
pub fn ProductionQueryResult::value(self : ProductionQueryResult) -> Double {
self.value
}
///|
pub fn ProductionQueryResult::count(self : ProductionQueryResult) -> Int {
self.count
}
///|
pub fn ProductionQueryResult::quality(self : ProductionQueryResult) -> Double {
self.quality
}
///|
pub fn ProductionQueryResult::summary(self : ProductionQueryResult) -> String {
self.metric +
":" +
production_window_function_name(self.function) +
"=" +
self.value.to_string() +
" count=" +
self.count.to_string() +
" quality=" +
self.quality.to_string()
}
///|
pub struct ProductionSeriesQuery {
metric : String
window : ProductionQueryWindow
function : ProductionWindowFunction
minimum_quality : Double
}
///|
pub fn ProductionSeriesQuery::new(
metric : String,
window : ProductionQueryWindow,
function? : ProductionWindowFunction = QueryMean,
minimum_quality? : Double = 0.0,
) -> ProductionSeriesQuery {
{
metric,
window,
function,
minimum_quality: clamp_probability(minimum_quality),
}
}
///|
pub fn ProductionSeriesQuery::metric(self : ProductionSeriesQuery) -> String {
self.metric
}
///|
pub fn ProductionSeriesQuery::window(
self : ProductionSeriesQuery,
) -> ProductionQueryWindow {
self.window
}
///|
pub fn ProductionSeriesQuery::function(
self : ProductionSeriesQuery,
) -> ProductionWindowFunction {
self.function
}
///|
pub fn ProductionSeriesQuery::minimum_quality(
self : ProductionSeriesQuery,
) -> Double {
self.minimum_quality
}
///|
fn production_query_values(
series : ProductionMetricSeries,
window : ProductionQueryWindow,
) -> (Array[Double], Int) {
let values : Array[Double] = []
let mut late = 0
for sample in series.window().to_array() {
if window.contains(sample.timestamp()) {
if is_finite(sample.value()) {
values.push(sample.value())
}
if sample.late() {
late += 1
}
}
}
(values, late)
}
///|
fn production_query_value(
function : ProductionWindowFunction,
values : Array[Double],
window : ProductionQueryWindow,
) -> Double {
match function {
QueryMean => mean(values)
QuerySum => sum(values)
QueryMinimum => array_minimum(values)
QueryMaximum => array_maximum(values)
QueryMedian => median(values)
QueryP95 => quantile(values, 0.95)
QueryCount => values.length().to_double()
QueryRate =>
if window.duration() <= 0L {
0.0
} else {
values.length().to_double() / window.duration().to_double()
}
QueryChange =>
if values.length() < 2 {
0.0
} else {
values[values.length() - 1] - values[0]
}
}
}
///|
pub fn ProductionSeriesQuery::execute(
self : ProductionSeriesQuery,
series : ProductionMetricSeries,
) -> ProductionQueryResult {
let selected = production_query_values(series, self.window)
let quality = if selected.0.length() + selected.1 == 0 {
1.0
} else {
selected.0.length().to_double() /
(selected.0.length() + selected.1).to_double()
}
{
metric: self.metric,
window: self.window,
function: self.function,
value: if quality >= self.minimum_quality {
production_query_value(self.function, selected.0, self.window)
} else {
0.0
},
count: selected.0.length(),
quality,
}
}
///|
pub fn production_query_rollup(
series : ProductionMetricSeries,
start : Int64,
end : Int64,
step : Int64,
function? : ProductionWindowFunction = QueryMean,
) -> Array[ProductionQueryResult] {
let result : Array[ProductionQueryResult] = []
let safe_step = if step < 1L { 1L } else { step }
let mut cursor = start
while cursor < end {
let window = ProductionQueryWindow::new(
cursor,
if cursor + safe_step > end {
end
} else {
cursor + safe_step
},
)
result.push(
ProductionSeriesQuery::new(series.name(), window, function~).execute(
series,
),
)
cursor += safe_step
}
result
}
///|
pub fn production_query_results_markdown(
results : Array[ProductionQueryResult],
) -> String {
let mut output = "| metric | start | end | function | value | count | quality |\n|---|---:|---:|---|---:|---:|---:|\n"
for result in results {
output = output +
"| " +
result.metric() +
" | " +
result.window().start().to_string() +
" | " +
result.window().end().to_string() +
" | " +
production_window_function_name(result.function()) +
" | " +
result.value().to_string() +
" | " +
result.count().to_string() +
" | " +
result.quality().to_string() +
" |\n"
}
output
}
///|
/// Returns the largest absolute changes in a numeric series.
pub fn production_top_changes(
values : Array[Double],
maximum? : Int = 8,
) -> Array[Int] {
let candidates : Array[(Int, Double)] = []
if values.length() < 2 {
return []
}
for i in 1.. candidates[i].1 {
let value = candidates[i]
candidates[i] = candidates[j]
candidates[j] = value
}
}
}
let result : Array[Int] = []
let limit = if maximum < 0 {
0
} else if maximum > candidates.length() {
candidates.length()
} else {
maximum
}
for i in 0..