timeout context for fetching jitsi stats and a fetch error metric

master
Marvin Steadfast 4 years ago
parent 5e5f992967
commit ea3684a182

@ -3,4 +3,26 @@
[![Build Status](https://cloud.drone.io/api/badges/xsteadfastx/jitsiexporter/status.svg)](https://cloud.drone.io/xsteadfastx/jitsiexporter)
[![Docker Repository on Quay](https://quay.io/repository/xsteadfastx/jitsiexporter/status "Docker Repository on Quay")](https://quay.io/repository/xsteadfastx/jitsiexporter)
a jitsi meet prometheus exporter
A Jitsi meet prometheus exporter.
Usage of ./jitsiexporter_linux_amd64:
-debug
Enable debug.
-host string
Host to listen on. (default "localhost")
-interval duration
Seconds to wait before scraping. (default 30s)
-port int
Port to listen on. (default 9700)
-url string
URL of Jitsi Videobridge Colibri Stats.
-version
Prints version.
## Usage
For a docker based setup, you can use the docker image [quay.io/xsteadfastx/jitsiexporter](https://quay.io/repository/xsteadfastx/jitsiexporter).
1. [Enable](https://github.com/jitsi/jitsi-videobridge/blob/master/doc/statistics.md) `/colibri/stats` for the Jitsi videobridge. When you use the Jitsi docker setup use environment variable `JVB_ENABLE_APIS=rest,colibri`.
2. Be sure that the exporter and the videobridge API can communicate. In the docker Jitsi setup: Add the `jitsiexporter` to the `jitsi-meet_meet.jitsi`-network. The url would be `http://jitsi-meet_jvb_1:8080`.
3. A failed scrape metric is exporter as `jitsi_fetch_errors`.

@ -15,7 +15,7 @@ func main() {
ver := flag.Bool("version", false, "Prints version.")
url := flag.String("url", "", "URL of Jitsi Videobridge Colibri Stats.")
debug := flag.Bool("debug", false, "Enable debug.")
interval := flag.Duration("interval", 10*time.Second, "Seconds to wait before scraping.") // nolint: gomnd
interval := flag.Duration("interval", 30*time.Second, "Seconds to wait before scraping.") // nolint: gomnd
port := flag.Int("port", 9700, "Port to listen on.")
host := flag.String("host", "localhost", "Host to listen on.")
flag.Parse()

@ -1,6 +1,7 @@
package jitsiexporter
import (
"context"
"encoding/json"
"fmt"
"net/http"
@ -20,17 +21,32 @@ type Metric struct {
}
type Metrics struct {
Metrics map[string]Metric
URL string
Stater Stater
mux sync.Mutex
Metrics map[string]Metric
URL string
Stater Stater
mux sync.Mutex
Errors prometheus.Counter
Interval time.Duration
}
func (m *Metrics) Update() {
now := m.Stater.Now(m.URL)
func (m *Metrics) Update() error {
m.mux.Lock()
defer m.mux.Unlock()
now, err := m.Stater.Now(m.URL)
if err != nil {
m.Errors.Inc()
for _, i := range m.Metrics {
prometheus.Unregister(i.Gauge)
}
return err
}
log.Debug(now)
m.mux.Lock()
for k, v := range now {
fieldLogger := log.WithFields(log.Fields{"key": k})
@ -65,48 +81,99 @@ func (m *Metrics) Update() {
continue
}
}
m.mux.Unlock()
return nil
}
type Response struct {
Resp *http.Response
Error error
}
func get(ctx context.Context, url string, resp chan Response) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
resp <- Response{Resp: nil, Error: err}
return
}
client := http.DefaultClient
res, err := client.Do(req.WithContext(ctx)) // nolint:bodyclose
if err != nil {
resp <- Response{Resp: nil, Error: err}
}
resp <- Response{Resp: res, Error: nil}
}
type Stater interface {
Now(url string) map[string]interface{}
Now(url string) (map[string]interface{}, error)
}
type colibri struct{}
func (c colibri) Now(url string) map[string]interface{} {
func (c colibri) Now(url string) (map[string]interface{}, error) {
s := make(map[string]interface{})
resp, err := http.Get(url) // nolint:gosec
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) // nolint:gomnd
defer cancel()
res := make(chan Response)
var resp *http.Response
var err error
go get(ctx, url, res)
select {
case <-ctx.Done():
return nil, ctx.Err()
case r := <-res:
err = r.Error
resp = r.Resp
defer resp.Body.Close()
}
if err != nil {
log.Fatal(err)
return nil, err
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&s)
if err != nil {
log.Fatal(err)
return nil, err
}
return s
return s, nil
}
func collect(m *Metrics) {
for {
m.Update()
time.Sleep(30 * time.Second) // nolint:gomnd
err := m.Update()
if err != nil {
log.Error(err)
}
time.Sleep(m.Interval) // nolint:gomnd
}
}
func Serve(url string, debug bool, interval time.Duration, port int, host string) {
s := colibri{}
e := prometheus.NewCounter(prometheus.CounterOpts{Name: "jitsi_fetch_errors"})
metrics := &Metrics{
URL: url,
Stater: s,
Metrics: make(map[string]Metric),
URL: url,
Stater: s,
Metrics: make(map[string]Metric),
Errors: e,
Interval: interval,
}
prometheus.MustRegister(e)
if debug {
log.SetLevel(log.DebugLevel)
}

@ -16,7 +16,7 @@ func TestUpdate(t *testing.T) {
s["bar"] = 1 // nolint:gomnd
s["zonk"] = float64(1) // nolint:gomnd
mockStater := &MockStater{}
mockStater.On("Now", "http://foo.tld").Return(s)
mockStater.On("Now", "http://foo.tld").Return(s, nil)
m := &Metrics{
URL: "http://foo.tld",

@ -10,7 +10,7 @@ type MockStater struct {
}
// Now provides a mock function with given fields: url
func (_m *MockStater) Now(url string) map[string]interface{} {
func (_m *MockStater) Now(url string) (map[string]interface{}, error) {
ret := _m.Called(url)
var r0 map[string]interface{}
@ -22,5 +22,12 @@ func (_m *MockStater) Now(url string) map[string]interface{} {
}
}
return r0
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(url)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

Loading…
Cancel
Save