Files
archived-frankenphp/metrics.go
Rob Landers 5d43fc2c8d add basic metrics (#966)
* add metrics

* change how counting works

* also replace dots

* check that metrics exist

* rename NullMetrics to nullMetrics

* update go.sum

* register collectors only once

* add tests

* add tests for metrics and fix bugs

* keep old metrics around for test

* properly reset during shutdown

* use the same method as frankenphp

* Revert "keep old metrics around for test"

This reverts commit 1f0df6f6bdaebf32aec346f068d6f42a0b5f4007.

* change to require.NoError

* compile regex only once

* remove name sanitizer

* use require

* parameterize host port because security software sucks

* remove need for renaming workers

* increase number of threads and add tests

* fix where frankenphp configuration was bleeding into later tests

* adds basic docs for metrics

* Add caddy metrics link

Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>

* Fix typos

Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>

* address feedback

* change comment to be much more "dangerous"

---------

Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>
2024-09-26 09:53:37 +02:00

253 lines
6.3 KiB
Go

package frankenphp
import (
"github.com/prometheus/client_golang/prometheus"
"path/filepath"
"regexp"
"sync"
"time"
)
var metricsNameRegex = regexp.MustCompile(`\W+`)
var metricsNameFixRegex = regexp.MustCompile(`^_+|_+$`)
type Metrics interface {
// StartWorker collects started workers
StartWorker(name string)
// StopWorker collects stopped workers
StopWorker(name string)
// TotalWorkers collects expected workers
TotalWorkers(name string, num int)
// TotalThreads collects total threads
TotalThreads(num int)
// StartRequest collects started requests
StartRequest()
// StopRequest collects stopped requests
StopRequest()
// StopWorkerRequest collects stopped worker requests
StopWorkerRequest(name string, duration time.Duration)
// StartWorkerRequest collects started worker requests
StartWorkerRequest(name string)
Shutdown()
}
type nullMetrics struct{}
func (n nullMetrics) StartWorker(name string) {
}
func (n nullMetrics) StopWorker(name string) {
}
func (n nullMetrics) TotalWorkers(name string, num int) {
}
func (n nullMetrics) TotalThreads(num int) {
}
func (n nullMetrics) StartRequest() {
}
func (n nullMetrics) StopRequest() {
}
func (n nullMetrics) StopWorkerRequest(name string, duration time.Duration) {
}
func (n nullMetrics) StartWorkerRequest(name string) {
}
func (n nullMetrics) Shutdown() {
}
type PrometheusMetrics struct {
registry prometheus.Registerer
totalThreads prometheus.Counter
busyThreads prometheus.Gauge
totalWorkers map[string]prometheus.Gauge
busyWorkers map[string]prometheus.Gauge
workerRequestTime map[string]prometheus.Counter
workerRequestCount map[string]prometheus.Counter
mu sync.Mutex
}
func (m *PrometheusMetrics) StartWorker(name string) {
m.busyThreads.Inc()
// tests do not register workers before starting them
if _, ok := m.totalWorkers[name]; !ok {
return
}
m.totalWorkers[name].Inc()
}
func (m *PrometheusMetrics) StopWorker(name string) {
m.busyThreads.Dec()
// tests do not register workers before starting them
if _, ok := m.totalWorkers[name]; !ok {
return
}
m.totalWorkers[name].Dec()
}
func (m *PrometheusMetrics) getIdentity(name string) (string, error) {
actualName, err := filepath.Abs(name)
if err != nil {
return name, err
}
return actualName, nil
}
func (m *PrometheusMetrics) TotalWorkers(name string, num int) {
m.mu.Lock()
defer m.mu.Unlock()
identity, err := m.getIdentity(name)
if err != nil {
// do not create metrics, let error propagate when worker is started
return
}
subsystem := getWorkerNameForMetrics(name)
if _, ok := m.totalWorkers[identity]; !ok {
m.totalWorkers[identity] = prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "frankenphp",
Subsystem: subsystem,
Name: "total_workers",
Help: "Total number of PHP workers for this worker",
})
m.registry.MustRegister(m.totalWorkers[identity])
}
if _, ok := m.busyWorkers[identity]; !ok {
m.busyWorkers[identity] = prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "frankenphp",
Subsystem: subsystem,
Name: "busy_workers",
Help: "Number of busy PHP workers for this worker",
})
m.registry.MustRegister(m.busyWorkers[identity])
}
if _, ok := m.workerRequestTime[identity]; !ok {
m.workerRequestTime[identity] = prometheus.NewCounter(prometheus.CounterOpts{
Namespace: "frankenphp",
Subsystem: subsystem,
Name: "worker_request_time",
})
m.registry.MustRegister(m.workerRequestTime[identity])
}
if _, ok := m.workerRequestCount[identity]; !ok {
m.workerRequestCount[identity] = prometheus.NewCounter(prometheus.CounterOpts{
Namespace: "frankenphp",
Subsystem: subsystem,
Name: "worker_request_count",
})
m.registry.MustRegister(m.workerRequestCount[identity])
}
}
func (m *PrometheusMetrics) TotalThreads(num int) {
m.totalThreads.Add(float64(num))
}
func (m *PrometheusMetrics) StartRequest() {
m.busyThreads.Inc()
}
func (m *PrometheusMetrics) StopRequest() {
m.busyThreads.Dec()
}
func (m *PrometheusMetrics) StopWorkerRequest(name string, duration time.Duration) {
if _, ok := m.workerRequestTime[name]; !ok {
return
}
m.workerRequestCount[name].Inc()
m.busyWorkers[name].Dec()
m.workerRequestTime[name].Add(duration.Seconds())
}
func (m *PrometheusMetrics) StartWorkerRequest(name string) {
if _, ok := m.busyWorkers[name]; !ok {
return
}
m.busyWorkers[name].Inc()
}
func (m *PrometheusMetrics) Shutdown() {
m.registry.Unregister(m.totalThreads)
m.registry.Unregister(m.busyThreads)
for _, g := range m.totalWorkers {
m.registry.Unregister(g)
}
for _, g := range m.busyWorkers {
m.registry.Unregister(g)
}
for _, c := range m.workerRequestTime {
m.registry.Unregister(c)
}
for _, c := range m.workerRequestCount {
m.registry.Unregister(c)
}
m.totalThreads = prometheus.NewCounter(prometheus.CounterOpts{
Name: "frankenphp_total_threads",
Help: "Total number of PHP threads",
})
m.busyThreads = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "frankenphp_busy_threads",
Help: "Number of busy PHP threads",
})
m.totalWorkers = map[string]prometheus.Gauge{}
m.busyWorkers = map[string]prometheus.Gauge{}
m.workerRequestTime = map[string]prometheus.Counter{}
m.workerRequestCount = map[string]prometheus.Counter{}
m.registry.MustRegister(m.totalThreads)
m.registry.MustRegister(m.busyThreads)
}
func getWorkerNameForMetrics(name string) string {
name = metricsNameRegex.ReplaceAllString(name, "_")
name = metricsNameFixRegex.ReplaceAllString(name, "")
return name
}
func NewPrometheusMetrics(registry prometheus.Registerer) *PrometheusMetrics {
if registry == nil {
registry = prometheus.NewRegistry()
}
m := &PrometheusMetrics{
registry: registry,
totalThreads: prometheus.NewCounter(prometheus.CounterOpts{
Name: "frankenphp_total_threads",
Help: "Total number of PHP threads",
}),
busyThreads: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "frankenphp_busy_threads",
Help: "Number of busy PHP threads",
}),
totalWorkers: map[string]prometheus.Gauge{},
busyWorkers: map[string]prometheus.Gauge{},
workerRequestTime: map[string]prometheus.Counter{},
workerRequestCount: map[string]prometheus.Counter{},
}
m.registry.MustRegister(m.totalThreads)
m.registry.MustRegister(m.busyThreads)
return m
}