Summary
The frankenphp_total_threads metric is registered as a Prometheus Counter, but its value is semantically a Gauge — it reflects the current configured PHP thread-pool size, which only changes on (re)configuration, not a monotonically increasing total.
In metrics.go (current main):
totalThreads prometheus.Counter
// ...
totalThreads: prometheus.NewCounter(prometheus.CounterOpts{
Name: "frankenphp_total_threads",
...
}),
// ...
func (m *PrometheusMetrics) TotalThreads(num int) {
...
m.totalThreads.Add(float64(num))
}
By contrast, the sibling thread metrics frankenphp_busy_threads and frankenphp_queue_depth are correctly registered with prometheus.NewGauge.
Two problems follow from the Counter registration:
- The exposition emits
# TYPE frankenphp_total_threads counter. Prometheus-conformant consumers (Datadog's OpenMetrics V2 check, Grafana Agent, etc.) honor the declared type and treat it as a rate / apply .as_count(), so a steady non-incrementing value reads as 0. Today consumers must add an explicit per-metric type: gauge override to recover the real value.
- The update path uses
Counter.Add(num), so any call to TotalThreads(num) after the first accumulates rather than reporting the current pool size — incorrect once the thread count changes (e.g. thread autoscaling / reconfiguration).
Reproduction
Enable metrics (e.g. servers { metrics }) and scrape the metrics endpoint:
$ curl -s <metrics-endpoint> | grep -A1 frankenphp_total_threads
# TYPE frankenphp_total_threads counter
frankenphp_total_threads <N>
Suggested fix
Register total_threads as a Gauge and report it with .Set():
totalThreads prometheus.Gauge
// ...
totalThreads: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "frankenphp_total_threads",
...
}),
// ...
func (m *PrometheusMetrics) TotalThreads(num int) {
...
m.totalThreads.Set(float64(num))
}
This matches the existing treatment of busy_threads / queue_depth and lets standard Prometheus consumers read it correctly without per-metric type overrides.
Happy to send a PR if this looks right.
Summary
The
frankenphp_total_threadsmetric is registered as a Prometheus Counter, but its value is semantically a Gauge — it reflects the current configured PHP thread-pool size, which only changes on (re)configuration, not a monotonically increasing total.In
metrics.go(currentmain):By contrast, the sibling thread metrics
frankenphp_busy_threadsandfrankenphp_queue_depthare correctly registered withprometheus.NewGauge.Two problems follow from the
Counterregistration:# TYPE frankenphp_total_threads counter. Prometheus-conformant consumers (Datadog's OpenMetrics V2 check, Grafana Agent, etc.) honor the declared type and treat it as a rate / apply.as_count(), so a steady non-incrementing value reads as 0. Today consumers must add an explicit per-metrictype: gaugeoverride to recover the real value.Counter.Add(num), so any call toTotalThreads(num)after the first accumulates rather than reporting the current pool size — incorrect once the thread count changes (e.g. thread autoscaling / reconfiguration).Reproduction
Enable metrics (e.g.
servers { metrics }) and scrape the metrics endpoint:Suggested fix
Register
total_threadsas a Gauge and report it with.Set():This matches the existing treatment of
busy_threads/queue_depthand lets standard Prometheus consumers read it correctly without per-metric type overrides.Happy to send a PR if this looks right.