@kikimoradev/kcloud 1.2.0 → 1.3.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/dist/main.js +348 -123
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -844,7 +844,6 @@ const errorCodes = Object.keys({
|
|
|
844
844
|
"backup.unavailable": "rule_violation",
|
|
845
845
|
"backup.corrupt": "rule_violation",
|
|
846
846
|
"backup.undecryptable": "rule_violation",
|
|
847
|
-
"volume.invalid_size": "rule_violation",
|
|
848
847
|
"notification.not_found": "not_found",
|
|
849
848
|
"logo.not_found": "not_found",
|
|
850
849
|
"logo.unreadable": "rule_violation",
|
|
@@ -1101,7 +1100,8 @@ agentEventSchema.options.map((option) => option.shape.event.value);
|
|
|
1101
1100
|
crashLooping: "platform.crashLooping",
|
|
1102
1101
|
provisioned: "platform.provisioned",
|
|
1103
1102
|
sandboxStarted: "platform.sandboxStarted",
|
|
1104
|
-
sandboxStopped: "platform.sandboxStopped"
|
|
1103
|
+
sandboxStopped: "platform.sandboxStopped",
|
|
1104
|
+
restartedByNode: "platform.restartedByNode"
|
|
1105
1105
|
}).rawLog;
|
|
1106
1106
|
const agentStateSchema = z.enum([
|
|
1107
1107
|
"created",
|
|
@@ -1198,41 +1198,11 @@ const degradedAgentSchema = z.object({
|
|
|
1198
1198
|
z.object({
|
|
1199
1199
|
status: z.enum(runtimeHealthStates),
|
|
1200
1200
|
database: z.object({ ok: z.boolean() }),
|
|
1201
|
-
engine: z.enum(runtimeEngineKinds),
|
|
1202
|
-
docker: z.object({
|
|
1203
|
-
state: z.enum(dockerStates),
|
|
1204
|
-
lastError: z.string().nullable(),
|
|
1205
|
-
checkedAt: z.iso.datetime().nullable()
|
|
1206
|
-
}),
|
|
1207
|
-
storage: z.object({
|
|
1208
|
-
data: storageStateSchema,
|
|
1209
|
-
engine: storageStateSchema
|
|
1210
|
-
}),
|
|
1211
1201
|
partitions: z.object({
|
|
1212
1202
|
defaultRows: z.int().nonnegative(),
|
|
1213
1203
|
ensuredThrough: z.string().nullable(),
|
|
1214
1204
|
lastMaintenanceAt: z.iso.datetime().nullable()
|
|
1215
1205
|
}),
|
|
1216
|
-
reconcile: z.object({
|
|
1217
|
-
lastTickAt: z.iso.datetime().nullable(),
|
|
1218
|
-
lastTickError: z.string().nullable(),
|
|
1219
|
-
agentsQueued: z.int().nonnegative(),
|
|
1220
|
-
agentsActive: z.int().nonnegative()
|
|
1221
|
-
}),
|
|
1222
|
-
ingest: z.object({
|
|
1223
|
-
readers: z.int().nonnegative(),
|
|
1224
|
-
oldestCursorLagSeconds: z.number().nonnegative().nullable()
|
|
1225
|
-
}),
|
|
1226
|
-
sandbox: z.object({
|
|
1227
|
-
enabled: z.boolean(),
|
|
1228
|
-
image: z.object({
|
|
1229
|
-
tag: z.string(),
|
|
1230
|
-
digest: z.string()
|
|
1231
|
-
}),
|
|
1232
|
-
agentsWithSandbox: z.int().nonnegative(),
|
|
1233
|
-
sidecarsRunning: z.int().nonnegative(),
|
|
1234
|
-
volumeBytes: z.int().nonnegative().nullable()
|
|
1235
|
-
}),
|
|
1236
1206
|
metrics: runtimeMetricsSchema,
|
|
1237
1207
|
deadLetters: z.array(deadLetterQueueSchema),
|
|
1238
1208
|
degradedAgents: z.array(degradedAgentSchema)
|
|
@@ -1706,7 +1676,8 @@ const poolUsageSchema = z.object({
|
|
|
1706
1676
|
memoryBytes: z.int().nonnegative(),
|
|
1707
1677
|
memoryMaxBytes: z.int().nonnegative().nullable(),
|
|
1708
1678
|
cpus: z.number().nonnegative().nullable(),
|
|
1709
|
-
memoryKills: z.int().nonnegative()
|
|
1679
|
+
memoryKills: z.int().nonnegative(),
|
|
1680
|
+
processors: z.int().positive().nullable().optional()
|
|
1710
1681
|
}).meta({ id: "PoolUsage" });
|
|
1711
1682
|
const nodePoolSchema = z.object({
|
|
1712
1683
|
slots: z.int().nonnegative(),
|
|
@@ -1715,8 +1686,26 @@ const nodePoolSchema = z.object({
|
|
|
1715
1686
|
holders: z.array(sessionHolderSchema).nullable(),
|
|
1716
1687
|
usage: poolUsageSchema.nullable()
|
|
1717
1688
|
}).meta({ id: "NodePool" });
|
|
1689
|
+
const sessionRefusalsSchema = z.object({
|
|
1690
|
+
count: z.int().positive(),
|
|
1691
|
+
lastAt: z.iso.datetime(),
|
|
1692
|
+
pool: sessionRoleSchema,
|
|
1693
|
+
reason: z.string()
|
|
1694
|
+
}).meta({ id: "SessionRefusals" });
|
|
1695
|
+
const poolLayoutSchema = z.object({
|
|
1696
|
+
cores: z.array(z.int().positive()).min(1),
|
|
1697
|
+
cpus: z.object({
|
|
1698
|
+
monitor: z.number().positive(),
|
|
1699
|
+
executor: z.number().positive()
|
|
1700
|
+
}).nullable(),
|
|
1701
|
+
topologyError: z.string().nullable(),
|
|
1702
|
+
clamped: z.boolean()
|
|
1703
|
+
}).meta({ id: "PoolLayout" });
|
|
1718
1704
|
const nodePoolsSchema = z.object({
|
|
1719
1705
|
enforcement: poolEnforcementSchema,
|
|
1706
|
+
agentsSlice: poolEnforcementSchema.nullable(),
|
|
1707
|
+
refused: sessionRefusalsSchema.nullable(),
|
|
1708
|
+
layout: poolLayoutSchema.nullable(),
|
|
1720
1709
|
monitor: nodePoolSchema,
|
|
1721
1710
|
executor: nodePoolSchema
|
|
1722
1711
|
}).meta({ id: "NodePools" });
|
|
@@ -1822,12 +1811,7 @@ const nodeAgentSchema = z.object({
|
|
|
1822
1811
|
const nodeRuntimeSchema = z.object({
|
|
1823
1812
|
reportedAt: z.iso.datetime(),
|
|
1824
1813
|
docker: z.object({
|
|
1825
|
-
state: z.enum(
|
|
1826
|
-
"ok",
|
|
1827
|
-
"unavailable",
|
|
1828
|
-
"disabled",
|
|
1829
|
-
"adopting"
|
|
1830
|
-
]),
|
|
1814
|
+
state: z.enum(dockerStates),
|
|
1831
1815
|
lastError: z.string().nullable()
|
|
1832
1816
|
}),
|
|
1833
1817
|
storage: z.object({
|
|
@@ -1840,7 +1824,20 @@ const nodeRuntimeSchema = z.object({
|
|
|
1840
1824
|
agentsQueued: z.int().nonnegative(),
|
|
1841
1825
|
agentsActive: z.int().nonnegative()
|
|
1842
1826
|
}),
|
|
1843
|
-
ingest: z.object({
|
|
1827
|
+
ingest: z.object({
|
|
1828
|
+
readers: z.int().nonnegative(),
|
|
1829
|
+
oldestCursorLagSeconds: z.number().nonnegative().nullable()
|
|
1830
|
+
}),
|
|
1831
|
+
sandbox: z.object({
|
|
1832
|
+
enabled: z.boolean(),
|
|
1833
|
+
image: z.object({
|
|
1834
|
+
tag: z.string(),
|
|
1835
|
+
digest: z.string()
|
|
1836
|
+
}),
|
|
1837
|
+
agentsWithSandbox: z.int().nonnegative(),
|
|
1838
|
+
sidecarsRunning: z.int().nonnegative(),
|
|
1839
|
+
volumeBytes: z.int().nonnegative().nullable()
|
|
1840
|
+
}),
|
|
1844
1841
|
sessions: z.object({
|
|
1845
1842
|
monitor: z.object({
|
|
1846
1843
|
effective: z.int().nonnegative(),
|
|
@@ -1859,6 +1856,28 @@ nodeSchema.extend({
|
|
|
1859
1856
|
update: nodeUpdateSchema.nullable()
|
|
1860
1857
|
}).meta({ id: "NodeDetail" });
|
|
1861
1858
|
z.object({ items: z.array(nodeSchema) }).meta({ id: "NodeList" });
|
|
1859
|
+
const adminNodeSessionsSchema = z.object({
|
|
1860
|
+
effective: z.int().nonnegative(),
|
|
1861
|
+
inUse: z.int().nonnegative()
|
|
1862
|
+
});
|
|
1863
|
+
const nodeHealthSchema = z.object({
|
|
1864
|
+
docker: z.enum(dockerStates),
|
|
1865
|
+
storage: z.enum(storageStates),
|
|
1866
|
+
reconcileError: z.string().nullable()
|
|
1867
|
+
}).meta({ id: "NodeHealth" });
|
|
1868
|
+
const adminNodeSchema = nodeSchema.extend({
|
|
1869
|
+
organization: z.object({
|
|
1870
|
+
id: idSchema,
|
|
1871
|
+
name: z.string().nullable()
|
|
1872
|
+
}),
|
|
1873
|
+
update: nodeUpdateSchema.nullable(),
|
|
1874
|
+
sessions: z.object({
|
|
1875
|
+
monitor: adminNodeSessionsSchema,
|
|
1876
|
+
executor: adminNodeSessionsSchema
|
|
1877
|
+
}).nullable(),
|
|
1878
|
+
health: nodeHealthSchema.nullable()
|
|
1879
|
+
}).meta({ id: "AdminNode" });
|
|
1880
|
+
z.object({ items: z.array(adminNodeSchema) }).meta({ id: "AdminNodeList" });
|
|
1862
1881
|
z.object({ nodeId: idSchema });
|
|
1863
1882
|
z.object({
|
|
1864
1883
|
organizationId: idSchema,
|
|
@@ -1932,6 +1951,30 @@ const splitFits = (split, host) => {
|
|
|
1932
1951
|
const overflow = splitOverflowOf(split, host);
|
|
1933
1952
|
return overflow.cpus <= 0 && overflow.memoryMb <= 0;
|
|
1934
1953
|
};
|
|
1954
|
+
const poolProcessorsOf = (cpus, hostCpus) => Math.max(1, Math.min(Math.round(cpus), Math.round(hostCpus)));
|
|
1955
|
+
const layoutAppliesSplit = (layout, split) => layout?.cpus != null && split !== null && layout.cpus.monitor === split.monitor.cpus && layout.cpus.executor === split.executor.cpus;
|
|
1956
|
+
const splitCoresOf = (split, cores) => {
|
|
1957
|
+
const processors = cores.reduce((total, size) => total + size, 0);
|
|
1958
|
+
const sizeAt = (index) => cores[Math.min(index, cores.length - 1)] ?? 1;
|
|
1959
|
+
const coresFrom = (first, cpus) => {
|
|
1960
|
+
const wanted = poolProcessorsOf(cpus, processors);
|
|
1961
|
+
let taken = sizeAt(first);
|
|
1962
|
+
let count = 1;
|
|
1963
|
+
while (taken + sizeAt(first + count) <= wanted) {
|
|
1964
|
+
taken += sizeAt(first + count);
|
|
1965
|
+
count += 1;
|
|
1966
|
+
}
|
|
1967
|
+
return count;
|
|
1968
|
+
};
|
|
1969
|
+
const executor = coresFrom(0, split.executor.cpus);
|
|
1970
|
+
const monitor = coresFrom(executor, split.monitor.cpus);
|
|
1971
|
+
return {
|
|
1972
|
+
cores: cores.length,
|
|
1973
|
+
monitor,
|
|
1974
|
+
executor,
|
|
1975
|
+
fits: executor + monitor < cores.length
|
|
1976
|
+
};
|
|
1977
|
+
};
|
|
1935
1978
|
//#endregion
|
|
1936
1979
|
//#region ../../packages/contracts/src/appearance.ts
|
|
1937
1980
|
const colorSchema = z.string().regex(hexColorPattern);
|
|
@@ -2571,9 +2614,6 @@ const containerHealthSchema = z.enum([
|
|
|
2571
2614
|
]);
|
|
2572
2615
|
const maxAgentDescriptionLength = 2e3;
|
|
2573
2616
|
const maxAgentPromptLength = 64e3;
|
|
2574
|
-
const minAgentCpus = .25;
|
|
2575
|
-
const maxAgentMemoryMb = 16384;
|
|
2576
|
-
const defaultAgentMemoryMb = 4096;
|
|
2577
2617
|
const workspaceDirectorySchema = z.string().regex(/^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/);
|
|
2578
2618
|
const timeZones = new Set(Intl.supportedValuesOf("timeZone"));
|
|
2579
2619
|
const timezoneSchema = z.string().trim().refine((value) => timeZones.has(value), { message: "expected an IANA time zone identifier" });
|
|
@@ -2584,11 +2624,13 @@ const agentDiskGbSchema = z.int().min(1).max(100);
|
|
|
2584
2624
|
const agentMachineSchema = z.object({
|
|
2585
2625
|
docker: z.boolean(),
|
|
2586
2626
|
browser: z.boolean(),
|
|
2587
|
-
cpus: z.number().min(minAgentCpus).max(8),
|
|
2588
|
-
memoryMb: z.int().min(512).max(maxAgentMemoryMb),
|
|
2589
2627
|
diskGb: agentDiskGbSchema
|
|
2590
|
-
})
|
|
2628
|
+
});
|
|
2591
2629
|
const agentMachineInputSchema = agentMachineSchema.extend({ diskGb: agentDiskGbSchema.optional() });
|
|
2630
|
+
const agentMachineDtoSchema = agentMachineSchema.extend({
|
|
2631
|
+
cpus: z.number().positive().optional().meta({ deprecated: true }),
|
|
2632
|
+
memoryMb: z.int().positive().optional().meta({ deprecated: true })
|
|
2633
|
+
}).meta({ id: "AgentMachine" });
|
|
2592
2634
|
const agentPromptsSchema = z.object({
|
|
2593
2635
|
monitor: agentPromptSchema,
|
|
2594
2636
|
executor: agentPromptSchema
|
|
@@ -2674,7 +2716,7 @@ agentListItemSchema.extend({
|
|
|
2674
2716
|
version: z.int().nonnegative(),
|
|
2675
2717
|
description: z.string(),
|
|
2676
2718
|
timezone: z.string(),
|
|
2677
|
-
machine:
|
|
2719
|
+
machine: agentMachineDtoSchema,
|
|
2678
2720
|
settings: kikimoraSettingsSchema,
|
|
2679
2721
|
effectiveSettings: workerSettingsSchema,
|
|
2680
2722
|
prompts: agentPromptsSchema,
|
|
@@ -2816,13 +2858,20 @@ z.object({
|
|
|
2816
2858
|
nodeId: idSchema
|
|
2817
2859
|
}).meta({ id: "RestoreAgentBody" });
|
|
2818
2860
|
z.object({ agentId: idSchema }).meta({ id: "RestoredAgent" });
|
|
2861
|
+
const agentBackupContentDigestSchema = z.string().regex(/^[0-9a-f]{64}$/u);
|
|
2819
2862
|
z.object({
|
|
2820
2863
|
agentId: idSchema,
|
|
2821
2864
|
trigger: z.enum(agentBackupTriggers),
|
|
2822
2865
|
kikimoraVersion: z.string().min(1).max(64),
|
|
2823
|
-
entries: z.coerce.number().int().nonnegative()
|
|
2866
|
+
entries: z.coerce.number().int().nonnegative(),
|
|
2867
|
+
contentDigest: agentBackupContentDigestSchema.optional()
|
|
2824
2868
|
});
|
|
2825
2869
|
z.object({ backupId: idSchema });
|
|
2870
|
+
z.object({
|
|
2871
|
+
agentId: idSchema,
|
|
2872
|
+
contentDigest: agentBackupContentDigestSchema
|
|
2873
|
+
});
|
|
2874
|
+
z.object({ backupId: idSchema.nullable() });
|
|
2826
2875
|
[
|
|
2827
2876
|
"application/vnd.oci.image.index.v1+json",
|
|
2828
2877
|
"application/vnd.oci.image.manifest.v1+json",
|
|
@@ -2830,21 +2879,12 @@ z.object({ backupId: idSchema });
|
|
|
2830
2879
|
"application/vnd.docker.distribution.manifest.v2+json"
|
|
2831
2880
|
].join(", ");
|
|
2832
2881
|
const pinnedPlatformImagePattern = /^[a-z0-9][a-z0-9.-]*(?::[0-9]{1,5})?(?:\/[a-z0-9][a-z0-9._-]*)+@sha256:[0-9a-f]{64}$/;
|
|
2833
|
-
|
|
2834
|
-
//#region ../../packages/contracts/src/infrastructure.ts
|
|
2835
|
-
const volumeRoles = ["data", "agents"];
|
|
2836
|
-
const volumeRoleSchema = z.enum(volumeRoles);
|
|
2882
|
+
const volumeRoleSchema = z.enum(["data", "agents"]);
|
|
2837
2883
|
const filesystemNameSchema = z.enum([
|
|
2838
2884
|
"root",
|
|
2839
2885
|
"data",
|
|
2840
2886
|
"agents"
|
|
2841
2887
|
]);
|
|
2842
|
-
z.enum([
|
|
2843
|
-
"digitalOcean",
|
|
2844
|
-
"spaces",
|
|
2845
|
-
"ghcr",
|
|
2846
|
-
"backup"
|
|
2847
|
-
]);
|
|
2848
2888
|
const backupKindSchema = z.enum(["scheduled", "manual"]);
|
|
2849
2889
|
const backupStatusSchema = z.enum([
|
|
2850
2890
|
"running",
|
|
@@ -2888,65 +2928,42 @@ const sha256HexPattern = /^[0-9a-f]{64}$/;
|
|
|
2888
2928
|
const platformSecretPattern = /^[\x21-\x7e]+$/;
|
|
2889
2929
|
const instantSchema$1 = z.iso.datetime();
|
|
2890
2930
|
const byteCountSchema$1 = z.int().nonnegative();
|
|
2891
|
-
|
|
2931
|
+
z.string().trim().min(1).max(maxPlatformSecretLength).regex(platformSecretPattern, { message: "expected printable ASCII characters without spaces" });
|
|
2892
2932
|
const secretPresenceSchema = z.object({ present: z.boolean() }).meta({ id: "SecretPresence" });
|
|
2893
2933
|
const regionSchema = z.string().regex(regionSlugPattern);
|
|
2934
|
+
const bucketNameSchema = z.string().regex(bucketNamePattern);
|
|
2894
2935
|
const platformVolumeSchema = z.object({
|
|
2895
2936
|
role: volumeRoleSchema,
|
|
2896
2937
|
id: z.guid(),
|
|
2897
2938
|
name: z.string().regex(volumeNamePattern)
|
|
2898
2939
|
}).meta({ id: "PlatformVolume" });
|
|
2899
|
-
const
|
|
2900
|
-
const
|
|
2940
|
+
const countOfRole = (volumes, role) => volumes.filter((volume) => volume.role === role).length;
|
|
2941
|
+
const oneDataVolumeAndAtMostOneAgentsVolume = (volumes) => countOfRole(volumes, "data") === 1 && countOfRole(volumes, "agents") <= 1;
|
|
2942
|
+
const platformVolumesSchema = z.array(platformVolumeSchema).refine(oneDataVolumeAndAtMostOneAgentsVolume, { message: "expected one data volume and at most one agents volume" });
|
|
2901
2943
|
const isOrigin = (value) => {
|
|
2902
2944
|
if (!URL.canParse(value)) return false;
|
|
2903
2945
|
const url = new URL(value);
|
|
2904
2946
|
return url.pathname === "/" && url.search === "" && url.hash === "" && url.username === "" && url.password === "";
|
|
2905
2947
|
};
|
|
2906
2948
|
const spacesEndpointSchema = z.url({ protocol: /^https?$/ }).max(2e3).refine(isOrigin, { message: "expected an origin without a path, a query, a fragment or credentials" });
|
|
2907
|
-
const digitalOceanShape = {
|
|
2908
|
-
dropletId: z.int().positive(),
|
|
2909
|
-
region: regionSchema,
|
|
2910
|
-
reservedIp: z.ipv4(),
|
|
2911
|
-
volumes: platformVolumesSchema
|
|
2912
|
-
};
|
|
2913
|
-
const spacesShape = {
|
|
2914
|
-
endpoint: spacesEndpointSchema,
|
|
2915
|
-
region: regionSchema,
|
|
2916
|
-
bucket: z.string().regex(bucketNamePattern)
|
|
2917
|
-
};
|
|
2918
2949
|
const backupSettingsSchema = z.object({ retentionDays: z.int().min(1).max(maxBackupRetentionDays) }).meta({ id: "BackupSettings" });
|
|
2919
2950
|
z.object({
|
|
2920
|
-
version: z.int().nonnegative(),
|
|
2921
2951
|
instance: z.string(),
|
|
2922
2952
|
domain: z.string(),
|
|
2923
2953
|
digitalOcean: z.object({
|
|
2924
|
-
|
|
2925
|
-
|
|
2954
|
+
dropletId: z.int().positive(),
|
|
2955
|
+
region: regionSchema,
|
|
2956
|
+
reservedIp: z.ipv4(),
|
|
2957
|
+
volumes: platformVolumesSchema
|
|
2926
2958
|
}).nullable(),
|
|
2927
2959
|
spaces: z.object({
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2960
|
+
endpoint: spacesEndpointSchema,
|
|
2961
|
+
region: regionSchema,
|
|
2962
|
+
bucket: bucketNameSchema
|
|
2931
2963
|
}).nullable(),
|
|
2932
2964
|
ghcrToken: secretPresenceSchema,
|
|
2933
|
-
backup: backupSettingsSchema
|
|
2934
|
-
updatedAt: instantSchema$1
|
|
2965
|
+
backup: backupSettingsSchema
|
|
2935
2966
|
}).meta({ id: "PlatformSettings" });
|
|
2936
|
-
z.object({
|
|
2937
|
-
version: z.int().nonnegative(),
|
|
2938
|
-
digitalOcean: z.object({
|
|
2939
|
-
...digitalOceanShape,
|
|
2940
|
-
token: platformSecretSchema.nullable()
|
|
2941
|
-
}).nullable(),
|
|
2942
|
-
spaces: z.object({
|
|
2943
|
-
...spacesShape,
|
|
2944
|
-
accessKey: platformSecretSchema.nullable(),
|
|
2945
|
-
secretKey: platformSecretSchema.nullable()
|
|
2946
|
-
}).nullable(),
|
|
2947
|
-
ghcrToken: platformSecretSchema.nullable(),
|
|
2948
|
-
backup: backupSettingsSchema.nullable()
|
|
2949
|
-
}).meta({ id: "UpdatePlatformSettingsBody" });
|
|
2950
2967
|
const storageReadingSchema = z.discriminatedUnion("measured", [z.object({
|
|
2951
2968
|
measured: z.literal(true),
|
|
2952
2969
|
path: z.string(),
|
|
@@ -3010,8 +3027,6 @@ z.object({
|
|
|
3010
3027
|
dockerRootDir: z.string().nullable()
|
|
3011
3028
|
})
|
|
3012
3029
|
}).meta({ id: "PlatformNode" });
|
|
3013
|
-
z.object({ role: volumeRoleSchema });
|
|
3014
|
-
z.object({ sizeGb: z.int().positive() }).meta({ id: "ResizeVolumeBody" });
|
|
3015
3030
|
const requesterSchema = z.object({
|
|
3016
3031
|
userId: idSchema,
|
|
3017
3032
|
label: z.string().nullable()
|
|
@@ -3148,12 +3163,15 @@ z.object({
|
|
|
3148
3163
|
versions: z.array(masterKeyVersionSchema),
|
|
3149
3164
|
rotationPending: z.boolean()
|
|
3150
3165
|
}).meta({ id: "MasterKey" });
|
|
3151
|
-
|
|
3166
|
+
//#endregion
|
|
3167
|
+
//#region ../../packages/contracts/src/metrics.ts
|
|
3168
|
+
const metricsRanges = [
|
|
3152
3169
|
"1h",
|
|
3153
3170
|
"24h",
|
|
3154
3171
|
"7d",
|
|
3155
3172
|
"90d"
|
|
3156
|
-
]
|
|
3173
|
+
];
|
|
3174
|
+
const metricsRangeSchema = z.enum(metricsRanges);
|
|
3157
3175
|
const agentMetricsRanges = [
|
|
3158
3176
|
"1h",
|
|
3159
3177
|
"24h",
|
|
@@ -3207,18 +3225,13 @@ const agentUsageSchema = z.object({
|
|
|
3207
3225
|
blockReadBytes: byteCountSchema,
|
|
3208
3226
|
blockWriteBytes: byteCountSchema
|
|
3209
3227
|
}).meta({ id: "AgentUsage" });
|
|
3210
|
-
const nodeHostSeriesSchema = z.object({
|
|
3211
|
-
nodeId: idSchema,
|
|
3212
|
-
nodeName: z.string(),
|
|
3213
|
-
points: z.array(hostPointSchema)
|
|
3214
|
-
}).meta({ id: "NodeHostSeries" });
|
|
3215
3228
|
z.object({
|
|
3216
3229
|
resolution: metricsResolutionSchema,
|
|
3217
3230
|
from: instantSchema,
|
|
3218
3231
|
to: instantSchema,
|
|
3219
|
-
|
|
3232
|
+
points: z.array(hostPointSchema),
|
|
3220
3233
|
agents: z.array(agentUsageSchema)
|
|
3221
|
-
}).meta({ id: "
|
|
3234
|
+
}).meta({ id: "NodeMetrics" });
|
|
3222
3235
|
z.object({
|
|
3223
3236
|
resolution: metricsResolutionSchema,
|
|
3224
3237
|
from: instantSchema,
|
|
@@ -3255,7 +3268,8 @@ const notificationTargetTypeSchema = z.enum([
|
|
|
3255
3268
|
"backup",
|
|
3256
3269
|
"queue",
|
|
3257
3270
|
"volume",
|
|
3258
|
-
"platform"
|
|
3271
|
+
"platform",
|
|
3272
|
+
"node"
|
|
3259
3273
|
]);
|
|
3260
3274
|
const hostOperationOutcomes = [
|
|
3261
3275
|
"succeeded",
|
|
@@ -4160,6 +4174,13 @@ const nodeDetailOf = async (api, { organizationId, nodeId }) => dataOf("describe
|
|
|
4160
4174
|
organizationId,
|
|
4161
4175
|
nodeId
|
|
4162
4176
|
} } }), nodeHints);
|
|
4177
|
+
const nodeMetricsOf = async (api, { organizationId, nodeId }, range) => dataOf("show what the node used", await api.GET("/organizations/{organizationId}/nodes/{nodeId}/metrics", { params: {
|
|
4178
|
+
path: {
|
|
4179
|
+
organizationId,
|
|
4180
|
+
nodeId
|
|
4181
|
+
},
|
|
4182
|
+
query: { range }
|
|
4183
|
+
} }), nodeHints);
|
|
4163
4184
|
const createdNode = async (api, organizationId, body) => {
|
|
4164
4185
|
validated(createNodeBodySchema, body, "a node");
|
|
4165
4186
|
return providerDataOf("create the node", await api.POST("/organizations/{organizationId}/nodes", {
|
|
@@ -4495,6 +4516,23 @@ const enforcementText = ({ state, reason }) => {
|
|
|
4495
4516
|
case "unsupported": return `The node holds no pool to a limit: ${reason === null ? "it cannot" : unsupportedReasons[reason] ?? reason}.`;
|
|
4496
4517
|
}
|
|
4497
4518
|
};
|
|
4519
|
+
const agentsSliceReasons = {
|
|
4520
|
+
cgroup_driver: "Docker on the host does not use the systemd cgroup driver",
|
|
4521
|
+
cgroup_driver_unread: "the node has not read the cgroup driver of Docker yet",
|
|
4522
|
+
host_unread: "the node has not read its host yet",
|
|
4523
|
+
configure_failed: "systemd did not take the limits",
|
|
4524
|
+
pending: "the node has not applied the limit yet",
|
|
4525
|
+
platform: "the node runs no pool service"
|
|
4526
|
+
};
|
|
4527
|
+
const agentsSliceText = ({ state, reason }) => {
|
|
4528
|
+
switch (state) {
|
|
4529
|
+
case "enforced": return "The node holds its agents and their sessions together to the host minus its reserve.";
|
|
4530
|
+
case "unsplit": return "The node has no split, so its agents outside their sessions have no limit.";
|
|
4531
|
+
case "unsupported": return `The node holds its agents outside their sessions to no limit: ${reason === null ? "it cannot" : agentsSliceReasons[reason] ?? reason}.`;
|
|
4532
|
+
}
|
|
4533
|
+
};
|
|
4534
|
+
const refusedText = ({ count, pool, lastAt, reason }) => `The session starter refused ${count === 1 ? "1 session" : `${count} sessions`} of the ${pool} pool in the last hour, the last at ${lastAt}: ${reason}`;
|
|
4535
|
+
const layoutTexts = (layout, split) => [...layout.topologyError === null ? [] : [`The node did not read its processor cores, so each logical processor counts as a core and a pool can share a physical core: ${layout.topologyError}`], ...layout.clamped && layoutAppliesSplit(layout, split) ? ["The split does not fit the cores of the node, so its pools run on fewer cores than the split asks for; kcloud nodes split <node> shows the cores each pool takes."] : []];
|
|
4498
4536
|
const memoryOf$1 = (pool) => {
|
|
4499
4537
|
if (pool.usage === null) return "-";
|
|
4500
4538
|
const { memoryBytes, memoryMaxBytes } = pool.usage;
|
|
@@ -4549,10 +4587,13 @@ const queueOf = (pools) => {
|
|
|
4549
4587
|
{ header: "WORK" }
|
|
4550
4588
|
], waiting);
|
|
4551
4589
|
};
|
|
4552
|
-
const poolsText = (pools) => {
|
|
4590
|
+
const poolsText = (pools, split) => {
|
|
4553
4591
|
const holders = holdersOf(pools);
|
|
4554
4592
|
return [
|
|
4555
4593
|
enforcementText(pools.enforcement),
|
|
4594
|
+
...pools.agentsSlice == null ? [] : [agentsSliceText(pools.agentsSlice)],
|
|
4595
|
+
...pools.refused == null ? [] : [refusedText(pools.refused)],
|
|
4596
|
+
...pools.layout == null ? [] : layoutTexts(pools.layout, split),
|
|
4556
4597
|
renderTable([
|
|
4557
4598
|
{ header: "POOL" },
|
|
4558
4599
|
{ header: "SLOTS IN USE" },
|
|
@@ -4603,8 +4644,36 @@ const agentPlanOf = (agent) => ({
|
|
|
4603
4644
|
prompts: agent.prompts,
|
|
4604
4645
|
settings: agent.settings,
|
|
4605
4646
|
timezone: agent.timezone,
|
|
4606
|
-
machine:
|
|
4647
|
+
machine: {
|
|
4648
|
+
docker: agent.machine.docker,
|
|
4649
|
+
browser: agent.machine.browser,
|
|
4650
|
+
diskGb: agent.machine.diskGb
|
|
4651
|
+
}
|
|
4607
4652
|
});
|
|
4653
|
+
const machineLimitKeys = /* @__PURE__ */ new Set(["cpus", "memoryMb"]);
|
|
4654
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4655
|
+
const warnIgnored = ({ context }, keys) => {
|
|
4656
|
+
const named = keys.map((key) => `machine.${key}`).join(" and ");
|
|
4657
|
+
context.terminal.error(context.terminal.errorStyles.warning(`warning ${named} ${keys.length === 1 ? "is" : "are"} ignored: an agent has no limits of its own, and the pools of its node limit its sessions.`));
|
|
4658
|
+
};
|
|
4659
|
+
const machineWithoutLimits = (run, machine) => {
|
|
4660
|
+
if (!isRecord(machine)) return machine;
|
|
4661
|
+
const ignored = Object.keys(machine).filter((key) => machineLimitKeys.has(key));
|
|
4662
|
+
if (ignored.length === 0) return machine;
|
|
4663
|
+
warnIgnored(run, ignored);
|
|
4664
|
+
return Object.fromEntries(Object.entries(machine).filter(([key]) => !machineLimitKeys.has(key)));
|
|
4665
|
+
};
|
|
4666
|
+
const agentFileWithoutLimits = (run, document) => isRecord(document) && "machine" in document ? {
|
|
4667
|
+
...document,
|
|
4668
|
+
machine: machineWithoutLimits(run, document.machine)
|
|
4669
|
+
} : document;
|
|
4670
|
+
const agentChangeWithoutLimits = (run, document) => isRecord(document) && isRecord(document.sections) && "machine" in document.sections ? {
|
|
4671
|
+
...document,
|
|
4672
|
+
sections: {
|
|
4673
|
+
...document.sections,
|
|
4674
|
+
machine: machineWithoutLimits(run, document.sections.machine)
|
|
4675
|
+
}
|
|
4676
|
+
} : document;
|
|
4608
4677
|
//#endregion
|
|
4609
4678
|
//#region src/commands/agents/config.ts
|
|
4610
4679
|
const agentsOf = async (scope, project) => {
|
|
@@ -4715,7 +4784,7 @@ const configCommands = (adaptersOf) => [
|
|
|
4715
4784
|
const agentId = await agentFrom(run, invocation.positionals[0], invocation.values.project);
|
|
4716
4785
|
const file = invocation.values.file;
|
|
4717
4786
|
if (file === void 0) throw new UsageError("agents set needs --file <path>.", "The file holds the sections to change; kcloud agents show <agent> --json shows the current ones.");
|
|
4718
|
-
const agent = await changedAgent(run.api, agentId, await documentOf(run, file));
|
|
4787
|
+
const agent = await changedAgent(run.api, agentId, agentChangeWithoutLimits(run, await documentOf(run, file)));
|
|
4719
4788
|
return printed(run, agent, (changed) => changed.restartRequired ? `Changed ${changed.name}; it needs kcloud agents update to take the change.` : `Changed ${changed.name}.`);
|
|
4720
4789
|
}
|
|
4721
4790
|
}),
|
|
@@ -4781,7 +4850,7 @@ const detailsOf = (agent) => [
|
|
|
4781
4850
|
["node", agent.node === null ? "none" : `${agent.node.name} (${agent.node.state})`],
|
|
4782
4851
|
["requires", agent.requiredCapabilities.length === 0 ? "nothing" : agent.requiredCapabilities.join(", ")],
|
|
4783
4852
|
["timezone", agent.timezone],
|
|
4784
|
-
["
|
|
4853
|
+
["disk", `${String(agent.machine.diskGb)} GB`],
|
|
4785
4854
|
["docker", agent.machine.docker ? "yes" : "no"],
|
|
4786
4855
|
["browser", agent.machine.browser ? "yes" : "no"],
|
|
4787
4856
|
["container", agent.runtime.containerId ?? "none"],
|
|
@@ -4976,15 +5045,13 @@ const askedPlan = async (run, projectId, values, required) => {
|
|
|
4976
5045
|
timezone: values.timezone ?? localTimezone(),
|
|
4977
5046
|
machine: {
|
|
4978
5047
|
docker: values.docker,
|
|
4979
|
-
browser: values.browser
|
|
4980
|
-
cpus: 2,
|
|
4981
|
-
memoryMb: defaultAgentMemoryMb
|
|
5048
|
+
browser: values.browser
|
|
4982
5049
|
}
|
|
4983
5050
|
};
|
|
4984
5051
|
};
|
|
4985
5052
|
const isObject = (document) => typeof document === "object" && document !== null && !Array.isArray(document);
|
|
4986
5053
|
const fileOf = async (run, projectId, { file, node }, required) => {
|
|
4987
|
-
const document = await documentOf(run, file);
|
|
5054
|
+
const document = agentFileWithoutLimits(run, await documentOf(run, file));
|
|
4988
5055
|
const placed = !isObject(document) || node === void 0 && "nodeId" in document ? document : {
|
|
4989
5056
|
...document,
|
|
4990
5057
|
nodeId: await placedNodeId(run, {
|
|
@@ -6689,6 +6756,115 @@ const agentTools = [
|
|
|
6689
6756
|
run: async ({ action, now, ...reference }, run) => actionOn(run, await agentIdOf(run, reference), action, action === "stop" && now)
|
|
6690
6757
|
})
|
|
6691
6758
|
];
|
|
6759
|
+
const percentOf = (ratio) => ratio === null ? "-" : `${(ratio * 100).toFixed(1)}%`;
|
|
6760
|
+
const disksOf = (point) => [
|
|
6761
|
+
point.diskRootUsedRatio,
|
|
6762
|
+
point.diskDataUsedRatio,
|
|
6763
|
+
point.diskAgentsUsedRatio
|
|
6764
|
+
].map(percentOf).join(" / ");
|
|
6765
|
+
const gpuMemoryOf = ({ gpuMemoryUsedBytesAvg: used, gpuMemoryTotalBytes: total }) => used === null || total === null ? "-" : `${bytesOf(used)} of ${bytesOf(total)}`;
|
|
6766
|
+
const gpuOf = (point) => `${percentOf(point.gpuUtilRatioAvg)}, ${gpuMemoryOf(point)}`;
|
|
6767
|
+
const readsAGpu = (points) => points.some((point) => point.gpuUtilRatioAvg !== null || point.gpuMemoryTotalBytes !== null);
|
|
6768
|
+
const hostText$1 = (points) => {
|
|
6769
|
+
const gpu = readsAGpu(points);
|
|
6770
|
+
return renderTable([
|
|
6771
|
+
{ header: "AT" },
|
|
6772
|
+
{
|
|
6773
|
+
header: "CPU",
|
|
6774
|
+
align: "right"
|
|
6775
|
+
},
|
|
6776
|
+
{
|
|
6777
|
+
header: "MEMORY",
|
|
6778
|
+
align: "right"
|
|
6779
|
+
},
|
|
6780
|
+
{
|
|
6781
|
+
header: "LOAD",
|
|
6782
|
+
align: "right"
|
|
6783
|
+
},
|
|
6784
|
+
{
|
|
6785
|
+
header: "DISKS ROOT / DATA / AGENTS",
|
|
6786
|
+
align: "right"
|
|
6787
|
+
},
|
|
6788
|
+
...gpu ? [{
|
|
6789
|
+
header: "GPU",
|
|
6790
|
+
align: "right"
|
|
6791
|
+
}] : []
|
|
6792
|
+
], points.map((point) => [
|
|
6793
|
+
point.ts,
|
|
6794
|
+
percentOf(point.cpuRatioAvg),
|
|
6795
|
+
`${bytesOf(point.memoryUsedBytesAvg)} of ${bytesOf(point.memoryTotalBytes)}`,
|
|
6796
|
+
point.load1Avg === null ? "-" : point.load1Avg.toFixed(2),
|
|
6797
|
+
disksOf(point),
|
|
6798
|
+
...gpu ? [gpuOf(point)] : []
|
|
6799
|
+
]));
|
|
6800
|
+
};
|
|
6801
|
+
const byFootprint = (left, right) => right.memoryBytesAvg - left.memoryBytesAvg || left.agentName.localeCompare(right.agentName);
|
|
6802
|
+
const agentsText = (agents) => renderTable([
|
|
6803
|
+
{ header: "PROJECT" },
|
|
6804
|
+
{ header: "AGENT" },
|
|
6805
|
+
{
|
|
6806
|
+
header: "CPU (CORES)",
|
|
6807
|
+
align: "right"
|
|
6808
|
+
},
|
|
6809
|
+
{
|
|
6810
|
+
header: "MEMORY AVG",
|
|
6811
|
+
align: "right"
|
|
6812
|
+
},
|
|
6813
|
+
{
|
|
6814
|
+
header: "MEMORY MAX",
|
|
6815
|
+
align: "right"
|
|
6816
|
+
},
|
|
6817
|
+
{
|
|
6818
|
+
header: "NETWORK IN / OUT",
|
|
6819
|
+
align: "right"
|
|
6820
|
+
},
|
|
6821
|
+
{
|
|
6822
|
+
header: "DISK READ / WRITE",
|
|
6823
|
+
align: "right"
|
|
6824
|
+
}
|
|
6825
|
+
], agents.toSorted(byFootprint).map((usage) => [
|
|
6826
|
+
usage.projectName,
|
|
6827
|
+
usage.agentName,
|
|
6828
|
+
usage.cpuRatioAvg === null ? "-" : usage.cpuRatioAvg.toFixed(2),
|
|
6829
|
+
bytesOf(usage.memoryBytesAvg),
|
|
6830
|
+
bytesOf(usage.memoryBytesMax),
|
|
6831
|
+
`${bytesOf(usage.networkRxBytes)} / ${bytesOf(usage.networkTxBytes)}`,
|
|
6832
|
+
`${bytesOf(usage.blockReadBytes)} / ${bytesOf(usage.blockWriteBytes)}`
|
|
6833
|
+
]));
|
|
6834
|
+
const nodeMetricsText = (metrics) => [
|
|
6835
|
+
`${metrics.from} to ${metrics.to}, one point per ${metrics.resolution}.`,
|
|
6836
|
+
"",
|
|
6837
|
+
metrics.points.length === 0 ? "The node sent no sample in that range." : hostText$1(metrics.points),
|
|
6838
|
+
"",
|
|
6839
|
+
metrics.agents.length === 0 ? "No agent you can see ran on the node in that range." : agentsText(metrics.agents)
|
|
6840
|
+
].join("\n");
|
|
6841
|
+
const rangeOf = (given) => {
|
|
6842
|
+
if (given === void 0) return "1h";
|
|
6843
|
+
const range = metricsRanges.find((candidate) => candidate === given);
|
|
6844
|
+
if (range === void 0) throw new UsageError(`--range ${given} is not a range of metrics.`, `The ranges are ${metricsRanges.join(", ")}.`);
|
|
6845
|
+
return range;
|
|
6846
|
+
};
|
|
6847
|
+
const createNodeMetricsCommand = (adaptersOf) => defineCommand({
|
|
6848
|
+
name: "metrics",
|
|
6849
|
+
summary: "Show what the host of a node and the agents on it used over a range.",
|
|
6850
|
+
usage: "nodes metrics <node> [--range <range>]",
|
|
6851
|
+
optionsUsage: [
|
|
6852
|
+
...nodeUsage,
|
|
6853
|
+
["--range <range>", `how far back: ${metricsRanges.join(", ")} (default: 1h)`],
|
|
6854
|
+
["", "the agents are those of the projects you belong to, or all for an admin"]
|
|
6855
|
+
],
|
|
6856
|
+
options: { range: { type: "string" } },
|
|
6857
|
+
positionals: {
|
|
6858
|
+
min: 1,
|
|
6859
|
+
max: 1
|
|
6860
|
+
},
|
|
6861
|
+
run: async (invocation) => {
|
|
6862
|
+
const range = rangeOf(invocation.values.range);
|
|
6863
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6864
|
+
const reference = await resolveNode(run, invocation.positionals[0] ?? "");
|
|
6865
|
+
return printed(run, await nodeMetricsOf(run.api, reference, range), nodeMetricsText);
|
|
6866
|
+
}
|
|
6867
|
+
});
|
|
6692
6868
|
//#endregion
|
|
6693
6869
|
//#region src/commands/nodes/split.ts
|
|
6694
6870
|
const splitFields = [
|
|
@@ -6744,6 +6920,8 @@ const changeOfParts = ({ reserve, monitor, executor }) => {
|
|
|
6744
6920
|
const cpusText = (cpus) => String(Math.round(cpus * 100) / 100);
|
|
6745
6921
|
const hostText = (host) => `${cpusText(host.cpus)} CPU and ${hostMemoryMbOf(host)} MB`;
|
|
6746
6922
|
const excessText = ({ cpus, memoryMb }) => [...cpus > 0 ? [`${cpusText(cpus)} CPU`] : [], ...memoryMb > 0 ? [`${memoryMb} MB`] : []].join(" and ");
|
|
6923
|
+
const coresText = (count) => count === 1 ? "1 core" : `${String(count)} cores`;
|
|
6924
|
+
const coresOfNode = (node) => node.runtime?.pools.layout?.cores ?? null;
|
|
6747
6925
|
const fittingSplit = (node, change, nameOf) => {
|
|
6748
6926
|
const merged = {
|
|
6749
6927
|
...node.split === null ? {} : valuesOf(node.split),
|
|
@@ -6757,9 +6935,25 @@ const fittingSplit = (node, change, nameOf) => {
|
|
|
6757
6935
|
validated(nodeSplitSchema, split, "a split of a node");
|
|
6758
6936
|
if (node.host === null) throw new CliError(`${node.name} has not reported its CPUs and memory yet, so it cannot be split.`, "The node reports them once its service runs; kcloud nodes show <node> shows its state.");
|
|
6759
6937
|
if (!splitFits(split, node.host)) throw new CliError(`The split asks for ${excessText(splitOverflowOf(split, node.host))} more than ${node.name} has.`, `${node.name} has ${hostText(node.host)}; shrink a pool or the reserve.`);
|
|
6938
|
+
const cores = coresOfNode(node);
|
|
6939
|
+
const fit = cores === null ? null : splitCoresOf(split, cores);
|
|
6940
|
+
if (fit !== null && !fit.fits) throw new CliError(`The pools take whole cores of ${node.name}: the executor pool would get ${coresText(fit.executor)} and the monitor pool ${coresText(fit.monitor)} of ${String(fit.cores)}, and the reserve keeps at least 1.`, "Shrink a pool, so the reserve keeps the core of processor 0.");
|
|
6760
6941
|
return split;
|
|
6761
6942
|
};
|
|
6943
|
+
const processorCountsOf = (node) => {
|
|
6944
|
+
const pools = node.runtime?.pools;
|
|
6945
|
+
if (pools === void 0 || !layoutAppliesSplit(pools.layout, node.split)) return null;
|
|
6946
|
+
const monitor = pools.monitor.usage?.processors ?? null;
|
|
6947
|
+
const executor = pools.executor.usage?.processors ?? null;
|
|
6948
|
+
return monitor === null && executor === null ? null : {
|
|
6949
|
+
monitor,
|
|
6950
|
+
executor
|
|
6951
|
+
};
|
|
6952
|
+
};
|
|
6953
|
+
const processorsText = (count) => count === 1 ? "1 processor" : `${String(count)} processors`;
|
|
6954
|
+
const poolCpusText = (cpus, processors) => processors === null ? cpusText(cpus) : `${cpusText(cpus)} (${processorsText(processors)})`;
|
|
6762
6955
|
const splitViewOf = (node) => {
|
|
6956
|
+
const cores = coresOfNode(node);
|
|
6763
6957
|
const overflow = node.split === null || node.host === null ? null : splitOverflowOf(node.split, node.host);
|
|
6764
6958
|
return {
|
|
6765
6959
|
id: node.id,
|
|
@@ -6770,6 +6964,8 @@ const splitViewOf = (node) => {
|
|
|
6770
6964
|
memoryMb: hostMemoryMbOf(node.host)
|
|
6771
6965
|
},
|
|
6772
6966
|
split: node.split,
|
|
6967
|
+
processors: processorCountsOf(node),
|
|
6968
|
+
cores: cores === null || node.split === null ? null : splitCoresOf(node.split, cores),
|
|
6773
6969
|
free: overflow === null ? null : {
|
|
6774
6970
|
cpus: -overflow.cpus,
|
|
6775
6971
|
memoryMb: -overflow.memoryMb
|
|
@@ -6779,10 +6975,12 @@ const splitViewOf = (node) => {
|
|
|
6779
6975
|
const resourceCells = ({ cpus, memoryMb }) => [cpusText(cpus), `${memoryMb} MB`];
|
|
6780
6976
|
const oneSlotOf = ({ cpus, memoryMb, slots }) => `${cpusText(cpus / slots)} CPU, ${Math.floor(memoryMb / slots)} MB`;
|
|
6781
6977
|
const hostLineOf = (view) => view.host === null ? `${view.name} has not reported its CPUs and memory yet.` : `The host has ${cpusText(view.host.cpus)} CPU and ${view.host.memoryMb} MB.`;
|
|
6978
|
+
const coresLineOf = ({ cores, executor, monitor, fits }) => fits ? `The pools take whole cores: the executor pool ${coresText(executor)} and the monitor pool ${coresText(monitor)}; the reserve keeps ${coresText(cores - executor - monitor)} of ${String(cores)}.` : `The executor pool would take ${coresText(executor)} and the monitor pool ${coresText(monitor)} of ${String(cores)}, so the node runs them on fewer cores and keeps 1 for the reserve.`;
|
|
6782
6979
|
const splitTableOf = (view, split) => {
|
|
6783
6980
|
const poolRow = (role) => [
|
|
6784
6981
|
role,
|
|
6785
|
-
|
|
6982
|
+
poolCpusText(split[role].cpus, view.processors?.[role] ?? null),
|
|
6983
|
+
`${String(split[role].memoryMb)} MB`,
|
|
6786
6984
|
String(split[role].slots),
|
|
6787
6985
|
oneSlotOf(split[role])
|
|
6788
6986
|
];
|
|
@@ -6830,7 +7028,8 @@ const splitTableOf = (view, split) => {
|
|
|
6830
7028
|
{ header: "ONE SLOT" }
|
|
6831
7029
|
], rows),
|
|
6832
7030
|
...view.host === null ? ["", hostLineOf(view)] : [],
|
|
6833
|
-
...over === "" ? [] : ["", `The split asks for ${over} more than the host has.`]
|
|
7031
|
+
...over === "" ? [] : ["", `The split asks for ${over} more than the host has.`],
|
|
7032
|
+
...view.cores === null ? [] : ["", coresLineOf(view.cores)]
|
|
6834
7033
|
].join("\n");
|
|
6835
7034
|
};
|
|
6836
7035
|
const splitText = (view) => view.split === null ? [
|
|
@@ -6838,10 +7037,14 @@ const splitText = (view) => view.split === null ? [
|
|
|
6838
7037
|
hostLineOf(view),
|
|
6839
7038
|
`kcloud nodes split set ${view.name} with every part splits it.`
|
|
6840
7039
|
].join("\n") : splitTableOf(view, view.split);
|
|
6841
|
-
const splitSummaryOf = (split) => {
|
|
7040
|
+
const splitSummaryOf = (split, processors = null) => {
|
|
6842
7041
|
if (split === null) return "none, slots counted from the host without hard limits";
|
|
6843
7042
|
const { reserve, monitor, executor } = split;
|
|
6844
|
-
const pool = (role, { cpus, memoryMb, slots }) =>
|
|
7043
|
+
const pool = (role, { cpus, memoryMb, slots }) => {
|
|
7044
|
+
const count = processors?.[role] ?? null;
|
|
7045
|
+
const onProcessors = count === null ? "" : ` (${processorsText(count)})`;
|
|
7046
|
+
return `${role} ${cpusText(cpus)} CPU${onProcessors}, ${memoryMb} MB, ${slots} slots`;
|
|
7047
|
+
};
|
|
6845
7048
|
return [
|
|
6846
7049
|
`reserve ${cpusText(reserve.cpus)} CPU, ${reserve.memoryMb} MB`,
|
|
6847
7050
|
pool("monitor", monitor),
|
|
@@ -7023,6 +7226,18 @@ const nodeTools = [
|
|
|
7023
7226
|
input: z.object({ node: nodeField }),
|
|
7024
7227
|
run: async ({ node }, run) => nodeDetailOf(run.api, await resolveNode(run, node))
|
|
7025
7228
|
}),
|
|
7229
|
+
defineTool({
|
|
7230
|
+
name: "node_metrics",
|
|
7231
|
+
title: "Show what the host of a node and its agents used",
|
|
7232
|
+
description: "Show the CPU, memory, load, disks and GPU of the host of a node over a range, and what each agent on it used over the whole range: the agents of the projects the caller belongs to, or every agent for an admin of the organization.",
|
|
7233
|
+
operations: ["getNodeMetrics"],
|
|
7234
|
+
safety: "read",
|
|
7235
|
+
input: z.object({
|
|
7236
|
+
node: nodeField,
|
|
7237
|
+
range: z.enum(metricsRanges).default("1h")
|
|
7238
|
+
}),
|
|
7239
|
+
run: async ({ node, range }, run) => nodeMetricsOf(run.api, await resolveNode(run, node), range)
|
|
7240
|
+
}),
|
|
7026
7241
|
defineTool({
|
|
7027
7242
|
name: "node_split_set",
|
|
7028
7243
|
title: "Split a node into pools",
|
|
@@ -9684,14 +9899,23 @@ const operationRowsOf = (operation) => {
|
|
|
9684
9899
|
...error === null ? [] : [["error", error]]
|
|
9685
9900
|
];
|
|
9686
9901
|
};
|
|
9687
|
-
const
|
|
9902
|
+
const lagOf = ({ oldestCursorLagSeconds }) => oldestCursorLagSeconds === null ? "-" : `${oldestCursorLagSeconds} s`;
|
|
9903
|
+
const sandboxRowsOf = ({ sandbox }) => sandbox.enabled ? [
|
|
9904
|
+
["sandboxes", `enabled, image ${sandbox.image.tag}`],
|
|
9905
|
+
["agents with docker", String(sandbox.agentsWithSandbox)],
|
|
9906
|
+
["sidecars running", String(sandbox.sidecarsRunning)],
|
|
9907
|
+
["sandbox volumes", sandbox.volumeBytes === null ? "not measured yet" : bytesOf(sandbox.volumeBytes)]
|
|
9908
|
+
] : [["sandboxes", "disabled, SANDBOX_ENABLED is off on the node"]];
|
|
9909
|
+
const runtimeRowsOf = (runtime, engine) => [
|
|
9688
9910
|
["reported", runtime.reportedAt],
|
|
9689
9911
|
["docker", runtime.docker.lastError === null ? runtime.docker.state : `${runtime.docker.state}: ${runtime.docker.lastError}`],
|
|
9690
9912
|
["data storage", storageOf(runtime.storage.data)],
|
|
9691
9913
|
["engine storage", storageOf(runtime.storage.engine)],
|
|
9692
9914
|
["last reconcile", runtime.reconcile.lastTickError === null ? runtime.reconcile.lastTickAt ?? "never" : `${runtime.reconcile.lastTickAt ?? "never"}: ${runtime.reconcile.lastTickError}`],
|
|
9693
9915
|
["agents", `${runtime.reconcile.agentsActive} active, ${runtime.reconcile.agentsQueued} queued`],
|
|
9694
|
-
["log readers", String(runtime.ingest.readers)]
|
|
9916
|
+
["log readers", String(runtime.ingest.readers)],
|
|
9917
|
+
["oldest cursor lag", lagOf(runtime.ingest)],
|
|
9918
|
+
...engine === "native" ? [] : sandboxRowsOf(runtime)
|
|
9695
9919
|
];
|
|
9696
9920
|
const sessionsText = (count, noun) => `${String(count)} ${noun}${count === 1 ? "" : "s"}`;
|
|
9697
9921
|
const heldPoolsOf = ({ held = [] }) => held;
|
|
@@ -9725,7 +9949,7 @@ const nodeDetailsText = (node) => [
|
|
|
9725
9949
|
["data root", node.host?.dataRoot ?? "-"],
|
|
9726
9950
|
["capabilities", capabilitiesOf(node)],
|
|
9727
9951
|
["offers", node.offered.length === 0 ? "-" : node.offered.join(", ")],
|
|
9728
|
-
["split", splitSummaryOf(node.split)],
|
|
9952
|
+
["split", splitSummaryOf(node.split, processorCountsOf(node))],
|
|
9729
9953
|
["registration", registrationOf(node)],
|
|
9730
9954
|
["connected", node.connectedAt ?? "never"],
|
|
9731
9955
|
["last seen", node.lastSeenAt ?? "never"],
|
|
@@ -9751,9 +9975,9 @@ const nodeDetailsText = (node) => [
|
|
|
9751
9975
|
])),
|
|
9752
9976
|
"",
|
|
9753
9977
|
...node.runtime === null ? ["The node has reported no runtime yet."] : [
|
|
9754
|
-
renderTable([{ header: "RUNTIME" }, { header: "VALUE" }], runtimeRowsOf(node.runtime)),
|
|
9978
|
+
renderTable([{ header: "RUNTIME" }, { header: "VALUE" }], runtimeRowsOf(node.runtime, node.engine)),
|
|
9755
9979
|
"",
|
|
9756
|
-
poolsText(node.runtime.pools)
|
|
9980
|
+
poolsText(node.runtime.pools, node.split)
|
|
9757
9981
|
]
|
|
9758
9982
|
].join("\n");
|
|
9759
9983
|
const installCommandsOf = (origin) => [
|
|
@@ -9880,6 +10104,7 @@ const createNodesCommand = (adaptersOf) => defineGroup({
|
|
|
9880
10104
|
return printed(run, await nodeDetailOf(run.api, reference), nodeDetailsText);
|
|
9881
10105
|
}
|
|
9882
10106
|
}),
|
|
10107
|
+
createNodeMetricsCommand(adaptersOf),
|
|
9883
10108
|
createSplitCommand(adaptersOf),
|
|
9884
10109
|
defineCommand({
|
|
9885
10110
|
name: "create",
|