diff --git a/docs/docs.go b/docs/docs.go index 9baa93ac..06030b97 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -839,6 +839,30 @@ const docTemplate = `{ } }, "/api/v3/metrics": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "List all known metrics with their description and labels", + "produces": [ + "application/json" + ], + "summary": "List all known metrics with their description and labels", + "operationId": "metrics-3-describe", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/api.MetricsDescription" + } + } + } + } + }, "post": { "security": [ { @@ -2926,6 +2950,23 @@ const docTemplate = `{ } } }, + "api.MetricsDescription": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string" + } + } + }, "api.MetricsQuery": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 35c2db46..9d81a348 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -831,6 +831,30 @@ } }, "/api/v3/metrics": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "List all known metrics with their description and labels", + "produces": [ + "application/json" + ], + "summary": "List all known metrics with their description and labels", + "operationId": "metrics-3-describe", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/api.MetricsDescription" + } + } + } + } + }, "post": { "security": [ { @@ -2918,6 +2942,23 @@ } } }, + "api.MetricsDescription": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string" + } + } + }, "api.MetricsQuery": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index c4990b87..4bfe0b0d 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -462,6 +462,17 @@ definitions: - password - username type: object + api.MetricsDescription: + properties: + description: + type: string + labels: + items: + type: string + type: array + name: + type: string + type: object api.MetricsQuery: properties: interval_sec: @@ -2264,6 +2275,21 @@ paths: - ApiKeyAuth: [] summary: Add JSON metadata under the given key /api/v3/metrics: + get: + description: List all known metrics with their description and labels + operationId: metrics-3-describe + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/api.MetricsDescription' + type: array + security: + - ApiKeyAuth: [] + summary: List all known metrics with their description and labels post: consumes: - application/json diff --git a/http/api/metrics.go b/http/api/metrics.go index add30c43..49b184f9 100644 --- a/http/api/metrics.go +++ b/http/api/metrics.go @@ -7,6 +7,12 @@ import ( "github.com/datarhei/core/v16/monitor" ) +type MetricsDescription struct { + Name string `json:"name"` + Description string `json:"description"` + Labels []string `json:"labels"` +} + type MetricsQueryMetric struct { Name string `json:"name"` Labels map[string]string `json:"labels"` diff --git a/http/handler/api/metrics.go b/http/handler/api/metrics.go index 03d4fc5b..d1356686 100644 --- a/http/handler/api/metrics.go +++ b/http/handler/api/metrics.go @@ -2,6 +2,7 @@ package api import ( "net/http" + "sort" "time" "github.com/datarhei/core/v16/http/api" @@ -28,6 +29,34 @@ func NewMetrics(config MetricsConfig) *MetricsHandler { } } +// Describe the known metrics +// @Summary List all known metrics with their description and labels +// @Description List all known metrics with their description and labels +// @ID metrics-3-describe +// @Produce json +// @Success 200 {array} api.MetricsDescription +// @Security ApiKeyAuth +// @Router /api/v3/metrics [get] +func (r *MetricsHandler) Describe(c echo.Context) error { + response := []api.MetricsDescription{} + + descriptors := r.metrics.Describe() + + for _, d := range descriptors { + response = append(response, api.MetricsDescription{ + Name: d.Name(), + Description: d.Description(), + Labels: d.Labels(), + }) + } + + sort.Slice(response, func(i, j int) bool { + return response[i].Name < response[j].Name + }) + + return c.JSON(http.StatusOK, response) +} + // Query the collected metrics // @Summary Query the collected metrics // @Description Query the collected metrics diff --git a/http/server.go b/http/server.go index a3dfcdad..4503a7b0 100644 --- a/http/server.go +++ b/http/server.go @@ -640,6 +640,7 @@ func (s *server) setRoutesV3(v3 *echo.Group) { // v3 Log v3.GET("/log", s.v3handler.log.Log) - // v3 Resources + // v3 Metrics + v3.GET("/metrics", s.v3handler.resources.Describe) v3.POST("/metrics", s.v3handler.resources.Metrics) } diff --git a/monitor/cpu.go b/monitor/cpu.go index 4ee4fd89..60b70ba9 100644 --- a/monitor/cpu.go +++ b/monitor/cpu.go @@ -20,11 +20,11 @@ func NewCPUCollector() metric.Collector { ncpu: 1, } - c.ncpuDescr = metric.NewDesc("cpu_ncpu", "", nil) - c.systemDescr = metric.NewDesc("cpu_system", "", nil) - c.userDescr = metric.NewDesc("cpu_user", "", nil) - c.idleDescr = metric.NewDesc("cpu_idle", "", nil) - c.otherDescr = metric.NewDesc("cpu_other", "", nil) + c.ncpuDescr = metric.NewDesc("cpu_ncpu", "Number of logical CPUs in the system", nil) + c.systemDescr = metric.NewDesc("cpu_system", "Percentage of CPU used for the system", nil) + c.userDescr = metric.NewDesc("cpu_user", "Percentage of CPU used for the user", nil) + c.idleDescr = metric.NewDesc("cpu_idle", "Percentage of idle CPU", nil) + c.otherDescr = metric.NewDesc("cpu_other", "Percentage of CPU used for other subsystems", nil) if ncpu, err := psutil.CPUCounts(true); err == nil { c.ncpu = ncpu diff --git a/monitor/disk.go b/monitor/disk.go index 03ce8bf1..7e1ba86d 100644 --- a/monitor/disk.go +++ b/monitor/disk.go @@ -17,8 +17,8 @@ func NewDiskCollector(path string) metric.Collector { path: path, } - c.totalDescr = metric.NewDesc("disk_total", "", []string{"path"}) - c.usageDescr = metric.NewDesc("disk_usage", "", []string{"path"}) + c.totalDescr = metric.NewDesc("disk_total", "Total size of the disk in bytes", []string{"path"}) + c.usageDescr = metric.NewDesc("disk_usage", "Number of used bytes on the disk", []string{"path"}) return c } diff --git a/monitor/ffmpeg.go b/monitor/ffmpeg.go index 2b6edbd1..a447901a 100644 --- a/monitor/ffmpeg.go +++ b/monitor/ffmpeg.go @@ -17,7 +17,7 @@ func NewFFmpegCollector(f ffmpeg.FFmpeg) metric.Collector { ffmpeg: f, } - c.processDescr = metric.NewDesc("ffmpeg_process", "", []string{"state"}) + c.processDescr = metric.NewDesc("ffmpeg_process", "State of the ffmpeg process", []string{"state"}) return c } diff --git a/monitor/filesystem.go b/monitor/filesystem.go index fa40020b..507dcc6f 100644 --- a/monitor/filesystem.go +++ b/monitor/filesystem.go @@ -19,9 +19,9 @@ func NewFilesystemCollector(name string, fs fs.Filesystem) metric.Collector { name: name, } - c.limitDescr = metric.NewDesc("filesystem_limit", "", []string{"name"}) - c.usageDescr = metric.NewDesc("filesystem_usage", "", []string{"name"}) - c.filesDescr = metric.NewDesc("filesystem_files", "", []string{"name"}) + c.limitDescr = metric.NewDesc("filesystem_limit", "Total size of the filesystem in bytes, negative if unlimited", []string{"name"}) + c.usageDescr = metric.NewDesc("filesystem_usage", "Number of used bytes on the filesystem", []string{"name"}) + c.filesDescr = metric.NewDesc("filesystem_files", "Number of files on the filesystem (excluding directories)", []string{"name"}) return c } diff --git a/monitor/mem.go b/monitor/mem.go index 8a6c8958..04fb8465 100644 --- a/monitor/mem.go +++ b/monitor/mem.go @@ -13,8 +13,8 @@ type memCollector struct { func NewMemCollector() metric.Collector { c := &memCollector{} - c.totalDescr = metric.NewDesc("mem_total", "", nil) - c.freeDescr = metric.NewDesc("mem_free", "", nil) + c.totalDescr = metric.NewDesc("mem_total", "Total available memory in bytes", nil) + c.freeDescr = metric.NewDesc("mem_free", "Free memory in bytes", nil) return c } diff --git a/monitor/metric/metric.go b/monitor/metric/metric.go index f1c81f97..a327c6d0 100644 --- a/monitor/metric/metric.go +++ b/monitor/metric/metric.go @@ -4,6 +4,7 @@ import ( "fmt" "regexp" "sort" + "strings" ) type Pattern interface { @@ -304,6 +305,10 @@ func NewDesc(name, description string, labels []string) *Description { } } +func (d *Description) String() string { + return fmt.Sprintf("%s: %s (%s)", d.name, d.description, strings.Join(d.labels, ",")) +} + func (d *Description) Name() string { return d.name } @@ -312,6 +317,13 @@ func (d *Description) Description() string { return d.description } +func (d *Description) Labels() []string { + labels := make([]string, len(d.labels)) + copy(labels, d.labels) + + return labels +} + type Collector interface { Prefix() string Describe() []*Description diff --git a/monitor/monitor.go b/monitor/monitor.go index d4985c98..47631781 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -10,9 +10,26 @@ import ( "github.com/datarhei/core/v16/monitor/metric" ) -type Monitor interface { - Register(c metric.Collector) +type Reader interface { Collect(patterns []metric.Pattern) metric.Metrics + Describe() []*metric.Description +} + +type Monitor interface { + Reader + Register(c metric.Collector) + UnregisterAll() +} + +type HistoryReader interface { + Reader + History(timerange, interval time.Duration, patterns []metric.Pattern) []HistoryMetrics + Resolution() (timerange, interval time.Duration) +} + +type HistoryMonitor interface { + HistoryReader + Register(c metric.Collector) UnregisterAll() } @@ -75,6 +92,26 @@ func (m *monitor) Collect(patterns []metric.Pattern) metric.Metrics { return metrics } +func (m *monitor) Describe() []*metric.Description { + descriptors := []*metric.Description{} + collectors := map[metric.Collector]struct{}{} + + m.lock.RLock() + defer m.lock.RUnlock() + + for _, c := range m.collectors { + if _, ok := collectors[c]; ok { + continue + } + + collectors[c] = struct{}{} + + descriptors = append(descriptors, c.Describe()...) + } + + return descriptors +} + func (m *monitor) UnregisterAll() { m.lock.Lock() defer m.lock.Unlock() @@ -86,12 +123,6 @@ func (m *monitor) UnregisterAll() { m.collectors = make(map[string]metric.Collector) } -type HistoryMonitor interface { - Monitor - History(timerange, interval time.Duration, patterns []metric.Pattern) []HistoryMetrics - Resolution() (timerange, interval time.Duration) -} - type historyMonitor struct { monitor Monitor @@ -209,6 +240,10 @@ func (m *historyMonitor) Collect(patterns []metric.Pattern) metric.Metrics { return m.monitor.Collect(patterns) } +func (m *historyMonitor) Describe() []*metric.Description { + return m.monitor.Describe() +} + func (m *historyMonitor) UnregisterAll() { m.monitor.UnregisterAll() @@ -327,13 +362,3 @@ func (m *historyMonitor) resample(values []HistoryMetrics, timerange, interval t return v } - -type Reader interface { - Collect(patterns []metric.Pattern) metric.Metrics -} - -type HistoryReader interface { - Reader - History(timerange, interval time.Duration, patterns []metric.Pattern) []HistoryMetrics - Resolution() (timerange, interval time.Duration) -} diff --git a/monitor/net.go b/monitor/net.go index 9f97cc86..87b2b8a3 100644 --- a/monitor/net.go +++ b/monitor/net.go @@ -13,8 +13,8 @@ type netCollector struct { func NewNetCollector() metric.Collector { c := &netCollector{} - c.rxDescr = metric.NewDesc("net_rx", "", []string{"interface"}) - c.txDescr = metric.NewDesc("net_tx", "", []string{"interface"}) + c.rxDescr = metric.NewDesc("net_rx", "Number of received bytes", []string{"interface"}) + c.txDescr = metric.NewDesc("net_tx", "Number of transmitted bytes", []string{"interface"}) return c } diff --git a/monitor/restream.go b/monitor/restream.go index 3d61fb3d..cfd069f4 100644 --- a/monitor/restream.go +++ b/monitor/restream.go @@ -22,10 +22,10 @@ func NewRestreamCollector(r restream.Restreamer) metric.Collector { r: r, } - c.restreamProcessDescr = metric.NewDesc("restream_process", "", []string{"processid", "state", "order", "name"}) - c.restreamProcessStatesDescr = metric.NewDesc("restream_process_states", "", []string{"processid", "state"}) - c.restreamProcessIODescr = metric.NewDesc("restream_io", "", []string{"processid", "type", "id", "address", "index", "stream", "media", "name"}) - c.restreamStatesDescr = metric.NewDesc("restream_state", "", []string{"state"}) + c.restreamProcessDescr = metric.NewDesc("restream_process", "Current process values by name", []string{"processid", "state", "order", "name"}) + c.restreamProcessStatesDescr = metric.NewDesc("restream_process_states", "Current process state", []string{"processid", "state"}) + c.restreamProcessIODescr = metric.NewDesc("restream_io", "Current process IO values by name", []string{"processid", "type", "id", "address", "index", "stream", "media", "name"}) + c.restreamStatesDescr = metric.NewDesc("restream_state", "Summarized process states", []string{"state"}) return c } diff --git a/monitor/session.go b/monitor/session.go index d2ac15b9..1447d4d5 100644 --- a/monitor/session.go +++ b/monitor/session.go @@ -31,17 +31,17 @@ func NewSessionCollector(r session.RegistryReader, collectors []string) metric.C c.collectors = r.Collectors() } - c.totalDescr = metric.NewDesc("session_total", "", []string{"collector"}) - c.limitDescr = metric.NewDesc("session_limit", "", []string{"collector"}) - c.activeDescr = metric.NewDesc("session_active", "", []string{"collector"}) - c.rxBytesDescr = metric.NewDesc("session_rxbytes", "", []string{"collector"}) - c.txBytesDescr = metric.NewDesc("session_txbytes", "", []string{"collector"}) + c.totalDescr = metric.NewDesc("session_total", "Total sessions", []string{"collector"}) + c.limitDescr = metric.NewDesc("session_limit", "Max. number of concurrent sessions", []string{"collector"}) + c.activeDescr = metric.NewDesc("session_active", "Number of current sessions", []string{"collector"}) + c.rxBytesDescr = metric.NewDesc("session_rxbytes", "Number of received bytes", []string{"collector"}) + c.txBytesDescr = metric.NewDesc("session_txbytes", "Number of transmitted bytes", []string{"collector"}) - c.rxBitrateDescr = metric.NewDesc("session_rxbitrate", "", []string{"collector"}) - c.txBitrateDescr = metric.NewDesc("session_txbitrate", "", []string{"collector"}) + c.rxBitrateDescr = metric.NewDesc("session_rxbitrate", "Current receiving bitrate in bit per second", []string{"collector"}) + c.txBitrateDescr = metric.NewDesc("session_txbitrate", "Current transmitting bitrate in bit per second", []string{"collector"}) - c.maxTxBitrateDescr = metric.NewDesc("session_maxtxbitrate", "", []string{"collector"}) - c.maxRxBitrateDescr = metric.NewDesc("session_maxrxbitrate", "", []string{"collector"}) + c.maxRxBitrateDescr = metric.NewDesc("session_maxrxbitrate", "Max. allowed receiving bitrate in bit per second", []string{"collector"}) + c.maxTxBitrateDescr = metric.NewDesc("session_maxtxbitrate", "Max. allowed transmitting bitrate in bit per second", []string{"collector"}) return c } diff --git a/monitor/uptime.go b/monitor/uptime.go index 8c65a0e8..b5e89425 100644 --- a/monitor/uptime.go +++ b/monitor/uptime.go @@ -16,7 +16,7 @@ func NewUptimeCollector() metric.Collector { t: time.Now(), } - c.uptimeDescr = metric.NewDesc("uptime_uptime", "", nil) + c.uptimeDescr = metric.NewDesc("uptime_uptime", "Current uptime in seconds", nil) return c }