From 69cbd562e354bd1c296da6585c3999c2c13eadd2 Mon Sep 17 00:00:00 2001 From: lillian Date: Mon, 13 Jul 2026 18:43:01 +0000 Subject: [PATCH] Report agent WireGuard transport metric (udp vs websocket) The agent now reports which transport it used to establish a WireGuard tunnel (plain UDP or the websocket proxy) to flyctl-metrics via a new agent_wireguard/transport metric. Since the agent is a long-lived daemon that never reaches the end-of-command metrics flush, this adds metrics.SendImmediate, which posts a single-message batch right away through the existing SendMetrics path (respecting send_metrics config, the dev-build guard, and metrics-token handling). Collector side: superfly/flyctl-metrics agent-wireguard-transport-metric Co-Authored-By: Claude Fable 5 --- agent/server/server.go | 8 ++++++++ internal/metrics/api.go | 29 +++++++++++++++++++++++++++++ internal/metrics/helpers.go | 7 +++++++ 3 files changed, 44 insertions(+) diff --git a/agent/server/server.go b/agent/server/server.go index d00b11a56d..f026a61e9b 100644 --- a/agent/server/server.go +++ b/agent/server/server.go @@ -19,6 +19,7 @@ import ( "github.com/superfly/flyctl/internal/config" "github.com/superfly/flyctl/internal/env" "github.com/superfly/flyctl/internal/flyutil" + "github.com/superfly/flyctl/internal/metrics" "github.com/superfly/flyctl/internal/metrics/synthetics" "github.com/superfly/flyctl/internal/sentry" "github.com/superfly/flyctl/internal/wireguard" @@ -245,7 +246,10 @@ func (s *server) buildTunnel(ctx context.Context, org *fly.Organization, reestab } // WIP: can't stay this way, need something more clever than this + transport := "udp" if env.IsCI() || os.Getenv("WSWG") != "" || s.ConfigWebsockets { + transport = "websocket" + if tunnel, err = wg.ConnectWS(ctx, state); err != nil { return } @@ -255,6 +259,10 @@ func (s *server) buildTunnel(ctx context.Context, org *fly.Organization, reestab } } + // use the agent's run context: the session context may be gone before the + // send completes + metrics.AgentWireGuardTransport(s.runCtx, transport) + s.tunnels[tk] = tunnel return diff --git a/internal/metrics/api.go b/internal/metrics/api.go index 34681c3d2b..86c94e38c0 100644 --- a/internal/metrics/api.go +++ b/internal/metrics/api.go @@ -32,6 +32,35 @@ func rawSend(parentCtx context.Context, metricSlug string, payload json.RawMessa queueMetric(message) } +// SendImmediate sends a single metric to the collector right away instead of +// queueing it for the end-of-command flush. Long-running processes (e.g. the +// agent) queue metrics that would otherwise never be flushed. +func SendImmediate[T any](ctx context.Context, metricSlug string, value T) { + if !shouldSendMetrics(ctx) { + return + } + + payload, err := json.Marshal(value) + if err != nil { + return + } + + buf, err := json.Marshal([]metricsMessage{{ + Metric: metricSlug, + Payload: payload, + }}) + if err != nil { + return + } + + done.Add(1) + go func() { + defer done.Done() + + _ = SendMetrics(ctx, string(buf)) + }() +} + func shouldSendMetrics(ctx context.Context) bool { if !Enabled { return false diff --git a/internal/metrics/helpers.go b/internal/metrics/helpers.go index 32dc03fde7..ea9d65ad38 100644 --- a/internal/metrics/helpers.go +++ b/internal/metrics/helpers.go @@ -140,6 +140,13 @@ func DeployStatus(ctx context.Context, payload DeployStatusPayload) { Send(ctx, "deploy/status", payload) } +// AgentWireGuardTransport reports which transport the agent used to establish +// a WireGuard tunnel: "udp" or "websocket". It sends immediately because the +// agent daemon never reaches the end-of-command metrics flush. +func AgentWireGuardTransport(ctx context.Context, transport string) { + SendImmediate(ctx, "agent_wireguard/transport", map[string]string{"transport": transport}) +} + func Send[T any](ctx context.Context, metricSlug string, value T) { valJson, err := json.Marshal(value) if err != nil {