mirror of
https://github.com/php/frankenphp.git
synced 2026-03-24 00:52:11 +01:00
* 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>
253 lines
6.3 KiB
Go
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
|
|
}
|