diff --git a/collector.v b/collector.v index 8b9bb28..149095b 100644 --- a/collector.v +++ b/collector.v @@ -6,36 +6,25 @@ mut: data u64 } -struct Gauge { +struct FloatSeries { metric Metric pub mut: - data f64 -} - -struct Histogram { - metric Metric -pub mut: - total_count u64 - sum f64 - buckets []f64 - bucket_counts []u64 + data []f64 } [heap] struct DefaultCollector { mut: - buckets map[string][]f64 counters shared map[string]&Counter - histograms shared map[string]&Histogram - gauges shared map[string]&Gauge + histograms shared map[string]&FloatSeries + gauges shared map[string]&FloatSeries } pub fn new_default_collector() &DefaultCollector { return &DefaultCollector{ - buckets: map[string][]f64{} counters: map[string]&Counter{} - histograms: map[string]&Histogram{} - gauges: map[string]&Gauge{} + histograms: map[string]&FloatSeries{} + gauges: map[string]&FloatSeries{} } } @@ -91,61 +80,38 @@ pub fn (c &DefaultCollector) counters() []Metric { return metrics } -pub fn (mut c DefaultCollector) histogram_buckets_set(name string, buckets []f64) { - lock c.histograms { - c.buckets[name] = buckets - } -} - -pub fn (mut c DefaultCollector) histogram_record(value f64, metric Metric) { +pub fn (c &DefaultCollector) histogram_record(value f64, metric Metric) { lock c.histograms { mut entry := c.histograms[metric.str()] or { - buckets := c.buckets[metric.name] or { [] } - hist := &Histogram{ + hist := &FloatSeries{ metric: metric - buckets: buckets - bucket_counts: []u64{len: buckets.len, init: 0} + data: []f64{} } c.histograms[metric.str()] = hist hist } - entry.sum += value - entry.total_count += 1 - - mut i := entry.buckets.len - 1 - - for i >= 0 && value <= entry.buckets[i] { - entry.bucket_counts[i]++ - - i -= 1 - } + entry.data << value } } -pub fn (c &DefaultCollector) histogram_get(metric Metric) ?Histogram { +pub fn (c &DefaultCollector) histogram_get(metric Metric) ?[]f64 { return rlock c.histograms { entry := c.histograms[metric.str()] or { return none } // Return a clone of the data to prevent user from altering // internal structure - Histogram{ - metric: metric - total_count: entry.total_count - sum: entry.sum - buckets: entry.buckets.clone() - bucket_counts: entry.bucket_counts.clone() - } + entry.data.clone() } } pub fn (mut c DefaultCollector) gauge_add(value f64, metric Metric) { lock c.gauges { mut entry := c.gauges[metric.str()] or { - gauge := &Gauge{ + gauge := &FloatSeries{ metric: metric - data: 0.0 + data: [0.0] } c.gauges[metric.str()] = gauge @@ -153,16 +119,16 @@ pub fn (mut c DefaultCollector) gauge_add(value f64, metric Metric) { gauge } - entry.data += value + entry.data[0] += value } } pub fn (mut c DefaultCollector) gauge_sub(value f64, metric Metric) { lock c.gauges { mut entry := c.gauges[metric.str()] or { - gauge := &Gauge{ + gauge := &FloatSeries{ metric: metric - data: 0.0 + data: [0.0] } c.gauges[metric.str()] = gauge @@ -170,16 +136,16 @@ pub fn (mut c DefaultCollector) gauge_sub(value f64, metric Metric) { gauge } - entry.data -= value + entry.data[0] -= value } } pub fn (mut c DefaultCollector) gauge_set(value f64, metric Metric) { lock c.gauges { mut entry := c.gauges[metric.str()] or { - gauge := &Gauge{ + gauge := &FloatSeries{ metric: metric - data: 0.0 + data: [0.0] } c.gauges[metric.str()] = gauge @@ -187,7 +153,7 @@ pub fn (mut c DefaultCollector) gauge_set(value f64, metric Metric) { gauge } - entry.data = value + entry.data[0] = value } } @@ -195,7 +161,7 @@ pub fn (c &DefaultCollector) gauge_get(metric Metric) ?f64 { return rlock c.gauges { entry := c.gauges[metric.str()] or { return none } - entry.data + entry.data[0] } } diff --git a/collector_test.v b/collector_test.v index 0e415c3..a78521f 100644 --- a/collector_test.v +++ b/collector_test.v @@ -32,28 +32,10 @@ fn test_histogram() { mut m := new_default_collector() m.histogram_record(5.0, name: 'test') - - assert m.histogram_get(name: 'test')? == Histogram{ - metric: Metric{ - name: 'test' - } - total_count: 1 - sum: 5.0 - buckets: [] - bucket_counts: [] - } + assert m.histogram_get(name: 'test')? == [5.0] m.histogram_record(7.0, name: 'test') - - assert m.histogram_get(name: 'test')? == Histogram{ - metric: Metric{ - name: 'test' - } - total_count: 2 - sum: 12.0 - buckets: [] - bucket_counts: [] - } + assert m.histogram_get(name: 'test')? == [5.0, 7.0] // Test with labels metric := Metric{ @@ -61,26 +43,11 @@ fn test_histogram() { labels: [['hi', 'label']!, ['hi2', 'label2']!] } - m.histogram_buckets_set('test2', [10.0]) m.histogram_record(5.0, metric) - - assert m.histogram_get(metric)? == Histogram{ - metric: metric - total_count: 1 - sum: 5.0 - buckets: [10.0] - bucket_counts: [u64(1)] - } + assert m.histogram_get(metric)? == [5.0] m.histogram_record(7.0, metric) - - assert m.histogram_get(metric)? == Histogram{ - metric: metric - total_count: 2 - sum: 12.0 - buckets: [10.0] - bucket_counts: [u64(2)] - } + assert m.histogram_get(metric)? == [5.0, 7.0] } fn test_gauge_add() { diff --git a/metrics.v b/metrics.v index 7f54e4a..d5cd886 100644 --- a/metrics.v +++ b/metrics.v @@ -16,7 +16,7 @@ pub fn (m &Metric) str() string { pub interface MetricsCollector { counter_get(metric Metric) ?u64 counters() []Metric - histogram_get(metric Metric) ?Histogram + histogram_get(metric Metric) ?[]f64 histograms() []Metric gauge_get(metric Metric) ?f64 gauges() []Metric diff --git a/null.v b/null.v index 300a208..2b2d072 100644 --- a/null.v +++ b/null.v @@ -21,7 +21,7 @@ pub fn (c &NullCollector) counters() []Metric { pub fn (c &NullCollector) histogram_record(value f64, metric Metric) {} -pub fn (c &NullCollector) histogram_get(metric Metric) ?Histogram { +pub fn (c &NullCollector) histogram_get(metric Metric) ?[]f64 { return none } diff --git a/prometheus.v b/prometheus.v index b4606fe..93f41e4 100644 --- a/prometheus.v +++ b/prometheus.v @@ -2,15 +2,19 @@ module metrics import strings import io +import arrays pub struct PrometheusExporter { + buckets []f64 mut: prefix string collector &MetricsCollector = unsafe { nil } } -pub fn new_prometheus_exporter() PrometheusExporter { - return PrometheusExporter{} +pub fn new_prometheus_exporter(buckets []f64) PrometheusExporter { + return PrometheusExporter{ + buckets: buckets + } } pub fn (mut e PrometheusExporter) load(prefix string, collector &MetricsCollector) { @@ -57,25 +61,45 @@ pub fn (mut e PrometheusExporter) export_to_writer(mut writer io.Writer) ! { } for hist in e.collector.histograms() { - hist_data := e.collector.histogram_get(hist) or { return error("This can't happen.") } + 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('${e.serialize_metric(m)} $hist_data.total_count\n'.bytes())! + writer.write('${e.serialize_metric(m)} $total_count\n'.bytes())! m = Metric{ ...hist name: '${hist.name}_sum' } - writer.write('${e.serialize_metric(m)} $hist_data.sum\n'.bytes())! + writer.write('${e.serialize_metric(m)} $sum\n'.bytes())! mut le_labels := [][2]string{} le_labels.prepend(hist.labels) le_labels << ['le', '']! - for j, bucket in hist_data.buckets { + for j, bucket in e.buckets { le_labels[le_labels.len - 1][1] = bucket.str() m = Metric{ @@ -83,19 +107,17 @@ pub fn (mut e PrometheusExporter) export_to_writer(mut writer io.Writer) ! { labels: le_labels } - writer.write('${e.serialize_metric(m)} ${hist_data.bucket_counts[j]}\n'.bytes())! + writer.write('${e.serialize_metric(m)} ${bucket_counts[j]}\n'.bytes())! } // Always output the +Inf bucket le_labels[le_labels.len - 1][1] = '+Inf' - if hist_data.buckets.len > 0 { - m = Metric{ - name: '${hist.name}_bucket' - labels: le_labels - } - - writer.write('${e.serialize_metric(m)} $hist_data.total_count\n'.bytes())! + m = Metric{ + name: '${hist.name}_bucket' + labels: le_labels } + + writer.write('${e.serialize_metric(m)} $total_count\n'.bytes())! } } diff --git a/prometheus_test.v b/prometheus_test.v index de1bfe9..ac42a21 100644 --- a/prometheus_test.v +++ b/prometheus_test.v @@ -4,7 +4,7 @@ fn test_only_counters() { mut m := new_default_collector() m.counter_increment(name: 'test') - mut e := new_prometheus_exporter() + mut e := new_prometheus_exporter([]) e.load('hi_', m) assert e.export_to_string()! == 'hi_test 1\n' @@ -23,7 +23,7 @@ fn test_only_gauges() { mut m := new_default_collector() m.gauge_set(3.25, name: 'test') - mut e := new_prometheus_exporter() + mut e := new_prometheus_exporter([]) e.load('hi_', m) assert e.export_to_string()! == 'hi_test 3.25\n' @@ -40,11 +40,10 @@ fn test_only_gauges() { fn test_single_histogram() { mut m := new_default_collector() - m.histogram_buckets_set('test', [0.5, 5.0]) m.histogram_record(5.0, name: 'test') m.histogram_record(7.0, name: 'test') - mut e := new_prometheus_exporter() + mut e := new_prometheus_exporter([0.5, 5.0]) e.load('hi_', m) assert e.export_to_string()! == 'hi_test_count 2\nhi_test_sum 12.0\nhi_test_bucket{le="0.5"} 0\nhi_test_bucket{le="5.0"} 1\nhi_test_bucket{le="+Inf"} 2\n'