feat: add initial prometheus exporter

mem-usage
Jef Roosens 2022-12-26 21:49:07 +01:00
parent 8fbad5d673
commit 80de5ba437
9 changed files with 252 additions and 73 deletions

View File

@ -145,3 +145,27 @@ pub fn (c &DefaultCollector) gauge_get(metric Metric) ?f64 {
entry.data[0] entry.data[0]
} }
} }
pub fn (c &DefaultCollector) histograms() []Metric {
mut metrics := []Metric{}
rlock c.histograms {
for _, entry in c.histograms {
metrics << entry.metric
}
}
return metrics
}
pub fn (c &DefaultCollector) gauges() []Metric {
mut metrics := []Metric{}
rlock c.gauges {
for _, entry in c.gauges {
metrics << entry.metric
}
}
return metrics
}

1
exporter.v 100644
View File

@ -0,0 +1 @@
module metrics

View File

@ -1,10 +0,0 @@
module exporter
import io
import metrics { MetricsCollector }
pub interface MetricsExporter {
load(collector MetricsCollector)
export_to_writer(writer io.Writer) !
export_to_string() string !
}

View File

@ -1,4 +0,0 @@
module exporter
pub struct PrometheusExporter {
}

View File

@ -1,5 +1,7 @@
module metrics module metrics
import io
[params] [params]
pub struct Metric { pub struct Metric {
name string [required] name string [required]
@ -8,7 +10,7 @@ pub struct Metric {
[inline] [inline]
fn join_two_array(arr [2]string) string { fn join_two_array(arr [2]string) string {
return arr[0] + '=' + arr[1] return '${arr[0]}="${arr[1]}"'
} }
pub fn (m &Metric) str() string { pub fn (m &Metric) str() string {
@ -25,12 +27,20 @@ pub interface MetricsCollector {
counters() []Metric counters() []Metric
histogram_record(value f64, metric Metric) histogram_record(value f64, metric Metric)
histogram_get(metric Metric) ?[]f64 histogram_get(metric Metric) ?[]f64
histograms() []Metric
gauge_add(value f64, metric Metric) gauge_add(value f64, metric Metric)
gauge_sub(value f64, metric Metric) gauge_sub(value f64, metric Metric)
gauge_set(value f64, metric Metric) gauge_set(value f64, metric Metric)
gauge_get(metric Metric) ?f64 gauge_get(metric Metric) ?f64
gauges() []Metric
mut: mut:
counter_register(value u64, metric Metric) counter_register(value u64, metric Metric)
histogram_register(metric Metric) histogram_register(metric Metric)
gauge_register(value f64, metric Metric) gauge_register(value f64, metric Metric)
} }
pub interface MetricsExporter {
load(collector MetricsCollector)
export_to_writer(writer io.Writer) !
export_to_string() !string
}

8
null.v
View File

@ -38,3 +38,11 @@ pub fn (c &NullCollector) gauge_set(value f64, metric Metric) {}
pub fn (c &NullCollector) gauge_get(metric Metric) ?f64 { pub fn (c &NullCollector) gauge_get(metric Metric) ?f64 {
return none return none
} }
pub fn (c &NullCollector) histograms() []Metric {
return []
}
pub fn (c &NullCollector) gauges() []Metric {
return []
}

96
prometheus.v 100644
View File

@ -0,0 +1,96 @@
module metrics
import strings
import io
import arrays
pub struct PrometheusExporter {
buckets []f64
mut:
collector MetricsCollector
}
pub fn new_prometheus_exporter(buckets []f64) PrometheusExporter {
return PrometheusExporter{
buckets: buckets
}
}
pub fn (mut e PrometheusExporter) load(collector MetricsCollector) {
e.collector = collector
}
pub fn (mut e PrometheusExporter) export_to_string() !string {
mut builder := strings.new_builder(64)
e.export_to_writer(mut builder)!
return builder.str()
}
pub fn (mut e PrometheusExporter) export_to_writer(mut writer io.Writer) ! {
for counter in e.collector.counters() {
val := e.collector.counter_get(counter) or { return error("This can't happen.") }
line := '$counter $val\n'
writer.write(line.bytes())!
}
for gauge in e.collector.gauges() {
val := e.collector.gauge_get(gauge) or { return error("This can't happen.") }
line := '$gauge $val\n'
writer.write(line.bytes())!
}
for hist in e.collector.histograms() {
data := e.collector.histogram_get(hist) or { return error("This can't happen.") }
sum := arrays.sum(data) or { 0.0 }
total_count := data.len
mut bucket_counts := []u64{len: e.buckets.len}
mut i := bucket_counts.len - 1
// For each data point, increment all buckets that the value is
// contained in. Because the buckets are sorted, we can stop once we
// encounter one that it doesn't fit in
for val in data {
for i >= 0 && val <= e.buckets[i] {
bucket_counts[i]++
i -= 1
}
i = bucket_counts.len - 1
}
mut m := Metric{
...hist
name: '${hist.name}_count'
}
writer.write('$m $total_count\n'.bytes())!
m = Metric{
...hist
name: '${hist.name}_sum'
}
writer.write('$m $sum\n'.bytes())!
mut le_labels := [][2]string{}
le_labels.prepend(hist.labels)
le_labels << ['le', '']!
for j, bucket in e.buckets {
le_labels[le_labels.len - 1][1] = bucket.str()
m = Metric{
name: '${hist.name}_bucket'
labels: le_labels
}
writer.write('$m ${bucket_counts[j]}\n'.bytes())!
}
}
}

54
prometheus_test.v 100644
View File

@ -0,0 +1,54 @@
module metrics
fn test_only_counters() {
mut m := new_default_collector()
m.counter_register(0, name: 'test')
m.counter_increment(name: 'test')
mut e := new_prometheus_exporter([])
e.load(m)
assert e.export_to_string()! == 'test 1\n'
metric := Metric{
name: 'test2'
labels: [['hi', 'label']!, ['hi2', 'label2']!]
}
m.counter_register(0, metric)
m.counter_increment(metric)
m.counter_increment(metric)
assert e.export_to_string()! == 'test 1\ntest2{hi="label",hi2="label2"} 2\n'
}
fn test_only_gauges() {
mut m := new_default_collector()
m.gauge_register(0.0, name: 'test')
m.gauge_set(3.25, name: 'test')
mut e := new_prometheus_exporter([])
e.load(m)
assert e.export_to_string()! == 'test 3.25\n'
metric := Metric{
name: 'test2'
labels: [['hi', 'label']!, ['hi2', 'label2']!]
}
m.gauge_register(0.0, metric)
m.gauge_add(2.5, metric)
assert e.export_to_string()! == 'test 3.25\ntest2{hi="label",hi2="label2"} 2.5\n'
}
fn test_single_histogram() {
mut m := new_default_collector()
m.histogram_register(name: 'test')
m.histogram_record(5.0, name: 'test')
mut e := new_prometheus_exporter([0.5, 5.0])
e.load(m)
assert e.export_to_string()! == 'test_count 1\ntest_sum 5.0\ntest_bucket{le="0.5"} 0\ntest_bucket{le="5.0"} 1\n'
}