querysub 0.700.0 → 0.702.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 +1 -1
- package/src/-a-archives/archives2.ts +9 -1
- package/src/deployManager/components/MachineDetailPage.tsx +9 -1
- package/src/deployManager/components/MachinesListPage.tsx +1 -1
- package/src/deployManager/machineApplyMainCode.ts +52 -6
- package/src/deployManager/machineSchema.ts +4 -0
- package/src/library-components/InputLabel.tsx +9 -2
- package/src/library-components/UsageBar.tsx +10 -1
package/package.json
CHANGED
|
@@ -41,6 +41,8 @@ function createSourceWindows(
|
|
|
41
41
|
|
|
42
42
|
function archiveBuilder(bucket: string, overrides: Partial<RemoteConfigBase>) {
|
|
43
43
|
const ONTARIO = `https://99-250-124-91.querysubtest.com:5234/file/${STORAGE_ACCOUNT}/${bucket}/storage/storagerouting.json`;
|
|
44
|
+
// NOTE: This is on an overly large extra disk that's on our 4090 machine. I prefer to not use it because it might slow down diffusion, but it's quite large (2tb, and diffusion will never use more than 1tb), so we could use it if we needed to.
|
|
45
|
+
const ONTARIO2 = `https://99-250-124-91.querysubtest.com:5235/file/${STORAGE_ACCOUNT}/${bucket}/storage/storagerouting.json`;
|
|
44
46
|
const HETZNER = `https://65-109-93-113.querysubtest.com:5234/file/${STORAGE_ACCOUNT}/${bucket}/storage/storagerouting.json`;
|
|
45
47
|
const BACKBLAZE = `https://f002.backblazeb2.com/file/${bucket}/storage/storagerouting.json`;
|
|
46
48
|
|
|
@@ -76,6 +78,12 @@ function archiveBuilder(bucket: string, overrides: Partial<RemoteConfigBase>) {
|
|
|
76
78
|
// NOTE: Once we add new sources, we need to add something at a new start time in the future so we have time to move the files.
|
|
77
79
|
// TODO: EVENTUALLY, Once we have enough data, instead of restriping, we need to bisect the data. That way we can slowly move over the data instead of trying to rewrite all of our data all at once. However, for the moment, moving all the data is probably fine. On the data center, we should be able to move terabytes of data every hour, so we should be able to handle quite a bit, especially if we have a few days to run the restriping.
|
|
78
80
|
// IMPORTANT! Every time we re-stripe, we need to increment the route version. Otherwise, we could reuse a folder that we previously used that's no longer in sync. which will cause us to resurrect old data, which is very bad. We don't want to do that...
|
|
81
|
+
// NOTE: We do full redundancy, so if the ranges are a, b, c, then its:
|
|
82
|
+
// s0=a s1=b s2=c
|
|
83
|
+
// s0=c s1=a s2=b
|
|
84
|
+
// s0=b s1=c s2=a
|
|
85
|
+
// Essentially, we just shift it every time. So every server has all the data, But the primary writers are divided between all the servers.
|
|
86
|
+
// (If you follow A, you can see it's diagonal, B is diagonal, they're all diagonal, like stripes).
|
|
79
87
|
...stripeSources([ONTARIO, HETZNER], { routeVersion: 2 }),
|
|
80
88
|
{ type: "backblaze", url: BACKBLAZE, name: "backblaze" },
|
|
81
89
|
]
|
|
@@ -83,7 +91,7 @@ function archiveBuilder(bucket: string, overrides: Partial<RemoteConfigBase>) {
|
|
|
83
91
|
]);
|
|
84
92
|
|
|
85
93
|
return createArchives({
|
|
86
|
-
version:
|
|
94
|
+
version: 29,
|
|
87
95
|
sources,
|
|
88
96
|
});
|
|
89
97
|
}
|
|
@@ -134,13 +134,21 @@ export class MachineDetailPage extends qreact.Component {
|
|
|
134
134
|
<ShowMore className={css.whiteSpace("pre-wrap")} maxHeight={80}>
|
|
135
135
|
{(() => {
|
|
136
136
|
if (typeof value === "object") {
|
|
137
|
-
return <UsageBar label={value.label || value.type} value={value.value} max={value.max} {...getUsageThresholds(value.type)} />;
|
|
137
|
+
return <UsageBar label={value.label || value.type} value={value.value} max={value.max} subValue={value.subValue} subLabel={value.subLabel} {...getUsageThresholds(value.type)} />;
|
|
138
138
|
}
|
|
139
139
|
return value;
|
|
140
140
|
})()}
|
|
141
141
|
</ShowMore>
|
|
142
142
|
</div>
|
|
143
143
|
))}
|
|
144
|
+
{machine.meminfo && (
|
|
145
|
+
<div className={css.hbox(10).hsla(0, 0, 0, 0.1).pad2(10, 2)}>
|
|
146
|
+
<b className={css.flexShrink0}>meminfo:</b>
|
|
147
|
+
<ShowMore className={css.whiteSpace("pre-wrap")} maxHeight={80}>
|
|
148
|
+
{Object.entries(machine.meminfo).map(([key, bytes]) => `${key}: ${formatNumber(bytes)}B`).join("\n")}
|
|
149
|
+
</ShowMore>
|
|
150
|
+
</div>
|
|
151
|
+
)}
|
|
144
152
|
</div>
|
|
145
153
|
</div>
|
|
146
154
|
|
|
@@ -187,7 +187,7 @@ export class MachinesListPage extends qreact.Component {
|
|
|
187
187
|
<RenderGitRefInfo gitRef={machineInfo.gitRef} />
|
|
188
188
|
{Object.values(machineInfo.info).map(value => {
|
|
189
189
|
if (typeof value === "string") return undefined;
|
|
190
|
-
return <UsageBar label={value.label || value.type} value={value.value} max={value.max} {...getUsageThresholds(value.type)} />;
|
|
190
|
+
return <UsageBar label={value.label || value.type} value={value.value} max={value.max} subValue={value.subValue} subLabel={value.subLabel} {...getUsageThresholds(value.type)} />;
|
|
191
191
|
})}
|
|
192
192
|
</div>
|
|
193
193
|
<div className={css.vbox(4).flexGrow(1)}>
|
|
@@ -63,6 +63,45 @@ const getMemoryInfo = measureWrap(async function getMemoryInfo(): Promise<{ memo
|
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
|
|
66
|
+
const MEMINFO_LINE_REGEX = /^(\S+):\s+(\d+)(?:\s+(\S+))?\s*$/;
|
|
67
|
+
const MEMINFO_UNIT_MULTIPLIERS: Record<string, number> = {
|
|
68
|
+
b: 1,
|
|
69
|
+
kb: 1024,
|
|
70
|
+
mb: 1024 * 1024,
|
|
71
|
+
gb: 1024 * 1024 * 1024,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export function parseMeminfo(raw: string): Record<string, number> {
|
|
75
|
+
let result: Record<string, number> = {};
|
|
76
|
+
for (let line of raw.split("\n")) {
|
|
77
|
+
let match = MEMINFO_LINE_REGEX.exec(line);
|
|
78
|
+
if (!match) continue;
|
|
79
|
+
let [, key, valueText, unit] = match;
|
|
80
|
+
let multiplier = unit ? MEMINFO_UNIT_MULTIPLIERS[unit.toLowerCase()] : 1;
|
|
81
|
+
if (multiplier === undefined) continue;
|
|
82
|
+
let value = parseInt(valueText);
|
|
83
|
+
if (!(value >= 0)) continue;
|
|
84
|
+
result[key] = value * multiplier;
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const getMeminfo = measureWrap(async function getMeminfo(): Promise<Record<string, number> | undefined> {
|
|
90
|
+
if (os.platform() === "win32") {
|
|
91
|
+
throw new Error("Windows is not supported for machine resource monitoring");
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
let raw = await fs.promises.readFile("/proc/meminfo", "utf8");
|
|
95
|
+
let parsed = parseMeminfo(raw);
|
|
96
|
+
if (Object.keys(parsed).length > 0) {
|
|
97
|
+
return parsed;
|
|
98
|
+
}
|
|
99
|
+
} catch (e: any) {
|
|
100
|
+
console.warn(`Error reading /proc/meminfo: ${e.stack}`);
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
});
|
|
104
|
+
|
|
66
105
|
const LARGE_DISK_MIN_BYTES = 100 * 1024 * 1024 * 1024;
|
|
67
106
|
const DF_LINE_REGEX = /^(\S+)\s+(\d+)\s+(\d+)\s+\d+\s+\S+\s+(.+?)\s*$/;
|
|
68
107
|
|
|
@@ -93,12 +132,12 @@ const getDiskInfos = measureWrap(async function getDiskInfos(): Promise<DiskInfo
|
|
|
93
132
|
}
|
|
94
133
|
disks.push({ device, mountPoint, value, max });
|
|
95
134
|
}
|
|
135
|
+
sort(disks, disk => -disk.max);
|
|
96
136
|
let large = disks.filter(disk => disk.max >= LARGE_DISK_MIN_BYTES);
|
|
97
|
-
if (large.length
|
|
98
|
-
|
|
137
|
+
if (large.length === 0) {
|
|
138
|
+
large = disks.slice(0, 1);
|
|
99
139
|
}
|
|
100
|
-
|
|
101
|
-
return disks;
|
|
140
|
+
return large;
|
|
102
141
|
} catch (e: any) {
|
|
103
142
|
console.warn(`Error getting disk info: ${e.stack}`);
|
|
104
143
|
}
|
|
@@ -117,9 +156,10 @@ const getLiveMachineInfo = measureWrap(async function getLiveMachineInfo() {
|
|
|
117
156
|
};
|
|
118
157
|
|
|
119
158
|
// Get system resource information
|
|
120
|
-
let [memoryInfo, diskInfos] = await Promise.all([
|
|
159
|
+
let [memoryInfo, diskInfos, meminfo] = await Promise.all([
|
|
121
160
|
getMemoryInfo(),
|
|
122
|
-
getDiskInfos()
|
|
161
|
+
getDiskInfos(),
|
|
162
|
+
getMeminfo(),
|
|
123
163
|
]);
|
|
124
164
|
|
|
125
165
|
if (memoryInfo?.memory) {
|
|
@@ -127,9 +167,15 @@ const getLiveMachineInfo = measureWrap(async function getLiveMachineInfo() {
|
|
|
127
167
|
type: "MEMORY",
|
|
128
168
|
value: memoryInfo.memory.value,
|
|
129
169
|
max: memoryInfo.memory.max,
|
|
170
|
+
subValue: meminfo?.Active,
|
|
171
|
+
subLabel: "active",
|
|
130
172
|
};
|
|
131
173
|
}
|
|
132
174
|
|
|
175
|
+
if (meminfo) {
|
|
176
|
+
machineInfo.meminfo = meminfo;
|
|
177
|
+
}
|
|
178
|
+
|
|
133
179
|
if (memoryInfo?.swap) {
|
|
134
180
|
machineInfo.info.swap = {
|
|
135
181
|
type: "SWAP",
|
|
@@ -28,12 +28,15 @@ export type InputLabelProps = Omit<InputProps, "label" | "title"> & {
|
|
|
28
28
|
tooltip?: string;
|
|
29
29
|
|
|
30
30
|
fillWidth?: boolean | number;
|
|
31
|
+
// Height a textarea opens at in edit mode; EDIT_TEXTAREA_MIN_HEIGHT when unset.
|
|
32
|
+
editMinHeight?: number;
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
// A checkbox is a small square that reads as belonging to the words beside it, so it sits tight against
|
|
34
36
|
// its label; every other input is a box of its own and wants room between the two.
|
|
35
37
|
const LABEL_GAP = 8;
|
|
36
38
|
const CHECKBOX_LABEL_GAP = 2;
|
|
39
|
+
export const EDIT_TEXTAREA_MIN_HEIGHT = 80;
|
|
37
40
|
|
|
38
41
|
function roundToDecimals(value: number, decimals: number) {
|
|
39
42
|
return Math.round(value * 10 ** decimals) / 10 ** decimals;
|
|
@@ -134,6 +137,10 @@ export class InputLabel extends qreact.Component<InputLabelProps> {
|
|
|
134
137
|
};
|
|
135
138
|
// Default focus on mount, as editting it is the only reason to click on it.
|
|
136
139
|
props.focusOnMount = props.focusOnMount ?? true;
|
|
140
|
+
if (props.textarea) {
|
|
141
|
+
props.fillWidth = props.fillWidth ?? true;
|
|
142
|
+
style.minHeight = style.minHeight ?? props.editMinHeight ?? EDIT_TEXTAREA_MIN_HEIGHT;
|
|
143
|
+
}
|
|
137
144
|
}
|
|
138
145
|
let input = <Input
|
|
139
146
|
{...props as any}
|
|
@@ -141,8 +148,8 @@ export class InputLabel extends qreact.Component<InputLabelProps> {
|
|
|
141
148
|
style={style}
|
|
142
149
|
/>;
|
|
143
150
|
if (props.edit && !this.state.editting) {
|
|
144
|
-
input = <span class={css.hbox(2).overflowHidden + " trigger-hover"}>
|
|
145
|
-
<span class={props.editClass}>
|
|
151
|
+
input = <span class={css.hbox(2).overflowHidden + (props.textarea && css.fillWidth.alignItems("start") || "") + " trigger-hover"}>
|
|
152
|
+
<span class={(props.textarea && css.whiteSpace("pre-wrap").fillWidth || "") + " " + props.editClass}>
|
|
146
153
|
{props.editValue ?? props.value}
|
|
147
154
|
</span>
|
|
148
155
|
<span class={css.opacity(0.1).opacity(1, "hover")}>
|
|
@@ -3,6 +3,8 @@ import { css } from "typesafecss";
|
|
|
3
3
|
import { formatNumber } from "socket-function/src/formatting/format";
|
|
4
4
|
|
|
5
5
|
const FLASH_CLASS_NAME = "UsageBar-flash";
|
|
6
|
+
const SUB_BAR_HEIGHT_PERCENT = 35;
|
|
7
|
+
const SUB_BAR_ALPHA = 0.3;
|
|
6
8
|
|
|
7
9
|
export const MEMORY_WARNING_THRESHOLD = 0.7;
|
|
8
10
|
export const MEMORY_ERROR_THRESHOLD = 0.85;
|
|
@@ -36,11 +38,14 @@ export class UsageBar extends qreact.Component<{
|
|
|
36
38
|
warningThreshold?: number;
|
|
37
39
|
/** Fraction of max at which the bar fill turns red and flashes. */
|
|
38
40
|
errorThreshold?: number;
|
|
41
|
+
subValue?: number;
|
|
42
|
+
subLabel?: string;
|
|
39
43
|
}> {
|
|
40
44
|
render() {
|
|
41
|
-
let { label, value, max, unit, warningThreshold, errorThreshold } = this.props;
|
|
45
|
+
let { label, value, max, unit, warningThreshold, errorThreshold, subValue, subLabel } = this.props;
|
|
42
46
|
let suffix = unit || "";
|
|
43
47
|
let fraction = max && value / max || 0;
|
|
48
|
+
let subFraction = subValue !== undefined && max && subValue / max || 0;
|
|
44
49
|
let isError = errorThreshold !== undefined && fraction >= errorThreshold;
|
|
45
50
|
let isWarning = !isError && warningThreshold !== undefined && fraction >= warningThreshold;
|
|
46
51
|
let fillClass = isError && css.hsl(0, 80, 60) || isWarning && css.hsl(45, 90, 60) || css.hsl(0, 0, 70);
|
|
@@ -50,8 +55,12 @@ export class UsageBar extends qreact.Component<{
|
|
|
50
55
|
+ fillClass
|
|
51
56
|
+ (isError && (" " + FLASH_CLASS_NAME) || "")
|
|
52
57
|
} />
|
|
58
|
+
{subValue !== undefined && <div className={
|
|
59
|
+
css.absolute.pos(0, `${100 - SUB_BAR_HEIGHT_PERCENT}%`).size(`${Math.min(subFraction, 1) * 100}%`, `${SUB_BAR_HEIGHT_PERCENT}%`).hsla(0, 0, 0, SUB_BAR_ALPHA)
|
|
60
|
+
} />}
|
|
53
61
|
<div className={css.relative}>
|
|
54
62
|
{label} ({formatNumber(value)}{suffix} / {formatNumber(max)}{suffix})
|
|
63
|
+
{subValue !== undefined && <span className={css.opacity(0.75)}>, {subLabel || "sub"} {formatNumber(subValue)}{suffix}</span>}
|
|
55
64
|
</div>
|
|
56
65
|
{isError && <style>{`
|
|
57
66
|
@keyframes ${FLASH_CLASS_NAME}-anim {
|