From 7b90301cf817f3cd784d3b5de16bf57798ae6853 Mon Sep 17 00:00:00 2001 From: amirhnajafiz Date: Sat, 31 Jan 2026 12:53:04 -0500 Subject: [PATCH 1/5] add: newGauge function --- cli/internal/metrics/metrics.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/cli/internal/metrics/metrics.go b/cli/internal/metrics/metrics.go index 0dad08b1..3df753c5 100644 --- a/cli/internal/metrics/metrics.go +++ b/cli/internal/metrics/metrics.go @@ -22,6 +22,7 @@ package metrics import ( "context" + "errors" "fmt" "net" "net/http" @@ -58,6 +59,26 @@ type GaugeFuncs struct { GetIdleSeconds func() float64 } +// build and register a new Prometheus gauge by accepting its options. +func newGauge(gaugeOpts prometheus.GaugeOpts) prometheus.Gauge { + ev := prometheus.NewGauge(gaugeOpts) + + err := prometheus.Register(ev) + if err != nil { + var are prometheus.AlreadyRegisteredError + if ok := errors.As(err, &are); ok { + ev, ok = are.ExistingCollector.(prometheus.Gauge) + if !ok { + panic("different metric type registration") + } + } else { + panic(err) + } + } + + return ev +} + // New creates a new Metrics instance with all metrics registered func New(gaugeFuncs GaugeFuncs) *Metrics { registry := prometheus.NewRegistry() From 35146ac9f5e72c2118fa82834076cf69545d0e67 Mon Sep 17 00:00:00 2001 From: amirhnajafiz Date: Sat, 31 Jan 2026 12:57:17 -0500 Subject: [PATCH 2/5] add: newGaugeVec function --- cli/internal/metrics/metrics.go | 44 +++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/cli/internal/metrics/metrics.go b/cli/internal/metrics/metrics.go index 3df753c5..89606401 100644 --- a/cli/internal/metrics/metrics.go +++ b/cli/internal/metrics/metrics.go @@ -79,6 +79,26 @@ func newGauge(gaugeOpts prometheus.GaugeOpts) prometheus.Gauge { return ev } +// build and register a new Prometheus vector gauge by accepting its options and labels. +func newGaugeVec(gaugeOpts prometheus.GaugeOpts, labels []string) *prometheus.GaugeVec { + ev := prometheus.NewGaugeVec(gaugeOpts, labels) + + err := prometheus.Register(ev) + if err != nil { + var are prometheus.AlreadyRegisteredError + if ok := errors.As(err, &are); ok { + ev, ok = are.ExistingCollector.(*prometheus.GaugeVec) + if !ok { + panic("different metric type registration") + } + } else { + panic(err) + } + } + + return ev +} + // New creates a new Metrics instance with all metrics registered func New(gaugeFuncs GaugeFuncs) *Metrics { registry := prometheus.NewRegistry() @@ -88,56 +108,56 @@ func New(gaugeFuncs GaugeFuncs) *Metrics { registry.MustRegister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{})) m := &Metrics{ - ConnectingClients: prometheus.NewGauge( + ConnectingClients: newGauge( prometheus.GaugeOpts{ Namespace: namespace, Name: "connecting_clients", Help: "Number of clients currently connecting to the proxy", }, ), - ConnectedClients: prometheus.NewGauge( + ConnectedClients: newGauge( prometheus.GaugeOpts{ Namespace: namespace, Name: "connected_clients", Help: "Number of clients currently connected to the proxy", }, ), - IsLive: prometheus.NewGauge( + IsLive: newGauge( prometheus.GaugeOpts{ Namespace: namespace, Name: "is_live", Help: "Whether the service is connected to the Psiphon broker (1 = connected, 0 = disconnected)", }, ), - MaxClients: prometheus.NewGauge( + MaxClients: newGauge( prometheus.GaugeOpts{ Namespace: namespace, Name: "max_clients", Help: "Maximum number of proxy clients allowed", }, ), - BandwidthLimit: prometheus.NewGauge( + BandwidthLimit: newGauge( prometheus.GaugeOpts{ Namespace: namespace, Name: "bandwidth_limit_bytes_per_second", Help: "Configured bandwidth limit in bytes per second (0 = unlimited)", }, ), - BytesUploaded: prometheus.NewGauge( + BytesUploaded: newGauge( prometheus.GaugeOpts{ Namespace: namespace, Name: "bytes_uploaded", Help: "Total number of bytes uploaded through the proxy", }, ), - BytesDownloaded: prometheus.NewGauge( + BytesDownloaded: newGauge( prometheus.GaugeOpts{ Namespace: namespace, Name: "bytes_downloaded", Help: "Total number of bytes downloaded through the proxy", }, ), - BuildInfo: prometheus.NewGaugeVec( + BuildInfo: newGaugeVec( prometheus.GaugeOpts{ Namespace: namespace, Name: "build_info", @@ -167,16 +187,8 @@ func New(gaugeFuncs GaugeFuncs) *Metrics { ) // Register all metrics - registry.MustRegister(m.ConnectingClients) - registry.MustRegister(m.ConnectedClients) - registry.MustRegister(m.IsLive) - registry.MustRegister(m.MaxClients) - registry.MustRegister(m.BandwidthLimit) registry.MustRegister(uptimeSeconds) registry.MustRegister(idleSeconds) - registry.MustRegister(m.BytesUploaded) - registry.MustRegister(m.BytesDownloaded) - registry.MustRegister(m.BuildInfo) // Set build info From c7d049bee27b585efe2d71201e861278c04fc78e Mon Sep 17 00:00:00 2001 From: amirhnajafiz Date: Sat, 31 Jan 2026 13:01:57 -0500 Subject: [PATCH 3/5] update: prometheus registration method from must register to register --- cli/internal/metrics/metrics.go | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/cli/internal/metrics/metrics.go b/cli/internal/metrics/metrics.go index 89606401..86da374a 100644 --- a/cli/internal/metrics/metrics.go +++ b/cli/internal/metrics/metrics.go @@ -79,7 +79,7 @@ func newGauge(gaugeOpts prometheus.GaugeOpts) prometheus.Gauge { return ev } -// build and register a new Prometheus vector gauge by accepting its options and labels. +// build and register a new Prometheus gauge vector by accepting its options and labels. func newGaugeVec(gaugeOpts prometheus.GaugeOpts, labels []string) *prometheus.GaugeVec { ev := prometheus.NewGaugeVec(gaugeOpts, labels) @@ -99,6 +99,26 @@ func newGaugeVec(gaugeOpts prometheus.GaugeOpts, labels []string) *prometheus.Ga return ev } +// build and register a new Prometheus gauge function by accepting its options and function. +func newGaugeFunc(gaugeOpts prometheus.GaugeOpts, function func() float64) prometheus.GaugeFunc { + ev := prometheus.NewGaugeFunc(gaugeOpts, function) + + err := prometheus.Register(ev) + if err != nil { + var are prometheus.AlreadyRegisteredError + if ok := errors.As(err, &are); ok { + ev, ok = are.ExistingCollector.(prometheus.GaugeFunc) + if !ok { + panic("different metric type registration") + } + } else { + panic(err) + } + } + + return ev +} + // New creates a new Metrics instance with all metrics registered func New(gaugeFuncs GaugeFuncs) *Metrics { registry := prometheus.NewRegistry() @@ -169,7 +189,7 @@ func New(gaugeFuncs GaugeFuncs) *Metrics { } // Create GaugeFunc metrics (computed at scrape time) - uptimeSeconds := prometheus.NewGaugeFunc( + newGaugeFunc( prometheus.GaugeOpts{ Namespace: namespace, Name: "uptime_seconds", @@ -177,7 +197,7 @@ func New(gaugeFuncs GaugeFuncs) *Metrics { }, gaugeFuncs.GetUptimeSeconds, ) - idleSeconds := prometheus.NewGaugeFunc( + newGaugeFunc( prometheus.GaugeOpts{ Namespace: namespace, Name: "idle_seconds", @@ -186,12 +206,7 @@ func New(gaugeFuncs GaugeFuncs) *Metrics { gaugeFuncs.GetIdleSeconds, ) - // Register all metrics - registry.MustRegister(uptimeSeconds) - registry.MustRegister(idleSeconds) - // Set build info - buildInfo := buildinfo.GetBuildInfo() m.BuildInfo.WithLabelValues(buildInfo.BuildRepo, buildInfo.BuildRev, buildInfo.GoVersion, buildInfo.ValuesRev).Set(1) From 2d184b9eacfd1767207767af8fd79fc49aabf4cc Mon Sep 17 00:00:00 2001 From: amirhnajafiz Date: Sat, 31 Jan 2026 13:09:22 -0500 Subject: [PATCH 4/5] add: subsystem for metrics --- cli/internal/metrics/metrics.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cli/internal/metrics/metrics.go b/cli/internal/metrics/metrics.go index 86da374a..7cb9d448 100644 --- a/cli/internal/metrics/metrics.go +++ b/cli/internal/metrics/metrics.go @@ -131,6 +131,7 @@ func New(gaugeFuncs GaugeFuncs) *Metrics { ConnectingClients: newGauge( prometheus.GaugeOpts{ Namespace: namespace, + Subsystem: "proxy", Name: "connecting_clients", Help: "Number of clients currently connecting to the proxy", }, @@ -138,6 +139,7 @@ func New(gaugeFuncs GaugeFuncs) *Metrics { ConnectedClients: newGauge( prometheus.GaugeOpts{ Namespace: namespace, + Subsystem: "proxy", Name: "connected_clients", Help: "Number of clients currently connected to the proxy", }, @@ -145,6 +147,7 @@ func New(gaugeFuncs GaugeFuncs) *Metrics { IsLive: newGauge( prometheus.GaugeOpts{ Namespace: namespace, + Subsystem: "service", Name: "is_live", Help: "Whether the service is connected to the Psiphon broker (1 = connected, 0 = disconnected)", }, @@ -152,6 +155,7 @@ func New(gaugeFuncs GaugeFuncs) *Metrics { MaxClients: newGauge( prometheus.GaugeOpts{ Namespace: namespace, + Subsystem: "service", Name: "max_clients", Help: "Maximum number of proxy clients allowed", }, @@ -159,6 +163,7 @@ func New(gaugeFuncs GaugeFuncs) *Metrics { BandwidthLimit: newGauge( prometheus.GaugeOpts{ Namespace: namespace, + Subsystem: "proxy", Name: "bandwidth_limit_bytes_per_second", Help: "Configured bandwidth limit in bytes per second (0 = unlimited)", }, @@ -166,6 +171,7 @@ func New(gaugeFuncs GaugeFuncs) *Metrics { BytesUploaded: newGauge( prometheus.GaugeOpts{ Namespace: namespace, + Subsystem: "proxy", Name: "bytes_uploaded", Help: "Total number of bytes uploaded through the proxy", }, @@ -173,6 +179,7 @@ func New(gaugeFuncs GaugeFuncs) *Metrics { BytesDownloaded: newGauge( prometheus.GaugeOpts{ Namespace: namespace, + Subsystem: "proxy", Name: "bytes_downloaded", Help: "Total number of bytes downloaded through the proxy", }, @@ -180,6 +187,7 @@ func New(gaugeFuncs GaugeFuncs) *Metrics { BuildInfo: newGaugeVec( prometheus.GaugeOpts{ Namespace: namespace, + Subsystem: "service", Name: "build_info", Help: "Build information about the Conduit service", }, @@ -192,6 +200,7 @@ func New(gaugeFuncs GaugeFuncs) *Metrics { newGaugeFunc( prometheus.GaugeOpts{ Namespace: namespace, + Subsystem: "service", Name: "uptime_seconds", Help: "Number of seconds since the service started", }, @@ -200,6 +209,7 @@ func New(gaugeFuncs GaugeFuncs) *Metrics { newGaugeFunc( prometheus.GaugeOpts{ Namespace: namespace, + Subsystem: "proxy", Name: "idle_seconds", Help: "Number of seconds the proxy has been idle (0 connecting and 0 connected clients)", }, From 1497ace9113a71487c47c5b1c4971e2497706f91 Mon Sep 17 00:00:00 2001 From: amirhnajafiz Date: Sat, 31 Jan 2026 13:12:49 -0500 Subject: [PATCH 5/5] set: prometheus server timeouts --- cli/internal/metrics/metrics.go | 72 +++++---------------------------- cli/internal/metrics/utils.go | 67 ++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 62 deletions(-) create mode 100644 cli/internal/metrics/utils.go diff --git a/cli/internal/metrics/metrics.go b/cli/internal/metrics/metrics.go index 7cb9d448..f749461c 100644 --- a/cli/internal/metrics/metrics.go +++ b/cli/internal/metrics/metrics.go @@ -26,6 +26,7 @@ import ( "fmt" "net" "net/http" + "time" "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/buildinfo" "github.com/prometheus/client_golang/prometheus" @@ -59,66 +60,6 @@ type GaugeFuncs struct { GetIdleSeconds func() float64 } -// build and register a new Prometheus gauge by accepting its options. -func newGauge(gaugeOpts prometheus.GaugeOpts) prometheus.Gauge { - ev := prometheus.NewGauge(gaugeOpts) - - err := prometheus.Register(ev) - if err != nil { - var are prometheus.AlreadyRegisteredError - if ok := errors.As(err, &are); ok { - ev, ok = are.ExistingCollector.(prometheus.Gauge) - if !ok { - panic("different metric type registration") - } - } else { - panic(err) - } - } - - return ev -} - -// build and register a new Prometheus gauge vector by accepting its options and labels. -func newGaugeVec(gaugeOpts prometheus.GaugeOpts, labels []string) *prometheus.GaugeVec { - ev := prometheus.NewGaugeVec(gaugeOpts, labels) - - err := prometheus.Register(ev) - if err != nil { - var are prometheus.AlreadyRegisteredError - if ok := errors.As(err, &are); ok { - ev, ok = are.ExistingCollector.(*prometheus.GaugeVec) - if !ok { - panic("different metric type registration") - } - } else { - panic(err) - } - } - - return ev -} - -// build and register a new Prometheus gauge function by accepting its options and function. -func newGaugeFunc(gaugeOpts prometheus.GaugeOpts, function func() float64) prometheus.GaugeFunc { - ev := prometheus.NewGaugeFunc(gaugeOpts, function) - - err := prometheus.Register(ev) - if err != nil { - var are prometheus.AlreadyRegisteredError - if ok := errors.As(err, &are); ok { - ev, ok = are.ExistingCollector.(prometheus.GaugeFunc) - if !ok { - panic("different metric type registration") - } - } else { - panic(err) - } - } - - return ev -} - // New creates a new Metrics instance with all metrics registered func New(gaugeFuncs GaugeFuncs) *Metrics { registry := prometheus.NewRegistry() @@ -265,7 +206,14 @@ func (m *Metrics) StartServer(addr string) error { EnableOpenMetrics: true, })) - m.server = &http.Server{Addr: addr, Handler: mux} + m.server = &http.Server{ + Addr: addr, + Handler: mux, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 30 * time.Second, + TLSConfig: nil, + } // Create a listener to verify the port is available before starting the server listener, err := net.Listen("tcp", addr) @@ -275,7 +223,7 @@ func (m *Metrics) StartServer(addr string) error { // Start server in background with the pre-created listener go func() { - if err := m.server.Serve(listener); err != nil && err != http.ErrServerClosed { + if err := m.server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) { fmt.Printf("[ERROR] Metrics server error: %v\n", err) } }() diff --git a/cli/internal/metrics/utils.go b/cli/internal/metrics/utils.go new file mode 100644 index 00000000..bade1dc1 --- /dev/null +++ b/cli/internal/metrics/utils.go @@ -0,0 +1,67 @@ +package metrics + +import ( + "errors" + + "github.com/prometheus/client_golang/prometheus" +) + +// build and register a new Prometheus gauge by accepting its options. +func newGauge(gaugeOpts prometheus.GaugeOpts) prometheus.Gauge { + ev := prometheus.NewGauge(gaugeOpts) + + err := prometheus.Register(ev) + if err != nil { + var are prometheus.AlreadyRegisteredError + if ok := errors.As(err, &are); ok { + ev, ok = are.ExistingCollector.(prometheus.Gauge) + if !ok { + panic("different metric type registration") + } + } else { + panic(err) + } + } + + return ev +} + +// build and register a new Prometheus gauge vector by accepting its options and labels. +func newGaugeVec(gaugeOpts prometheus.GaugeOpts, labels []string) *prometheus.GaugeVec { + ev := prometheus.NewGaugeVec(gaugeOpts, labels) + + err := prometheus.Register(ev) + if err != nil { + var are prometheus.AlreadyRegisteredError + if ok := errors.As(err, &are); ok { + ev, ok = are.ExistingCollector.(*prometheus.GaugeVec) + if !ok { + panic("different metric type registration") + } + } else { + panic(err) + } + } + + return ev +} + +// build and register a new Prometheus gauge function by accepting its options and function. +func newGaugeFunc(gaugeOpts prometheus.GaugeOpts, function func() float64) prometheus.GaugeFunc { + ev := prometheus.NewGaugeFunc(gaugeOpts, function) + + err := prometheus.Register(ev) + if err != nil { + var are prometheus.AlreadyRegisteredError + if ok := errors.As(err, &are); ok { + ev, ok = are.ExistingCollector.(prometheus.GaugeFunc) + if !ok { + panic("different metric type registration") + } + } else { + panic(err) + } + } + + return ev +}