@kikimoradev/kcloud 1.2.0 → 1.4.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 +352 -127
- 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)
|
|
@@ -1641,8 +1611,10 @@ const admissionHoldSchema = z.object({
|
|
|
1641
1611
|
const nodeStateSchema = z.enum([
|
|
1642
1612
|
"pending",
|
|
1643
1613
|
"online",
|
|
1644
|
-
"offline"
|
|
1614
|
+
"offline",
|
|
1615
|
+
"outdated"
|
|
1645
1616
|
]);
|
|
1617
|
+
const minimumNodeVersion = "1.3.0";
|
|
1646
1618
|
const nodeProviders = ["self_hosted", "digitalocean"];
|
|
1647
1619
|
const nodeProviderSchema = z.enum(nodeProviders);
|
|
1648
1620
|
const nodeNameSchema = z.string().trim().regex(slugPattern);
|
|
@@ -1706,7 +1678,8 @@ const poolUsageSchema = z.object({
|
|
|
1706
1678
|
memoryBytes: z.int().nonnegative(),
|
|
1707
1679
|
memoryMaxBytes: z.int().nonnegative().nullable(),
|
|
1708
1680
|
cpus: z.number().nonnegative().nullable(),
|
|
1709
|
-
memoryKills: z.int().nonnegative()
|
|
1681
|
+
memoryKills: z.int().nonnegative(),
|
|
1682
|
+
processors: z.int().positive().nullable().optional()
|
|
1710
1683
|
}).meta({ id: "PoolUsage" });
|
|
1711
1684
|
const nodePoolSchema = z.object({
|
|
1712
1685
|
slots: z.int().nonnegative(),
|
|
@@ -1715,8 +1688,26 @@ const nodePoolSchema = z.object({
|
|
|
1715
1688
|
holders: z.array(sessionHolderSchema).nullable(),
|
|
1716
1689
|
usage: poolUsageSchema.nullable()
|
|
1717
1690
|
}).meta({ id: "NodePool" });
|
|
1691
|
+
const sessionRefusalsSchema = z.object({
|
|
1692
|
+
count: z.int().positive(),
|
|
1693
|
+
lastAt: z.iso.datetime(),
|
|
1694
|
+
pool: sessionRoleSchema,
|
|
1695
|
+
reason: z.string()
|
|
1696
|
+
}).meta({ id: "SessionRefusals" });
|
|
1697
|
+
const poolLayoutSchema = z.object({
|
|
1698
|
+
cores: z.array(z.int().positive()).min(1),
|
|
1699
|
+
cpus: z.object({
|
|
1700
|
+
monitor: z.number().positive(),
|
|
1701
|
+
executor: z.number().positive()
|
|
1702
|
+
}).nullable(),
|
|
1703
|
+
topologyError: z.string().nullable(),
|
|
1704
|
+
clamped: z.boolean()
|
|
1705
|
+
}).meta({ id: "PoolLayout" });
|
|
1718
1706
|
const nodePoolsSchema = z.object({
|
|
1719
1707
|
enforcement: poolEnforcementSchema,
|
|
1708
|
+
agentsSlice: poolEnforcementSchema.nullable(),
|
|
1709
|
+
refused: sessionRefusalsSchema.nullable(),
|
|
1710
|
+
layout: poolLayoutSchema.nullable(),
|
|
1720
1711
|
monitor: nodePoolSchema,
|
|
1721
1712
|
executor: nodePoolSchema
|
|
1722
1713
|
}).meta({ id: "NodePools" });
|
|
@@ -1822,12 +1813,7 @@ const nodeAgentSchema = z.object({
|
|
|
1822
1813
|
const nodeRuntimeSchema = z.object({
|
|
1823
1814
|
reportedAt: z.iso.datetime(),
|
|
1824
1815
|
docker: z.object({
|
|
1825
|
-
state: z.enum(
|
|
1826
|
-
"ok",
|
|
1827
|
-
"unavailable",
|
|
1828
|
-
"disabled",
|
|
1829
|
-
"adopting"
|
|
1830
|
-
]),
|
|
1816
|
+
state: z.enum(dockerStates),
|
|
1831
1817
|
lastError: z.string().nullable()
|
|
1832
1818
|
}),
|
|
1833
1819
|
storage: z.object({
|
|
@@ -1840,7 +1826,20 @@ const nodeRuntimeSchema = z.object({
|
|
|
1840
1826
|
agentsQueued: z.int().nonnegative(),
|
|
1841
1827
|
agentsActive: z.int().nonnegative()
|
|
1842
1828
|
}),
|
|
1843
|
-
ingest: z.object({
|
|
1829
|
+
ingest: z.object({
|
|
1830
|
+
readers: z.int().nonnegative(),
|
|
1831
|
+
oldestCursorLagSeconds: z.number().nonnegative().nullable()
|
|
1832
|
+
}),
|
|
1833
|
+
sandbox: z.object({
|
|
1834
|
+
enabled: z.boolean(),
|
|
1835
|
+
image: z.object({
|
|
1836
|
+
tag: z.string(),
|
|
1837
|
+
digest: z.string()
|
|
1838
|
+
}),
|
|
1839
|
+
agentsWithSandbox: z.int().nonnegative(),
|
|
1840
|
+
sidecarsRunning: z.int().nonnegative(),
|
|
1841
|
+
volumeBytes: z.int().nonnegative().nullable()
|
|
1842
|
+
}),
|
|
1844
1843
|
sessions: z.object({
|
|
1845
1844
|
monitor: z.object({
|
|
1846
1845
|
effective: z.int().nonnegative(),
|
|
@@ -1859,6 +1858,28 @@ nodeSchema.extend({
|
|
|
1859
1858
|
update: nodeUpdateSchema.nullable()
|
|
1860
1859
|
}).meta({ id: "NodeDetail" });
|
|
1861
1860
|
z.object({ items: z.array(nodeSchema) }).meta({ id: "NodeList" });
|
|
1861
|
+
const adminNodeSessionsSchema = z.object({
|
|
1862
|
+
effective: z.int().nonnegative(),
|
|
1863
|
+
inUse: z.int().nonnegative()
|
|
1864
|
+
});
|
|
1865
|
+
const nodeHealthSchema = z.object({
|
|
1866
|
+
docker: z.enum(dockerStates),
|
|
1867
|
+
storage: z.enum(storageStates),
|
|
1868
|
+
reconcileError: z.string().nullable()
|
|
1869
|
+
}).meta({ id: "NodeHealth" });
|
|
1870
|
+
const adminNodeSchema = nodeSchema.extend({
|
|
1871
|
+
organization: z.object({
|
|
1872
|
+
id: idSchema,
|
|
1873
|
+
name: z.string().nullable()
|
|
1874
|
+
}),
|
|
1875
|
+
update: nodeUpdateSchema.nullable(),
|
|
1876
|
+
sessions: z.object({
|
|
1877
|
+
monitor: adminNodeSessionsSchema,
|
|
1878
|
+
executor: adminNodeSessionsSchema
|
|
1879
|
+
}).nullable(),
|
|
1880
|
+
health: nodeHealthSchema.nullable()
|
|
1881
|
+
}).meta({ id: "AdminNode" });
|
|
1882
|
+
z.object({ items: z.array(adminNodeSchema) }).meta({ id: "AdminNodeList" });
|
|
1862
1883
|
z.object({ nodeId: idSchema });
|
|
1863
1884
|
z.object({
|
|
1864
1885
|
organizationId: idSchema,
|
|
@@ -1932,6 +1953,30 @@ const splitFits = (split, host) => {
|
|
|
1932
1953
|
const overflow = splitOverflowOf(split, host);
|
|
1933
1954
|
return overflow.cpus <= 0 && overflow.memoryMb <= 0;
|
|
1934
1955
|
};
|
|
1956
|
+
const poolProcessorsOf = (cpus, hostCpus) => Math.max(1, Math.min(Math.round(cpus), Math.round(hostCpus)));
|
|
1957
|
+
const layoutAppliesSplit = (layout, split) => layout?.cpus != null && split !== null && layout.cpus.monitor === split.monitor.cpus && layout.cpus.executor === split.executor.cpus;
|
|
1958
|
+
const splitCoresOf = (split, cores) => {
|
|
1959
|
+
const processors = cores.reduce((total, size) => total + size, 0);
|
|
1960
|
+
const sizeAt = (index) => cores[Math.min(index, cores.length - 1)] ?? 1;
|
|
1961
|
+
const coresFrom = (first, cpus) => {
|
|
1962
|
+
const wanted = poolProcessorsOf(cpus, processors);
|
|
1963
|
+
let taken = sizeAt(first);
|
|
1964
|
+
let count = 1;
|
|
1965
|
+
while (taken + sizeAt(first + count) <= wanted) {
|
|
1966
|
+
taken += sizeAt(first + count);
|
|
1967
|
+
count += 1;
|
|
1968
|
+
}
|
|
1969
|
+
return count;
|
|
1970
|
+
};
|
|
1971
|
+
const executor = coresFrom(0, split.executor.cpus);
|
|
1972
|
+
const monitor = coresFrom(executor, split.monitor.cpus);
|
|
1973
|
+
return {
|
|
1974
|
+
cores: cores.length,
|
|
1975
|
+
monitor,
|
|
1976
|
+
executor,
|
|
1977
|
+
fits: executor + monitor < cores.length
|
|
1978
|
+
};
|
|
1979
|
+
};
|
|
1935
1980
|
//#endregion
|
|
1936
1981
|
//#region ../../packages/contracts/src/appearance.ts
|
|
1937
1982
|
const colorSchema = z.string().regex(hexColorPattern);
|
|
@@ -2571,9 +2616,6 @@ const containerHealthSchema = z.enum([
|
|
|
2571
2616
|
]);
|
|
2572
2617
|
const maxAgentDescriptionLength = 2e3;
|
|
2573
2618
|
const maxAgentPromptLength = 64e3;
|
|
2574
|
-
const minAgentCpus = .25;
|
|
2575
|
-
const maxAgentMemoryMb = 16384;
|
|
2576
|
-
const defaultAgentMemoryMb = 4096;
|
|
2577
2619
|
const workspaceDirectorySchema = z.string().regex(/^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/);
|
|
2578
2620
|
const timeZones = new Set(Intl.supportedValuesOf("timeZone"));
|
|
2579
2621
|
const timezoneSchema = z.string().trim().refine((value) => timeZones.has(value), { message: "expected an IANA time zone identifier" });
|
|
@@ -2584,11 +2626,10 @@ const agentDiskGbSchema = z.int().min(1).max(100);
|
|
|
2584
2626
|
const agentMachineSchema = z.object({
|
|
2585
2627
|
docker: z.boolean(),
|
|
2586
2628
|
browser: z.boolean(),
|
|
2587
|
-
cpus: z.number().min(minAgentCpus).max(8),
|
|
2588
|
-
memoryMb: z.int().min(512).max(maxAgentMemoryMb),
|
|
2589
2629
|
diskGb: agentDiskGbSchema
|
|
2590
|
-
})
|
|
2630
|
+
});
|
|
2591
2631
|
const agentMachineInputSchema = agentMachineSchema.extend({ diskGb: agentDiskGbSchema.optional() });
|
|
2632
|
+
const agentMachineDtoSchema = agentMachineSchema.meta({ id: "AgentMachine" });
|
|
2592
2633
|
const agentPromptsSchema = z.object({
|
|
2593
2634
|
monitor: agentPromptSchema,
|
|
2594
2635
|
executor: agentPromptSchema
|
|
@@ -2674,7 +2715,7 @@ agentListItemSchema.extend({
|
|
|
2674
2715
|
version: z.int().nonnegative(),
|
|
2675
2716
|
description: z.string(),
|
|
2676
2717
|
timezone: z.string(),
|
|
2677
|
-
machine:
|
|
2718
|
+
machine: agentMachineDtoSchema,
|
|
2678
2719
|
settings: kikimoraSettingsSchema,
|
|
2679
2720
|
effectiveSettings: workerSettingsSchema,
|
|
2680
2721
|
prompts: agentPromptsSchema,
|
|
@@ -2816,13 +2857,20 @@ z.object({
|
|
|
2816
2857
|
nodeId: idSchema
|
|
2817
2858
|
}).meta({ id: "RestoreAgentBody" });
|
|
2818
2859
|
z.object({ agentId: idSchema }).meta({ id: "RestoredAgent" });
|
|
2860
|
+
const agentBackupContentDigestSchema = z.string().regex(/^[0-9a-f]{64}$/u);
|
|
2819
2861
|
z.object({
|
|
2820
2862
|
agentId: idSchema,
|
|
2821
2863
|
trigger: z.enum(agentBackupTriggers),
|
|
2822
2864
|
kikimoraVersion: z.string().min(1).max(64),
|
|
2823
|
-
entries: z.coerce.number().int().nonnegative()
|
|
2865
|
+
entries: z.coerce.number().int().nonnegative(),
|
|
2866
|
+
contentDigest: agentBackupContentDigestSchema.optional()
|
|
2824
2867
|
});
|
|
2825
2868
|
z.object({ backupId: idSchema });
|
|
2869
|
+
z.object({
|
|
2870
|
+
agentId: idSchema,
|
|
2871
|
+
contentDigest: agentBackupContentDigestSchema
|
|
2872
|
+
});
|
|
2873
|
+
z.object({ backupId: idSchema.nullable() });
|
|
2826
2874
|
[
|
|
2827
2875
|
"application/vnd.oci.image.index.v1+json",
|
|
2828
2876
|
"application/vnd.oci.image.manifest.v1+json",
|
|
@@ -2830,21 +2878,12 @@ z.object({ backupId: idSchema });
|
|
|
2830
2878
|
"application/vnd.docker.distribution.manifest.v2+json"
|
|
2831
2879
|
].join(", ");
|
|
2832
2880
|
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);
|
|
2881
|
+
const volumeRoleSchema = z.enum(["data", "agents"]);
|
|
2837
2882
|
const filesystemNameSchema = z.enum([
|
|
2838
2883
|
"root",
|
|
2839
2884
|
"data",
|
|
2840
2885
|
"agents"
|
|
2841
2886
|
]);
|
|
2842
|
-
z.enum([
|
|
2843
|
-
"digitalOcean",
|
|
2844
|
-
"spaces",
|
|
2845
|
-
"ghcr",
|
|
2846
|
-
"backup"
|
|
2847
|
-
]);
|
|
2848
2887
|
const backupKindSchema = z.enum(["scheduled", "manual"]);
|
|
2849
2888
|
const backupStatusSchema = z.enum([
|
|
2850
2889
|
"running",
|
|
@@ -2888,65 +2927,42 @@ const sha256HexPattern = /^[0-9a-f]{64}$/;
|
|
|
2888
2927
|
const platformSecretPattern = /^[\x21-\x7e]+$/;
|
|
2889
2928
|
const instantSchema$1 = z.iso.datetime();
|
|
2890
2929
|
const byteCountSchema$1 = z.int().nonnegative();
|
|
2891
|
-
|
|
2930
|
+
z.string().trim().min(1).max(maxPlatformSecretLength).regex(platformSecretPattern, { message: "expected printable ASCII characters without spaces" });
|
|
2892
2931
|
const secretPresenceSchema = z.object({ present: z.boolean() }).meta({ id: "SecretPresence" });
|
|
2893
2932
|
const regionSchema = z.string().regex(regionSlugPattern);
|
|
2933
|
+
const bucketNameSchema = z.string().regex(bucketNamePattern);
|
|
2894
2934
|
const platformVolumeSchema = z.object({
|
|
2895
2935
|
role: volumeRoleSchema,
|
|
2896
2936
|
id: z.guid(),
|
|
2897
2937
|
name: z.string().regex(volumeNamePattern)
|
|
2898
2938
|
}).meta({ id: "PlatformVolume" });
|
|
2899
|
-
const
|
|
2900
|
-
const
|
|
2939
|
+
const countOfRole = (volumes, role) => volumes.filter((volume) => volume.role === role).length;
|
|
2940
|
+
const oneDataVolumeAndAtMostOneAgentsVolume = (volumes) => countOfRole(volumes, "data") === 1 && countOfRole(volumes, "agents") <= 1;
|
|
2941
|
+
const platformVolumesSchema = z.array(platformVolumeSchema).refine(oneDataVolumeAndAtMostOneAgentsVolume, { message: "expected one data volume and at most one agents volume" });
|
|
2901
2942
|
const isOrigin = (value) => {
|
|
2902
2943
|
if (!URL.canParse(value)) return false;
|
|
2903
2944
|
const url = new URL(value);
|
|
2904
2945
|
return url.pathname === "/" && url.search === "" && url.hash === "" && url.username === "" && url.password === "";
|
|
2905
2946
|
};
|
|
2906
2947
|
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
2948
|
const backupSettingsSchema = z.object({ retentionDays: z.int().min(1).max(maxBackupRetentionDays) }).meta({ id: "BackupSettings" });
|
|
2919
2949
|
z.object({
|
|
2920
|
-
version: z.int().nonnegative(),
|
|
2921
2950
|
instance: z.string(),
|
|
2922
2951
|
domain: z.string(),
|
|
2923
2952
|
digitalOcean: z.object({
|
|
2924
|
-
|
|
2925
|
-
|
|
2953
|
+
dropletId: z.int().positive(),
|
|
2954
|
+
region: regionSchema,
|
|
2955
|
+
reservedIp: z.ipv4(),
|
|
2956
|
+
volumes: platformVolumesSchema
|
|
2926
2957
|
}).nullable(),
|
|
2927
2958
|
spaces: z.object({
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2959
|
+
endpoint: spacesEndpointSchema,
|
|
2960
|
+
region: regionSchema,
|
|
2961
|
+
bucket: bucketNameSchema
|
|
2931
2962
|
}).nullable(),
|
|
2932
2963
|
ghcrToken: secretPresenceSchema,
|
|
2933
|
-
backup: backupSettingsSchema
|
|
2934
|
-
updatedAt: instantSchema$1
|
|
2964
|
+
backup: backupSettingsSchema
|
|
2935
2965
|
}).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
2966
|
const storageReadingSchema = z.discriminatedUnion("measured", [z.object({
|
|
2951
2967
|
measured: z.literal(true),
|
|
2952
2968
|
path: z.string(),
|
|
@@ -3010,8 +3026,6 @@ z.object({
|
|
|
3010
3026
|
dockerRootDir: z.string().nullable()
|
|
3011
3027
|
})
|
|
3012
3028
|
}).meta({ id: "PlatformNode" });
|
|
3013
|
-
z.object({ role: volumeRoleSchema });
|
|
3014
|
-
z.object({ sizeGb: z.int().positive() }).meta({ id: "ResizeVolumeBody" });
|
|
3015
3029
|
const requesterSchema = z.object({
|
|
3016
3030
|
userId: idSchema,
|
|
3017
3031
|
label: z.string().nullable()
|
|
@@ -3137,7 +3151,6 @@ const masterKeyVersionSchema = z.object({
|
|
|
3137
3151
|
secrets: z.object({
|
|
3138
3152
|
credentials: secretCountSchema,
|
|
3139
3153
|
toolSecrets: secretCountSchema,
|
|
3140
|
-
platformSecrets: secretCountSchema,
|
|
3141
3154
|
providerAccounts: secretCountSchema.default(0)
|
|
3142
3155
|
}),
|
|
3143
3156
|
backups: secretCountSchema,
|
|
@@ -3148,12 +3161,15 @@ z.object({
|
|
|
3148
3161
|
versions: z.array(masterKeyVersionSchema),
|
|
3149
3162
|
rotationPending: z.boolean()
|
|
3150
3163
|
}).meta({ id: "MasterKey" });
|
|
3151
|
-
|
|
3164
|
+
//#endregion
|
|
3165
|
+
//#region ../../packages/contracts/src/metrics.ts
|
|
3166
|
+
const metricsRanges = [
|
|
3152
3167
|
"1h",
|
|
3153
3168
|
"24h",
|
|
3154
3169
|
"7d",
|
|
3155
3170
|
"90d"
|
|
3156
|
-
]
|
|
3171
|
+
];
|
|
3172
|
+
const metricsRangeSchema = z.enum(metricsRanges);
|
|
3157
3173
|
const agentMetricsRanges = [
|
|
3158
3174
|
"1h",
|
|
3159
3175
|
"24h",
|
|
@@ -3207,18 +3223,13 @@ const agentUsageSchema = z.object({
|
|
|
3207
3223
|
blockReadBytes: byteCountSchema,
|
|
3208
3224
|
blockWriteBytes: byteCountSchema
|
|
3209
3225
|
}).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
3226
|
z.object({
|
|
3216
3227
|
resolution: metricsResolutionSchema,
|
|
3217
3228
|
from: instantSchema,
|
|
3218
3229
|
to: instantSchema,
|
|
3219
|
-
|
|
3230
|
+
points: z.array(hostPointSchema),
|
|
3220
3231
|
agents: z.array(agentUsageSchema)
|
|
3221
|
-
}).meta({ id: "
|
|
3232
|
+
}).meta({ id: "NodeMetrics" });
|
|
3222
3233
|
z.object({
|
|
3223
3234
|
resolution: metricsResolutionSchema,
|
|
3224
3235
|
from: instantSchema,
|
|
@@ -3255,7 +3266,8 @@ const notificationTargetTypeSchema = z.enum([
|
|
|
3255
3266
|
"backup",
|
|
3256
3267
|
"queue",
|
|
3257
3268
|
"volume",
|
|
3258
|
-
"platform"
|
|
3269
|
+
"platform",
|
|
3270
|
+
"node"
|
|
3259
3271
|
]);
|
|
3260
3272
|
const hostOperationOutcomes = [
|
|
3261
3273
|
"succeeded",
|
|
@@ -4160,6 +4172,13 @@ const nodeDetailOf = async (api, { organizationId, nodeId }) => dataOf("describe
|
|
|
4160
4172
|
organizationId,
|
|
4161
4173
|
nodeId
|
|
4162
4174
|
} } }), nodeHints);
|
|
4175
|
+
const nodeMetricsOf = async (api, { organizationId, nodeId }, range) => dataOf("show what the node used", await api.GET("/organizations/{organizationId}/nodes/{nodeId}/metrics", { params: {
|
|
4176
|
+
path: {
|
|
4177
|
+
organizationId,
|
|
4178
|
+
nodeId
|
|
4179
|
+
},
|
|
4180
|
+
query: { range }
|
|
4181
|
+
} }), nodeHints);
|
|
4163
4182
|
const createdNode = async (api, organizationId, body) => {
|
|
4164
4183
|
validated(createNodeBodySchema, body, "a node");
|
|
4165
4184
|
return providerDataOf("create the node", await api.POST("/organizations/{organizationId}/nodes", {
|
|
@@ -4271,7 +4290,8 @@ const agentHints = {
|
|
|
4271
4290
|
"agent.busy": "The agent is working on another action; try again in a moment.",
|
|
4272
4291
|
"concurrency.conflict": "Someone changed the agent meanwhile; read it again and retry.",
|
|
4273
4292
|
"agent.node_unsuitable": "The node lacks a capability the agent requires; kcloud nodes show <node> lists what it offers.",
|
|
4274
|
-
"node.offline": "The node of the agent is not connected; kcloud nodes show <node> tells when it was last seen."
|
|
4293
|
+
"node.offline": "The node of the agent is not connected; kcloud nodes show <node> tells when it was last seen.",
|
|
4294
|
+
"node.outdated": "The node of the agent runs an older release; kcloud nodes show <node> names the release it runs and its update."
|
|
4275
4295
|
};
|
|
4276
4296
|
const agentFrom = async (run, reference, project) => {
|
|
4277
4297
|
if (reference === void 0 || reference === "") throw new UsageError("This command needs an agent.", "Name it as <project>/<agent>, or pass --project.");
|
|
@@ -4495,6 +4515,23 @@ const enforcementText = ({ state, reason }) => {
|
|
|
4495
4515
|
case "unsupported": return `The node holds no pool to a limit: ${reason === null ? "it cannot" : unsupportedReasons[reason] ?? reason}.`;
|
|
4496
4516
|
}
|
|
4497
4517
|
};
|
|
4518
|
+
const agentsSliceReasons = {
|
|
4519
|
+
cgroup_driver: "Docker on the host does not use the systemd cgroup driver",
|
|
4520
|
+
cgroup_driver_unread: "the node has not read the cgroup driver of Docker yet",
|
|
4521
|
+
host_unread: "the node has not read its host yet",
|
|
4522
|
+
configure_failed: "systemd did not take the limits",
|
|
4523
|
+
pending: "the node has not applied the limit yet",
|
|
4524
|
+
platform: "the node runs no pool service"
|
|
4525
|
+
};
|
|
4526
|
+
const agentsSliceText = ({ state, reason }) => {
|
|
4527
|
+
switch (state) {
|
|
4528
|
+
case "enforced": return "The node holds its agents and their sessions together to the host minus its reserve.";
|
|
4529
|
+
case "unsplit": return "The node has no split, so its agents outside their sessions have no limit.";
|
|
4530
|
+
case "unsupported": return `The node holds its agents outside their sessions to no limit: ${reason === null ? "it cannot" : agentsSliceReasons[reason] ?? reason}.`;
|
|
4531
|
+
}
|
|
4532
|
+
};
|
|
4533
|
+
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}`;
|
|
4534
|
+
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
4535
|
const memoryOf$1 = (pool) => {
|
|
4499
4536
|
if (pool.usage === null) return "-";
|
|
4500
4537
|
const { memoryBytes, memoryMaxBytes } = pool.usage;
|
|
@@ -4549,10 +4586,13 @@ const queueOf = (pools) => {
|
|
|
4549
4586
|
{ header: "WORK" }
|
|
4550
4587
|
], waiting);
|
|
4551
4588
|
};
|
|
4552
|
-
const poolsText = (pools) => {
|
|
4589
|
+
const poolsText = (pools, split) => {
|
|
4553
4590
|
const holders = holdersOf(pools);
|
|
4554
4591
|
return [
|
|
4555
4592
|
enforcementText(pools.enforcement),
|
|
4593
|
+
...pools.agentsSlice == null ? [] : [agentsSliceText(pools.agentsSlice)],
|
|
4594
|
+
...pools.refused == null ? [] : [refusedText(pools.refused)],
|
|
4595
|
+
...pools.layout == null ? [] : layoutTexts(pools.layout, split),
|
|
4556
4596
|
renderTable([
|
|
4557
4597
|
{ header: "POOL" },
|
|
4558
4598
|
{ header: "SLOTS IN USE" },
|
|
@@ -4603,8 +4643,36 @@ const agentPlanOf = (agent) => ({
|
|
|
4603
4643
|
prompts: agent.prompts,
|
|
4604
4644
|
settings: agent.settings,
|
|
4605
4645
|
timezone: agent.timezone,
|
|
4606
|
-
machine:
|
|
4646
|
+
machine: {
|
|
4647
|
+
docker: agent.machine.docker,
|
|
4648
|
+
browser: agent.machine.browser,
|
|
4649
|
+
diskGb: agent.machine.diskGb
|
|
4650
|
+
}
|
|
4607
4651
|
});
|
|
4652
|
+
const machineLimitKeys = /* @__PURE__ */ new Set(["cpus", "memoryMb"]);
|
|
4653
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4654
|
+
const warnIgnored = ({ context }, keys) => {
|
|
4655
|
+
const named = keys.map((key) => `machine.${key}`).join(" and ");
|
|
4656
|
+
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.`));
|
|
4657
|
+
};
|
|
4658
|
+
const machineWithoutLimits = (run, machine) => {
|
|
4659
|
+
if (!isRecord(machine)) return machine;
|
|
4660
|
+
const ignored = Object.keys(machine).filter((key) => machineLimitKeys.has(key));
|
|
4661
|
+
if (ignored.length === 0) return machine;
|
|
4662
|
+
warnIgnored(run, ignored);
|
|
4663
|
+
return Object.fromEntries(Object.entries(machine).filter(([key]) => !machineLimitKeys.has(key)));
|
|
4664
|
+
};
|
|
4665
|
+
const agentFileWithoutLimits = (run, document) => isRecord(document) && "machine" in document ? {
|
|
4666
|
+
...document,
|
|
4667
|
+
machine: machineWithoutLimits(run, document.machine)
|
|
4668
|
+
} : document;
|
|
4669
|
+
const agentChangeWithoutLimits = (run, document) => isRecord(document) && isRecord(document.sections) && "machine" in document.sections ? {
|
|
4670
|
+
...document,
|
|
4671
|
+
sections: {
|
|
4672
|
+
...document.sections,
|
|
4673
|
+
machine: machineWithoutLimits(run, document.sections.machine)
|
|
4674
|
+
}
|
|
4675
|
+
} : document;
|
|
4608
4676
|
//#endregion
|
|
4609
4677
|
//#region src/commands/agents/config.ts
|
|
4610
4678
|
const agentsOf = async (scope, project) => {
|
|
@@ -4715,7 +4783,7 @@ const configCommands = (adaptersOf) => [
|
|
|
4715
4783
|
const agentId = await agentFrom(run, invocation.positionals[0], invocation.values.project);
|
|
4716
4784
|
const file = invocation.values.file;
|
|
4717
4785
|
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));
|
|
4786
|
+
const agent = await changedAgent(run.api, agentId, agentChangeWithoutLimits(run, await documentOf(run, file)));
|
|
4719
4787
|
return printed(run, agent, (changed) => changed.restartRequired ? `Changed ${changed.name}; it needs kcloud agents update to take the change.` : `Changed ${changed.name}.`);
|
|
4720
4788
|
}
|
|
4721
4789
|
}),
|
|
@@ -4781,7 +4849,7 @@ const detailsOf = (agent) => [
|
|
|
4781
4849
|
["node", agent.node === null ? "none" : `${agent.node.name} (${agent.node.state})`],
|
|
4782
4850
|
["requires", agent.requiredCapabilities.length === 0 ? "nothing" : agent.requiredCapabilities.join(", ")],
|
|
4783
4851
|
["timezone", agent.timezone],
|
|
4784
|
-
["
|
|
4852
|
+
["disk", `${String(agent.machine.diskGb)} GB`],
|
|
4785
4853
|
["docker", agent.machine.docker ? "yes" : "no"],
|
|
4786
4854
|
["browser", agent.machine.browser ? "yes" : "no"],
|
|
4787
4855
|
["container", agent.runtime.containerId ?? "none"],
|
|
@@ -4976,15 +5044,13 @@ const askedPlan = async (run, projectId, values, required) => {
|
|
|
4976
5044
|
timezone: values.timezone ?? localTimezone(),
|
|
4977
5045
|
machine: {
|
|
4978
5046
|
docker: values.docker,
|
|
4979
|
-
browser: values.browser
|
|
4980
|
-
cpus: 2,
|
|
4981
|
-
memoryMb: defaultAgentMemoryMb
|
|
5047
|
+
browser: values.browser
|
|
4982
5048
|
}
|
|
4983
5049
|
};
|
|
4984
5050
|
};
|
|
4985
5051
|
const isObject = (document) => typeof document === "object" && document !== null && !Array.isArray(document);
|
|
4986
5052
|
const fileOf = async (run, projectId, { file, node }, required) => {
|
|
4987
|
-
const document = await documentOf(run, file);
|
|
5053
|
+
const document = agentFileWithoutLimits(run, await documentOf(run, file));
|
|
4988
5054
|
const placed = !isObject(document) || node === void 0 && "nodeId" in document ? document : {
|
|
4989
5055
|
...document,
|
|
4990
5056
|
nodeId: await placedNodeId(run, {
|
|
@@ -6689,6 +6755,115 @@ const agentTools = [
|
|
|
6689
6755
|
run: async ({ action, now, ...reference }, run) => actionOn(run, await agentIdOf(run, reference), action, action === "stop" && now)
|
|
6690
6756
|
})
|
|
6691
6757
|
];
|
|
6758
|
+
const percentOf = (ratio) => ratio === null ? "-" : `${(ratio * 100).toFixed(1)}%`;
|
|
6759
|
+
const disksOf = (point) => [
|
|
6760
|
+
point.diskRootUsedRatio,
|
|
6761
|
+
point.diskDataUsedRatio,
|
|
6762
|
+
point.diskAgentsUsedRatio
|
|
6763
|
+
].map(percentOf).join(" / ");
|
|
6764
|
+
const gpuMemoryOf = ({ gpuMemoryUsedBytesAvg: used, gpuMemoryTotalBytes: total }) => used === null || total === null ? "-" : `${bytesOf(used)} of ${bytesOf(total)}`;
|
|
6765
|
+
const gpuOf = (point) => `${percentOf(point.gpuUtilRatioAvg)}, ${gpuMemoryOf(point)}`;
|
|
6766
|
+
const readsAGpu = (points) => points.some((point) => point.gpuUtilRatioAvg !== null || point.gpuMemoryTotalBytes !== null);
|
|
6767
|
+
const hostText$1 = (points) => {
|
|
6768
|
+
const gpu = readsAGpu(points);
|
|
6769
|
+
return renderTable([
|
|
6770
|
+
{ header: "AT" },
|
|
6771
|
+
{
|
|
6772
|
+
header: "CPU",
|
|
6773
|
+
align: "right"
|
|
6774
|
+
},
|
|
6775
|
+
{
|
|
6776
|
+
header: "MEMORY",
|
|
6777
|
+
align: "right"
|
|
6778
|
+
},
|
|
6779
|
+
{
|
|
6780
|
+
header: "LOAD",
|
|
6781
|
+
align: "right"
|
|
6782
|
+
},
|
|
6783
|
+
{
|
|
6784
|
+
header: "DISKS ROOT / DATA / AGENTS",
|
|
6785
|
+
align: "right"
|
|
6786
|
+
},
|
|
6787
|
+
...gpu ? [{
|
|
6788
|
+
header: "GPU",
|
|
6789
|
+
align: "right"
|
|
6790
|
+
}] : []
|
|
6791
|
+
], points.map((point) => [
|
|
6792
|
+
point.ts,
|
|
6793
|
+
percentOf(point.cpuRatioAvg),
|
|
6794
|
+
`${bytesOf(point.memoryUsedBytesAvg)} of ${bytesOf(point.memoryTotalBytes)}`,
|
|
6795
|
+
point.load1Avg === null ? "-" : point.load1Avg.toFixed(2),
|
|
6796
|
+
disksOf(point),
|
|
6797
|
+
...gpu ? [gpuOf(point)] : []
|
|
6798
|
+
]));
|
|
6799
|
+
};
|
|
6800
|
+
const byFootprint = (left, right) => right.memoryBytesAvg - left.memoryBytesAvg || left.agentName.localeCompare(right.agentName);
|
|
6801
|
+
const agentsText = (agents) => renderTable([
|
|
6802
|
+
{ header: "PROJECT" },
|
|
6803
|
+
{ header: "AGENT" },
|
|
6804
|
+
{
|
|
6805
|
+
header: "CPU (CORES)",
|
|
6806
|
+
align: "right"
|
|
6807
|
+
},
|
|
6808
|
+
{
|
|
6809
|
+
header: "MEMORY AVG",
|
|
6810
|
+
align: "right"
|
|
6811
|
+
},
|
|
6812
|
+
{
|
|
6813
|
+
header: "MEMORY MAX",
|
|
6814
|
+
align: "right"
|
|
6815
|
+
},
|
|
6816
|
+
{
|
|
6817
|
+
header: "NETWORK IN / OUT",
|
|
6818
|
+
align: "right"
|
|
6819
|
+
},
|
|
6820
|
+
{
|
|
6821
|
+
header: "DISK READ / WRITE",
|
|
6822
|
+
align: "right"
|
|
6823
|
+
}
|
|
6824
|
+
], agents.toSorted(byFootprint).map((usage) => [
|
|
6825
|
+
usage.projectName,
|
|
6826
|
+
usage.agentName,
|
|
6827
|
+
usage.cpuRatioAvg === null ? "-" : usage.cpuRatioAvg.toFixed(2),
|
|
6828
|
+
bytesOf(usage.memoryBytesAvg),
|
|
6829
|
+
bytesOf(usage.memoryBytesMax),
|
|
6830
|
+
`${bytesOf(usage.networkRxBytes)} / ${bytesOf(usage.networkTxBytes)}`,
|
|
6831
|
+
`${bytesOf(usage.blockReadBytes)} / ${bytesOf(usage.blockWriteBytes)}`
|
|
6832
|
+
]));
|
|
6833
|
+
const nodeMetricsText = (metrics) => [
|
|
6834
|
+
`${metrics.from} to ${metrics.to}, one point per ${metrics.resolution}.`,
|
|
6835
|
+
"",
|
|
6836
|
+
metrics.points.length === 0 ? "The node sent no sample in that range." : hostText$1(metrics.points),
|
|
6837
|
+
"",
|
|
6838
|
+
metrics.agents.length === 0 ? "No agent you can see ran on the node in that range." : agentsText(metrics.agents)
|
|
6839
|
+
].join("\n");
|
|
6840
|
+
const rangeOf = (given) => {
|
|
6841
|
+
if (given === void 0) return "1h";
|
|
6842
|
+
const range = metricsRanges.find((candidate) => candidate === given);
|
|
6843
|
+
if (range === void 0) throw new UsageError(`--range ${given} is not a range of metrics.`, `The ranges are ${metricsRanges.join(", ")}.`);
|
|
6844
|
+
return range;
|
|
6845
|
+
};
|
|
6846
|
+
const createNodeMetricsCommand = (adaptersOf) => defineCommand({
|
|
6847
|
+
name: "metrics",
|
|
6848
|
+
summary: "Show what the host of a node and the agents on it used over a range.",
|
|
6849
|
+
usage: "nodes metrics <node> [--range <range>]",
|
|
6850
|
+
optionsUsage: [
|
|
6851
|
+
...nodeUsage,
|
|
6852
|
+
["--range <range>", `how far back: ${metricsRanges.join(", ")} (default: 1h)`],
|
|
6853
|
+
["", "the agents are those of the projects you belong to, or all for an admin"]
|
|
6854
|
+
],
|
|
6855
|
+
options: { range: { type: "string" } },
|
|
6856
|
+
positionals: {
|
|
6857
|
+
min: 1,
|
|
6858
|
+
max: 1
|
|
6859
|
+
},
|
|
6860
|
+
run: async (invocation) => {
|
|
6861
|
+
const range = rangeOf(invocation.values.range);
|
|
6862
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6863
|
+
const reference = await resolveNode(run, invocation.positionals[0] ?? "");
|
|
6864
|
+
return printed(run, await nodeMetricsOf(run.api, reference, range), nodeMetricsText);
|
|
6865
|
+
}
|
|
6866
|
+
});
|
|
6692
6867
|
//#endregion
|
|
6693
6868
|
//#region src/commands/nodes/split.ts
|
|
6694
6869
|
const splitFields = [
|
|
@@ -6744,6 +6919,8 @@ const changeOfParts = ({ reserve, monitor, executor }) => {
|
|
|
6744
6919
|
const cpusText = (cpus) => String(Math.round(cpus * 100) / 100);
|
|
6745
6920
|
const hostText = (host) => `${cpusText(host.cpus)} CPU and ${hostMemoryMbOf(host)} MB`;
|
|
6746
6921
|
const excessText = ({ cpus, memoryMb }) => [...cpus > 0 ? [`${cpusText(cpus)} CPU`] : [], ...memoryMb > 0 ? [`${memoryMb} MB`] : []].join(" and ");
|
|
6922
|
+
const coresText = (count) => count === 1 ? "1 core" : `${String(count)} cores`;
|
|
6923
|
+
const coresOfNode = (node) => node.runtime?.pools.layout?.cores ?? null;
|
|
6747
6924
|
const fittingSplit = (node, change, nameOf) => {
|
|
6748
6925
|
const merged = {
|
|
6749
6926
|
...node.split === null ? {} : valuesOf(node.split),
|
|
@@ -6757,9 +6934,25 @@ const fittingSplit = (node, change, nameOf) => {
|
|
|
6757
6934
|
validated(nodeSplitSchema, split, "a split of a node");
|
|
6758
6935
|
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
6936
|
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.`);
|
|
6937
|
+
const cores = coresOfNode(node);
|
|
6938
|
+
const fit = cores === null ? null : splitCoresOf(split, cores);
|
|
6939
|
+
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
6940
|
return split;
|
|
6761
6941
|
};
|
|
6942
|
+
const processorCountsOf = (node) => {
|
|
6943
|
+
const pools = node.runtime?.pools;
|
|
6944
|
+
if (pools === void 0 || !layoutAppliesSplit(pools.layout, node.split)) return null;
|
|
6945
|
+
const monitor = pools.monitor.usage?.processors ?? null;
|
|
6946
|
+
const executor = pools.executor.usage?.processors ?? null;
|
|
6947
|
+
return monitor === null && executor === null ? null : {
|
|
6948
|
+
monitor,
|
|
6949
|
+
executor
|
|
6950
|
+
};
|
|
6951
|
+
};
|
|
6952
|
+
const processorsText = (count) => count === 1 ? "1 processor" : `${String(count)} processors`;
|
|
6953
|
+
const poolCpusText = (cpus, processors) => processors === null ? cpusText(cpus) : `${cpusText(cpus)} (${processorsText(processors)})`;
|
|
6762
6954
|
const splitViewOf = (node) => {
|
|
6955
|
+
const cores = coresOfNode(node);
|
|
6763
6956
|
const overflow = node.split === null || node.host === null ? null : splitOverflowOf(node.split, node.host);
|
|
6764
6957
|
return {
|
|
6765
6958
|
id: node.id,
|
|
@@ -6770,6 +6963,8 @@ const splitViewOf = (node) => {
|
|
|
6770
6963
|
memoryMb: hostMemoryMbOf(node.host)
|
|
6771
6964
|
},
|
|
6772
6965
|
split: node.split,
|
|
6966
|
+
processors: processorCountsOf(node),
|
|
6967
|
+
cores: cores === null || node.split === null ? null : splitCoresOf(node.split, cores),
|
|
6773
6968
|
free: overflow === null ? null : {
|
|
6774
6969
|
cpus: -overflow.cpus,
|
|
6775
6970
|
memoryMb: -overflow.memoryMb
|
|
@@ -6779,10 +6974,12 @@ const splitViewOf = (node) => {
|
|
|
6779
6974
|
const resourceCells = ({ cpus, memoryMb }) => [cpusText(cpus), `${memoryMb} MB`];
|
|
6780
6975
|
const oneSlotOf = ({ cpus, memoryMb, slots }) => `${cpusText(cpus / slots)} CPU, ${Math.floor(memoryMb / slots)} MB`;
|
|
6781
6976
|
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.`;
|
|
6977
|
+
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
6978
|
const splitTableOf = (view, split) => {
|
|
6783
6979
|
const poolRow = (role) => [
|
|
6784
6980
|
role,
|
|
6785
|
-
|
|
6981
|
+
poolCpusText(split[role].cpus, view.processors?.[role] ?? null),
|
|
6982
|
+
`${String(split[role].memoryMb)} MB`,
|
|
6786
6983
|
String(split[role].slots),
|
|
6787
6984
|
oneSlotOf(split[role])
|
|
6788
6985
|
];
|
|
@@ -6830,7 +7027,8 @@ const splitTableOf = (view, split) => {
|
|
|
6830
7027
|
{ header: "ONE SLOT" }
|
|
6831
7028
|
], rows),
|
|
6832
7029
|
...view.host === null ? ["", hostLineOf(view)] : [],
|
|
6833
|
-
...over === "" ? [] : ["", `The split asks for ${over} more than the host has.`]
|
|
7030
|
+
...over === "" ? [] : ["", `The split asks for ${over} more than the host has.`],
|
|
7031
|
+
...view.cores === null ? [] : ["", coresLineOf(view.cores)]
|
|
6834
7032
|
].join("\n");
|
|
6835
7033
|
};
|
|
6836
7034
|
const splitText = (view) => view.split === null ? [
|
|
@@ -6838,10 +7036,14 @@ const splitText = (view) => view.split === null ? [
|
|
|
6838
7036
|
hostLineOf(view),
|
|
6839
7037
|
`kcloud nodes split set ${view.name} with every part splits it.`
|
|
6840
7038
|
].join("\n") : splitTableOf(view, view.split);
|
|
6841
|
-
const splitSummaryOf = (split) => {
|
|
7039
|
+
const splitSummaryOf = (split, processors = null) => {
|
|
6842
7040
|
if (split === null) return "none, slots counted from the host without hard limits";
|
|
6843
7041
|
const { reserve, monitor, executor } = split;
|
|
6844
|
-
const pool = (role, { cpus, memoryMb, slots }) =>
|
|
7042
|
+
const pool = (role, { cpus, memoryMb, slots }) => {
|
|
7043
|
+
const count = processors?.[role] ?? null;
|
|
7044
|
+
const onProcessors = count === null ? "" : ` (${processorsText(count)})`;
|
|
7045
|
+
return `${role} ${cpusText(cpus)} CPU${onProcessors}, ${memoryMb} MB, ${slots} slots`;
|
|
7046
|
+
};
|
|
6845
7047
|
return [
|
|
6846
7048
|
`reserve ${cpusText(reserve.cpus)} CPU, ${reserve.memoryMb} MB`,
|
|
6847
7049
|
pool("monitor", monitor),
|
|
@@ -7023,6 +7225,18 @@ const nodeTools = [
|
|
|
7023
7225
|
input: z.object({ node: nodeField }),
|
|
7024
7226
|
run: async ({ node }, run) => nodeDetailOf(run.api, await resolveNode(run, node))
|
|
7025
7227
|
}),
|
|
7228
|
+
defineTool({
|
|
7229
|
+
name: "node_metrics",
|
|
7230
|
+
title: "Show what the host of a node and its agents used",
|
|
7231
|
+
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.",
|
|
7232
|
+
operations: ["getNodeMetrics"],
|
|
7233
|
+
safety: "read",
|
|
7234
|
+
input: z.object({
|
|
7235
|
+
node: nodeField,
|
|
7236
|
+
range: z.enum(metricsRanges).default("1h")
|
|
7237
|
+
}),
|
|
7238
|
+
run: async ({ node, range }, run) => nodeMetricsOf(run.api, await resolveNode(run, node), range)
|
|
7239
|
+
}),
|
|
7026
7240
|
defineTool({
|
|
7027
7241
|
name: "node_split_set",
|
|
7028
7242
|
title: "Split a node into pools",
|
|
@@ -9663,6 +9877,7 @@ const capabilitiesOf = (node) => {
|
|
|
9663
9877
|
const { sandbox, uidIsolation } = node.capabilities;
|
|
9664
9878
|
return `sandbox ${sandbox ? "yes" : "no"}, uid isolation ${uidIsolation ? "yes" : "no"}`;
|
|
9665
9879
|
};
|
|
9880
|
+
const stateOf = (node) => node.state === "outdated" ? `outdated: runs ${node.version ?? "-"}, needs an update to ${minimumNodeVersion} or later` : node.state;
|
|
9666
9881
|
const registrationOf = (node) => node.registrationExpiresAt === null ? "-" : `a token waits until ${node.registrationExpiresAt}`;
|
|
9667
9882
|
const storageOf = ({ state, freeBytes, totalBytes, path }) => `${state}, ${gibibytes(freeBytes)} free of ${gibibytes(totalBytes)} at ${path}`;
|
|
9668
9883
|
const shapeText = ({ size, volumeGb }) => `${size} with a volume of ${volumeGb} GB`;
|
|
@@ -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;
|
|
@@ -9714,7 +9938,7 @@ const lastUpdateOf = ({ update }) => {
|
|
|
9714
9938
|
const nodeDetailsText = (node) => [
|
|
9715
9939
|
renderTable([{ header: "FIELD" }, { header: "VALUE" }], [
|
|
9716
9940
|
["name", node.name],
|
|
9717
|
-
["state", node
|
|
9941
|
+
["state", stateOf(node)],
|
|
9718
9942
|
["provider", node.provider],
|
|
9719
9943
|
...machineRowsOf(node.machine),
|
|
9720
9944
|
...operationRowsOf(node.operation),
|
|
@@ -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",
|