feat: add initial prometheus exporter
parent
8fbad5d673
commit
80de5ba437
24
collector.v
24
collector.v
|
@ -145,3 +145,27 @@ pub fn (c &DefaultCollector) gauge_get(metric Metric) ?f64 {
|
|||
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
|
||||
}
|
||||
|
|
116
collector_test.v
116
collector_test.v
|
@ -18,13 +18,13 @@ fn test_counter_increment() {
|
|||
m.counter_increment(name: 'test')
|
||||
assert m.counter_get(name: 'test')? == u64(2)
|
||||
|
||||
// Test with labels
|
||||
metric := Metric{
|
||||
name: 'test2'
|
||||
labels: [['hi', 'label']!, ['hi2', 'label2']!]
|
||||
}
|
||||
// Test with labels
|
||||
metric := Metric{
|
||||
name: 'test2'
|
||||
labels: [['hi', 'label']!, ['hi2', 'label2']!]
|
||||
}
|
||||
|
||||
m.counter_register(15, metric)
|
||||
m.counter_register(15, metric)
|
||||
m.counter_increment(metric)
|
||||
assert m.counter_get(metric)? == u64(16)
|
||||
}
|
||||
|
@ -32,81 +32,81 @@ fn test_counter_increment() {
|
|||
fn test_histogram() {
|
||||
mut m := new_default_collector()
|
||||
|
||||
m.histogram_register(name: 'test')
|
||||
m.histogram_record(5.0, name: 'test')
|
||||
assert m.histogram_get(name: 'test')? == [5.0]
|
||||
m.histogram_register(name: 'test')
|
||||
m.histogram_record(5.0, name: 'test')
|
||||
assert m.histogram_get(name: 'test')? == [5.0]
|
||||
|
||||
m.histogram_record(7.0, name: 'test')
|
||||
assert m.histogram_get(name: 'test')? == [5.0, 7.0]
|
||||
m.histogram_record(7.0, name: 'test')
|
||||
assert m.histogram_get(name: 'test')? == [5.0, 7.0]
|
||||
|
||||
// Test with labels
|
||||
metric := Metric{
|
||||
name: 'test2'
|
||||
labels: [['hi', 'label']!, ['hi2', 'label2']!]
|
||||
}
|
||||
// Test with labels
|
||||
metric := Metric{
|
||||
name: 'test2'
|
||||
labels: [['hi', 'label']!, ['hi2', 'label2']!]
|
||||
}
|
||||
|
||||
m.histogram_register(metric)
|
||||
m.histogram_record(5.0, metric)
|
||||
assert m.histogram_get(metric)? == [5.0]
|
||||
m.histogram_register(metric)
|
||||
m.histogram_record(5.0, metric)
|
||||
assert m.histogram_get(metric)? == [5.0]
|
||||
|
||||
m.histogram_record(7.0, metric)
|
||||
assert m.histogram_get(metric)? == [5.0, 7.0]
|
||||
m.histogram_record(7.0, metric)
|
||||
assert m.histogram_get(metric)? == [5.0, 7.0]
|
||||
}
|
||||
|
||||
fn test_gauge_add() {
|
||||
mut m := new_default_collector()
|
||||
|
||||
m.gauge_register(0.0, name: 'test')
|
||||
m.gauge_add(5.0, name: 'test')
|
||||
assert m.gauge_get(name: 'test')? == 5.0
|
||||
m.gauge_register(0.0, name: 'test')
|
||||
m.gauge_add(5.0, name: 'test')
|
||||
assert m.gauge_get(name: 'test')? == 5.0
|
||||
|
||||
// Test with labels
|
||||
metric := Metric{
|
||||
name: 'test2'
|
||||
labels: [['hi', 'label']!, ['hi2', 'label2']!]
|
||||
}
|
||||
// Test with labels
|
||||
metric := Metric{
|
||||
name: 'test2'
|
||||
labels: [['hi', 'label']!, ['hi2', 'label2']!]
|
||||
}
|
||||
|
||||
m.gauge_register(3.0, metric)
|
||||
m.gauge_add(5.0, metric)
|
||||
assert m.gauge_get(metric)? == 8.0
|
||||
m.gauge_register(3.0, metric)
|
||||
m.gauge_add(5.0, metric)
|
||||
assert m.gauge_get(metric)? == 8.0
|
||||
}
|
||||
|
||||
fn test_gauge_sub() {
|
||||
mut m := new_default_collector()
|
||||
|
||||
m.gauge_register(0.0, name: 'test')
|
||||
m.gauge_sub(5.0, name: 'test')
|
||||
assert m.gauge_get(name: 'test')? == -5.0
|
||||
m.gauge_register(0.0, name: 'test')
|
||||
m.gauge_sub(5.0, name: 'test')
|
||||
assert m.gauge_get(name: 'test')? == -5.0
|
||||
|
||||
// Test with labels
|
||||
metric := Metric{
|
||||
name: 'test2'
|
||||
labels: [['hi', 'label']!, ['hi2', 'label2']!]
|
||||
}
|
||||
// Test with labels
|
||||
metric := Metric{
|
||||
name: 'test2'
|
||||
labels: [['hi', 'label']!, ['hi2', 'label2']!]
|
||||
}
|
||||
|
||||
m.gauge_register(3.0, metric)
|
||||
m.gauge_sub(5.0, metric)
|
||||
assert m.gauge_get(metric)? == -2.0
|
||||
m.gauge_register(3.0, metric)
|
||||
m.gauge_sub(5.0, metric)
|
||||
assert m.gauge_get(metric)? == -2.0
|
||||
}
|
||||
|
||||
fn test_gauge_set() {
|
||||
mut m := new_default_collector()
|
||||
|
||||
m.gauge_register(0.0, name: 'test')
|
||||
m.gauge_set(3.0, name: 'test')
|
||||
assert m.gauge_get(name: 'test')? == 3.0
|
||||
m.gauge_set(5.0, name: 'test')
|
||||
assert m.gauge_get(name: 'test')? == 5.0
|
||||
m.gauge_register(0.0, name: 'test')
|
||||
m.gauge_set(3.0, name: 'test')
|
||||
assert m.gauge_get(name: 'test')? == 3.0
|
||||
m.gauge_set(5.0, name: 'test')
|
||||
assert m.gauge_get(name: 'test')? == 5.0
|
||||
|
||||
// Test with labels
|
||||
metric := Metric{
|
||||
name: 'test2'
|
||||
labels: [['hi', 'label']!, ['hi2', 'label2']!]
|
||||
}
|
||||
// Test with labels
|
||||
metric := Metric{
|
||||
name: 'test2'
|
||||
labels: [['hi', 'label']!, ['hi2', 'label2']!]
|
||||
}
|
||||
|
||||
m.gauge_register(0.0, metric)
|
||||
m.gauge_set(3.0, metric)
|
||||
assert m.gauge_get(metric)? == 3.0
|
||||
m.gauge_set(5.0, metric)
|
||||
assert m.gauge_get(metric)? == 5.0
|
||||
m.gauge_register(0.0, metric)
|
||||
m.gauge_set(3.0, metric)
|
||||
assert m.gauge_get(metric)? == 3.0
|
||||
m.gauge_set(5.0, metric)
|
||||
assert m.gauge_get(metric)? == 5.0
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
module metrics
|
|
@ -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 !
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
module exporter
|
||||
|
||||
pub struct PrometheusExporter {
|
||||
}
|
12
metrics.v
12
metrics.v
|
@ -1,5 +1,7 @@
|
|||
module metrics
|
||||
|
||||
import io
|
||||
|
||||
[params]
|
||||
pub struct Metric {
|
||||
name string [required]
|
||||
|
@ -8,7 +10,7 @@ pub struct Metric {
|
|||
|
||||
[inline]
|
||||
fn join_two_array(arr [2]string) string {
|
||||
return arr[0] + '=' + arr[1]
|
||||
return '${arr[0]}="${arr[1]}"'
|
||||
}
|
||||
|
||||
pub fn (m &Metric) str() string {
|
||||
|
@ -25,12 +27,20 @@ pub interface MetricsCollector {
|
|||
counters() []Metric
|
||||
histogram_record(value f64, metric Metric)
|
||||
histogram_get(metric Metric) ?[]f64
|
||||
histograms() []Metric
|
||||
gauge_add(value f64, metric Metric)
|
||||
gauge_sub(value f64, metric Metric)
|
||||
gauge_set(value f64, metric Metric)
|
||||
gauge_get(metric Metric) ?f64
|
||||
gauges() []Metric
|
||||
mut:
|
||||
counter_register(value u64, metric Metric)
|
||||
histogram_register(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
8
null.v
|
@ -38,3 +38,11 @@ pub fn (c &NullCollector) gauge_set(value f64, metric Metric) {}
|
|||
pub fn (c &NullCollector) gauge_get(metric Metric) ?f64 {
|
||||
return none
|
||||
}
|
||||
|
||||
pub fn (c &NullCollector) histograms() []Metric {
|
||||
return []
|
||||
}
|
||||
|
||||
pub fn (c &NullCollector) gauges() []Metric {
|
||||
return []
|
||||
}
|
||||
|
|
|
@ -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())!
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
}
|
Loading…
Reference in New Issue