querysub 0.520.0 → 0.522.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/bin/function-public.js +0 -2
- package/package.json +2 -2
- package/src/-f-node-discovery/TrafficTracking.ts +49 -28
- package/src/3-path-functions/PathFunctionRunner.ts +6 -5
- package/src/3-path-functions/PathFunctionRunnerMain.ts +4 -7
- package/src/config.ts +1 -3
- package/src/deployManager/components/ServiceDetailPage.tsx +32 -8
- package/src/diagnostics/misc-pages/RoutingTablePage.tsx +303 -76
- package/src/library-components/LatencyGraph.tsx +156 -736
package/bin/function-public.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "querysub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.522.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",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"pako": "^2.1.0",
|
|
71
71
|
"peggy": "^5.0.6",
|
|
72
72
|
"sliftutils": "^1.7.5",
|
|
73
|
-
"socket-function": "^1.2.
|
|
73
|
+
"socket-function": "^1.2.12",
|
|
74
74
|
"terser": "^5.31.0",
|
|
75
75
|
"typenode": "^6.6.1",
|
|
76
76
|
"typesafecss": "^0.32.0",
|
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
Per-server, in-memory traffic counters (never written to the database). Everything is just an integer increment
|
|
3
3
|
into a 5-minute bucket; we keep the last 60 minutes (12 buckets) and drop older ones, so memory is bounded.
|
|
4
4
|
|
|
5
|
+
Reads return PER-SECOND RATES, not totals. Each bucket also records the timestamp it first received data, so the
|
|
6
|
+
rate window runs from the oldest retained bucket's first data up to now — which stays accurate right after startup
|
|
7
|
+
(when only a partial window of data exists) instead of dividing a small burst by the full 60-minute window.
|
|
8
|
+
|
|
5
9
|
Byte traffic comes from SocketFunction.trackMessageSizes (upload/download per connection). The connection nodeId is
|
|
6
10
|
sometimes a raw client-connection id, so we map it to the nice node id (debugNodeId) lazily at read time — the hot
|
|
7
|
-
path is just a Map increment keyed by the raw id. At read we split traffic into per-known-node
|
|
11
|
+
path is just a Map increment keyed by the raw id. At read we split traffic into per-known-node rates and a single
|
|
8
12
|
"outside the network" aggregate for anything not in our cached node list (clients, etc.).
|
|
9
13
|
*/
|
|
10
14
|
|
|
@@ -17,21 +21,28 @@ import { debugNodeId } from "../-c-identity/IdentityController";
|
|
|
17
21
|
|
|
18
22
|
const BUCKET_MS = timeInMinute * 5;
|
|
19
23
|
const BUCKET_COUNT = 12;
|
|
24
|
+
// Floor on the rate window so a burst in the first fraction of a second after startup can't divide out to an absurd rate.
|
|
25
|
+
const MIN_WINDOW_SECONDS = 1;
|
|
20
26
|
// Sentinel key for traffic that isn't a network node (client connections, etc.). Aggregated so memory stays O(nodes),
|
|
21
27
|
// not O(client connections) — clients churn through many raw ids per hour and we never want a bucket per client.
|
|
22
28
|
const OUTSIDE_KEY = "";
|
|
23
29
|
|
|
24
30
|
export type NodeDataTraffic = { sent: number; received: number; };
|
|
31
|
+
// All numbers are PER-SECOND rates (see file header).
|
|
25
32
|
export type TrafficStats = {
|
|
26
33
|
functionsExecuted: number;
|
|
27
34
|
pathValuesSent: number;
|
|
28
35
|
pathValuesReceived: number;
|
|
29
36
|
querysubCalls: number;
|
|
30
|
-
//
|
|
37
|
+
// Byte rates to/from each known network node, plus one aggregate for everything outside the node list.
|
|
31
38
|
perNode: { [nodeId: string]: NodeDataTraffic };
|
|
32
39
|
outside: NodeDataTraffic;
|
|
33
40
|
};
|
|
34
41
|
|
|
42
|
+
// count = amount accumulated in the bucket; firstMs = when the bucket first received data (for the rate window).
|
|
43
|
+
type Bucket = { count: number; firstMs: number; };
|
|
44
|
+
type BucketMap = Map<number, Bucket>;
|
|
45
|
+
|
|
35
46
|
function currentBucket() {
|
|
36
47
|
return Math.floor(Date.now() / BUCKET_MS);
|
|
37
48
|
}
|
|
@@ -39,20 +50,24 @@ function oldestBucket() {
|
|
|
39
50
|
return currentBucket() - BUCKET_COUNT + 1;
|
|
40
51
|
}
|
|
41
52
|
|
|
42
|
-
|
|
43
|
-
let
|
|
44
|
-
let
|
|
45
|
-
let
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
let
|
|
49
|
-
let bytesReceived = new Map<string, Map<number, number>>();
|
|
53
|
+
let functionsExecuted: BucketMap = new Map();
|
|
54
|
+
let pathValuesSent: BucketMap = new Map();
|
|
55
|
+
let pathValuesReceived: BucketMap = new Map();
|
|
56
|
+
let querysubCalls: BucketMap = new Map();
|
|
57
|
+
// raw connection nodeId => buckets
|
|
58
|
+
let bytesSent = new Map<string, BucketMap>();
|
|
59
|
+
let bytesReceived = new Map<string, BucketMap>();
|
|
50
60
|
|
|
51
|
-
function addSimple(buckets:
|
|
61
|
+
function addSimple(buckets: BucketMap, amount: number) {
|
|
52
62
|
let bucket = currentBucket();
|
|
53
|
-
|
|
63
|
+
let entry = buckets.get(bucket);
|
|
64
|
+
if (!entry) {
|
|
65
|
+
entry = { count: 0, firstMs: Date.now() };
|
|
66
|
+
buckets.set(bucket, entry);
|
|
67
|
+
}
|
|
68
|
+
entry.count += amount;
|
|
54
69
|
}
|
|
55
|
-
function addKeyed(map: Map<string,
|
|
70
|
+
function addKeyed(map: Map<string, BucketMap>, key: string, amount: number) {
|
|
56
71
|
let buckets = map.get(key);
|
|
57
72
|
if (!buckets) {
|
|
58
73
|
buckets = new Map();
|
|
@@ -84,17 +99,23 @@ function trafficKey(nodeId: string): string {
|
|
|
84
99
|
SocketFunction.trackMessageSizes.upload.push((size, nodeId) => addKeyed(bytesSent, trafficKey(nodeId), size));
|
|
85
100
|
SocketFunction.trackMessageSizes.download.push((size, nodeId) => addKeyed(bytesReceived, trafficKey(nodeId), size));
|
|
86
101
|
|
|
87
|
-
|
|
102
|
+
// Per-second rate over the retained window: total accumulated / (now - oldest retained bucket's first-data time).
|
|
103
|
+
// Also prunes expired buckets as it goes.
|
|
104
|
+
function bucketRate(buckets: BucketMap): number {
|
|
88
105
|
let oldest = oldestBucket();
|
|
89
106
|
let total = 0;
|
|
90
|
-
|
|
107
|
+
let firstMs = Infinity;
|
|
108
|
+
for (let [bucket, entry] of Array.from(buckets)) {
|
|
91
109
|
if (bucket < oldest) {
|
|
92
110
|
buckets.delete(bucket);
|
|
93
111
|
continue;
|
|
94
112
|
}
|
|
95
|
-
total += count;
|
|
113
|
+
total += entry.count;
|
|
114
|
+
if (entry.firstMs < firstMs) firstMs = entry.firstMs;
|
|
96
115
|
}
|
|
97
|
-
return
|
|
116
|
+
if (total <= 0 || !Number.isFinite(firstMs)) return 0;
|
|
117
|
+
let windowSeconds = Math.max(MIN_WINDOW_SECONDS, (Date.now() - firstMs) / 1000);
|
|
118
|
+
return total / windowSeconds;
|
|
98
119
|
}
|
|
99
120
|
|
|
100
121
|
function aggregateData(): { perNode: { [nodeId: string]: NodeDataTraffic }; outside: NodeDataTraffic; } {
|
|
@@ -103,10 +124,10 @@ function aggregateData(): { perNode: { [nodeId: string]: NodeDataTraffic }; outs
|
|
|
103
124
|
let outside: NodeDataTraffic = { sent: 0, received: 0 };
|
|
104
125
|
// At most (node count + 1) keys, so this whole pass is O(nodes). debugNodeId (a linear lookup) is only used for
|
|
105
126
|
// the rare server-style id that isn't already a known node.
|
|
106
|
-
let attribute = (rawNodeId: string, field: "sent" | "received",
|
|
107
|
-
if (
|
|
127
|
+
let attribute = (rawNodeId: string, field: "sent" | "received", rate: number) => {
|
|
128
|
+
if (rate <= 0) return;
|
|
108
129
|
if (rawNodeId === OUTSIDE_KEY) {
|
|
109
|
-
outside[field] +=
|
|
130
|
+
outside[field] += rate;
|
|
110
131
|
return;
|
|
111
132
|
}
|
|
112
133
|
let nice = known.has(rawNodeId) ? rawNodeId : debugNodeId(rawNodeId);
|
|
@@ -116,17 +137,17 @@ function aggregateData(): { perNode: { [nodeId: string]: NodeDataTraffic }; outs
|
|
|
116
137
|
entry = { sent: 0, received: 0 };
|
|
117
138
|
perNode[nice] = entry;
|
|
118
139
|
}
|
|
119
|
-
entry[field] +=
|
|
140
|
+
entry[field] += rate;
|
|
120
141
|
} else {
|
|
121
|
-
outside[field] +=
|
|
142
|
+
outside[field] += rate;
|
|
122
143
|
}
|
|
123
144
|
};
|
|
124
145
|
for (let [rawNodeId, buckets] of Array.from(bytesSent)) {
|
|
125
|
-
attribute(rawNodeId, "sent",
|
|
146
|
+
attribute(rawNodeId, "sent", bucketRate(buckets));
|
|
126
147
|
if (buckets.size === 0) bytesSent.delete(rawNodeId);
|
|
127
148
|
}
|
|
128
149
|
for (let [rawNodeId, buckets] of Array.from(bytesReceived)) {
|
|
129
|
-
attribute(rawNodeId, "received",
|
|
150
|
+
attribute(rawNodeId, "received", bucketRate(buckets));
|
|
130
151
|
if (buckets.size === 0) bytesReceived.delete(rawNodeId);
|
|
131
152
|
}
|
|
132
153
|
return { perNode, outside };
|
|
@@ -135,10 +156,10 @@ function aggregateData(): { perNode: { [nodeId: string]: NodeDataTraffic }; outs
|
|
|
135
156
|
export function getTrafficStats(): TrafficStats {
|
|
136
157
|
let data = aggregateData();
|
|
137
158
|
return {
|
|
138
|
-
functionsExecuted:
|
|
139
|
-
pathValuesSent:
|
|
140
|
-
pathValuesReceived:
|
|
141
|
-
querysubCalls:
|
|
159
|
+
functionsExecuted: bucketRate(functionsExecuted),
|
|
160
|
+
pathValuesSent: bucketRate(pathValuesSent),
|
|
161
|
+
pathValuesReceived: bucketRate(pathValuesReceived),
|
|
162
|
+
querysubCalls: bucketRate(querysubCalls),
|
|
142
163
|
perNode: data.perNode,
|
|
143
164
|
outside: data.outside,
|
|
144
165
|
};
|
|
@@ -21,7 +21,7 @@ import { getOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
|
|
|
21
21
|
import { getScheduledShutdownTime } from "../-g-core-values/scheduledShutdown";
|
|
22
22
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
23
23
|
import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
|
|
24
|
-
import { getDomain, isPublic
|
|
24
|
+
import { getDomain, isPublic } from "../config";
|
|
25
25
|
import { getGitRefSync, getGitURLSync } from "../4-deploy/git";
|
|
26
26
|
import type { DeployProgress } from "../4-deploy/deployFunctions";
|
|
27
27
|
import { getRoutingOverride, getRoutingOverridePart } from "../0-path-value-core/PathRouterRouteOverride";
|
|
@@ -363,14 +363,15 @@ export class PathFunctionRunner {
|
|
|
363
363
|
secondaryShardRange?: { startFraction: number, endFraction: number };
|
|
364
364
|
PermissionsChecker: PermissionsCheckType | undefined;
|
|
365
365
|
// The networks we listen to calls on. Unset is equivalent to ["default"].
|
|
366
|
-
networks
|
|
366
|
+
networks: string[];
|
|
367
367
|
}) {
|
|
368
|
+
if (!config.networks.length) throw new Error(`networks is required`);
|
|
368
369
|
PathFunctionRunner.hostControllers();
|
|
369
370
|
debugFunctionRunnerShards.push({
|
|
370
371
|
domainName: config.domainName,
|
|
371
372
|
shardRange: config.shardRange,
|
|
372
373
|
secondaryShardRange: config.secondaryShardRange,
|
|
373
|
-
networks: config.networks
|
|
374
|
+
networks: config.networks,
|
|
374
375
|
isPublic: isPublic(),
|
|
375
376
|
});
|
|
376
377
|
logErrors(this.startWatching());
|
|
@@ -401,7 +402,7 @@ export class PathFunctionRunner {
|
|
|
401
402
|
|
|
402
403
|
let outstandingCalls = 0;
|
|
403
404
|
|
|
404
|
-
let networks = this.config.networks
|
|
405
|
+
let networks = this.config.networks;
|
|
405
406
|
|
|
406
407
|
let watchModuleCalls = cache((moduleId: string) => {
|
|
407
408
|
for (let network of networks) {
|
|
@@ -691,7 +692,7 @@ export class PathFunctionRunner {
|
|
|
691
692
|
}
|
|
692
693
|
|
|
693
694
|
let callNetwork = callSpec.network;
|
|
694
|
-
let ourNetworks = this.config.networks
|
|
695
|
+
let ourNetworks = this.config.networks;
|
|
695
696
|
if (!ourNetworks.includes(callNetwork)) {
|
|
696
697
|
return;
|
|
697
698
|
}
|
|
@@ -15,7 +15,7 @@ import { SocketFunction } from "socket-function/SocketFunction";
|
|
|
15
15
|
import { getThreadKeyCert } from "sliftutils/misc/https/certs";
|
|
16
16
|
import { ClientWatcher } from "../1-path-client/pathValueClientWatcher";
|
|
17
17
|
import { timeInMinute } from "socket-function/src/misc";
|
|
18
|
-
import { getDomain, getNetworks, isLocal, isPublic
|
|
18
|
+
import { getDomain, getNetworks, isLocal, isPublic } from "../config";
|
|
19
19
|
import { green, magenta } from "socket-function/src/formatting/logColors";
|
|
20
20
|
import path from "path";
|
|
21
21
|
import { IndexedLogs } from "../diagnostics/logs/IndexedLogs/IndexedLogs";
|
|
@@ -32,6 +32,9 @@ async function main() {
|
|
|
32
32
|
// ClientWatcher.DEBUG_TRIGGERS = "heavy";
|
|
33
33
|
// authorityStorage.DEBUG_UNWATCH = true;
|
|
34
34
|
|
|
35
|
+
let networks = getNetworks();
|
|
36
|
+
if (!networks.length) throw new Error(`No networks found. Use --network to specify the networks this function runner should listen on.`);
|
|
37
|
+
|
|
35
38
|
PathFunctionRunner.DEBUG_CALLS = true;
|
|
36
39
|
// debugCoreMode();
|
|
37
40
|
|
|
@@ -60,8 +63,6 @@ async function main() {
|
|
|
60
63
|
console.log(green(`Sharding from ${shardStart} to ${shardEnd}`));
|
|
61
64
|
}
|
|
62
65
|
|
|
63
|
-
let networks = getNetworks();
|
|
64
|
-
|
|
65
66
|
new PathFunctionRunner({
|
|
66
67
|
domainName: getDomain(),
|
|
67
68
|
shardRange: { startFraction: shardStart, endFraction: shardEnd },
|
|
@@ -76,9 +77,5 @@ async function main() {
|
|
|
76
77
|
let deployPath = path.resolve("./deploy.ts");
|
|
77
78
|
await import(deployPath);
|
|
78
79
|
}
|
|
79
|
-
|
|
80
|
-
if (networks.length !== 1 || networks[0] !== DEFAULT_NETWORK) {
|
|
81
|
-
console.log(magenta(`Only running functions on the network(s): ${networks.join(", ")}. Use --network ${networks[0]} in your http server to route calls here.`));
|
|
82
|
-
}
|
|
83
80
|
}
|
|
84
81
|
logErrors(main());
|
package/src/config.ts
CHANGED
|
@@ -44,8 +44,6 @@ if (isNode()) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
export const DEFAULT_NETWORK = "default";
|
|
48
|
-
|
|
49
47
|
export function expandHomePath(path: string): string {
|
|
50
48
|
if (path === "~" || path.startsWith("~/") || path.startsWith("~\\")) {
|
|
51
49
|
return os.homedir() + path.slice(1);
|
|
@@ -76,7 +74,7 @@ export function getNetworks(): string[] {
|
|
|
76
74
|
result = [...result, ...networkFileNetworks()];
|
|
77
75
|
}
|
|
78
76
|
result = Array.from(new Set(result));
|
|
79
|
-
if (result.length === 0) return [
|
|
77
|
+
if (result.length === 0) return [];
|
|
80
78
|
return result;
|
|
81
79
|
}
|
|
82
80
|
|
|
@@ -169,20 +169,24 @@ export class ServiceDetailPage extends qreact.Component {
|
|
|
169
169
|
private renderTemplateVariables(config: ServiceConfig) {
|
|
170
170
|
let templateVariables = getCommandTemplateVariables(config.parameters.command);
|
|
171
171
|
let targets = getMachineTargets(config.parameters);
|
|
172
|
-
|
|
173
|
-
if (templateVariables.length === 0 && !anyVariablesSet) return undefined;
|
|
172
|
+
if (targets.length === 0) return undefined;
|
|
174
173
|
|
|
175
|
-
const
|
|
174
|
+
const updateTargets = (change: (targets: ReturnType<typeof getMachineTargets>) => void) => {
|
|
176
175
|
let updated = deepCloneJSON(config);
|
|
177
176
|
let updatedTargets = getMachineTargets(updated.parameters);
|
|
178
|
-
|
|
179
|
-
updatedTargets[entryIndex].variables[name] = value;
|
|
180
|
-
} else {
|
|
181
|
-
delete updatedTargets[entryIndex].variables[name];
|
|
182
|
-
}
|
|
177
|
+
change(updatedTargets);
|
|
183
178
|
setMachineTargets(updated.parameters, updatedTargets);
|
|
184
179
|
this.updateEditorState(updated);
|
|
185
180
|
};
|
|
181
|
+
const setVariable = (entryIndex: number, name: string, value: string) => {
|
|
182
|
+
updateTargets(targets => {
|
|
183
|
+
if (value) {
|
|
184
|
+
targets[entryIndex].variables[name] = value;
|
|
185
|
+
} else {
|
|
186
|
+
delete targets[entryIndex].variables[name];
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
};
|
|
186
190
|
|
|
187
191
|
let dupIndexes = new Map<string, number>();
|
|
188
192
|
return <div className={css.vbox(10).fillWidth.pad2(12).bord2(0, 0, 20)}>
|
|
@@ -201,6 +205,26 @@ export class ServiceDetailPage extends qreact.Component {
|
|
|
201
205
|
onChangeValue={value => setVariable(entryIndex, name, value)}
|
|
202
206
|
/>;
|
|
203
207
|
})}
|
|
208
|
+
<button
|
|
209
|
+
className={css.pad2(10, 4).button.bord2(0, 0, 20).hsl(120, 70, 90)}
|
|
210
|
+
title="Duplicate this entry (same machine and variables)"
|
|
211
|
+
onClick={() => {
|
|
212
|
+
updateTargets(targets => {
|
|
213
|
+
targets.splice(entryIndex + 1, 0, deepCloneJSON(targets[entryIndex]));
|
|
214
|
+
});
|
|
215
|
+
}}>
|
|
216
|
+
+
|
|
217
|
+
</button>
|
|
218
|
+
<button
|
|
219
|
+
className={css.pad2(10, 4).button.bord2(0, 0, 20).hsl(0, 70, 90)}
|
|
220
|
+
title="Remove this entry"
|
|
221
|
+
onClick={() => {
|
|
222
|
+
updateTargets(targets => {
|
|
223
|
+
targets.splice(entryIndex, 1);
|
|
224
|
+
});
|
|
225
|
+
}}>
|
|
226
|
+
−
|
|
227
|
+
</button>
|
|
204
228
|
</div>;
|
|
205
229
|
})}
|
|
206
230
|
</div>;
|