timeout context for fetching jitsi stats and a fetch error metric
This commit is contained in:
parent
5e5f992967
commit
ea3684a182
24
README.md
24
README.md
@ -3,4 +3,26 @@
|
|||||||
[](https://cloud.drone.io/xsteadfastx/jitsiexporter)
|
[](https://cloud.drone.io/xsteadfastx/jitsiexporter)
|
||||||
[](https://quay.io/repository/xsteadfastx/jitsiexporter)
|
[](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.")
|
ver := flag.Bool("version", false, "Prints version.")
|
||||||
url := flag.String("url", "", "URL of Jitsi Videobridge Colibri Stats.")
|
url := flag.String("url", "", "URL of Jitsi Videobridge Colibri Stats.")
|
||||||
debug := flag.Bool("debug", false, "Enable debug.")
|
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.")
|
port := flag.Int("port", 9700, "Port to listen on.")
|
||||||
host := flag.String("host", "localhost", "Host to listen on.")
|
host := flag.String("host", "localhost", "Host to listen on.")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
109
jitsiexporter.go
109
jitsiexporter.go
@ -1,6 +1,7 @@
|
|||||||
package jitsiexporter
|
package jitsiexporter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -20,17 +21,32 @@ type Metric struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Metrics struct {
|
type Metrics struct {
|
||||||
Metrics map[string]Metric
|
Metrics map[string]Metric
|
||||||
URL string
|
URL string
|
||||||
Stater Stater
|
Stater Stater
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
|
Errors prometheus.Counter
|
||||||
|
Interval time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Metrics) Update() {
|
func (m *Metrics) Update() error {
|
||||||
now := m.Stater.Now(m.URL)
|
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)
|
log.Debug(now)
|
||||||
|
|
||||||
m.mux.Lock()
|
|
||||||
for k, v := range now {
|
for k, v := range now {
|
||||||
fieldLogger := log.WithFields(log.Fields{"key": k})
|
fieldLogger := log.WithFields(log.Fields{"key": k})
|
||||||
|
|
||||||
@ -65,48 +81,99 @@ func (m *Metrics) Update() {
|
|||||||
continue
|
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 {
|
type Stater interface {
|
||||||
Now(url string) map[string]interface{}
|
Now(url string) (map[string]interface{}, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type colibri struct{}
|
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{})
|
s := make(map[string]interface{})
|
||||||
resp, err := http.Get(url) // nolint:gosec
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) // nolint:gomnd
|
||||||
|
|
||||||
if err != nil {
|
defer cancel()
|
||||||
log.Fatal(err)
|
|
||||||
|
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 {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
err = json.NewDecoder(resp.Body).Decode(&s)
|
err = json.NewDecoder(resp.Body).Decode(&s)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func collect(m *Metrics) {
|
func collect(m *Metrics) {
|
||||||
for {
|
for {
|
||||||
m.Update()
|
err := m.Update()
|
||||||
time.Sleep(30 * time.Second) // nolint:gomnd
|
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) {
|
func Serve(url string, debug bool, interval time.Duration, port int, host string) {
|
||||||
s := colibri{}
|
s := colibri{}
|
||||||
|
e := prometheus.NewCounter(prometheus.CounterOpts{Name: "jitsi_fetch_errors"})
|
||||||
metrics := &Metrics{
|
metrics := &Metrics{
|
||||||
URL: url,
|
URL: url,
|
||||||
Stater: s,
|
Stater: s,
|
||||||
Metrics: make(map[string]Metric),
|
Metrics: make(map[string]Metric),
|
||||||
|
Errors: e,
|
||||||
|
Interval: interval,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prometheus.MustRegister(e)
|
||||||
|
|
||||||
if debug {
|
if debug {
|
||||||
log.SetLevel(log.DebugLevel)
|
log.SetLevel(log.DebugLevel)
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ func TestUpdate(t *testing.T) {
|
|||||||
s["bar"] = 1 // nolint:gomnd
|
s["bar"] = 1 // nolint:gomnd
|
||||||
s["zonk"] = float64(1) // nolint:gomnd
|
s["zonk"] = float64(1) // nolint:gomnd
|
||||||
mockStater := &MockStater{}
|
mockStater := &MockStater{}
|
||||||
mockStater.On("Now", "http://foo.tld").Return(s)
|
mockStater.On("Now", "http://foo.tld").Return(s, nil)
|
||||||
|
|
||||||
m := &Metrics{
|
m := &Metrics{
|
||||||
URL: "http://foo.tld",
|
URL: "http://foo.tld",
|
||||||
|
@ -10,7 +10,7 @@ type MockStater struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Now provides a mock function with given fields: url
|
// 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)
|
ret := _m.Called(url)
|
||||||
|
|
||||||
var r0 map[string]interface{}
|
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…
Reference in New Issue
Block a user