mirror of
https://github.com/safing/portmaster
synced 2025-04-21 03:19:10 +00:00
* Move portbase into monorepo * Add new simple module mgr * [WIP] Switch to new simple module mgr * Add StateMgr and more worker variants * [WIP] Switch more modules * [WIP] Switch more modules * [WIP] swtich more modules * [WIP] switch all SPN modules * [WIP] switch all service modules * [WIP] Convert all workers to the new module system * [WIP] add new task system to module manager * [WIP] Add second take for scheduling workers * [WIP] Add FIXME for bugs in new scheduler * [WIP] Add minor improvements to scheduler * [WIP] Add new worker scheduler * [WIP] Fix more bug related to new module system * [WIP] Fix start handing of the new module system * [WIP] Improve startup process * [WIP] Fix minor issues * [WIP] Fix missing subsystem in settings * [WIP] Initialize managers in constructor * [WIP] Move module event initialization to constrictors * [WIP] Fix setting for enabling and disabling the SPN module * [WIP] Move API registeration into module construction * [WIP] Update states mgr for all modules * [WIP] Add CmdLine operation support * Add state helper methods to module group and instance * Add notification and module status handling to status package * Fix starting issues * Remove pilot widget and update security lock to new status data * Remove debug logs * Improve http server shutdown * Add workaround for cleanly shutting down firewall+netquery * Improve logging * Add syncing states with notifications for new module system * Improve starting, stopping, shutdown; resolve FIXMEs/TODOs * [WIP] Fix most unit tests * Review new module system and fix minor issues * Push shutdown and restart events again via API * Set sleep mode via interface * Update example/template module * [WIP] Fix spn/cabin unit test * Remove deprecated UI elements * Make log output more similar for the logging transition phase * Switch spn hub and observer cmds to new module system * Fix log sources * Make worker mgr less error prone * Fix tests and minor issues * Fix observation hub * Improve shutdown and restart handling * Split up big connection.go source file * Move varint and dsd packages to structures repo * Improve expansion test * Fix linter warnings * Fix interception module on windows * Fix linter errors --------- Co-authored-by: Vladimir Stoilov <vladimir@safing.io>
165 lines
4.9 KiB
Go
165 lines
4.9 KiB
Go
package metrics
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
|
|
vm "github.com/VictoriaMetrics/metrics"
|
|
|
|
"github.com/safing/portmaster/base/api"
|
|
"github.com/safing/portmaster/base/config"
|
|
)
|
|
|
|
// PrometheusFormatRequirement is required format defined by prometheus for
|
|
// metric and label names.
|
|
const (
|
|
prometheusBaseFormt = "[a-zA-Z_][a-zA-Z0-9_]*"
|
|
PrometheusFormatRequirement = "^" + prometheusBaseFormt + "$"
|
|
)
|
|
|
|
var prometheusFormat = regexp.MustCompile(PrometheusFormatRequirement)
|
|
|
|
// Metric represents one or more metrics.
|
|
type Metric interface {
|
|
ID() string
|
|
LabeledID() string
|
|
Opts() *Options
|
|
WritePrometheus(w io.Writer)
|
|
}
|
|
|
|
type metricBase struct {
|
|
Identifier string
|
|
Labels map[string]string
|
|
LabeledIdentifier string
|
|
Options *Options
|
|
set *vm.Set
|
|
}
|
|
|
|
// Options can be used to set advanced metric settings.
|
|
type Options struct {
|
|
// Name defines an optional human readable name for the metric.
|
|
Name string
|
|
|
|
// InternalID specifies an alternative internal ID that will be used when
|
|
// exposing the metric via the API in a structured format.
|
|
InternalID string
|
|
|
|
// AlertLimit defines an upper limit that triggers an alert.
|
|
AlertLimit float64
|
|
|
|
// AlertTimeframe defines an optional timeframe in seconds for which the
|
|
// AlertLimit should be interpreted in.
|
|
AlertTimeframe float64
|
|
|
|
// Permission defines the permission that is required to read the metric.
|
|
Permission api.Permission
|
|
|
|
// ExpertiseLevel defines the expertise level that the metric is meant for.
|
|
ExpertiseLevel config.ExpertiseLevel
|
|
|
|
// Persist enabled persisting the metric on shutdown and loading the previous
|
|
// value at start. This is only supported for counters.
|
|
Persist bool
|
|
}
|
|
|
|
func newMetricBase(id string, labels map[string]string, opts Options) (*metricBase, error) {
|
|
// Check formats.
|
|
if !prometheusFormat.MatchString(strings.ReplaceAll(id, "/", "_")) {
|
|
return nil, fmt.Errorf("metric name %q must match %s", id, PrometheusFormatRequirement)
|
|
}
|
|
for labelName := range labels {
|
|
if !prometheusFormat.MatchString(labelName) {
|
|
return nil, fmt.Errorf("metric label name %q must match %s", labelName, PrometheusFormatRequirement)
|
|
}
|
|
}
|
|
|
|
// Check permission.
|
|
if opts.Permission < api.PermitAnyone {
|
|
// Default to PermitUser.
|
|
opts.Permission = api.PermitUser
|
|
}
|
|
|
|
// Ensure that labels is a map.
|
|
if labels == nil {
|
|
labels = make(map[string]string)
|
|
}
|
|
|
|
// Create metric base.
|
|
base := &metricBase{
|
|
Identifier: id,
|
|
Labels: labels,
|
|
Options: &opts,
|
|
set: vm.NewSet(),
|
|
}
|
|
base.LabeledIdentifier = base.buildLabeledID()
|
|
return base, nil
|
|
}
|
|
|
|
// ID returns the given ID of the metric.
|
|
func (m *metricBase) ID() string {
|
|
return m.Identifier
|
|
}
|
|
|
|
// LabeledID returns the Prometheus-compatible labeled ID of the metric.
|
|
func (m *metricBase) LabeledID() string {
|
|
return m.LabeledIdentifier
|
|
}
|
|
|
|
// Opts returns the metric options. They may not be modified.
|
|
func (m *metricBase) Opts() *Options {
|
|
return m.Options
|
|
}
|
|
|
|
// WritePrometheus writes the metric in the prometheus format to the given writer.
|
|
func (m *metricBase) WritePrometheus(w io.Writer) {
|
|
m.set.WritePrometheus(w)
|
|
}
|
|
|
|
func (m *metricBase) buildLabeledID() string {
|
|
// Because we use the namespace and the global flags here, we need to flag
|
|
// them as immutable.
|
|
registryLock.Lock()
|
|
defer registryLock.Unlock()
|
|
firstMetricRegistered = true
|
|
|
|
// Build ID from Identifier.
|
|
metricID := strings.TrimSpace(strings.ReplaceAll(m.Identifier, "/", "_"))
|
|
|
|
// Add namespace to ID.
|
|
if metricNamespace != "" {
|
|
metricID = metricNamespace + "_" + metricID
|
|
}
|
|
|
|
// Return now if no labels are defined.
|
|
if len(globalLabels) == 0 && len(m.Labels) == 0 {
|
|
return metricID
|
|
}
|
|
|
|
// Add global labels to the custom ones, if they don't exist yet.
|
|
for labelName, labelValue := range globalLabels {
|
|
if _, ok := m.Labels[labelName]; !ok {
|
|
m.Labels[labelName] = labelValue
|
|
}
|
|
}
|
|
|
|
// Render labels into a slice and sort them in order to make the labeled ID
|
|
// reproducible.
|
|
labels := make([]string, 0, len(m.Labels))
|
|
for labelName, labelValue := range m.Labels {
|
|
labels = append(labels, fmt.Sprintf("%s=%q", labelName, labelValue))
|
|
}
|
|
sort.Strings(labels)
|
|
|
|
// Return fully labaled ID.
|
|
return fmt.Sprintf("%s{%s}", metricID, strings.Join(labels, ","))
|
|
}
|
|
|
|
// Split metrics into sets, according to the API Auth Levels, which will also correspond to the UI Mode levels. SPN // nodes will also allow public access to metrics with the permission "PermitAnyone".
|
|
// Save "life-long" metrics on shutdown and load them at start.
|
|
// Generate the correct metric name and labels.
|
|
// Expose metrics via http, but also via the runtime DB in order to push metrics to the UI.
|
|
// The UI will have to parse the prometheus metrics format and will not be able to immediately present historical data, // but data will have to be built.
|
|
// Provide the option to push metrics to a prometheus push gateway, this is especially helpful when gathering data from // loads of SPN nodes.
|