diff --git a/jitsiexporter.go b/jitsiexporter.go index 927a127..fa44a50 100644 --- a/jitsiexporter.go +++ b/jitsiexporter.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "net/http" - "sort" "sync" "time" @@ -22,7 +21,6 @@ type Metric struct { type Metrics struct { Metrics map[string]Metric - Exclude []string URL string Stater Stater mux sync.Mutex @@ -36,31 +34,36 @@ func (m *Metrics) Update() { for k, v := range now { fieldLogger := log.WithFields(log.Fields{"key": k}) - if sort.SearchStrings(m.Exclude, k) != len(m.Exclude) { - fieldLogger.Info("exclude") + // skipping anything else than float64. + switch t := v.(type) { + case float64: + fieldLogger.Debugf("found '%v'", t) + + name := fmt.Sprintf("jitsi_%s", k) + if _, ok := m.Metrics[name]; !ok { + fieldLogger.Info("creating and registering metric") + + m.Metrics[name] = Metric{ + Name: name, + Gauge: prometheus.NewGauge( + prometheus.GaugeOpts{ + Name: name, + }, + ), + } + fieldLogger.Debugf("%+v", m.Metrics[name]) + prometheus.MustRegister(m.Metrics[name].Gauge) + } - continue - } + value := v.(float64) + fieldLogger.Infof("set to %f", value) + m.Metrics[name].Gauge.Set(value) + default: + fieldLogger.Debugf("found %v", t) + fieldLogger.Info("skipping") - name := fmt.Sprintf("jitsi_%s", k) - if _, ok := m.Metrics[name]; !ok { - fieldLogger.Info("creating and registerting metric") - - m.Metrics[name] = Metric{ - Name: name, - Gauge: prometheus.NewGauge( - prometheus.GaugeOpts{ - Name: name, - }, - ), - } - fieldLogger.Debugf("%+v", m.Metrics[name]) - prometheus.MustRegister(m.Metrics[name].Gauge) + continue } - - value := v.(float64) - fieldLogger.Infof("set to %f", value) - m.Metrics[name].Gauge.Set(value) } m.mux.Unlock() } @@ -99,11 +102,6 @@ func collect(m *Metrics) { func Serve(url string) { s := colibri{} metrics := &Metrics{ - Exclude: []string{ - "conference_sizes", - "current_timestamp", - "graceful_shutdown", - }, URL: url, Stater: s, Metrics: make(map[string]Metric), diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..a32ed00 --- /dev/null +++ b/main_test.go @@ -0,0 +1,33 @@ +package jitsiexporter + +import ( + "testing" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" + "github.com/stretchr/testify/assert" +) + +func TestUpdate(t *testing.T) { + assert := assert.New(t) + + s := make(map[string]interface{}) + s["foo"] = "foo" + s["bar"] = 1 + s["zonk"] = float64(1) + mockStater := &MockStater{} + mockStater.On("Now", "http://foo.tld").Return(s) + + m := &Metrics{ + URL: "http://foo.tld", + Metrics: make(map[string]Metric), + Stater: mockStater, + } + + m.Update() + + assert.Equal(testutil.ToFloat64(m.Metrics["jitsi_zonk"].Gauge), float64(1)) + assert.Equal(m.Metrics["jitsi_foo"], Metric{Name: "", Gauge: prometheus.Gauge(nil)}) + assert.Equal(m.Metrics["jitsi_bar"], Metric{Name: "", Gauge: prometheus.Gauge(nil)}) + assert.Equal(len(m.Metrics), 1) +} diff --git a/vendor/github.com/prometheus/client_golang/prometheus/testutil/testutil.go b/vendor/github.com/prometheus/client_golang/prometheus/testutil/testutil.go new file mode 100644 index 0000000..cb09839 --- /dev/null +++ b/vendor/github.com/prometheus/client_golang/prometheus/testutil/testutil.go @@ -0,0 +1,214 @@ +// Copyright 2018 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package testutil provides helpers to test code using the prometheus package +// of client_golang. +// +// While writing unit tests to verify correct instrumentation of your code, it's +// a common mistake to mostly test the instrumentation library instead of your +// own code. Rather than verifying that a prometheus.Counter's value has changed +// as expected or that it shows up in the exposition after registration, it is +// in general more robust and more faithful to the concept of unit tests to use +// mock implementations of the prometheus.Counter and prometheus.Registerer +// interfaces that simply assert that the Add or Register methods have been +// called with the expected arguments. However, this might be overkill in simple +// scenarios. The ToFloat64 function is provided for simple inspection of a +// single-value metric, but it has to be used with caution. +// +// End-to-end tests to verify all or larger parts of the metrics exposition can +// be implemented with the CollectAndCompare or GatherAndCompare functions. The +// most appropriate use is not so much testing instrumentation of your code, but +// testing custom prometheus.Collector implementations and in particular whole +// exporters, i.e. programs that retrieve telemetry data from a 3rd party source +// and convert it into Prometheus metrics. +package testutil + +import ( + "bytes" + "fmt" + "io" + + "github.com/prometheus/common/expfmt" + + dto "github.com/prometheus/client_model/go" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/internal" +) + +// ToFloat64 collects all Metrics from the provided Collector. It expects that +// this results in exactly one Metric being collected, which must be a Gauge, +// Counter, or Untyped. In all other cases, ToFloat64 panics. ToFloat64 returns +// the value of the collected Metric. +// +// The Collector provided is typically a simple instance of Gauge or Counter, or +// – less commonly – a GaugeVec or CounterVec with exactly one element. But any +// Collector fulfilling the prerequisites described above will do. +// +// Use this function with caution. It is computationally very expensive and thus +// not suited at all to read values from Metrics in regular code. This is really +// only for testing purposes, and even for testing, other approaches are often +// more appropriate (see this package's documentation). +// +// A clear anti-pattern would be to use a metric type from the prometheus +// package to track values that are also needed for something else than the +// exposition of Prometheus metrics. For example, you would like to track the +// number of items in a queue because your code should reject queuing further +// items if a certain limit is reached. It is tempting to track the number of +// items in a prometheus.Gauge, as it is then easily available as a metric for +// exposition, too. However, then you would need to call ToFloat64 in your +// regular code, potentially quite often. The recommended way is to track the +// number of items conventionally (in the way you would have done it without +// considering Prometheus metrics) and then expose the number with a +// prometheus.GaugeFunc. +func ToFloat64(c prometheus.Collector) float64 { + var ( + m prometheus.Metric + mCount int + mChan = make(chan prometheus.Metric) + done = make(chan struct{}) + ) + + go func() { + for m = range mChan { + mCount++ + } + close(done) + }() + + c.Collect(mChan) + close(mChan) + <-done + + if mCount != 1 { + panic(fmt.Errorf("collected %d metrics instead of exactly 1", mCount)) + } + + pb := &dto.Metric{} + m.Write(pb) + if pb.Gauge != nil { + return pb.Gauge.GetValue() + } + if pb.Counter != nil { + return pb.Counter.GetValue() + } + if pb.Untyped != nil { + return pb.Untyped.GetValue() + } + panic(fmt.Errorf("collected a non-gauge/counter/untyped metric: %s", pb)) +} + +// CollectAndCount collects all Metrics from the provided Collector and returns their number. +// +// This can be used to assert the number of metrics collected by a given collector after certain operations. +// +// This function is only for testing purposes, and even for testing, other approaches +// are often more appropriate (see this package's documentation). +func CollectAndCount(c prometheus.Collector) int { + var ( + mCount int + mChan = make(chan prometheus.Metric) + done = make(chan struct{}) + ) + + go func() { + for range mChan { + mCount++ + } + close(done) + }() + + c.Collect(mChan) + close(mChan) + <-done + + return mCount +} + +// CollectAndCompare registers the provided Collector with a newly created +// pedantic Registry. It then does the same as GatherAndCompare, gathering the +// metrics from the pedantic Registry. +func CollectAndCompare(c prometheus.Collector, expected io.Reader, metricNames ...string) error { + reg := prometheus.NewPedanticRegistry() + if err := reg.Register(c); err != nil { + return fmt.Errorf("registering collector failed: %s", err) + } + return GatherAndCompare(reg, expected, metricNames...) +} + +// GatherAndCompare gathers all metrics from the provided Gatherer and compares +// it to an expected output read from the provided Reader in the Prometheus text +// exposition format. If any metricNames are provided, only metrics with those +// names are compared. +func GatherAndCompare(g prometheus.Gatherer, expected io.Reader, metricNames ...string) error { + got, err := g.Gather() + if err != nil { + return fmt.Errorf("gathering metrics failed: %s", err) + } + if metricNames != nil { + got = filterMetrics(got, metricNames) + } + var tp expfmt.TextParser + wantRaw, err := tp.TextToMetricFamilies(expected) + if err != nil { + return fmt.Errorf("parsing expected metrics failed: %s", err) + } + want := internal.NormalizeMetricFamilies(wantRaw) + + return compare(got, want) +} + +// compare encodes both provided slices of metric families into the text format, +// compares their string message, and returns an error if they do not match. +// The error contains the encoded text of both the desired and the actual +// result. +func compare(got, want []*dto.MetricFamily) error { + var gotBuf, wantBuf bytes.Buffer + enc := expfmt.NewEncoder(&gotBuf, expfmt.FmtText) + for _, mf := range got { + if err := enc.Encode(mf); err != nil { + return fmt.Errorf("encoding gathered metrics failed: %s", err) + } + } + enc = expfmt.NewEncoder(&wantBuf, expfmt.FmtText) + for _, mf := range want { + if err := enc.Encode(mf); err != nil { + return fmt.Errorf("encoding expected metrics failed: %s", err) + } + } + + if wantBuf.String() != gotBuf.String() { + return fmt.Errorf(` +metric output does not match expectation; want: + +%s +got: + +%s`, wantBuf.String(), gotBuf.String()) + + } + return nil +} + +func filterMetrics(metrics []*dto.MetricFamily, names []string) []*dto.MetricFamily { + var filtered []*dto.MetricFamily + for _, m := range metrics { + for _, name := range names { + if m.GetName() == name { + filtered = append(filtered, m) + break + } + } + } + return filtered +} diff --git a/vendor/modules.txt b/vendor/modules.txt index d43c41b..4ff2f39 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -21,6 +21,7 @@ github.com/pmezard/go-difflib/difflib github.com/prometheus/client_golang/prometheus github.com/prometheus/client_golang/prometheus/internal github.com/prometheus/client_golang/prometheus/promhttp +github.com/prometheus/client_golang/prometheus/testutil # github.com/prometheus/client_model v0.2.0 github.com/prometheus/client_model/go # github.com/prometheus/common v0.9.1