Compare commits
No commits in common. "main" and "v0.0.1" have entirely different histories.
|
@ -1,9 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
result="$(gofmt -s -l . | grep -v '^vendor/' )"
|
|
||||||
if [ -n "$result" ]; then
|
|
||||||
echo "Go code is not formatted, run 'gofmt -s -w .'" >&2
|
|
||||||
echo "$result"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
# checks if every desired package has test files
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
source_re = re.compile(".*\.go")
|
|
||||||
test_re = re.compile(".*_test\.go")
|
|
||||||
missing = False
|
|
||||||
|
|
||||||
for root, dirs, files in os.walk("."):
|
|
||||||
# ignore some paths
|
|
||||||
if root == "." or root.startswith("./vendor") or root.startswith("./.") or root.startswith("./docs"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# source files but not test files?
|
|
||||||
if len([f for f in files if source_re.match(f)]) > 0 and len([f for f in files if test_re.match(f)]) == 0:
|
|
||||||
print("no test files for {}".format(root))
|
|
||||||
missing = True
|
|
||||||
|
|
||||||
if missing:
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
print("every package has test files")
|
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
image: "golang:latest"
|
|
||||||
|
|
||||||
variables:
|
|
||||||
# Tell 'docker:dind' to enable TLS (recommended)
|
|
||||||
# and generate certificates in the specified directory.
|
|
||||||
DOCKER_TLS_CERTDIR: "/certs"
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- test
|
|
||||||
- build
|
|
||||||
|
|
||||||
test-lint:
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- go install github.com/client9/misspell/cmd/misspell@latest
|
|
||||||
- find . -type f -not -path "./webroot/assets" | grep -v "models/.*_testdata.*.go" | xargs misspell -error
|
|
||||||
- ./.ci/check-gofmt
|
|
||||||
- ./.ci/check-testfiles
|
|
||||||
|
|
||||||
build-linux:
|
|
||||||
stage: build
|
|
||||||
except:
|
|
||||||
- tags
|
|
||||||
- master
|
|
||||||
- main
|
|
||||||
script:
|
|
||||||
# build app with version
|
|
||||||
- go install
|
|
||||||
- mv "/go/bin/$CI_PROJECT_NAME" "$CI_PROJECT_DIR/$CI_PROJECT_NAME"
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- config_example.toml
|
|
||||||
- "$CI_PROJECT_NAME"
|
|
||||||
|
|
||||||
build-docker-latest:
|
|
||||||
stage: build
|
|
||||||
image: docker:latest
|
|
||||||
services:
|
|
||||||
- docker:dind
|
|
||||||
only:
|
|
||||||
- main
|
|
||||||
- master
|
|
||||||
script:
|
|
||||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
|
||||||
- docker build --build-arg VERSION=$CI_COMMIT_TAG
|
|
||||||
-t $CI_REGISTRY_IMAGE:latest
|
|
||||||
.
|
|
||||||
- docker push $CI_REGISTRY_IMAGE:latest
|
|
||||||
|
|
||||||
build-docker-release:
|
|
||||||
stage: build
|
|
||||||
image: docker:latest
|
|
||||||
services:
|
|
||||||
- docker:dind
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
script:
|
|
||||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
|
||||||
- docker build --build-arg VERSION=$CI_COMMIT_TAG
|
|
||||||
-t $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
|
|
||||||
-t $CI_REGISTRY_IMAGE:${CI_COMMIT_TAG%\.[0-9]*}
|
|
||||||
-t $CI_REGISTRY_IMAGE:${CI_COMMIT_TAG%\.[0-9]*\.[0-9]*}
|
|
||||||
.
|
|
||||||
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
|
|
||||||
- docker push $CI_REGISTRY_IMAGE:${CI_COMMIT_TAG%\.[0-9]*}
|
|
||||||
- docker push $CI_REGISTRY_IMAGE:${CI_COMMIT_TAG%\.[0-9]*\.[0-9]*}
|
|
||||||
|
|
||||||
build-release:
|
|
||||||
stage: build
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
script:
|
|
||||||
# build app with version
|
|
||||||
- go install
|
|
||||||
- mv "/go/bin/$CI_PROJECT_NAME" "$CI_PROJECT_DIR/$CI_PROJECT_NAME"
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- config_example.toml
|
|
||||||
- "$CI_PROJECT_NAME"
|
|
||||||
expire_in: never
|
|
|
@ -1,20 +0,0 @@
|
||||||
##
|
|
||||||
# Compile application
|
|
||||||
##
|
|
||||||
FROM docker.io/library/golang:alpine AS build-env
|
|
||||||
WORKDIR /app
|
|
||||||
COPY . .
|
|
||||||
# ge dependencies
|
|
||||||
RUN go mod tidy
|
|
||||||
# build binary
|
|
||||||
RUN CGO_ENABLED=0 go build -ldflags="-w -s" -o oven-exporter
|
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
# Build Image
|
|
||||||
##
|
|
||||||
FROM scratch
|
|
||||||
COPY --from=build-env ["/etc/ssl/cert.pem", "/etc/ssl/certs/ca-certificates.crt"]
|
|
||||||
COPY --from=build-env /app/oven-exporter /oven-exporter
|
|
||||||
WORKDIR /
|
|
||||||
ENTRYPOINT ["/oven-exporter", "-c", ""]
|
|
59
README.adoc
59
README.adoc
|
@ -1,59 +0,0 @@
|
||||||
== Oven-Exporter
|
|
||||||
|
|
||||||
An Prometheus Exporter for OvenMediaEngine
|
|
||||||
|
|
||||||
(it provides also a small API-Client for OvenMediaEngine) Be welcome to improve it.
|
|
||||||
|
|
||||||
=== Configure OvenMediaEngine
|
|
||||||
|
|
||||||
This Exporter use the REST-API of OvenMediaEngine, to setting it up, that a look in there Documentation https://airensoft.gitbook.io/ovenmediaengine/rest-api[OvenMediaEngine REST-API].
|
|
||||||
|
|
||||||
=== Setup Exporter
|
|
||||||
|
|
||||||
==== Compile
|
|
||||||
|
|
||||||
Install https://golang.org/doc/install[golang].
|
|
||||||
|
|
||||||
Run: `go install -v codeberg.org/Mediathek/oven-exporter@latest`
|
|
||||||
|
|
||||||
==== Configuration
|
|
||||||
|
|
||||||
Read comments in config_example.toml for more information.
|
|
||||||
|
|
||||||
Maybe a good place to store this file is: `/etc/ovenmediaengine/exporter.conf`
|
|
||||||
|
|
||||||
OR use env variables:
|
|
||||||
|
|
||||||
....
|
|
||||||
OVEN_E_LISTEN=:8080
|
|
||||||
OVEN_E_API__URL=http://1.2.3.4:8081
|
|
||||||
OVEN_E_API__TOKEN=ome-access-token
|
|
||||||
OVEN_E_API__DEFAULT_VHOST=
|
|
||||||
OVEN_E_API__DEFAULT_APP=
|
|
||||||
....
|
|
||||||
|
|
||||||
(File read could be disabled by call `oven-exporter -c ''`
|
|
||||||
|
|
||||||
==== Startup
|
|
||||||
|
|
||||||
Create a systemd.service file e.g. under `/etc/systemd/system/oven-exporter.service` with maybe a content like this:
|
|
||||||
|
|
||||||
[source,ini]
|
|
||||||
----
|
|
||||||
[Unit]
|
|
||||||
Description = Prometheus exporter for OvenMediaEngine
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
ExecStart=/usr/local/bin/oven-exporter -c /etc/ovenmediaengine/exporter.conf
|
|
||||||
Restart=always
|
|
||||||
RestartSec=5s
|
|
||||||
Environment=PATH=/usr/bin:/usr/local/bin
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
----
|
|
||||||
|
|
||||||
PS: maybe you need to adjust the binary path and configuration path.
|
|
||||||
|
|
||||||
Start and enable on boot: `systemctl enable --now oven-exporter.service`
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Oven-Exporter
|
||||||
|
An Prometheus Exporter for OvenMediaEngine
|
||||||
|
|
||||||
|
(it provides also a small API-Client for OvenMediaEngine)
|
||||||
|
Be welcome to improve it.
|
|
@ -4,16 +4,14 @@ import "encoding/base64"
|
||||||
|
|
||||||
// A Client for the API
|
// A Client for the API
|
||||||
type Client struct {
|
type Client struct {
|
||||||
Token string `config:"token"`
|
Token string `toml:"token"`
|
||||||
URL string `config:"url"`
|
Host string `toml:"host"`
|
||||||
DefaultVHost string `config:"default_vhost"`
|
|
||||||
DefaultApp string `config:"default_app"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New Client from host and token
|
// New Client from host and token
|
||||||
func New(url, token string) *Client {
|
func New(host, token string) *Client {
|
||||||
c := &Client{
|
c := &Client{
|
||||||
URL: url,
|
Host: host,
|
||||||
}
|
}
|
||||||
c.SetToken(token)
|
c.SetToken(token)
|
||||||
return c
|
return c
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
package api
|
|
|
@ -1,33 +1,18 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Request to API and unmarshal result
|
// Request to API and unmarshal result
|
||||||
func (c *Client) Request(method, url string, body, value interface{}) error {
|
func (c *Client) Request(url string, value interface{}) error {
|
||||||
netClient := &http.Client{
|
netClient := &http.Client{
|
||||||
Timeout: time.Second * 20,
|
Timeout: time.Second * 20,
|
||||||
}
|
}
|
||||||
var jsonBody io.Reader
|
req, err := http.NewRequest(http.MethodGet, c.Host+url, nil)
|
||||||
if body != nil {
|
|
||||||
if strBody, ok := body.(string); ok {
|
|
||||||
jsonBody = strings.NewReader(strBody)
|
|
||||||
} else {
|
|
||||||
jsonBodyArray, err := json.Marshal(body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
jsonBody = bytes.NewBuffer(jsonBodyArray)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
req, err := http.NewRequest(method, c.URL+url, jsonBody)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -17,42 +16,29 @@ type ResponseList struct {
|
||||||
Data []string `json:"response,omitempty"`
|
Data []string `json:"response,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestListVHosts to get list of vhosts
|
|
||||||
func (c *Client) RequestListVHosts() (*ResponseList, error) {
|
func (c *Client) RequestListVHosts() (*ResponseList, error) {
|
||||||
req := ResponseList{}
|
req := ResponseList{}
|
||||||
url := fmt.Sprintf(URLRequestListVHost)
|
url := fmt.Sprintf(URLRequestListVHost)
|
||||||
if err := c.Request(http.MethodGet, url, nil, &req); err != nil {
|
if err := c.Request(url, &req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &req, nil
|
return &req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestListApps to get list of apps on given vhost
|
|
||||||
func (c *Client) RequestListApps(vhost string) (*ResponseList, error) {
|
func (c *Client) RequestListApps(vhost string) (*ResponseList, error) {
|
||||||
req := ResponseList{}
|
req := ResponseList{}
|
||||||
url := fmt.Sprintf(URLRequestListApp, vhost)
|
url := fmt.Sprintf(URLRequestListApp, vhost)
|
||||||
if err := c.Request(http.MethodGet, url, nil, &req); err != nil {
|
if err := c.Request(url, &req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &req, nil
|
return &req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestDefaultListApps to get list of apps on default vhost
|
|
||||||
func (c *Client) RequestDefaultListApps() (*ResponseList, error) {
|
|
||||||
return c.RequestListApps(c.DefaultVHost)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestDefaultListStreams to get list of streams on given vhost and app
|
|
||||||
func (c *Client) RequestListStreams(vhost, app string) (*ResponseList, error) {
|
func (c *Client) RequestListStreams(vhost, app string) (*ResponseList, error) {
|
||||||
req := ResponseList{}
|
req := ResponseList{}
|
||||||
url := fmt.Sprintf(URLRequestListStream, vhost, app)
|
url := fmt.Sprintf(URLRequestListStream, vhost, app)
|
||||||
if err := c.Request(http.MethodGet, url, nil, &req); err != nil {
|
if err := c.Request(url, &req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &req, nil
|
return &req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestDefaultListStreams to get list of streams on default vhost and app
|
|
||||||
func (c *Client) RequestDefaultListStreams() (*ResponseList, error) {
|
|
||||||
return c.RequestListStreams(c.DefaultVHost, c.DefaultApp)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,106 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// API URLS for Push
|
|
||||||
const (
|
|
||||||
URLRequestPushStatus = "/v1/vhosts/%s/apps/%s:pushes"
|
|
||||||
URLRequestPushStart = "/v1/vhosts/%s/apps/%s:startPush"
|
|
||||||
URLRequestPushStop = "/v1/vhosts/%s/apps/%s:stopPush"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ResponsePushStatus JSON Message with Push status data
|
|
||||||
type ResponsePushStatus struct {
|
|
||||||
Message string `json:"message"`
|
|
||||||
StatusCode int `json:"statusCode"`
|
|
||||||
Data []*ResponsePushData `json:"response,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResponsePushData one push configuration
|
|
||||||
type ResponsePushData struct {
|
|
||||||
VHost string `json:"vhost" example:"default"`
|
|
||||||
App string `json:"app" example:"live"`
|
|
||||||
ID string `json:"id" example:"youtube"`
|
|
||||||
Stream *ResponsePushDataStream `json:"stream"`
|
|
||||||
State string `json:"state" example:"ready"`
|
|
||||||
Protocol string `json:"protocol" example:"rtmp"`
|
|
||||||
URL string `json:"url" example:"rtmp://a.rtmp.youtube.com/live2"`
|
|
||||||
StreamKey string `json:"streamKey" example:"SUPERSECRET"`
|
|
||||||
// - timestamp - time.TIme has problem with nanosecond in JSON
|
|
||||||
CreatedTime string `json:"createdTime" example:"2021-07-19T23:13:12.162+0200"`
|
|
||||||
FinishedTime string `json:"finishedTime" example:"2021-07-19T23:23:27.274+0200"`
|
|
||||||
StartTime string `json:"startTime" example:"2021-07-19T23:23:27.077+0200"`
|
|
||||||
// - coonnections
|
|
||||||
Sequence int `json:"sequence" example:"1"`
|
|
||||||
// - traffic
|
|
||||||
SentBytes uint64 `json:"sentBytes" example:"0"`
|
|
||||||
SentTime uint64 `json:"sentTime" example:"0"`
|
|
||||||
TotalSentBytes uint64 `json:"totalsentBytes" example:"356233652"`
|
|
||||||
TotalSentTime uint64 `json:"totalsentTime" example:"933808"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResponsePushStart one push configuration
|
|
||||||
type ResponsePushStart struct {
|
|
||||||
ID string `json:"id" example:"youtube"`
|
|
||||||
Stream *ResponsePushDataStream `json:"stream"`
|
|
||||||
Protocol string `json:"protocol" example:"rtmp"`
|
|
||||||
URL string `json:"url" example:"rtmp://a.rtmp.youtube.com/live2"`
|
|
||||||
StreamKey string `json:"streamKey" example:"SUPERSECRET"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResponsePushDataStream of data of stream
|
|
||||||
type ResponsePushDataStream struct {
|
|
||||||
Name string `json:"name" example:"..."`
|
|
||||||
Tracks []int `json:"tracks" example:"[]"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestPushStatus to get list of pushes and his configuration
|
|
||||||
func (c *Client) RequestPushStatus(vhost, app string) (*ResponsePushStatus, error) {
|
|
||||||
req := ResponsePushStatus{}
|
|
||||||
url := fmt.Sprintf(URLRequestPushStatus, vhost, app)
|
|
||||||
if err := c.Request(http.MethodGet, url, nil, &req); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestPushStatusDefault to get list of pushes and his configuration for default vhost and app
|
|
||||||
func (c *Client) RequestPushStatusDefault() (*ResponsePushStatus, error) {
|
|
||||||
return c.RequestPushStatus(c.DefaultVHost, c.DefaultApp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartPush to delete an push
|
|
||||||
func (c *Client) StartPush(vhost, app string, data *ResponsePushStart) (*ResponsePushStatus, error) {
|
|
||||||
req := ResponsePushStatus{}
|
|
||||||
url := fmt.Sprintf(URLRequestPushStart, vhost, app)
|
|
||||||
if err := c.Request(http.MethodPost, url, &data, &req); err != nil {
|
|
||||||
return &req, err
|
|
||||||
}
|
|
||||||
return &req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartPushDefault to delete an push and on default vhost and app
|
|
||||||
func (c *Client) StartPushDefault(data *ResponsePushStart) (*ResponsePushStatus, error) {
|
|
||||||
return c.StartPush(c.DefaultVHost, c.DefaultApp, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeletePush to delete an push
|
|
||||||
func (c *Client) DeletePush(vhost, app, id string) error {
|
|
||||||
type idJSON struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
}
|
|
||||||
url := fmt.Sprintf(URLRequestPushStop, vhost, app)
|
|
||||||
data := idJSON{ID: id}
|
|
||||||
if err := c.Request(http.MethodPost, url, &data, nil); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeletePushDefault to delete an push and on default vhost and app
|
|
||||||
func (c *Client) DeletePushDefault(id string) error {
|
|
||||||
return c.DeletePush(c.DefaultVHost, c.DefaultApp, id)
|
|
||||||
}
|
|
|
@ -2,7 +2,6 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -35,7 +34,7 @@ type ResponseStatsData struct {
|
||||||
func (c *Client) RequestStatsVHost(vhost string) (*ResponseStats, error) {
|
func (c *Client) RequestStatsVHost(vhost string) (*ResponseStats, error) {
|
||||||
req := ResponseStats{}
|
req := ResponseStats{}
|
||||||
url := fmt.Sprintf(URLRequestStatsVHost, vhost)
|
url := fmt.Sprintf(URLRequestStatsVHost, vhost)
|
||||||
if err := c.Request(http.MethodGet, url, nil, &req); err != nil {
|
if err := c.Request(url, &req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &req, nil
|
return &req, nil
|
||||||
|
@ -44,7 +43,7 @@ func (c *Client) RequestStatsVHost(vhost string) (*ResponseStats, error) {
|
||||||
func (c *Client) RequestStatsApp(vhost, app string) (*ResponseStats, error) {
|
func (c *Client) RequestStatsApp(vhost, app string) (*ResponseStats, error) {
|
||||||
req := ResponseStats{}
|
req := ResponseStats{}
|
||||||
url := fmt.Sprintf(URLRequestStatsApp, vhost, app)
|
url := fmt.Sprintf(URLRequestStatsApp, vhost, app)
|
||||||
if err := c.Request(http.MethodGet, url, nil, &req); err != nil {
|
if err := c.Request(url, &req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &req, nil
|
return &req, nil
|
||||||
|
@ -53,7 +52,7 @@ func (c *Client) RequestStatsApp(vhost, app string) (*ResponseStats, error) {
|
||||||
func (c *Client) RequestStatsStream(vhost, app, stream string) (*ResponseStats, error) {
|
func (c *Client) RequestStatsStream(vhost, app, stream string) (*ResponseStats, error) {
|
||||||
req := ResponseStats{}
|
req := ResponseStats{}
|
||||||
url := fmt.Sprintf(URLRequestStatsStream, vhost, app, stream)
|
url := fmt.Sprintf(URLRequestStatsStream, vhost, app, stream)
|
||||||
if err := c.Request(http.MethodGet, url, nil, &req); err != nil {
|
if err := c.Request(url, &req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &req, nil
|
return &req, nil
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
# (Address +) Port where the Exporter is listen for Prometheus request
|
|
||||||
listen = ":8080"
|
listen = ":8080"
|
||||||
|
|
||||||
[api]
|
[api]
|
||||||
# URL to REST-API of OvenMediaEngine
|
host = "http://1.2.3.4:8081"
|
||||||
url = "http://1.2.3.4:8081"
|
|
||||||
# AccessToken of OvenMediaEngine's REST-API
|
|
||||||
token = "ome-access-token"
|
token = "ome-access-token"
|
||||||
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
[Unit]
|
|
||||||
Description = Prometheus exporter for OvenMediaEngine
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
ExecStart=/usr/local/bin/oven-exporter -c /etc/ovenmediaengine/exporter.conf
|
|
||||||
Restart=always
|
|
||||||
RestartSec=5s
|
|
||||||
Environment=PATH=/usr/bin:/usr/local/bin
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
|
@ -1,9 +0,0 @@
|
||||||
name: oven-exporter
|
|
||||||
title: Oven Exporter
|
|
||||||
# use from git tag
|
|
||||||
version:
|
|
||||||
v(?<version>+({0..9}).+({0..9}).+({0..9})): $<version>
|
|
||||||
main: latest
|
|
||||||
|
|
||||||
nav:
|
|
||||||
- "modules/ROOT/nav.adoc"
|
|
|
@ -1 +0,0 @@
|
||||||
../../../../README.adoc
|
|
19
go.mod
19
go.mod
|
@ -1,16 +1,13 @@
|
||||||
module codeberg.org/Mediathek/oven-exporter
|
module dev.sum7.eu/genofire/oven-exporter
|
||||||
|
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
dev.sum7.eu/genofire/golang-lib v0.0.0-20210719163544-fb766ca32b7c
|
||||||
github.com/knadh/koanf v1.5.0
|
github.com/bdlm/log v0.1.20
|
||||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
github.com/bdlm/std v1.0.1 // indirect
|
||||||
github.com/prometheus/client_golang v1.17.0
|
github.com/prometheus/client_golang v1.10.0
|
||||||
github.com/prometheus/client_model v0.5.0 // indirect
|
github.com/stretchr/testify v1.7.0 // indirect
|
||||||
github.com/prometheus/common v0.45.0 // indirect
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
|
||||||
github.com/prometheus/procfs v0.12.0 // indirect
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
|
||||||
github.com/stretchr/testify v1.8.4
|
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
|
||||||
go.uber.org/zap v1.26.0
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
package helper
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/hmac"
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Policy struct {
|
|
||||||
URLExpire int64 `json:"url_expire"`
|
|
||||||
URLActivate int64 `json:"url_activate,omitempty"`
|
|
||||||
StreamExpire int64 `json:"stream_expire,omitempty"`
|
|
||||||
AllowIP string `json:"allow_ip,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Policy) Encode() (string, error) {
|
|
||||||
str, err := json.Marshal(p)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return base64.RawStdEncoding.EncodeToString(str), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SignEncodedPolicy(u *url.URL, secretKey string) string {
|
|
||||||
hasher := hmac.New(sha1.New, []byte(secretKey))
|
|
||||||
hasher.Write([]byte(u.String()))
|
|
||||||
return base64.RawURLEncoding.EncodeToString(hasher.Sum(nil))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Policy) SignWithQuery(u *url.URL, secretKey, encodeQuery string) (string, error) {
|
|
||||||
encode, err := p.Encode()
|
|
||||||
if err != nil {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
query := u.Query()
|
|
||||||
query.Add(encodeQuery, encode)
|
|
||||||
u.RawQuery = query.Encode()
|
|
||||||
return SignEncodedPolicy(u, secretKey), nil
|
|
||||||
}
|
|
||||||
func (p Policy) Sign(u *url.URL, secretKey string) (string, error) {
|
|
||||||
return p.SignWithQuery(u, secretKey, "policy")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Policy) SignURLWithQuery(u *url.URL, secretKey, encodeQuery, signatureQuery string) error {
|
|
||||||
encode, err := p.Encode()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
query := u.Query()
|
|
||||||
query.Add(encodeQuery, encode)
|
|
||||||
u.RawQuery = query.Encode()
|
|
||||||
|
|
||||||
signature := SignEncodedPolicy(u, secretKey)
|
|
||||||
query.Add(signatureQuery, signature)
|
|
||||||
u.RawQuery = query.Encode()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (p Policy) SignURL(u *url.URL, secretKey string) error {
|
|
||||||
return p.SignURLWithQuery(u, secretKey, "policy", "signature")
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
// https://airensoft.gitbook.io/ovenmediaengine/access-control/signedpolicy
|
|
||||||
package helper
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/url"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
examplePolicyEncode = "eyJ1cmxfZXhwaXJlIjoxMzk5NzIxNTgxfQ"
|
|
||||||
exampleSecretKey = "1kU^b6"
|
|
||||||
exampleURL = "ws://192.168.0.100:3333/app/stream"
|
|
||||||
exampleSignature = "dvVdBpoxAeCPl94Kt5RoiqLI0YE"
|
|
||||||
exampleURLWithSignatureAndPolicy = "ws://192.168.0.100/app/stream?policy=eyJ1cmxfZXhwaXJlIjoxMzk5NzIxNTgxfQ&signature=dvVdBpoxAeCPl94Kt5RoiqLI0YE"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
examplePolicy = Policy{
|
|
||||||
URLExpire: 1399721581,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPolicyEncode(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
encode, err := examplePolicy.Encode()
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.Equal(examplePolicyEncode, encode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPolicySign(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
u, err := url.Parse(exampleURL)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
sign, err := examplePolicy.Sign(u, exampleSecretKey)
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.Equal(exampleSignature, sign)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPolicySignURL(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
u, err := url.Parse(exampleURL)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
err = examplePolicy.SignURL(u, exampleSecretKey)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
// drop port -> is not part of example
|
|
||||||
u.Host = u.Hostname()
|
|
||||||
|
|
||||||
assert.Equal(exampleURLWithSignatureAndPolicy, u.String())
|
|
||||||
}
|
|
70
main.go
70
main.go
|
@ -3,86 +3,34 @@ package main
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/knadh/koanf"
|
"dev.sum7.eu/genofire/golang-lib/file"
|
||||||
"github.com/knadh/koanf/parsers/json"
|
"github.com/bdlm/log"
|
||||||
"github.com/knadh/koanf/parsers/toml"
|
|
||||||
"github.com/knadh/koanf/parsers/yaml"
|
|
||||||
"github.com/knadh/koanf/providers/env"
|
|
||||||
"github.com/knadh/koanf/providers/file"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
"go.uber.org/zap"
|
|
||||||
|
|
||||||
"codeberg.org/Mediathek/oven-exporter/api"
|
"dev.sum7.eu/genofire/oven-exporter/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
var configExtParser = map[string]koanf.Parser{
|
|
||||||
".json": json.Parser(),
|
|
||||||
".toml": toml.Parser(),
|
|
||||||
".yaml": yaml.Parser(),
|
|
||||||
".yml": yaml.Parser(),
|
|
||||||
}
|
|
||||||
|
|
||||||
type configData struct {
|
type configData struct {
|
||||||
log *zap.Logger
|
API *api.Client `toml:"api"`
|
||||||
Log *zap.Config `config:"log"`
|
Listen string `toml:"listen"`
|
||||||
API api.Client `config:"api"`
|
|
||||||
Listen string `config:"listen"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
configPath := "config.toml"
|
configPath := "config.toml"
|
||||||
|
|
||||||
log, _ := zap.NewProduction()
|
|
||||||
|
|
||||||
flag.StringVar(&configPath, "c", configPath, "path to configuration file")
|
flag.StringVar(&configPath, "c", configPath, "path to configuration file")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
k := koanf.New("/")
|
|
||||||
|
|
||||||
if configPath != "" {
|
|
||||||
fileExt := filepath.Ext(configPath)
|
|
||||||
parser, ok := configExtParser[fileExt]
|
|
||||||
if !ok {
|
|
||||||
log.Panic("unsupported file extension:",
|
|
||||||
zap.String("config-path", configPath),
|
|
||||||
zap.String("file-ext", fileExt),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if err := k.Load(file.Provider(configPath), parser); err != nil {
|
|
||||||
log.Panic("load file config:", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := k.Load(env.Provider("OVEN_E_", "/", func(s string) string {
|
|
||||||
return strings.Replace(strings.ToLower(
|
|
||||||
strings.TrimPrefix(s, "OVEN_E_")), "__", "/", -1)
|
|
||||||
}), nil); err != nil {
|
|
||||||
log.Panic("load env:", zap.Error(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &configData{}
|
config := &configData{}
|
||||||
if err := k.UnmarshalWithConf("", &config, koanf.UnmarshalConf{Tag: "config"}); err != nil {
|
if err := file.ReadTOML(configPath, config); err != nil {
|
||||||
log.Panic("reading config", zap.Error(err))
|
log.Panicf("open config file: %s", err)
|
||||||
}
|
}
|
||||||
if config.Log != nil {
|
|
||||||
l, err := config.Log.Build()
|
|
||||||
if err != nil {
|
|
||||||
log.Panic("generate logger from config", zap.Error(err))
|
|
||||||
}
|
|
||||||
log = l
|
|
||||||
}
|
|
||||||
config.log = log
|
|
||||||
//config.SetLogger(log)
|
|
||||||
config.API.SetToken(config.API.Token)
|
config.API.SetToken(config.API.Token)
|
||||||
|
|
||||||
prometheus.MustRegister(config)
|
prometheus.MustRegister(config)
|
||||||
http.Handle("/metrics", promhttp.Handler())
|
http.Handle("/metrics", promhttp.Handler())
|
||||||
if err := http.ListenAndServe(config.Listen, nil); err != nil {
|
log.Fatal(http.ListenAndServe(config.Listen, nil))
|
||||||
log.Fatal("crash webserver", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"codeberg.org/Mediathek/oven-exporter/api"
|
"dev.sum7.eu/genofire/oven-exporter/api"
|
||||||
|
"github.com/bdlm/log"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -39,17 +39,6 @@ var (
|
||||||
promDescStatsStreamBytesInTotal,
|
promDescStatsStreamBytesInTotal,
|
||||||
promDescStatsStreamBytesOutTotal,
|
promDescStatsStreamBytesOutTotal,
|
||||||
}
|
}
|
||||||
|
|
||||||
promDescPushUp = prometheus.NewDesc("oven_push_up", "state of push", []string{"vhost", "app", "stream", "id", "state"}, prometheus.Labels{})
|
|
||||||
promDescPushSequence = prometheus.NewDesc("oven_push_sequence", "sequence of started pushes", []string{"vhost", "app", "stream", "id"}, prometheus.Labels{})
|
|
||||||
promDescPushSentBytes = prometheus.NewDesc("oven_push_send_byte", "bytes send on push", []string{"vhost", "app", "stream", "id"}, prometheus.Labels{})
|
|
||||||
promDescPushTotalSentBytes = prometheus.NewDesc("oven_push_total_send_bytes", "total bytes send on push", []string{"vhost", "app", "stream", "id"}, prometheus.Labels{})
|
|
||||||
promDescPush = []*prometheus.Desc{
|
|
||||||
promDescPushUp,
|
|
||||||
promDescPushSequence,
|
|
||||||
promDescPushSentBytes,
|
|
||||||
promDescPushTotalSentBytes,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func ResponseStatsToMetrics(resp *api.ResponseStats, descs []*prometheus.Desc, labels ...string) []prometheus.Metric {
|
func ResponseStatsToMetrics(resp *api.ResponseStats, descs []*prometheus.Desc, labels ...string) []prometheus.Metric {
|
||||||
|
@ -60,13 +49,13 @@ func ResponseStatsToMetrics(resp *api.ResponseStats, descs []*prometheus.Desc, l
|
||||||
if m, err := prometheus.NewConstMetric(descs[0], prometheus.GaugeValue, float64(resp.Data.TotalConnections), labels...); err == nil {
|
if m, err := prometheus.NewConstMetric(descs[0], prometheus.GaugeValue, float64(resp.Data.TotalConnections), labels...); err == nil {
|
||||||
list = append(list, m)
|
list = append(list, m)
|
||||||
}
|
}
|
||||||
if m, err := prometheus.NewConstMetric(descs[1], prometheus.CounterValue, float64(resp.Data.MaxTotalConnections), labels...); err == nil {
|
if m, err := prometheus.NewConstMetric(descs[1], prometheus.GaugeValue, float64(resp.Data.MaxTotalConnections), labels...); err == nil {
|
||||||
list = append(list, m)
|
list = append(list, m)
|
||||||
}
|
}
|
||||||
if m, err := prometheus.NewConstMetric(descs[2], prometheus.CounterValue, float64(resp.Data.TotalBytesIn), labels...); err == nil {
|
if m, err := prometheus.NewConstMetric(descs[2], prometheus.GaugeValue, float64(resp.Data.TotalBytesIn), labels...); err == nil {
|
||||||
list = append(list, m)
|
list = append(list, m)
|
||||||
}
|
}
|
||||||
if m, err := prometheus.NewConstMetric(descs[3], prometheus.CounterValue, float64(resp.Data.TotalBytesOut), labels...); err == nil {
|
if m, err := prometheus.NewConstMetric(descs[3], prometheus.GaugeValue, float64(resp.Data.TotalBytesOut), labels...); err == nil {
|
||||||
list = append(list, m)
|
list = append(list, m)
|
||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
|
@ -82,18 +71,15 @@ func (c *configData) Describe(d chan<- *prometheus.Desc) {
|
||||||
for _, desc := range promDescStatsStream {
|
for _, desc := range promDescStatsStream {
|
||||||
d <- desc
|
d <- desc
|
||||||
}
|
}
|
||||||
for _, desc := range promDescPush {
|
|
||||||
d <- desc
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *configData) Collect(metrics chan<- prometheus.Metric) {
|
func (c *configData) Collect(metrics chan<- prometheus.Metric) {
|
||||||
respList, err := c.API.RequestListVHosts()
|
respList, err := c.API.RequestListVHosts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.log.Panic("unable to fetch vhosts", zap.Error(err))
|
log.Panicf("unable to fetch vhosts: %s", err)
|
||||||
}
|
}
|
||||||
for _, vhost := range respList.Data {
|
for _, vhost := range respList.Data {
|
||||||
logVhost := c.log.With(zap.String("vhost", vhost))
|
logVhost := log.WithField("vhost", vhost)
|
||||||
if resp, err := c.API.RequestStatsVHost(vhost); err == nil {
|
if resp, err := c.API.RequestStatsVHost(vhost); err == nil {
|
||||||
for _, m := range ResponseStatsToMetrics(resp, promDescStatsVHost, vhost) {
|
for _, m := range ResponseStatsToMetrics(resp, promDescStatsVHost, vhost) {
|
||||||
metrics <- m
|
metrics <- m
|
||||||
|
@ -101,38 +87,19 @@ func (c *configData) Collect(metrics chan<- prometheus.Metric) {
|
||||||
}
|
}
|
||||||
respList, err = c.API.RequestListApps(vhost)
|
respList, err = c.API.RequestListApps(vhost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logVhost.Error("unable to fetch apps", zap.Error(err))
|
logVhost.Errorf("unable to fetch apps: %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, app := range respList.Data {
|
for _, app := range respList.Data {
|
||||||
logApp := logVhost.With(zap.String("app", app))
|
logApp := logVhost.WithField("app", app)
|
||||||
if resp, err := c.API.RequestStatsApp(vhost, app); err == nil {
|
if resp, err := c.API.RequestStatsApp(vhost, app); err == nil {
|
||||||
for _, m := range ResponseStatsToMetrics(resp, promDescStatsApp, vhost, app) {
|
for _, m := range ResponseStatsToMetrics(resp, promDescStatsApp, vhost, app) {
|
||||||
metrics <- m
|
metrics <- m
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if resp, err := c.API.RequestPushStatus(vhost, app); err != nil {
|
|
||||||
logApp.Error("unable to fetch pushes", zap.Error(err))
|
|
||||||
} else {
|
|
||||||
for _, data := range resp.Data {
|
|
||||||
if m, err := prometheus.NewConstMetric(promDescPushUp, prometheus.GaugeValue, 1, vhost, app, data.Stream.Name, data.ID, data.State); err == nil {
|
|
||||||
metrics <- m
|
|
||||||
}
|
|
||||||
labels := []string{vhost, app, data.Stream.Name, data.ID}
|
|
||||||
if m, err := prometheus.NewConstMetric(promDescPushSequence, prometheus.CounterValue, float64(data.Sequence), labels...); err == nil {
|
|
||||||
metrics <- m
|
|
||||||
}
|
|
||||||
if m, err := prometheus.NewConstMetric(promDescPushSentBytes, prometheus.GaugeValue, float64(data.SentBytes), labels...); err == nil {
|
|
||||||
metrics <- m
|
|
||||||
}
|
|
||||||
if m, err := prometheus.NewConstMetric(promDescPushTotalSentBytes, prometheus.CounterValue, float64(data.TotalSentBytes), labels...); err == nil {
|
|
||||||
metrics <- m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
respList, err = c.API.RequestListStreams(vhost, app)
|
respList, err = c.API.RequestListStreams(vhost, app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logApp.Error("unable to fetch stream", zap.Error(err))
|
logApp.Errorf("unable to fetch stream: %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, stream := range respList.Data {
|
for _, stream := range respList.Data {
|
||||||
|
|
Loading…
Reference in New Issue