querysub 0.616.0 → 0.617.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.616.0",
3
+ "version": "0.617.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
@@ -73,7 +73,7 @@ function archiveBuilder(bucket: string, overrides: Partial<RemoteConfigBase>) {
73
73
  ]);
74
74
 
75
75
  return createArchives({
76
- version: 21,
76
+ version: 22,
77
77
  sources,
78
78
  });
79
79
  }
@@ -131,28 +131,28 @@ function getSafeDomain() {
131
131
  }
132
132
 
133
133
  export function getArchives2(folder: string) {
134
- return nestBucket(folder, archivesBuilderCache(getSafeDomain(), { noFullSync: true, }));
134
+ return nestBucket(folder, archivesBuilderCache(getSafeDomain(), {}));
135
135
  }
136
136
 
137
137
  /** The rights are buffered server-side, so if you do many writes at once, it won't slow down the disk and it won't result in many writes to back backblades. Of course, this does mean if the server crashes, you do lose some data.
138
138
  */
139
139
  export function getArchives2Buffered(folder: string, bufferDelay = timeInMinute * 5) {
140
- return nestBucket(folder, archivesBuilderCache(getSafeDomain() + "-fast", { noFullSync: true, fast: true, writeDelay: bufferDelay }));
140
+ return nestBucket(folder, archivesBuilderCache(getSafeDomain() + "-fast", { fast: true, writeDelay: bufferDelay }));
141
141
  }
142
142
 
143
143
  export function getArchives2BufferedPublic(folder: string, bufferDelay = timeInMinute * 5) {
144
- return nestBucket(folder, archivesBuilderCache(getSafeDomain() + "-public-fast", { noFullSync: true, fast: true, writeDelay: bufferDelay, public: true }));
144
+ return nestBucket(folder, archivesBuilderCache(getSafeDomain() + "-public-fast", { fast: true, writeDelay: bufferDelay, public: true }));
145
145
  }
146
146
 
147
147
  export function getArchives2PrivateImmutable(folder: string) {
148
- return nestBucket(folder, archivesBuilderCache(getSafeDomain() + "-private-immutable", { noFullSync: true, immutable: true }));
148
+ return nestBucket(folder, archivesBuilderCache(getSafeDomain() + "-private-immutable", { immutable: true }));
149
149
  }
150
150
  function getAllowedOrigins(domain: string) {
151
151
  return [`https://${domain}`, `https://127-0-0-1.${domain}:7007`];
152
152
  }
153
153
  export function getArchives2PublicImmutable(folder: string) {
154
154
  let domain = getSafeDomain();
155
- return nestBucket(folder, archivesBuilderCache(domain + "-public-immutable", { noFullSync: true, immutable: true, public: true, allowedOrigins: getAllowedOrigins(getDomain()) }));
155
+ return nestBucket(folder, archivesBuilderCache(domain + "-public-immutable", { immutable: true, public: true, allowedOrigins: getAllowedOrigins(getDomain()) }));
156
156
  }
