From beef4c37c403167bc8a03812dacdf3fb348f2136 Mon Sep 17 00:00:00 2001 From: Daniel Woelfel Date: Fri, 7 Aug 2026 21:27:20 -0700 Subject: [PATCH] show proxied connections on the overview page --- client/www/pages/intern/overview.tsx | 65 +++++++++++++++++++++++- server/src/instant/app_proxy.clj | 13 +++++ server/src/instant/dash/routes.clj | 10 +++- server/src/instant/machine_summaries.clj | 27 ++++++++++ 4 files changed, 112 insertions(+), 3 deletions(-) diff --git a/client/www/pages/intern/overview.tsx b/client/www/pages/intern/overview.tsx index b9cd6f92a5..9ad7916314 100644 --- a/client/www/pages/intern/overview.tsx +++ b/client/www/pages/intern/overview.tsx @@ -33,7 +33,12 @@ type AppSessions = { }; type MachineSessions = Record; type SessionReports = Record; -type MinuteOverview = { 'session-reports': SessionReports }; +type ProxiedConnection = { count: number; target: string | null }; +type ProxiedConnections = Record>; +type MinuteOverview = { + 'session-reports': SessionReports; + 'proxied-connections': ProxiedConnections | null; +}; async function fetchMinuteOverview(token: string): Promise { return jsonFetch(`${config.apiURI}/dash/overview/minute`, { @@ -171,6 +176,29 @@ function flattenedSessionReports(machineToReport: SessionReports) { return items; } +type FlatProxiedConnection = { + 'app-id': AppId; + target: string | null; + count: number; +}; + +function flattenedProxiedConnections( + machineToConnections: ProxiedConnections | null, +): FlatProxiedConnection[] { + const res: Record = {}; + for (const machineId in machineToConnections) { + const machineConnections = machineToConnections[machineId]; + for (const appId in machineConnections) { + const curr = machineConnections[appId]; + const prev = res[appId]; + res[appId] = prev + ? { ...prev, count: prev.count + curr.count } + : { 'app-id': appId, target: curr.target, count: curr.count }; + } + } + return Object.values(res).toSorted((a, b) => b.count - a.count); +} + function makeMachineSummary( machineToReport: SessionReports, ): Record { @@ -390,6 +418,14 @@ const MinuteStatsSection = ({ ); const totalApps = Object.keys(sessions).length; + const proxiedConnections = flattenedProxiedConnections( + minute.data['proxied-connections'], + ); + const totalProxied = proxiedConnections.reduce( + (acc: number, x) => acc + x.count, + 0, + ); + return (
+ {proxiedConnections.length > 0 && ( +
+
+

Proxied Connections

+ + {Intl.NumberFormat().format(totalProxied)} across{' '} + {proxiedConnections.length} app + {proxiedConnections.length > 1 ? 's' : ''} + +
+
+ + + {proxiedConnections.map((conn) => ( + + + + + + ))} + +
+ {Intl.NumberFormat().format(conn.count)} + {conn['app-id']}{conn.target || '-'}
+
+
+ )}
diff --git a/server/src/instant/app_proxy.clj b/server/src/instant/app_proxy.clj index a5d8213015..e9e53135ca 100644 --- a/server/src/instant/app_proxy.clj +++ b/server/src/instant/app_proxy.clj @@ -172,6 +172,19 @@ (into [] cat (vals @proxied-websockets)) opts)) +(defn local-proxied-connections + "Summarizes the WebSocket connections this instance is currently proxying to + another backend, keyed by app id. The target comes from the live routing + table so it reflects the current config." + [] + (let [table (flags/app-proxy-targets)] + (reduce-kv + (fn [acc app-id connections] + (assoc acc app-id {:count (count connections) + :target (some-> ^URI (get table app-id) str)})) + {} + @proxied-websockets))) + (defn- changed-app-ids [old-targets new-targets] ;; Includes apps that were added or removed as well as apps whose target ;; origin changed. diff --git a/server/src/instant/dash/routes.clj b/server/src/instant/dash/routes.clj index 67a783b822..850b56269a 100644 --- a/server/src/instant/dash/routes.clj +++ b/server/src/instant/dash/routes.clj @@ -383,9 +383,15 @@ (defn admin-overview-minute-get [req] (let [{:keys [email]} (req->auth-user! req) _ (assert-admin-email! email) - session-reports (machine-summaries/get-session-reports-cached)] + session-reports (machine-summaries/get-session-reports-cached) + ;; Gate the cross-machine task behind a flag: older machines that + ;; predate proxied-connections-task can't run it, so only fan out once + ;; every machine has been updated and the flag is flipped on. + proxied-connections (when (flags/flag :proxied-connections-overview-enabled false) + (machine-summaries/get-proxied-connections-cached))] (response/ok - {:session-reports session-reports}))) + {:session-reports session-reports + :proxied-connections proxied-connections}))) (defn app-stats-get [req] (let [{{app-id :id} :app} (req->app-and-user! :collaborator req) diff --git a/server/src/instant/machine_summaries.clj b/server/src/instant/machine_summaries.clj index b0b09d0967..ce38d7284b 100644 --- a/server/src/instant/machine_summaries.clj +++ b/server/src/instant/machine_summaries.clj @@ -1,5 +1,6 @@ (ns instant.machine-summaries (:require + [instant.app-proxy :as app-proxy] [instant.flags :as flags] [instant.reactive.ephemeral :as eph] [instant.reactive.store :as rs] @@ -59,6 +60,32 @@ (fn [_] (get-session-reports (eph/get-hz)))))) +;; proxied connections + +(defn proxied-connections-task + [] + (app-proxy/local-proxied-connections)) + +(defn get-proxied-connections [hz] + (let [executor (HazelcastInstance/.getExecutorService hz "proxied-connections-executor") + futures (IExecutorService/.submitToAllMembers executor (hz/->Task #'proxied-connections-task))] + (into {} (for [[member fut] futures] + [(str (or (Member/.getAttribute member "instance-id") + (Member/.getAddress member))) + @fut])))) + +(comment + (get-proxied-connections (eph/get-hz))) + +(def proxied-connections-cache + (cache/make + {:ttl 5000 + :value-fn (fn [_] + (get-proxied-connections (eph/get-hz)))})) + +(defn get-proxied-connections-cached [] + (cache/get proxied-connections-cache :proxied-connections)) + ;; num sessions (defn num-sessions-task