Add Fase 0 telemetry for the server-client migration baseline
Adds dev-only counters, with no behavior change, to answer the open
questions from the migration plan before touching the transport:
- frontend.state/get-invoke-stats: per-thread-api-method count/total-ms/
max-ms for every <invoke-db-worker call, to census how many invokes a
typical navigation generates and their latency distribution.
- db-worker-node: per-repo in-flight /v1/invoke counter that logs a new
concurrency high-water mark, to check whether concurrent client writes
actually overlap before deciding on a write queue.
- subs-loader/get-stats: dedup-hit vs new-entry counts for load!, and
batch-size distribution per slot kind at flush, to check whether
in-flight snapshot requests are already deduplicated and whether the
{:children 25 :resources 25} limits are worth raising for network RTT.
Verified with clojure -M:cljs compile app and compile db-worker-node
(0 warnings on both).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,22 @@
|
|||||||
|
|
||||||
(defonce ^:private *batch (atom {}))
|
(defonce ^:private *batch (atom {}))
|
||||||
|
|
||||||
|
(defonce ^:private *stats (atom {:dedup-hits 0 :new-entries 0 :flushes 0 :batch-sizes {}}))
|
||||||
|
|
||||||
|
(defn get-stats
|
||||||
|
"Snapshot of subs-loader batching counters: :dedup-hits counts load! calls
|
||||||
|
that reused an already-pending slot request within the same unflushed
|
||||||
|
batch window (see `load!`); :batch-sizes tallies entries per slot kind
|
||||||
|
across flushes. Dev-only instrumentation for the server-client migration
|
||||||
|
latency baseline (see docs/agent-guide); does not affect batching or the
|
||||||
|
`limits` above."
|
||||||
|
[]
|
||||||
|
@*stats)
|
||||||
|
|
||||||
|
(defn reset-stats!
|
||||||
|
[]
|
||||||
|
(reset! *stats {:dedup-hits 0 :new-entries 0 :flushes 0 :batch-sizes {}}))
|
||||||
|
|
||||||
(defn- reject!
|
(defn- reject!
|
||||||
[entries error]
|
[entries error]
|
||||||
(doseq [{:keys [result]} entries]
|
(doseq [{:keys [result]} entries]
|
||||||
@@ -45,6 +61,13 @@
|
|||||||
[]
|
[]
|
||||||
(when @state/db-worker-ready?
|
(when @state/db-worker-ready?
|
||||||
(let [batch (take-batch!)]
|
(let [batch (take-batch!)]
|
||||||
|
(swap! *stats
|
||||||
|
(fn [s]
|
||||||
|
(-> s
|
||||||
|
(update :flushes inc)
|
||||||
|
(update :batch-sizes
|
||||||
|
#(merge-with + %
|
||||||
|
(frequencies (map (comp slot-kind first :slot-key) batch)))))))
|
||||||
(doseq [[graph-id graph-entries] (group-by :graph-id batch)
|
(doseq [[graph-id graph-entries] (group-by :graph-id batch)
|
||||||
entries (request-groups graph-entries)]
|
entries (request-groups graph-entries)]
|
||||||
(-> (state/<invoke-db-worker :thread-api/get-render-snapshots
|
(-> (state/<invoke-db-worker :thread-api/get-render-snapshots
|
||||||
@@ -72,9 +95,11 @@
|
|||||||
[graph-id slot-key schedule!]
|
[graph-id slot-key schedule!]
|
||||||
(let [entry-key [graph-id slot-key]]
|
(let [entry-key [graph-id slot-key]]
|
||||||
(if-let [result (get-in @*batch [entry-key :result])]
|
(if-let [result (get-in @*batch [entry-key :result])]
|
||||||
result
|
(do (swap! *stats update :dedup-hits inc)
|
||||||
|
result)
|
||||||
(let [result (p/deferred)
|
(let [result (p/deferred)
|
||||||
schedule? (empty? @*batch)]
|
schedule? (empty? @*batch)]
|
||||||
|
(swap! *stats update :new-entries inc)
|
||||||
(swap! *batch assoc entry-key
|
(swap! *batch assoc entry-key
|
||||||
{:graph-id graph-id :slot-key slot-key :result result})
|
{:graph-id graph-id :slot-key slot-key :result result})
|
||||||
(when schedule?
|
(when schedule?
|
||||||
|
|||||||
@@ -63,6 +63,28 @@
|
|||||||
(reset! ready? (fn? worker))))
|
(reset! ready? (fn? worker))))
|
||||||
ready?))
|
ready?))
|
||||||
|
|
||||||
|
(defonce ^:private *invoke-stats (atom {}))
|
||||||
|
|
||||||
|
(defn get-invoke-stats
|
||||||
|
"Snapshot of db-worker invoke counters, keyed by thread-api method kw. Each
|
||||||
|
entry is {:count N :total-ms N :max-ms N}. Dev-only instrumentation for the
|
||||||
|
server-client migration latency baseline (see docs/agent-guide); does not
|
||||||
|
affect invocation behavior."
|
||||||
|
[]
|
||||||
|
@*invoke-stats)
|
||||||
|
|
||||||
|
(defn reset-invoke-stats!
|
||||||
|
[]
|
||||||
|
(reset! *invoke-stats {}))
|
||||||
|
|
||||||
|
(defn- record-invoke-stat!
|
||||||
|
[qkw elapsed-ms]
|
||||||
|
(swap! *invoke-stats update qkw
|
||||||
|
(fn [{:keys [count total-ms max-ms] :or {count 0 total-ms 0 max-ms 0}}]
|
||||||
|
{:count (inc count)
|
||||||
|
:total-ms (+ total-ms elapsed-ms)
|
||||||
|
:max-ms (max max-ms elapsed-ms)})))
|
||||||
|
|
||||||
(defn <invoke-db-worker
|
(defn <invoke-db-worker
|
||||||
"invoke db-worker thread api"
|
"invoke db-worker thread api"
|
||||||
[qkw & args]
|
[qkw & args]
|
||||||
@@ -70,11 +92,13 @@
|
|||||||
(when (nil? worker)
|
(when (nil? worker)
|
||||||
(prn :<invoke-db-worker-error qkw)
|
(prn :<invoke-db-worker-error qkw)
|
||||||
(throw (ex-info "db-worker has not been initialized" {})))
|
(throw (ex-info "db-worker has not been initialized" {})))
|
||||||
(p/let [result (apply worker qkw args)]
|
(let [started-at (js/performance.now)]
|
||||||
|
(-> (p/let [result (apply worker qkw args)]
|
||||||
(if (or (instance? ExceptionInfo result)
|
(if (or (instance? ExceptionInfo result)
|
||||||
(instance? js/Error result))
|
(instance? js/Error result))
|
||||||
(p/rejected result)
|
(p/rejected result)
|
||||||
result))))
|
result))
|
||||||
|
(p/finally (fn [] (record-invoke-stat! qkw (- (js/performance.now) started-at))))))))
|
||||||
|
|
||||||
(def ^:private export-block-text-indent-styles #{"dashes" "spaces" "no-indent"})
|
(def ^:private export-block-text-indent-styles #{"dashes" "spaces" "no-indent"})
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,25 @@
|
|||||||
(defonce ^:private *sse-clients (atom #{}))
|
(defonce ^:private *sse-clients (atom #{}))
|
||||||
(defonce ^:private *lock-info (atom nil))
|
(defonce ^:private *lock-info (atom nil))
|
||||||
(defonce ^:private *server-list-file (atom nil))
|
(defonce ^:private *server-list-file (atom nil))
|
||||||
|
(defonce ^:private *inflight-by-repo (atom {}))
|
||||||
|
(defonce ^:private *max-inflight-by-repo (atom {}))
|
||||||
|
|
||||||
|
(defn- invoke-started!
|
||||||
|
"Increments the /v1/invoke in-flight counter for repo and logs a new
|
||||||
|
concurrency high-water mark when observed. Dev-only instrumentation for the
|
||||||
|
server-client migration multi-client baseline (see docs/agent-guide); has no
|
||||||
|
effect on request handling."
|
||||||
|
[repo]
|
||||||
|
(let [n (get (swap! *inflight-by-repo update repo (fnil inc 0)) repo)
|
||||||
|
prev-max (get @*max-inflight-by-repo repo 0)]
|
||||||
|
(when (> n prev-max)
|
||||||
|
(swap! *max-inflight-by-repo assoc repo n)
|
||||||
|
(log/info :db-worker-node-invoke-concurrency-high-water-mark
|
||||||
|
{:repo repo :concurrent n}))))
|
||||||
|
|
||||||
|
(defn- invoke-finished!
|
||||||
|
[repo]
|
||||||
|
(swap! *inflight-by-repo update repo (fnil dec 0)))
|
||||||
|
|
||||||
(defn- server-list-file-path
|
(defn- server-list-file-path
|
||||||
[root-dir]
|
[root-dir]
|
||||||
@@ -376,7 +395,9 @@
|
|||||||
(p/let [_ (when-not (contains? non-repo-methods method-kw)
|
(p/let [_ (when-not (contains? non-repo-methods method-kw)
|
||||||
(let [{:keys [path lock]} @*lock-info]
|
(let [{:keys [path lock]} @*lock-info]
|
||||||
(db-lock/assert-lock-owner! path lock)))
|
(db-lock/assert-lock-owner! path lock)))
|
||||||
result (<invoke! proxy method-str method-kw args')]
|
_ (invoke-started! bound-repo)
|
||||||
|
result (-> (<invoke! proxy method-str method-kw args')
|
||||||
|
(p/finally (fn [] (invoke-finished! bound-repo))))]
|
||||||
(send-json! res 200 {:ok true :resultTransit result}))))
|
(send-json! res 200 {:ok true :resultTransit result}))))
|
||||||
(p/catch (fn [error]
|
(p/catch (fn [error]
|
||||||
(log-invoke-error! res error method-kw)))))
|
(log-invoke-error! res error method-kw)))))
|
||||||
|
|||||||
Reference in New Issue
Block a user