157
157
  export function getArchives2Public(folder: string) {
158
158
  let domain = getSafeDomain();
@@ -13,6 +13,7 @@ import { getSlowdown, isDiskAudit, getDomain } from "../config";
13
13
  import { decodeNodeId } from "sliftutils/misc/https/certs";
14
14
  import { areNodeIdsEqual, isOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
15
15
  import { recordPathValuesSent, recordPathValuesReceived } from "../-f-node-discovery/TrafficTracking";
16
+ import { recordPathValuesSentStats, recordPathValuesReceivedStats } from "./PathValueStats";
16
17
  import { getNodeIdIP } from "socket-function/src/nodeCache";
17
18
  import { authorityLookup } from "./AuthorityLookup";
18
19
  import { timeoutToError } from "../errors";
@@ -60,6 +61,7 @@ export class PathValueControllerBase {
60
61
  let { pathValues, nodeId } = config;
61
62
 
62
63
  pathValueSendCount += pathValues.length;
64
+ recordPathValuesSentStats(pathValues);
63
65
  let serializedValues = await measureBlock(() => pathValueSerializer.serialize(pathValues, { compress: Querysub.COMPRESS_NETWORK }), "createValues|serialize");
64
66
  if (isDebugLogEnabled()) {
65
67
  for (let value of pathValues) {
@@ -119,6 +121,7 @@ export class PathValueControllerBase {
119
121
  let { nodeId, initialTriggers } = config;
120
122
  pathValueSendCount += changes.length;
121
123
  recordPathValuesSent({ count: changes.length, nodeId });
124
+ recordPathValuesSentStats(changes);
122
125
  let buffers = await measureBlock(() => pathValueSerializer.serialize(changes, {
123
126
  noLocks: !config.keepLocks,
124
127
  compress: getCompressNetwork(),
@@ -156,6 +159,7 @@ export class PathValueControllerBase {
156
159
  values = await measureBlock(() => pathValueSerializer.deserialize(valueBuffers), "sendData|deserialize");
157
160
  ActionsHistory.OnRead(values);
158
161
  recordPathValuesReceived({ count: values.length, nodeId: callerId });
162
+ recordPathValuesReceivedStats(values);
159
163
  }
160
164
  if (debugOnSendData) {
161
165
  debugOnSendData(values, callerId);
@@ -0,0 +1,83 @@
1
+ import { SocketFunction } from "socket-function/SocketFunction";
2
+ import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
3
+ import { TreeSummary } from "sliftutils/treeSummary";
4
+ import type { SummaryEntry } from "sliftutils/treeSummary";
5
+
6
+ /*
7
+ Per-server, in-memory stats for path values this node has sent and received (never persisted). Two things are
8
+ tracked: a running total count for each direction, and a TreeSummary of the paths in each direction — a bounded
9
+ prefix tree that stays O(1000s of nodes) no matter how many distinct paths flow through, so we can show which
10
+ paths dominate without keeping every path. Cleared wholesale by the clear endpoint.
11
+ */
12
+
13
+ const PATH_SUMMARY_EXPECTED_OUTPUT_COUNT = 100;
14
+
15
+ export type PathValueSummaryState = { count: number };
16
+ export type PathValueTotals = { sent: number; received: number };
17
+ export type PathValueDirection = "sent" | "received";
18
+
19
+ // Only the path matters for the tree; callers pass whole PathValues but we never look at anything else.
20
+ type PathValueLike = { path: string };
21
+
22
+ function makeTree() {
23
+ return new TreeSummary<PathValueLike, PathValueSummaryState>({
24
+ getPath: value => value.path,
25
+ createSummary: () => ({ count: 0 }),
26
+ addToSummary: (_value, summary) => { summary.count++; },
27
+ mergeSummaries: (target, source) => { target.count += source.count; },
28
+ getWeight: summary => summary.count,
29
+ expectedOutputCount: PATH_SUMMARY_EXPECTED_OUTPUT_COUNT,
30
+ });
31
+ }
32
+
33
+ let totalSent = 0;
34
+ let totalReceived = 0;
35
+ let sentTree = makeTree();
36
+ let receivedTree = makeTree();
37
+
38
+ export function recordPathValuesSentStats(pathValues: PathValueLike[]) {
39
+ totalSent += pathValues.length;
40
+ for (let value of pathValues) {
41
+ sentTree.add(value);
42
+ }
43
+ }
44
+ export function recordPathValuesReceivedStats(pathValues: PathValueLike[]) {
45
+ totalReceived += pathValues.length;
46
+ for (let value of pathValues) {
47
+ receivedTree.add(value);
48
+ }
49
+ }
50
+ export function clearPathValueStats() {
51
+ totalSent = 0;
52
+ totalReceived = 0;
53
+ sentTree = makeTree();
54
+ receivedTree = makeTree();
55
+ }
56
+
57
+ class PathValueStatsControllerBase {
58
+ // Cheap: just the two running totals, so the server list can render every server without pulling any tree data.
59
+ public async getTotals(): Promise<PathValueTotals> {
60
+ return { sent: totalSent, received: totalReceived };
61
+ }
62
+ // Heavy: the top-N path breakdown for one direction. Only fetched when a server row is expanded.
63
+ public async getSummary(direction: PathValueDirection, maxCount: number): Promise<SummaryEntry<PathValueSummaryState>[]> {
64
+ let tree = direction === "sent" ? sentTree : receivedTree;
65
+ return tree.getSummaries(maxCount);
66
+ }
67
+ public async clear(): Promise<void> {
68
+ clearPathValueStats();
69
+ }
70
+ }
71
+
72
+ export const PathValueStatsController = SocketFunction.register(
73
+ "PathValueStatsController-3f9a1c72-2b64-4e8d-9c1a-7d5e0b3f6a21",
74
+ new PathValueStatsControllerBase(),
75
+ () => ({
76
+ getTotals: {},
77
+ getSummary: {},
78
+ clear: {},
79
+ }),
80
+ () => ({
81
+ hooks: [requiresNetworkTrustHook],
82
+ })
83
+ );
@@ -0,0 +1,38 @@
1
+ module.allowclient = true;
2
+
3
+ import { qreact } from "../../4-dom/qreact";
4
+ import { css } from "typesafecss";
5
+ import { formatTime } from "socket-function/src/formatting/format";
6
+ import { getFunctionRunnerIndex } from "../../4-querysub/FunctionRunnerTracking";
7
+ import { FUNCTION_RUNNER_COLOR } from "../../misc/nodeCategoryColors";
8
+ import { AuthorityRangeBar } from "./RoutingTablePage";
9
+
10
+ export class FunctionRunnersSection extends qreact.Component {
11
+ render() {
12
+ let index = getFunctionRunnerIndex();
13
+ let nodes = index?.nodes || [];
14
+ return <div className={css.vbox(8).fillWidth}>
15
+ <h2>Function Runners ({nodes.length})</h2>
16
+ {nodes.length === 0 && <div className={css.colorhsl(0, 0, 50)}>(no function runners found yet)</div>}
17
+ {nodes.map(node =>
18
+ <div className={css.vbox(4).pad2(10).fillWidth.bord2(0, 0, 85).hsl(0, 0, 99)}>
19
+ <div className={css.hbox(10).fillWidth}>
20
+ <span className={css.boldStyle}>{node.nodeId}</span>
21
+ <span className={css.color(FUNCTION_RUNNER_COLOR)}>networks: {node.networks.join(", ")}</span>
22
+ {!node.isPublic && <span className={css.colorhsl(0, 70, 35)}>(non-public)</span>}
23
+ <span>latency {formatTime(node.averageLatency)}</span>
24
+ <span>up for {formatTime(Date.now() - node.startupTime)}</span>
25
+ <span className={css.colorhsl(0, 0, 40).ellipsis}>{node.entryPoint}</span>
26
+ </div>
27
+ {node.shards.map(shard =>
28
+ <div className={css.hbox(10).fillWidth}>
29
+ <span>{shard.shardRange.startFraction.toFixed(4)} - {shard.shardRange.endFraction.toFixed(4)}</span>
30
+ <AuthorityRangeBar start={shard.shardRange.startFraction} end={shard.shardRange.endFraction} />
31
+ {shard.secondaryShardRange && <span className={css.colorhsl(0, 0, 50)}>secondary {shard.secondaryShardRange.startFraction.toFixed(4)} - {shard.secondaryShardRange.endFraction.toFixed(4)}</span>}
32
+ </div>
33
+ )}
34
+ </div>
35
+ )}
36
+ </div>;
37
+ }
38
+ }