Skip to content

frankenphp_total_threads is registered as a Counter but is semantically a Gauge #2481

Closed

Description

@cattz

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:

  1. 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.
  2. 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.

Activity

  1. withinboredom commented on Jul 5, 2026

    @withinboredom
    Member

    PR welcome! This is likely an artifact from before frankenphp could stop threads permanently (a long time ago, in a repo far away, they could only be restarted).

  2. added a commit that references this issue on Jul 22, 2026
    b1ccb0e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions