///|
pub(all) struct CounterSample {
name : String
value : Int
}
///|
pub(all) struct Metrics {
mut counters : Array[CounterSample]
mut gauges : Array[CounterSample]
mut samples : Array[CounterSample]
}
///|
pub fn Metrics::new() -> Metrics {
{ counters: [], gauges: [], samples: [] }
}
///|
pub fn Metrics::inc(self : Metrics, name : String, delta? : Int = 1) -> Unit {
let mut i = 0
while i < self.counters.length() {
if self.counters[i].name == name {
self.counters[i] = { name, value: self.counters[i].value + delta }
return
}
i += 1
}
self.counters.push({ name, value: delta })
}
///|
pub fn Metrics::counter(self : Metrics, name : String) -> Int {
for sample in self.counters {
if sample.name == name {
return sample.value
}
}
0
}
///|
pub fn Metrics::set_gauge(self : Metrics, name : String, value : Int) -> Unit {
set_named_value(self.gauges, name, value)
}
///|
pub fn Metrics::gauge(self : Metrics, name : String) -> Int {
get_named_value(self.gauges, name)
}
///|
pub fn Metrics::sample(self : Metrics, name : String, value : Int) -> Unit {
self.samples.push({ name, value })
}
///|
pub fn Metrics::sample_count(self : Metrics, name : String) -> Int {
let mut count = 0
for sample in self.samples {
if sample.name == name {
count += 1
}
}
count
}
///|
pub(all) struct SampleSummary {
name : String
count : Int
min : Int
max : Int
sum : Int
}
///|
pub(all) struct SampleDistribution {
name : String
count : Int
min : Int
max : Int
median : Int
p90 : Int
}
///|
pub(all) struct MetricSnapshot {
counters : Array[CounterSample]
gauges : Array[CounterSample]
samples : Array[CounterSample]
}
///|
pub(all) struct MetricDelta {
name : String
before : Int
after : Int
delta : Int
}
///|
pub(all) struct MetricDiff {
counters : Array[MetricDelta]
gauges : Array[MetricDelta]
sample_delta : Int
}
///|
pub fn SampleSummary::average(self : SampleSummary) -> Double {
if self.count == 0 {
0.0
} else {
self.sum.to_double() / self.count.to_double()
}
}
///|
pub fn Metrics::summary(self : Metrics, name : String) -> SampleSummary {
let mut count = 0
let mut min = 0
let mut max = 0
let mut sum = 0
for sample in self.samples {
if sample.name == name {
if count == 0 {
min = sample.value
max = sample.value
} else {
if sample.value < min {
min = sample.value
}
if sample.value > max {
max = sample.value
}
}
count += 1
sum += sample.value
}
}
{ name, count, min, max, sum }
}
///|
pub fn Metrics::distribution(
self : Metrics,
name : String,
) -> SampleDistribution {
let values = self.sample_values(name)
values.sort()
if values.length() == 0 {
{ name, count: 0, min: 0, max: 0, median: 0, p90: 0 }
} else {
let median_index = values.length() / 2
let p90_index = percentile_index(values.length(), 90)
{
name,
count: values.length(),
min: values[0],
max: values[values.length() - 1],
median: values[median_index],
p90: values[p90_index],
}
}
}
///|
pub fn Metrics::sample_values(self : Metrics, name : String) -> Array[Int] {
let values : Array[Int] = []
for sample in self.samples {
if sample.name == name {
values.push(sample.value)
}
}
values
}
///|
fn percentile_index(length : Int, percentile : Int) -> Int {
if length <= 1 {
0
} else {
let raw = (length - 1) * percentile / 100
if raw < 0 {
0
} else if raw >= length {
length - 1
} else {
raw
}
}
}
///|
pub fn Metrics::snapshot(self : Metrics) -> Array[CounterSample] {
self.counters.copy()
}
///|
pub fn Metrics::gauge_snapshot(self : Metrics) -> Array[CounterSample] {
self.gauges.copy()
}
///|
pub fn Metrics::sample_snapshot(self : Metrics) -> Array[CounterSample] {
self.samples.copy()
}
///|
pub fn Metrics::metric_snapshot(self : Metrics) -> MetricSnapshot {
{
counters: self.snapshot(),
gauges: self.gauge_snapshot(),
samples: self.sample_snapshot(),
}
}
///|
pub fn diff_metrics(
before : MetricSnapshot,
after : MetricSnapshot,
) -> MetricDiff {
{
counters: diff_counter_samples(before.counters, after.counters),
gauges: diff_counter_samples(before.gauges, after.gauges),
sample_delta: after.samples.length() - before.samples.length(),
}
}
///|
pub fn Metrics::diff_from(
self : Metrics,
before : MetricSnapshot,
) -> MetricDiff {
diff_metrics(before, self.metric_snapshot())
}
///|
fn diff_counter_samples(
before : Array[CounterSample],
after : Array[CounterSample],
) -> Array[MetricDelta] {
let deltas : Array[MetricDelta] = []
for item in after {
let start = counter_sample_value(before, item.name)
deltas.push({
name: item.name,
before: start,
after: item.value,
delta: item.value - start,
})
}
for item in before {
if !counter_sample_has(after, item.name) {
deltas.push({
name: item.name,
before: item.value,
after: 0,
delta: 0 - item.value,
})
}
}
deltas
}
///|
fn counter_sample_value(items : Array[CounterSample], name : String) -> Int {
for item in items {
if item.name == name {
return item.value
}
}
0
}
///|
fn counter_sample_has(items : Array[CounterSample], name : String) -> Bool {
for item in items {
if item.name == name {
return true
}
}
false
}
///|
pub fn MetricDiff::summary(self : MetricDiff) -> String {
"counters=" +
self.counters.length().to_string() +
" gauges=" +
self.gauges.length().to_string() +
" sample_delta=" +
self.sample_delta.to_string()
}
///|
pub fn metrics_counter_delta(
left : Metrics,
right : Metrics,
name : String,
) -> Int {
right.counter(name) - left.counter(name)
}
///|
fn set_named_value(
items : Array[CounterSample],
name : String,
value : Int,
) -> Unit {
let mut i = 0
while i < items.length() {
if items[i].name == name {
items[i] = { name, value }
return
}
i += 1
}
items.push({ name, value })
}
///|
fn get_named_value(items : Array[CounterSample], name : String) -> Int {
for item in items {
if item.name == name {
return item.value
}
}
0
}