querysub 0.590.0 → 0.592.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/stop-machine.js +4 -0
- package/package.json +5 -2
- package/src/-a-archives/archiveCache.ts +28 -7
- package/src/-a-archives/archiveCache2.ts +5 -2
- package/src/-a-archives/archives.ts +2 -63
- package/src/-a-archives/archivesDisk.ts +2 -13
- package/src/-a-archives/archivesMemoryCache.ts +27 -17
- package/src/-a-archives/archivesMemoryCache2.ts +23 -0
- package/src/-d-trust/NetworkTrust2.ts +2 -2
- package/src/-e-certs/certAuthority.ts +2 -2
- package/src/-f-node-discovery/NodeDiscovery.ts +2 -2
- package/src/0-path-value-core/AuthorityLookup.ts +0 -4
- package/src/0-path-value-core/ShardPrefixes.ts +2 -4
- package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +23 -10
- package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +0 -1
- package/src/0-path-value-core/pathValueArchives.ts +1 -0
- package/src/4-deploy/edgeBootstrap.ts +50 -11
- package/src/4-deploy/edgeNodes.ts +7 -13
- package/src/4-querysub/Querysub.ts +2 -2
- package/src/config2.ts +1 -7
- package/src/deployManager/components/ServiceMeasureBars.tsx +60 -50
- package/src/deployManager/components/ServicesListPage.tsx +29 -13
- package/src/deployManager/machineDaemonShared.ts +2 -0
- package/src/deployManager/machineSchema.ts +5 -6
- package/src/deployManager/setupMachineMain.ts +1 -2
- package/src/deployManager/stopMachineMain.ts +45 -0
- package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +34 -21
- package/src/diagnostics/logs/IndexedLogs/MCPIndexedLogs.ts +3 -3
- package/src/diagnostics/logs/IndexedLogs/TimeFileTree.ts +11 -2
- package/src/diagnostics/logs/IndexedLogs/moveIndexLogsToPublic.ts +3 -4
- package/src/diagnostics/logs/errorTickets/tickets.ts +3 -4
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +2 -4
- package/src/diagnostics/watchdog.ts +0 -5
- package/src/-a-archives/archivesBackBlaze.ts +0 -156
- package/src/-a-archives/archivesCborT.ts +0 -52
- package/src/-a-archives/archivesLimitedCache.ts +0 -307
- package/src/-a-archives/archivesPrivateFileSystem.ts +0 -326
- package/src/-a-archives/copyLocalToBackblaze.ts +0 -24
- package/src/-b-authorities/cdnAuthority.ts +0 -53
|
@@ -3,14 +3,12 @@ import { getOwnMachineId } from "sliftutils/misc/https/certs";
|
|
|
3
3
|
import { devDebugbreak, getDomain, isLocal, isPublic, noSyncing } from "../config";
|
|
4
4
|
import child_process from "child_process";
|
|
5
5
|
import { getAllNodeIds, getOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
|
|
6
|
-
import {
|
|
7
|
-
import { nestArchives } from "../-a-archives/archives";
|
|
6
|
+
import { getArchives2Public } from "../-a-archives/archives2";
|
|
8
7
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
9
8
|
import { delay, runInSerial, runInfinitePoll, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
10
9
|
import { compare, compareArray, isNodeTrue, sort, timeInMinute, timeInSecond } from "socket-function/src/misc";
|
|
11
10
|
import { cacheLimited, lazy } from "socket-function/src/caching";
|
|
12
11
|
import { canHaveChildren } from "socket-function/src/types";
|
|
13
|
-
import { hostArchives } from "../-b-authorities/cdnAuthority";
|
|
14
12
|
import { getModuleFromConfig } from "../3-path-functions/pathFunctionLoader";
|
|
15
13
|
import path from "path";
|
|
16
14
|
import debugbreak from "debugbreak";
|
|
@@ -31,7 +29,7 @@ import type { FunctionRunnerIndex } from "../4-querysub/FunctionRunnerTracking";
|
|
|
31
29
|
const UPDATE_POLL_INTERVAL = timeInMinute * 15;
|
|
32
30
|
const DEAD_NODE_COUNT_THRESHOLD = 15;
|
|
33
31
|
|
|
34
|
-
const edgeNodeStorage = isNodeTrue() &&
|
|
32
|
+
const edgeNodeStorage = isNodeTrue() && getArchives2Public("edgenodes/");
|
|
35
33
|
const edgeNodeIndexFile = "edge-nodes-index.json";
|
|
36
34
|
|
|
37
35
|
const getEdgeNodeConfig = cacheLimited(10000, async (fileName: string): Promise<EdgeNodeConfig | undefined> => {
|
|
@@ -265,19 +263,15 @@ async function publishOwnScheduledShutdown(time: number) {
|
|
|
265
263
|
await updateEdgeNodesFile();
|
|
266
264
|
}
|
|
267
265
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
domain: getDomain(),
|
|
273
|
-
});
|
|
274
|
-
return await getURL(edgeNodeIndexFile);
|
|
275
|
-
});
|
|
266
|
+
/** Every public URL that can serve the edge node index (write node first, then fastest-first). Not cached: the underlying source config can change (scheduled window switchovers), and after the one initialization await this is synchronous anyway. */
|
|
267
|
+
export async function getEdgeNodeConfigURLs(): Promise<string[]> {
|
|
268
|
+
return await edgeNodeStorage.getURLs(edgeNodeIndexFile);
|
|
269
|
+
}
|
|
276
270
|
|
|
277
271
|
const startUpdateLoop = lazy(async () => {
|
|
278
272
|
startEdgeNotifier();
|
|
279
273
|
SocketFunction.expose(EdgeNodeController);
|
|
280
|
-
await
|
|
274
|
+
await getEdgeNodeConfigURLs();
|
|
281
275
|
await runInfinitePollCallAtStart(UPDATE_POLL_INTERVAL * (1 + Math.random() * 0.1), updateLoop);
|
|
282
276
|
});
|
|
283
277
|
|
|
@@ -825,9 +825,9 @@ export class Querysub {
|
|
|
825
825
|
|
|
826
826
|
RequireController.injectHTMLBeforeStartup(async () => {
|
|
827
827
|
const { getEdgeBootstrapScript } = await import("../4-deploy/edgeBootstrap");
|
|
828
|
-
const {
|
|
828
|
+
const { getEdgeNodeConfigURLs } = await import("../4-deploy/edgeNodes");
|
|
829
829
|
let edgeBootstrapFile = await getEdgeBootstrapScript({
|
|
830
|
-
|
|
830
|
+
edgeNodeConfigURLs: await getEdgeNodeConfigURLs(),
|
|
831
831
|
});
|
|
832
832
|
return `<script>${edgeBootstrapFile}</script>`;
|
|
833
833
|
});
|
package/src/config2.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { hasArchivesPermissions } from "./-a-archives/archives";
|
|
3
|
-
import { baseIsClient, getDomain } from "./config";
|
|
4
|
-
import { JSONLACKS } from "socket-function/src/JSONLACKS/JSONLACKS";
|
|
5
|
-
import { rootPathStr, prependToPathStr, getPathDepth } from "./path";
|
|
6
|
-
import fs from "fs";
|
|
7
|
-
import { AuthoritySpec } from "./0-path-value-core/PathRouter";
|
|
1
|
+
import { baseIsClient } from "./config";
|
|
8
2
|
|
|
9
3
|
export function isClient() {
|
|
10
4
|
return baseIsClient();
|
|
@@ -2,7 +2,8 @@ import { SocketFunction } from "socket-function/SocketFunction";
|
|
|
2
2
|
import { qreact } from "../../4-dom/qreact";
|
|
3
3
|
import { css } from "typesafecss";
|
|
4
4
|
import { formatNumber, formatTime, formatVeryNiceDateTime } from "socket-function/src/formatting/format";
|
|
5
|
-
import {
|
|
5
|
+
import { timeInSecond } from "socket-function/src/misc";
|
|
6
|
+
import { timeoutToError } from "../../errors";
|
|
6
7
|
import { StatsValue, getStatsTop } from "socket-function/src/profiling/stats";
|
|
7
8
|
import type { MeasureChunk, MeasureSummary } from "../../diagnostics/watchdog";
|
|
8
9
|
import { NodeCapabilitiesController } from "../../-g-core-values/NodeCapabilities";
|
|
@@ -10,19 +11,19 @@ import { getSyncedController } from "../../library-components/SyncedController";
|
|
|
10
11
|
import { assertIsManagementUser } from "../../diagnostics/managementPages";
|
|
11
12
|
|
|
12
13
|
const NODE_CALL_TIMEOUT = timeInSecond * 10;
|
|
13
|
-
const BAR_HEIGHT_PX =
|
|
14
|
-
const
|
|
14
|
+
const BAR_HEIGHT_PX = 6;
|
|
15
|
+
const MIN_BAR_WIDTH_PX = 30;
|
|
16
|
+
// Green at idle, red at fully busy.
|
|
17
|
+
const BAR_IDLE_HUE = 120;
|
|
18
|
+
const BAR_FLASH_THRESHOLD = 0.6;
|
|
19
|
+
const FLASH_CLASS_NAME = "ServiceMeasureBars-flash";
|
|
15
20
|
|
|
16
21
|
class ServiceMeasureControllerBase {
|
|
17
|
-
public async
|
|
18
|
-
|
|
19
|
-
await Promise.all(nodeIds.map(async nodeId => {
|
|
20
|
-
result[nodeId] = await timeoutToUndefinedSilent(NODE_CALL_TIMEOUT, NodeCapabilitiesController.nodes[nodeId].getMeasureSummary());
|
|
21
|
-
}));
|
|
22
|
-
return result;
|
|
22
|
+
public async getMeasureSummary(nodeId: string): Promise<MeasureSummary | undefined> {
|
|
23
|
+
return await timeoutToError(NODE_CALL_TIMEOUT, NodeCapabilitiesController.nodes[nodeId].getMeasureSummary(), () => new Error(`Timed out asking ${nodeId} for its measure summary`));
|
|
23
24
|
}
|
|
24
25
|
public async getMeasureBreakdown(nodeId: string, range: { startTime: number; endTime: number }): Promise<{ chunk?: MeasureChunk }> {
|
|
25
|
-
let chunk = await
|
|
26
|
+
let chunk = await timeoutToError(NODE_CALL_TIMEOUT, NodeCapabilitiesController.nodes[nodeId].getMeasureBreakdown(range), () => new Error(`Timed out asking ${nodeId} for its measure breakdown`));
|
|
26
27
|
return { chunk };
|
|
27
28
|
}
|
|
28
29
|
}
|
|
@@ -30,7 +31,7 @@ export const ServiceMeasureController = getSyncedController(SocketFunction.regis
|
|
|
30
31
|
"ServiceMeasureController-3f8c1de2-5b74-4e0f-a6c9-8d21f47b90a5",
|
|
31
32
|
new ServiceMeasureControllerBase(),
|
|
32
33
|
() => ({
|
|
33
|
-
|
|
34
|
+
getMeasureSummary: { hooks: [assertIsManagementUser], compress: true },
|
|
34
35
|
getMeasureBreakdown: { hooks: [assertIsManagementUser], compress: true },
|
|
35
36
|
}),
|
|
36
37
|
() => ({
|
|
@@ -41,52 +42,55 @@ function percent(value: number) {
|
|
|
41
42
|
return `${(value * 100).toFixed(2)}%`;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
function nameHue(name: string) {
|
|
45
|
-
let hash = 0;
|
|
46
|
-
for (let i = 0; i < name.length; i++) {
|
|
47
|
-
hash = (hash * 31 + name.charCodeAt(i)) | 0;
|
|
48
|
-
}
|
|
49
|
-
return Math.abs(hash) % 360;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
45
|
export class ServiceMeasureBars extends qreact.Component<{
|
|
53
46
|
serviceNodeIds: string[];
|
|
54
|
-
/** Every node id on the page, so all rows share one cached call. */
|
|
55
|
-
allNodeIds: string[];
|
|
56
47
|
expandedNodeId: string;
|
|
57
48
|
onToggleNode: (nodeId: string) => void;
|
|
58
49
|
}> {
|
|
59
50
|
render() {
|
|
60
51
|
let controller = ServiceMeasureController(SocketFunction.browserNodeId());
|
|
61
|
-
let summaries = controller.getMeasureSummaries(this.props.allNodeIds);
|
|
62
|
-
if (!summaries) return undefined;
|
|
63
52
|
|
|
64
|
-
return <div className={css.vbox(
|
|
53
|
+
return <div className={css.vbox(1).alignItems("stretch")}>
|
|
65
54
|
{this.props.serviceNodeIds.map(nodeId => {
|
|
66
|
-
let
|
|
55
|
+
let isExpanded = this.props.expandedNodeId === nodeId;
|
|
56
|
+
let summary: MeasureSummary | undefined;
|
|
57
|
+
try {
|
|
58
|
+
summary = controller.getMeasureSummary(nodeId);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
return <div key={nodeId}
|
|
61
|
+
className={css.height(BAR_HEIGHT_PX).hsl(0, 70, 55)}
|
|
62
|
+
title={`Node did not respond (it likely doesn't have the measure API yet)\n${nodeId}\n${(err as Error).stack ?? err}`}
|
|
63
|
+
/>;
|
|
64
|
+
}
|
|
67
65
|
if (!summary) return undefined;
|
|
68
66
|
let timeRunFor = summary.endTime - summary.startTime;
|
|
69
67
|
if (timeRunFor <= 0) return undefined;
|
|
70
|
-
let
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
{summary.entries.map(entry => {
|
|
79
|
-
let fraction = entry.ownTime / timeRunFor;
|
|
80
|
-
return <div key={entry.name}
|
|
81
|
-
className={css
|
|
82
|
-
.height(BAR_HEIGHT_PX)
|
|
68
|
+
let fraction = summary.profiledTime / timeRunFor;
|
|
69
|
+
let clampedFraction = Math.min(1, Math.max(0, fraction));
|
|
70
|
+
let hue = BAR_IDLE_HUE * (1 - clampedFraction);
|
|
71
|
+
let isFlashing = fraction > BAR_FLASH_THRESHOLD;
|
|
72
|
+
return <div key={nodeId} className={css.hbox(0)}>
|
|
73
|
+
<div
|
|
74
|
+
className={
|
|
75
|
+
css.height(BAR_HEIGHT_PX)
|
|
83
76
|
.width(`${fraction * 100}%`)
|
|
84
|
-
.minWidth(
|
|
85
|
-
.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
77
|
+
.minWidth(MIN_BAR_WIDTH_PX)
|
|
78
|
+
.button
|
|
79
|
+
.hsl(hue, 70, isExpanded ? 40 : 55)
|
|
80
|
+
+ (isFlashing && (" " + FLASH_CLASS_NAME) || "")
|
|
81
|
+
}
|
|
82
|
+
title={`Profiled ${formatTime(summary.profiledTime)} of ${formatTime(timeRunFor)} (${percent(fraction)} CPU)\n${nodeId}`}
|
|
83
|
+
onClick={() => this.props.onToggleNode(nodeId)}
|
|
84
|
+
/>
|
|
85
|
+
{isFlashing && <style>{`
|
|
86
|
+
@keyframes ${FLASH_CLASS_NAME}-anim {
|
|
87
|
+
0%, 100% { opacity: 1; }
|
|
88
|
+
50% { opacity: 0.3; }
|
|
89
|
+
}
|
|
90
|
+
.${FLASH_CLASS_NAME} {
|
|
91
|
+
animation: ${FLASH_CLASS_NAME}-anim 0.8s infinite;
|
|
92
|
+
}
|
|
93
|
+
`}</style>}
|
|
90
94
|
</div>;
|
|
91
95
|
})}
|
|
92
96
|
</div>;
|
|
@@ -95,15 +99,21 @@ export class ServiceMeasureBars extends qreact.Component<{
|
|
|
95
99
|
|
|
96
100
|
export class ServiceMeasureBreakdown extends qreact.Component<{
|
|
97
101
|
nodeId: string;
|
|
98
|
-
allNodeIds: string[];
|
|
99
102
|
}> {
|
|
100
103
|
render() {
|
|
101
104
|
let controller = ServiceMeasureController(SocketFunction.browserNodeId());
|
|
102
|
-
let
|
|
103
|
-
let
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
let summary: MeasureSummary | undefined;
|
|
106
|
+
let result: { chunk?: MeasureChunk } | undefined;
|
|
107
|
+
try {
|
|
108
|
+
summary = controller.getMeasureSummary(this.props.nodeId);
|
|
109
|
+
if (!summary) return undefined;
|
|
110
|
+
result = controller.getMeasureBreakdown(this.props.nodeId, { startTime: summary.startTime, endTime: summary.endTime });
|
|
111
|
+
} catch (err) {
|
|
112
|
+
return <div className={css.pad2(10).hsl(0, 70, 92).bord2(0, 0, 20)}>
|
|
113
|
+
<div className={css.boldStyle.colorhsl(0, 70, 35)}>Node did not respond (it likely doesn't have the measure API yet)</div>
|
|
114
|
+
<pre className={css.whiteSpace("pre-wrap").fontSize(12)}>{(err as Error).stack ?? String(err)}</pre>
|
|
115
|
+
</div>;
|
|
116
|
+
}
|
|
107
117
|
if (!result) {
|
|
108
118
|
return <div className={css.pad2(10).hsl(0, 0, 98).bord2(0, 0, 20)}>Loading breakdown...</div>;
|
|
109
119
|
}
|
|
@@ -17,10 +17,12 @@ import { PendingDeployInfo, UpdateButtons, UpdateServiceButtons } from "./deploy
|
|
|
17
17
|
import { isDefined } from "../../misc";
|
|
18
18
|
import { formatDateJSX } from "../../misc/formatJSX";
|
|
19
19
|
import { Tools } from "./Tools";
|
|
20
|
-
import { ServiceMeasureBars, ServiceMeasureBreakdown } from "./ServiceMeasureBars";
|
|
20
|
+
import { ServiceMeasureBars, ServiceMeasureBreakdown, ServiceMeasureController } from "./ServiceMeasureBars";
|
|
21
21
|
|
|
22
22
|
module.hotreload = true;
|
|
23
23
|
|
|
24
|
+
const INVALIDATE_FLASH_TIME = timeInSecond;
|
|
25
|
+
|
|
24
26
|
/** What kind of service this is, when we can tell from its command, linking to wherever that kind is administered. Its own box beside the service's, so clicking the service still opens the service. */
|
|
25
27
|
class ServiceCategoryBadge extends qreact.Component<{ config: ServiceConfig }> {
|
|
26
28
|
render() {
|
|
@@ -45,6 +47,7 @@ export class ServicesListPage extends qreact.Component {
|
|
|
45
47
|
state = t.state({
|
|
46
48
|
measurementsEnabled: t.atomic<boolean>(true),
|
|
47
49
|
expandedMeasureNodes: t.lookup(t.string),
|
|
50
|
+
invalidateClickTime: t.atomic<number>(0),
|
|
48
51
|
});
|
|
49
52
|
|
|
50
53
|
render() {
|
|
@@ -81,7 +84,6 @@ export class ServicesListPage extends qreact.Component {
|
|
|
81
84
|
serviceNodeIds.set(serviceId, nodeIds);
|
|
82
85
|
}
|
|
83
86
|
}
|
|
84
|
-
let allNodeIds = sort(Array.from(new Set(Array.from(serviceNodeIds.values()).flat())), x => x);
|
|
85
87
|
|
|
86
88
|
return <div className={css.vbox(16)}>
|
|
87
89
|
<div className={css.hbox(12).wrap}>
|
|
@@ -124,13 +126,29 @@ export class ServicesListPage extends qreact.Component {
|
|
|
124
126
|
</button>
|
|
125
127
|
<UpdateButtons services={services.map(x => x[1]).filter(isDefined)} />
|
|
126
128
|
</div>
|
|
127
|
-
<div
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
<div className={css.hbox(8)}>
|
|
130
|
+
<div
|
|
131
|
+
className={css.pad2(12, 8).button.bord2(0, 0, 20).hsl(0, 0, 95)}
|
|
132
|
+
onClick={() => {
|
|
133
|
+
this.state.measurementsEnabled = !this.state.measurementsEnabled;
|
|
134
|
+
}}
|
|
135
|
+
>
|
|
136
|
+
<span className={css.boldStyle}>{this.state.measurementsEnabled ? "☑" : "☐"} Measurements</span>
|
|
137
|
+
</div>
|
|
138
|
+
{this.state.measurementsEnabled && (() => {
|
|
139
|
+
let justInvalidated = now < this.state.invalidateClickTime + INVALIDATE_FLASH_TIME;
|
|
140
|
+
return <div
|
|
141
|
+
className={css.pad2(12, 8).button.bord2(0, 0, 20)
|
|
142
|
+
+ (justInvalidated && css.hsl(120, 50, 80) || css.hsl(0, 0, 95))
|
|
143
|
+
}
|
|
144
|
+
onClick={() => {
|
|
145
|
+
this.state.invalidateClickTime = Date.now();
|
|
146
|
+
ServiceMeasureController.resetAll();
|
|
147
|
+
}}
|
|
148
|
+
>
|
|
149
|
+
{justInvalidated ? "Invalidated ✓" : "Invalidate"}
|
|
150
|
+
</div>;
|
|
151
|
+
})()}
|
|
134
152
|
</div>
|
|
135
153
|
<Tools />
|
|
136
154
|
<div className={css.vbox(8)}>
|
|
@@ -168,10 +186,10 @@ export class ServicesListPage extends qreact.Component {
|
|
|
168
186
|
let duplicateKey = (keyCounts.get(config.parameters.key || "") || 0) > 1;
|
|
169
187
|
let measureNodeIds = serviceNodeIds.get(serviceId) || [];
|
|
170
188
|
let expandedMeasureNodeId = this.state.expandedMeasureNodes[serviceId] || "";
|
|
171
|
-
return <div className={css.vbox(4)} key={serviceId}>
|
|
189
|
+
return <div className={css.vbox(4).alignItems("stretch")} key={serviceId}>
|
|
172
190
|
<div className={css.hbox(10)}>
|
|
173
191
|
<ServiceCategoryBadge config={config} />
|
|
174
|
-
<div className={css.vbox(4)}>
|
|
192
|
+
<div className={css.vbox(4).alignItems("stretch")}>
|
|
175
193
|
<Anchor noStyles
|
|
176
194
|
values={[currentViewParam.getOverride("service-detail"), selectedServiceIdParam.getOverride(serviceId)]}
|
|
177
195
|
className={
|
|
@@ -240,7 +258,6 @@ export class ServicesListPage extends qreact.Component {
|
|
|
240
258
|
</Anchor>
|
|
241
259
|
{this.state.measurementsEnabled && measureNodeIds.length > 0 && <ServiceMeasureBars
|
|
242
260
|
serviceNodeIds={measureNodeIds}
|
|
243
|
-
allNodeIds={allNodeIds}
|
|
244
261
|
expandedNodeId={expandedMeasureNodeId}
|
|
245
262
|
onToggleNode={nodeId => {
|
|
246
263
|
if (this.state.expandedMeasureNodes[serviceId] === nodeId) {
|
|
@@ -256,7 +273,6 @@ export class ServicesListPage extends qreact.Component {
|
|
|
256
273
|
</div>
|
|
257
274
|
{this.state.measurementsEnabled && expandedMeasureNodeId && <ServiceMeasureBreakdown
|
|
258
275
|
nodeId={expandedMeasureNodeId}
|
|
259
|
-
allNodeIds={allNodeIds}
|
|
260
276
|
/>}
|
|
261
277
|
</div>
|
|
262
278
|
;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { isNodeTrue, list, timeInDay, timeInMinute, timeInSecond } from "socket-function/src/misc";
|
|
2
|
-
import {
|
|
3
|
-
import { getArchivesBackblaze } from "../-a-archives/archivesBackBlaze";
|
|
2
|
+
import { getArchives2 } from "../-a-archives/archives2";
|
|
4
3
|
import { getDomain } from "../config";
|
|
5
4
|
import { archiveJSONT } from "../-a-archives/archivesJSONT";
|
|
6
5
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
@@ -187,9 +186,9 @@ export type MachineConfig = {
|
|
|
187
186
|
machineId: string;
|
|
188
187
|
disabled: boolean;
|
|
189
188
|
};
|
|
190
|
-
export const machineInfos = archiveJSONT<MachineInfo>(() =>
|
|
191
|
-
export const serviceConfigs = archiveJSONT<ServiceConfig>(() =>
|
|
192
|
-
export const machineConfigs = archiveJSONT<MachineConfig>(() =>
|
|
189
|
+
export const machineInfos = archiveJSONT<MachineInfo>(() => getArchives2("machines/machine-heartbeats/"));
|
|
190
|
+
export const serviceConfigs = archiveJSONT<ServiceConfig>(() => getArchives2("machines/service-configs/"));
|
|
191
|
+
export const machineConfigs = archiveJSONT<MachineConfig>(() => getArchives2("machines/machine-configs/"));
|
|
193
192
|
|
|
194
193
|
export type LaunchRecord = {
|
|
195
194
|
serviceId: string;
|
|
@@ -207,7 +206,7 @@ export type LaunchSummary = {
|
|
|
207
206
|
key: string;
|
|
208
207
|
};
|
|
209
208
|
|
|
210
|
-
const launches = lazy(() =>
|
|
209
|
+
const launches = lazy(() => getArchives2("machines/launches/"));
|
|
211
210
|
|
|
212
211
|
function formatLaunchDay(time: number): string {
|
|
213
212
|
let date = new Date(time);
|
|
@@ -8,13 +8,12 @@ import readline from "readline";
|
|
|
8
8
|
import open from "open";
|
|
9
9
|
import { fsExistsAsync } from "../fs";
|
|
10
10
|
import { delay } from "socket-function/src/batching";
|
|
11
|
+
import { SERVICE_NAME, SERVICE_UNIT_NAME } from "./machineDaemonShared";
|
|
11
12
|
// Import querysub, to fix missing dependencies
|
|
12
13
|
Querysub;
|
|
13
14
|
|
|
14
15
|
const pinnedNodeVersion = 22;
|
|
15
16
|
|
|
16
|
-
const SERVICE_NAME = "machine-alwaysup";
|
|
17
|
-
const SERVICE_UNIT_NAME = `${SERVICE_NAME}.service`;
|
|
18
17
|
const DAEMON_SCRIPT_NAME = "machine-daemon.sh";
|
|
19
18
|
// How long after the process dies before it is started again. Short, because until it is back nothing on the machine is being deployed or repaired.
|
|
20
19
|
const SERVICE_RESTART_DELAY_SECONDS = 5;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { runPromise } from "../functional/runCommand";
|
|
2
|
+
import { SERVICE_NAME, SERVICE_UNIT_NAME } from "./machineDaemonShared";
|
|
3
|
+
|
|
4
|
+
const DPLY_SCREEN_SUFFIX = "-dply";
|
|
5
|
+
|
|
6
|
+
async function main() {
|
|
7
|
+
let sshRemote = process.argv.slice(2).join(" ");
|
|
8
|
+
if (!sshRemote) {
|
|
9
|
+
console.error("Incorrect usage. Examples:\nyarn stop-machine 153.34.64.2\nyarn stop-machine devops@153.34.64.2");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
await runPromise(`ssh ${sshRemote} whoami`);
|
|
14
|
+
|
|
15
|
+
let daemonState = (await runPromise(`ssh ${sshRemote} "systemctl is-active ${SERVICE_UNIT_NAME} || true"`, { nothrow: true })).trim();
|
|
16
|
+
if (daemonState === "active") {
|
|
17
|
+
console.log(`Stopping the ${SERVICE_UNIT_NAME} daemon...`);
|
|
18
|
+
await runPromise(`ssh ${sshRemote} "sudo systemctl stop ${SERVICE_UNIT_NAME}"`);
|
|
19
|
+
console.log(`✅ Daemon stopped`);
|
|
20
|
+
} else {
|
|
21
|
+
console.log(`✅ The ${SERVICE_UNIT_NAME} daemon is not running (state: ${daemonState || "unknown"})`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let sessions = (await runPromise(`ssh ${sshRemote} "tmux list-sessions -F '#{session_name}' 2>/dev/null || true"`, { nothrow: true }))
|
|
25
|
+
.split("\n").map(x => x.trim()).filter(x => x);
|
|
26
|
+
// The machine's own screen first, so it cannot restart a service screen while the rest are being killed
|
|
27
|
+
let toKill = [
|
|
28
|
+
...sessions.filter(name => name === SERVICE_NAME),
|
|
29
|
+
...sessions.filter(name => name.endsWith(DPLY_SCREEN_SUFFIX)),
|
|
30
|
+
];
|
|
31
|
+
if (toKill.length === 0) {
|
|
32
|
+
console.log(`✅ No ${SERVICE_NAME} or *${DPLY_SCREEN_SUFFIX} tmux sessions to kill (sessions: ${sessions.join(", ") || "none"})`);
|
|
33
|
+
}
|
|
34
|
+
for (let name of toKill) {
|
|
35
|
+
console.log(`Killing tmux session ${name}...`);
|
|
36
|
+
await runPromise(`ssh ${sshRemote} "tmux kill-session -t ${name}"`);
|
|
37
|
+
}
|
|
38
|
+
if (toKill.length > 0) {
|
|
39
|
+
console.log(`✅ Killed ${toKill.length} tmux session(s): ${toKill.join(", ")}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log(`\n🛑 Machine stopped. Start it again with: ssh ${sshRemote} "~/machine-startup.sh"`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
main().catch(console.error).finally(() => process.exit(0));
|
|
@@ -5,15 +5,17 @@ import { BufferIndex } from "./BufferIndex";
|
|
|
5
5
|
import { delay, runInParallel, runInSerial, runInfinitePoll, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
6
6
|
import { IndexedLogResults, Reader, SearchParams, addReadToResults, createEmptyIndexedLogResults, INDEX_EXTENSION, mergeIndexedLogResults } from "./BufferIndexHelpers";
|
|
7
7
|
import { getDomain, isPublic } from "../../../config";
|
|
8
|
-
import { getArchivesHome
|
|
9
|
-
import {
|
|
8
|
+
import { getArchivesHome } from "../../../-a-archives/archivesDisk";
|
|
9
|
+
import { getArchives2 } from "../../../-a-archives/archives2";
|
|
10
10
|
import { getMachineId, getOwnThreadId } from "sliftutils/misc/https/certs";
|
|
11
11
|
import { ArchivesMemoryCacheStats, createArchivesMemoryCache } from "../../../-a-archives/archivesMemoryCache";
|
|
12
|
+
import { createArchivesMemoryCache2 } from "../../../-a-archives/archivesMemoryCache2";
|
|
13
|
+
import { wrapArchivesWithCache2 } from "../../../-a-archives/archiveCache2";
|
|
12
14
|
import { registerShutdownHandler } from "../../periodic";
|
|
13
15
|
import { measureBlock, measureFnc, measureWrap } from "socket-function/src/profiling/measure";
|
|
14
16
|
import { isNode } from "typesafecss";
|
|
15
17
|
import { getOwnMachineId, isNodeIdOnOwnMachineId, isOwnNodeId } from "../../../-f-node-discovery/NodeDiscovery";
|
|
16
|
-
import { TimeFilePath, TimeFileTree } from "./TimeFileTree";
|
|
18
|
+
import { LogArchives, TimeFilePath, TimeFileTree } from "./TimeFileTree";
|
|
17
19
|
import { LogStreamer } from "./LogStreamer";
|
|
18
20
|
import { moveLogsToPublic } from "./moveIndexLogsToPublic";
|
|
19
21
|
import { MAX_SINGLE_FILE_DATA, MAX_COUNT_PER_FILE, DISK_FLUSH_INTERVAL, PUBLIC_MOVE_THRESHOLD, MOVING_TIMEOUT } from "./BufferIndexLogsOptimizationConstants";
|
|
@@ -83,7 +85,7 @@ export class IndexedLogs<T> {
|
|
|
83
85
|
|
|
84
86
|
private getLocalLogs = lazy((): Archives => {
|
|
85
87
|
let baseDisk = getArchivesHome(getDomain());
|
|
86
|
-
let archives = nestArchives("indexed-logs/" + this.config.name, baseDisk);
|
|
88
|
+
let archives = nestArchives("indexed-logs/" + this.config.name, baseDisk) as Archives;
|
|
87
89
|
archives = createArchivesMemoryCache(archives, {
|
|
88
90
|
maxSize: 1024 * 1024 * 512,
|
|
89
91
|
maxCount: 1000 * 100,
|
|
@@ -94,32 +96,43 @@ export class IndexedLogs<T> {
|
|
|
94
96
|
});
|
|
95
97
|
return archives;
|
|
96
98
|
});
|
|
97
|
-
private getPublicLogsBase(loadPublic: boolean):
|
|
98
|
-
let basePublic: Archives = getArchivesHome(getDomain());
|
|
99
|
-
// NOTE: The local disk is so fast that reading in 10 megabytes is nothing, And if we read in too small of a value, the overhead per read ends up making this take forever.
|
|
100
|
-
let extraReadSize = 1024 * 1024 * 10;
|
|
99
|
+
private getPublicLogsBase(loadPublic: boolean): LogArchives {
|
|
101
100
|
if (loadPublic) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
return createArchivesMemoryCache2(getArchives2("final-indexed-logs/" + this.config.name), {
|
|
102
|
+
maxSize: 1024 * 1024 * 1024 * 2,
|
|
103
|
+
maxCount: 1000 * 500,
|
|
104
|
+
fullyImmutable: true,
|
|
105
|
+
// NOTE: While the latency to the remote is high, now we're reading in parallel, so it shouldn't be as big of an issue.
|
|
106
|
+
extraReadSize: 1024 * 1024 * 1,
|
|
107
|
+
stats: this.backblazeLogsStats,
|
|
108
|
+
sizeCache: this.fileSizeCache,
|
|
109
|
+
});
|
|
105
110
|
}
|
|
106
|
-
let archives = nestArchives("final-indexed-logs/" + this.config.name,
|
|
107
|
-
|
|
111
|
+
let archives = nestArchives("final-indexed-logs/" + this.config.name, getArchivesHome(getDomain()));
|
|
112
|
+
return createArchivesMemoryCache(archives, {
|
|
108
113
|
maxSize: 1024 * 1024 * 1024 * 2,
|
|
109
114
|
maxCount: 1000 * 500,
|
|
110
115
|
fullyImmutable: true,
|
|
111
|
-
|
|
116
|
+
// NOTE: The local disk is so fast that reading in 10 megabytes is nothing, And if we read in too small of a value, the overhead per read ends up making this take forever.
|
|
117
|
+
extraReadSize: 1024 * 1024 * 10,
|
|
112
118
|
stats: this.backblazeLogsStats,
|
|
113
119
|
sizeCache: this.fileSizeCache,
|
|
114
120
|
});
|
|
115
|
-
return archives;
|
|
116
121
|
};
|
|
117
122
|
public debugGetCachedLogs = cache((config: {
|
|
118
123
|
type: "local" | "public";
|
|
119
|
-
}) => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
124
|
+
}): LogArchives => {
|
|
125
|
+
if (config.type === "public") {
|
|
126
|
+
let archives2 = wrapArchivesWithCache2(getArchives2("final-indexed-logs/" + this.config.name), {
|
|
127
|
+
immutable: true,
|
|
128
|
+
});
|
|
129
|
+
return createArchivesMemoryCache2(archives2, {
|
|
130
|
+
maxSize: 1024 * 1024 * 1024 * 12,
|
|
131
|
+
maxCount: 1000 * 500,
|
|
132
|
+
fullyImmutable: true,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
let archives = nestArchives("final-indexed-logs/" + this.config.name, getArchivesHome(getDomain())) as Archives;
|
|
123
136
|
archives = wrapArchivesWithCache(archives, {
|
|
124
137
|
immutable: true,
|
|
125
138
|
});
|
|
@@ -133,10 +146,10 @@ export class IndexedLogs<T> {
|
|
|
133
146
|
public debugIsPublic() {
|
|
134
147
|
return isPublic();
|
|
135
148
|
}
|
|
136
|
-
private getPublicLogs = lazy(():
|
|
149
|
+
private getPublicLogs = lazy((): LogArchives => {
|
|
137
150
|
return this.getPublicLogsBase(isPublic());
|
|
138
151
|
});
|
|
139
|
-
private getRealPublicLogs = lazy(():
|
|
152
|
+
private getRealPublicLogs = lazy((): LogArchives => {
|
|
140
153
|
return this.getPublicLogsBase(true);
|
|
141
154
|
});
|
|
142
155
|
|
|
@@ -5,7 +5,7 @@ import { getDomain } from "../../../config";
|
|
|
5
5
|
import { getAllNodeIds, getOwnMachineId, isOwnNodeId } from "../../../-f-node-discovery/NodeDiscovery";
|
|
6
6
|
import { NodeCapabilitiesController } from "../../../-g-core-values/NodeCapabilities";
|
|
7
7
|
import { getLoggers2Async, LogDatum } from "../diskLogger";
|
|
8
|
-
import {
|
|
8
|
+
import { LogArchives } from "./TimeFileTree";
|
|
9
9
|
import { TimeFilePath, TimeFileTree } from "./TimeFileTree";
|
|
10
10
|
import { BufferReader, INDEX_EXTENSION, createEmptyIndexedLogResults } from "./BufferIndexHelpers";
|
|
11
11
|
import { BufferIndex } from "./BufferIndex";
|
|
@@ -104,7 +104,7 @@ type Direction = "fromStart" | "fromEnd";
|
|
|
104
104
|
type FileEntry = {
|
|
105
105
|
path: TimeFilePath;
|
|
106
106
|
loggerName: LoggerName;
|
|
107
|
-
archives:
|
|
107
|
+
archives: LogArchives;
|
|
108
108
|
};
|
|
109
109
|
|
|
110
110
|
// Mutable accumulators the file/block walk appends into, instead of returning
|
|
@@ -570,7 +570,7 @@ export class MCPIndexedLogs {
|
|
|
570
570
|
// Caches TimeFileTree.findAllPaths by hour-aligned bucket so repeated
|
|
571
571
|
// searches over similar windows reuse the slow folder walk.
|
|
572
572
|
private async getCachedPaths(config: {
|
|
573
|
-
archives:
|
|
573
|
+
archives: LogArchives;
|
|
574
574
|
type: "local" | "public";
|
|
575
575
|
loggerName: LoggerName;
|
|
576
576
|
startTime: number;
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { timeInDay } from "socket-function/src/misc";
|
|
2
|
-
import { Archives } from "../../../-a-archives/archives";
|
|
3
2
|
import { getOwnThreadId, getOwnMachineId } from "../../../-f-node-discovery/NodeDiscovery";
|
|
4
3
|
|
|
4
|
+
/** The subset of archive operations the log system uses, so both the old disk Archives and archives2 (IArchives) work interchangeably. */
|
|
5
|
+
export type LogArchives = {
|
|
6
|
+
getDebugName(): string;
|
|
7
|
+
get(path: string, config?: { range?: { start: number; end: number; } }): Promise<Buffer | undefined>;
|
|
8
|
+
set(path: string, data: Buffer): Promise<unknown>;
|
|
9
|
+
del(path: string): Promise<void>;
|
|
10
|
+
getInfo(path: string): Promise<{ writeTime: number; size: number; } | undefined>;
|
|
11
|
+
find(prefix: string, config?: { shallow?: boolean; type: "files" | "folders" }): Promise<string[]>;
|
|
12
|
+
};
|
|
13
|
+
|
|
5
14
|
export type TimeFilePath = {
|
|
6
15
|
fullPath: string;
|
|
7
16
|
|
|
@@ -113,7 +122,7 @@ export function decodeLogFilePath(path: string): TimeFilePath | undefined {
|
|
|
113
122
|
|
|
114
123
|
// NOTE: This class will add its own extensions to the files, and it'll use it to detect these files. You can append your own extension and then we will ignore those files.
|
|
115
124
|
export class TimeFileTree {
|
|
116
|
-
public constructor(public archives:
|
|
125
|
+
public constructor(public archives: LogArchives) { }
|
|
117
126
|
|
|
118
127
|
public async findAllPaths(config: {
|
|
119
128
|
startTime: number;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { delay } from "socket-function/src/batching";
|
|
2
2
|
import { sort, keyByArray } from "socket-function/src/misc";
|
|
3
|
-
import { Archives } from "../../../-a-archives/archives";
|
|
4
3
|
import { getOwnThreadId } from "../../../-f-node-discovery/NodeDiscovery";
|
|
5
4
|
import { BufferIndex } from "./BufferIndex";
|
|
6
5
|
import { LogStreamer } from "./LogStreamer";
|
|
7
|
-
import { TimeFileTree } from "./TimeFileTree";
|
|
6
|
+
import { LogArchives, TimeFileTree } from "./TimeFileTree";
|
|
8
7
|
import { blue, green, magenta } from "socket-function/src/formatting/logColors";
|
|
9
8
|
import { formatDateTime, formatNumber, formatTime } from "socket-function/src/formatting/format";
|
|
10
9
|
|
|
@@ -17,8 +16,8 @@ export async function moveLogsToPublic(config: {
|
|
|
17
16
|
movingTimeout: number;
|
|
18
17
|
// Unsafe, but useful for testing
|
|
19
18
|
forceAll: boolean;
|
|
20
|
-
localLogs:
|
|
21
|
-
publicLogs:
|
|
19
|
+
localLogs: LogArchives;
|
|
20
|
+
publicLogs: LogArchives;
|
|
22
21
|
indexExtension: string;
|
|
23
22
|
}) {
|
|
24
23
|
let { forceAll, localLogs, publicLogs, publicMoveThreshold, maxSingleFileData, indexExtension, movingTimeout } = config;
|
|
@@ -2,10 +2,9 @@ import { lazy } from "socket-function/src/caching";
|
|
|
2
2
|
import { batchFunction, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
3
3
|
import { timeInMinute } from "socket-function/src/misc";
|
|
4
4
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
5
|
-
import {
|
|
6
|
-
import { getArchivesBackblaze } from "../../../-a-archives/archivesBackBlaze";
|
|
5
|
+
import { getArchives2 } from "../../../-a-archives/archives2";
|
|
7
6
|
import { archiveJSONT } from "../../../-a-archives/archivesJSONT";
|
|
8
|
-
import {
|
|
7
|
+
import { isPublic } from "../../../config";
|
|
9
8
|
import { getControllerNodeId } from "../../../-g-core-values/NodeCapabilities";
|
|
10
9
|
import { assertIsManagementUser } from "../../managementPages";
|
|
11
10
|
import { getSyncedController } from "../../../library-components/SyncedController";
|
|
@@ -14,7 +13,7 @@ import { Ticket, TicketComment, TicketPatchStatus, TicketState } from "./ticketT
|
|
|
14
13
|
|
|
15
14
|
const TICKET_CACHE_POLL_INTERVAL = timeInMinute * 5;
|
|
16
15
|
|
|
17
|
-
export const ticketsArchive = archiveJSONT<Ticket>(() =>
|
|
16
|
+
export const ticketsArchive = archiveJSONT<Ticket>(() => getArchives2("logs/error-tickets/"));
|
|
18
17
|
|
|
19
18
|
let ticketCache = new Map<string, Ticket>();
|
|
20
19
|
let ensureWatching = lazy(async () => {
|