mirror of
https://github.com/dunglas/frankenphp.git
synced 2025-12-24 13:38:11 +08:00
335 lines
8.5 KiB
Go
335 lines
8.5 KiB
Go
package frankenphp
|
|
|
|
import (
|
|
"path/filepath"
|
|
"regexp"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
)
|
|
|
|
var metricsNameRegex = regexp.MustCompile(`\W+`)
|
|
var metricsNameFixRegex = regexp.MustCompile(`^_+|_+$`)
|
|
|
|
const (
|
|
StopReasonCrash = iota
|
|
StopReasonRestart
|
|
StopReasonShutdown
|
|
)
|
|
|
|
type StopReason int
|
|
|
|
type Metrics interface {
|
|
// StartWorker collects started workers
|
|
StartWorker(name string)
|
|
// ReadyWorker collects ready workers
|
|
ReadyWorker(name string)
|
|
// StopWorker collects stopped workers
|
|
StopWorker(name string, reason StopReason)
|
|
// 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(string) {
|
|
}
|
|
|
|
func (n nullMetrics) ReadyWorker(string) {
|
|
}
|
|
|
|
func (n nullMetrics) StopWorker(string, StopReason) {
|
|
}
|
|
|
|
func (n nullMetrics) TotalWorkers(string, int) {
|
|
}
|
|
|
|
func (n nullMetrics) TotalThreads(int) {
|
|
}
|
|
|
|
func (n nullMetrics) StartRequest() {
|
|
}
|
|
|
|
func (n nullMetrics) StopRequest() {
|
|
}
|
|
|
|
func (n nullMetrics) StopWorkerRequest(string, time.Duration) {
|
|
}
|
|
|
|
func (n nullMetrics) StartWorkerRequest(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
|
|
readyWorkers map[string]prometheus.Gauge
|
|
workerCrashes map[string]prometheus.Counter
|
|
workerRestarts map[string]prometheus.Counter
|
|
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) ReadyWorker(name string) {
|
|
if _, ok := m.totalWorkers[name]; !ok {
|
|
return
|
|
}
|
|
|
|
m.readyWorkers[name].Inc()
|
|
}
|
|
|
|
func (m *PrometheusMetrics) StopWorker(name string, reason StopReason) {
|
|
m.busyThreads.Dec()
|
|
|
|
// tests do not register workers before starting them
|
|
if _, ok := m.totalWorkers[name]; !ok {
|
|
return
|
|
}
|
|
m.totalWorkers[name].Dec()
|
|
m.readyWorkers[name].Dec()
|
|
|
|
if reason == StopReasonCrash {
|
|
m.workerCrashes[name].Inc()
|
|
} else if reason == StopReasonRestart {
|
|
m.workerRestarts[name].Inc()
|
|
} else if reason == StopReasonShutdown {
|
|
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, _ 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.workerCrashes[identity]; !ok {
|
|
m.workerCrashes[identity] = prometheus.NewCounter(prometheus.CounterOpts{
|
|
Namespace: "frankenphp",
|
|
Subsystem: subsystem,
|
|
Name: "worker_crashes",
|
|
Help: "Number of PHP worker crashes for this worker",
|
|
})
|
|
m.registry.MustRegister(m.workerCrashes[identity])
|
|
}
|
|
|
|
if _, ok := m.workerRestarts[identity]; !ok {
|
|
m.workerRestarts[identity] = prometheus.NewCounter(prometheus.CounterOpts{
|
|
Namespace: "frankenphp",
|
|
Subsystem: subsystem,
|
|
Name: "worker_restarts",
|
|
Help: "Number of PHP worker restarts for this worker",
|
|
})
|
|
m.registry.MustRegister(m.workerRestarts[identity])
|
|
}
|
|
|
|
if _, ok := m.readyWorkers[identity]; !ok {
|
|
m.readyWorkers[identity] = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Namespace: "frankenphp",
|
|
Subsystem: subsystem,
|
|
Name: "ready_workers",
|
|
Help: "Running workers that have successfully called frankenphp_handle_request at least once",
|
|
})
|
|
m.registry.MustRegister(m.readyWorkers[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)
|
|
}
|
|
|
|
for _, c := range m.workerCrashes {
|
|
m.registry.Unregister(c)
|
|
}
|
|
|
|
for _, c := range m.workerRestarts {
|
|
m.registry.Unregister(c)
|
|
}
|
|
|
|
for _, g := range m.readyWorkers {
|
|
m.registry.Unregister(g)
|
|
}
|
|
|
|
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.workerRestarts = map[string]prometheus.Counter{}
|
|
m.workerCrashes = map[string]prometheus.Counter{}
|
|
m.readyWorkers = map[string]prometheus.Gauge{}
|
|
|
|
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{},
|
|
workerRestarts: map[string]prometheus.Counter{},
|
|
workerCrashes: map[string]prometheus.Counter{},
|
|
readyWorkers: map[string]prometheus.Gauge{},
|
|
}
|
|
|
|
m.registry.MustRegister(m.totalThreads)
|
|
m.registry.MustRegister(m.busyThreads)
|
|
|
|
return m
|
|
}
|