@ai-ecoverse/slicc-kernel 1.66.0 → 1.67.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/node.js CHANGED
@@ -1,6 +1,11 @@
1
1
  // src/node.ts
2
2
  import { MessageChannel as MessageChannel2, Worker } from "node:worker_threads";
3
3
 
4
+ // src/bytes.ts
5
+ function fresh(bytes, start2 = 0, end = bytes.length) {
6
+ return new Uint8Array(bytes.subarray(start2, end));
7
+ }
8
+
4
9
  // src/cdp/port.ts
5
10
  function serveCdpPort(port, hook, request, onDone) {
6
11
  let conn;
@@ -2034,7 +2039,7 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
2034
2039
  }
2035
2040
  if (typeof data === "string") peer.postMessage({ net: "ws-message", nid, data });
2036
2041
  else {
2037
- const bytes = data.slice();
2042
+ const bytes = fresh(data);
2038
2043
  peer.postMessage({ net: "ws-message", nid, data: bytes }, [bytes.buffer]);
2039
2044
  }
2040
2045
  flow.inflight += payloadSize(data);
@@ -2072,7 +2077,7 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
2072
2077
  open.delete(nid);
2073
2078
  peer.postMessage({ net: "end", nid });
2074
2079
  } else {
2075
- const bytes = next2.value.slice();
2080
+ const bytes = fresh(next2.value);
2076
2081
  peer.postMessage({ net: "chunk", nid, bytes }, [bytes.buffer]);
2077
2082
  }
2078
2083
  } catch (e) {
@@ -2366,7 +2371,7 @@ var UserDb = class {
2366
2371
  joined(name2, extra);
2367
2372
  group.push({ name: name2, gid: uid, members: [] });
2368
2373
  passwd.push(row);
2369
- const fresh = !await this.fs.exists(home);
2374
+ const fresh2 = !await this.fs.exists(home);
2370
2375
  await this.fs.mkdir(home, { recursive: true });
2371
2376
  try {
2372
2377
  await this.provision(row);
@@ -2374,7 +2379,7 @@ var UserDb = class {
2374
2379
  await this.fs.mkdir("/etc", { recursive: true });
2375
2380
  await this.commit(formatPasswd(passwd), formatGroup(group));
2376
2381
  } catch (err) {
2377
- if (fresh) await this.fs.rm(home, { recursive: true, force: true });
2382
+ if (fresh2) await this.fs.rm(home, { recursive: true, force: true });
2378
2383
  throw err;
2379
2384
  }
2380
2385
  remember();
@@ -2519,7 +2524,7 @@ async function fsCall(fs, method, args, owner, g) {
2519
2524
  }
2520
2525
  switch (method) {
2521
2526
  case "readFile": {
2522
- const bytes = (await fs.readFileBuffer(path(0))).slice();
2527
+ const bytes = fresh(await fs.readFileBuffer(path(0)));
2523
2528
  return { result: bytes, transfer: [bytes.buffer] };
2524
2529
  }
2525
2530
  case "writeFile":
@@ -5530,11 +5535,11 @@ var ChildTable = class {
5530
5535
  return [...this.children.keys()];
5531
5536
  }
5532
5537
  captured(pid, slot2) {
5533
- const slots = this.leftovers.get(pid);
5534
- const chunks2 = slots?.get(slot2);
5535
- if (!slots || !chunks2) return new Uint8Array(0);
5536
- slots.delete(slot2);
5537
- if (slots.size === 0) this.leftovers.delete(pid);
5538
+ const slots2 = this.leftovers.get(pid);
5539
+ const chunks2 = slots2?.get(slot2);
5540
+ if (!slots2 || !chunks2) return new Uint8Array(0);
5541
+ slots2.delete(slot2);
5542
+ if (slots2.size === 0) this.leftovers.delete(pid);
5538
5543
  return concat2(chunks2);
5539
5544
  }
5540
5545
  };
@@ -5721,9 +5726,9 @@ var HeldPaths = class extends Map {
5721
5726
  return this.keptHandles.get(handle);
5722
5727
  }
5723
5728
  keep(path, file) {
5724
- const fresh = this.handlesAt(path).filter((id2) => !this.keptHandles.has(id2));
5725
- for (const id2 of fresh) this.keptHandles.set(id2, file);
5726
- this.uses.set(file, Math.max(fresh.length, 1));
5729
+ const fresh2 = this.handlesAt(path).filter((id2) => !this.keptHandles.has(id2));
5730
+ for (const id2 of fresh2) this.keptHandles.set(id2, file);
5731
+ this.uses.set(file, Math.max(fresh2.length, 1));
5727
5732
  this.unlinked.set(path, file);
5728
5733
  }
5729
5734
  unkeep(path, file) {
@@ -6279,7 +6284,7 @@ var MountFs = class {
6279
6284
  const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
6280
6285
  try {
6281
6286
  for (let offset = 0; offset < bytes.length; offset += chunk2) {
6282
- const part = bytes.slice(offset, offset + chunk2);
6287
+ const part = fresh(bytes, offset, offset + chunk2);
6283
6288
  await mount.conn.call({ op: "write", fh, offset, bytes: part }, [part.buffer]);
6284
6289
  }
6285
6290
  } finally {
@@ -6376,7 +6381,7 @@ var MountFs = class {
6376
6381
  async writeRuns(mount, fh, offset, bytes, transfer) {
6377
6382
  const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
6378
6383
  for (let at = 0; at < bytes.length; at += chunk2) {
6379
- const part = handed(bytes, transfer, chunk2) ? bytes : bytes.slice(at, at + chunk2);
6384
+ const part = handed(bytes, transfer, chunk2) ? bytes : fresh(bytes, at, at + chunk2);
6380
6385
  await mount.conn.call({ op: "write", fh, offset: offset + at, bytes: part }, [part.buffer]);
6381
6386
  }
6382
6387
  }
@@ -6608,8 +6613,8 @@ var MountFs = class {
6608
6613
  }
6609
6614
  this.writable(mount, real);
6610
6615
  const { uid, gid, mode } = change;
6611
- const fresh = change.kind === "new" && { uid, gid, mode };
6612
- const set2 = fresh ? await this.driverOwner(found, real, fresh, false) : change;
6616
+ const fresh2 = change.kind === "new" && { uid, gid, mode };
6617
+ const set2 = fresh2 ? await this.driverOwner(found, real, fresh2, false) : change;
6613
6618
  this.table.forget(mount, real);
6614
6619
  const attrs = Object.entries({ uid: set2.uid, gid: set2.gid, mode: set2.mode });
6615
6620
  await mount.conn.call({
@@ -7006,7 +7011,9 @@ async function dispatchOne(entry, req) {
7006
7011
 
7007
7012
  // src/realm/sync-sab-wire.ts
7008
7013
  var SAB_HEADER_I32 = 16;
7009
- var SAB_HEADER_BYTES = SAB_HEADER_I32 * 4;
7014
+ var SAB_CLOCK_OFFSET = SAB_HEADER_I32 * 4;
7015
+ var SAB_CLOCK_I64 = 3;
7016
+ var SAB_HEADER_BYTES = SAB_CLOCK_OFFSET + SAB_CLOCK_I64 * 8;
7010
7017
  var SAB_I_STATE = 0;
7011
7018
  var SAB_I_SEQ = 1;
7012
7019
  var SAB_I_STATUS = 2;
@@ -7156,6 +7163,146 @@ function attachSyncSabResponder(port, sab, token, opts) {
7156
7163
  };
7157
7164
  }
7158
7165
 
7166
+ // src/kernel/cpu-clock.ts
7167
+ var IDLE_OPS = /* @__PURE__ */ new Set([
7168
+ "fd-select",
7169
+ "proc-wait",
7170
+ "proc-exec",
7171
+ "sig-pause",
7172
+ "sock-accept",
7173
+ "sock-connect"
7174
+ ]);
7175
+ var SYS_CAP_US = 2e4;
7176
+ var LOCAL = SAB_CLOCK_OFFSET;
7177
+ var IDLE = 1;
7178
+ function epochMicros() {
7179
+ return Math.floor((performance.timeOrigin + performance.now()) * 1e3);
7180
+ }
7181
+ function slots(sab) {
7182
+ return new BigInt64Array(sab, LOCAL, SAB_CLOCK_I64);
7183
+ }
7184
+ var rawWait = Atomics.wait.bind(Atomics);
7185
+ function localIdle(sab, now) {
7186
+ const state = Number(Atomics.load(slots(sab), IDLE));
7187
+ if (state >= 0) return { idle: state, waiting: false };
7188
+ return { idle: Math.max(0, now + state), waiting: true };
7189
+ }
7190
+ var CpuClock = class {
7191
+ start;
7192
+ sys = 0;
7193
+ idle = 0;
7194
+ since = 0;
7195
+ kind = "sys";
7196
+ stopped = false;
7197
+ last = { utime: 0, stime: 0 };
7198
+ sab;
7199
+ now;
7200
+ constructor(sab, now = epochMicros) {
7201
+ this.sab = sab;
7202
+ this.now = now;
7203
+ this.start = now();
7204
+ }
7205
+ enter(op) {
7206
+ this.since = this.now();
7207
+ this.kind = this.stopped || IDLE_OPS.has(op) ? "idle" : "sys";
7208
+ }
7209
+ leave() {
7210
+ if (this.since === 0) return;
7211
+ this.add((this.frozen ?? this.now()) - this.since);
7212
+ this.since = 0;
7213
+ }
7214
+ stop(stopped) {
7215
+ if (stopped === this.stopped) return;
7216
+ this.stopped = stopped;
7217
+ if (this.since === 0 || !stopped || this.frozen !== void 0) return;
7218
+ const now = this.now();
7219
+ this.add(now - this.since);
7220
+ this.since = now;
7221
+ this.kind = "idle";
7222
+ }
7223
+ frozen;
7224
+ freeze() {
7225
+ this.frozen ??= this.now();
7226
+ }
7227
+ running() {
7228
+ if (this.frozen !== void 0) return false;
7229
+ const now = this.now();
7230
+ const busy = this.since === 0 || this.kind === "sys" && now - this.since < SYS_CAP_US;
7231
+ return busy && !localIdle(this.sab, now).waiting;
7232
+ }
7233
+ times() {
7234
+ const now = this.frozen ?? this.now();
7235
+ const open = this.since === 0 ? 0 : Math.max(0, now - this.since);
7236
+ const busy = this.kind === "sys" ? Math.min(open, SYS_CAP_US) : 0;
7237
+ const sys = this.sys + busy;
7238
+ const idle = this.idle + (open - busy) + localIdle(this.sab, now).idle;
7239
+ const user = Math.max(0, now - this.start - sys - idle);
7240
+ this.last = {
7241
+ utime: Math.max(this.last.utime, Math.floor(user / 1e3)),
7242
+ stime: Math.max(this.last.stime, Math.floor(sys / 1e3))
7243
+ };
7244
+ return this.last;
7245
+ }
7246
+ add(micros) {
7247
+ const spent = Math.max(0, micros);
7248
+ const busy = this.kind === "sys" ? Math.min(spent, SYS_CAP_US) : 0;
7249
+ this.sys += busy;
7250
+ this.idle += spent - busy;
7251
+ }
7252
+ };
7253
+ var ProcessClocks = class {
7254
+ main;
7255
+ threads = /* @__PURE__ */ new Map();
7256
+ ended = { utime: 0, stime: 0 };
7257
+ enabled;
7258
+ constructor(sab, enabled = true, states) {
7259
+ this.main = new CpuClock(sab);
7260
+ this.enabled = enabled;
7261
+ states?.onState((state) => this.stop(state === "stopped"));
7262
+ }
7263
+ thread(tid, sab) {
7264
+ const clock = new CpuClock(sab);
7265
+ this.threads.set(tid, clock);
7266
+ return clock;
7267
+ }
7268
+ threadEnded(tid) {
7269
+ const spent = this.threads.get(tid)?.times();
7270
+ this.threads.delete(tid);
7271
+ if (!spent) return;
7272
+ this.ended.utime += spent.utime;
7273
+ this.ended.stime += spent.stime;
7274
+ }
7275
+ stop(stopped) {
7276
+ for (const c of this.all()) c.stop(stopped);
7277
+ }
7278
+ freeze() {
7279
+ for (const c of this.all()) c.freeze();
7280
+ }
7281
+ times() {
7282
+ if (!this.enabled) return void 0;
7283
+ const sum = { ...this.ended };
7284
+ for (const c of this.all()) {
7285
+ const t = c.times();
7286
+ sum.utime += t.utime;
7287
+ sum.stime += t.stime;
7288
+ }
7289
+ return sum;
7290
+ }
7291
+ running() {
7292
+ return this.enabled ? this.all().filter((c) => c.running()).length : 0;
7293
+ }
7294
+ timed(clock, dispatch2) {
7295
+ const c = clock ?? this.main;
7296
+ return (req) => {
7297
+ c.enter(req.op);
7298
+ return dispatch2(req).finally(() => c.leave());
7299
+ };
7300
+ }
7301
+ all() {
7302
+ return [this.main, ...this.threads.values()];
7303
+ }
7304
+ };
7305
+
7159
7306
  // src/kernel/net/loopback-names.ts
7160
7307
  var HOST_LOOPBACK = "host.slicc.internal";
7161
7308
  var HOST_LOOPBACK_ADDRESS = [10, 0, 2, 2];
@@ -7239,9 +7386,9 @@ var Pages = class {
7239
7386
  const page = (start2 + at) / PAGE;
7240
7387
  if (this.pages.has(page)) continue;
7241
7388
  const part = bytes.subarray(at, Math.min(at + PAGE, bytes.length));
7242
- const fresh = this.take(part.length);
7243
- fresh.set(part);
7244
- this.pages.set(page, fresh);
7389
+ const fresh2 = this.take(part.length);
7390
+ fresh2.set(part);
7391
+ this.pages.set(page, fresh2);
7245
7392
  }
7246
7393
  }
7247
7394
  read(at, length2) {
@@ -7467,8 +7614,8 @@ function pidStat(info, boot) {
7467
7614
  0,
7468
7615
  0,
7469
7616
  0,
7470
- 0,
7471
- 0,
7617
+ ticks(info.utime ?? 0),
7618
+ ticks(info.stime ?? 0),
7472
7619
  0,
7473
7620
  0,
7474
7621
  20,
@@ -7562,19 +7709,23 @@ function systemFile(name2, listing, now, memory) {
7562
7709
  if (name2 === "meminfo") {
7563
7710
  return memoryInfo(
7564
7711
  memory,
7565
- listing.processes.reduce((sum, p) => sum + p.memory, 0)
7712
+ listing.processes.reduce((sum2, p) => sum2 + p.memory, 0)
7566
7713
  );
7567
7714
  }
7568
7715
  const live = listing.processes.filter((p) => p.state !== "Z");
7569
7716
  if (name2 === "loadavg") {
7570
7717
  const last = Math.max(0, ...listing.processes.map((p) => p.pid));
7571
- return `0.00 0.00 0.00 1/${live.length} ${last}
7718
+ const [one, five, fifteen] = (listing.load ?? [0, 0, 0]).map((n) => n.toFixed(2));
7719
+ return `${one} ${five} ${fifteen} 1/${live.length} ${last}
7572
7720
  `;
7573
7721
  }
7574
- const idle = ticks(now - listing.boot);
7722
+ const sum = (key) => listing.cpu?.[key] ?? listing.processes.reduce((n, p) => n + (p[key] ?? 0), 0);
7723
+ const user = ticks(sum("utime"));
7724
+ const system = ticks(sum("stime"));
7725
+ const idle = listing.cpu ? ticks(listing.cpu.idle) : Math.max(0, ticks(now - listing.boot) - user - system);
7575
7726
  return [
7576
- `cpu 0 0 0 ${idle} 0 0 0 0 0 0`,
7577
- `cpu0 0 0 0 ${idle} 0 0 0 0 0 0`,
7727
+ `cpu ${user} 0 ${system} ${idle} 0 0 0 0 0 0`,
7728
+ `cpu0 ${user} 0 ${system} ${idle} 0 0 0 0 0 0`,
7578
7729
  "intr 0",
7579
7730
  "ctxt 0",
7580
7731
  `btime ${Math.floor(listing.boot / 1e3)}`,
@@ -11340,10 +11491,10 @@ var WasmProcess = class {
11340
11491
  this.fds.installAt(fd, this.openVfsFile(open));
11341
11492
  } else if (req.create) {
11342
11493
  await nodes.onEntry(path, async () => {
11343
- const fresh = this.options.owners && !await present2(this.options.fs, path, true);
11494
+ const fresh2 = this.options.owners && !await present2(this.options.fs, path, true);
11344
11495
  this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
11345
11496
  await this.fds.get(fd).file.stat?.();
11346
- if (fresh) await this.options.fs.chown?.(path, { ...this.newOwner(), kind: "new" });
11497
+ if (fresh2) await this.options.fs.chown?.(path, { ...this.newOwner(), kind: "new" });
11347
11498
  });
11348
11499
  } else {
11349
11500
  this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
@@ -12034,6 +12185,9 @@ function killThread(thread, caught, sig) {
12034
12185
  blocked.abort();
12035
12186
  return true;
12036
12187
  }
12188
+ function threadSignalsOf(sab) {
12189
+ return { header: new Int32Array(sab, 0, SAB_HEADER_I32), interrupt: new AbortController() };
12190
+ }
12037
12191
  function threadCancel(thread) {
12038
12192
  return Atomics.load(thread.header, SAB_I_SIGNALS) !== 0 ? AbortSignal.abort() : thread.interrupt.signal;
12039
12193
  }
@@ -12159,6 +12313,22 @@ function processOptions(opts, header4, hooks, created3) {
12159
12313
  ...opts.program.abi === "wasi" || opts.program.abi === "js" ? { pendingBits: () => Atomics.load(header4, SAB_I_SIGNALS) } : {}
12160
12314
  };
12161
12315
  }
12316
+ function threadInit(opts, program, sab, cred, thread) {
12317
+ return {
12318
+ type: WASM_THREAD_INIT,
12319
+ pid: opts.shownPid ?? opts.pid,
12320
+ program,
12321
+ argv0: opts.argv0,
12322
+ args: opts.args,
12323
+ env: opts.env,
12324
+ cwd: opts.cwd,
12325
+ sab,
12326
+ cred: copyCred(cred),
12327
+ owners: opts.owners === true,
12328
+ ...opts.ppid !== void 0 ? { ppid: opts.ppid } : {},
12329
+ thread
12330
+ };
12331
+ }
12162
12332
  function spawnWasmProcess(opts) {
12163
12333
  const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
12164
12334
  const header4 = new Int32Array(sab, 0, SAB_HEADER_I32);
@@ -12191,7 +12361,10 @@ function spawnWasmProcess(opts) {
12191
12361
  const worker = opts.createWorker();
12192
12362
  const dying = new Dying();
12193
12363
  const dispatch2 = (req) => dying.serving(() => isWasmSyscall(req) ? process2.syscall(req) : dispatchSyncFs(req));
12194
- const responder = attachSyncSabResponder(worker, sab, token, { dispatch: dispatch2 });
12364
+ const clocks = new ProcessClocks(sab, opts.program.abi !== "js", process2);
12365
+ const responder = attachSyncSabResponder(worker, sab, token, {
12366
+ dispatch: clocks.timed(void 0, dispatch2)
12367
+ });
12195
12368
  const lane = opts.program.abi === "js" ? openLane(token, dispatch2) : void 0;
12196
12369
  const { promise: exited, resolve: settle } = Promise.withResolvers();
12197
12370
  let done4 = false;
@@ -12199,6 +12372,7 @@ function spawnWasmProcess(opts) {
12199
12372
  const finish = (code, sig) => {
12200
12373
  if (done4) return;
12201
12374
  done4 = true;
12375
+ clocks.freeze();
12202
12376
  dying.clear();
12203
12377
  endedBy = sig ?? process2.execTermsig;
12204
12378
  worker.removeEventListener("message", onMessage);
@@ -12239,6 +12413,7 @@ function spawnWasmProcess(opts) {
12239
12413
  const endThread = (tid) => {
12240
12414
  const end = threads.get(tid);
12241
12415
  threads.delete(tid);
12416
+ clocks.threadEnded(tid);
12242
12417
  process2.threadEnded(tid);
12243
12418
  end?.();
12244
12419
  };
@@ -12252,15 +12427,14 @@ function spawnWasmProcess(opts) {
12252
12427
  }
12253
12428
  const tsab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
12254
12429
  const tw = opts.createWorker();
12255
- const tsignals = {
12256
- header: new Int32Array(tsab, 0, SAB_HEADER_I32),
12257
- interrupt: new AbortController()
12258
- };
12430
+ const tsignals = threadSignalsOf(tsab);
12259
12431
  threadSignals.set(thread.tid, tsignals);
12260
12432
  const tdispatch = (req) => dying.serving(
12261
12433
  () => isWasmSyscall(req) ? process2.syscall(req, threadCancel(tsignals)) : dispatchSyncFs(req)
12262
12434
  );
12263
- const tresponder = attachSyncSabResponder(tw, tsab, token, { dispatch: tdispatch });
12435
+ const tresponder = attachSyncSabResponder(tw, tsab, token, {
12436
+ dispatch: clocks.timed(clocks.thread(thread.tid, tsab), tdispatch)
12437
+ });
12264
12438
  const onThreadMessage = (event) => {
12265
12439
  const data = event.data;
12266
12440
  if (data?.type === WASM_THREAD_EXIT) endThread(thread.tid);
@@ -12275,21 +12449,7 @@ function spawnWasmProcess(opts) {
12275
12449
  tresponder.dispose();
12276
12450
  tw.terminate();
12277
12451
  });
12278
- const tinit = {
12279
- type: WASM_THREAD_INIT,
12280
- pid: opts.shownPid ?? opts.pid,
12281
- program,
12282
- argv0: opts.argv0,
12283
- args: opts.args,
12284
- env: opts.env,
12285
- cwd: opts.cwd,
12286
- sab: tsab,
12287
- cred: copyCred(process2.cred),
12288
- owners: opts.owners === true,
12289
- ...opts.ppid !== void 0 ? { ppid: opts.ppid } : {},
12290
- thread
12291
- };
12292
- tw.postMessage(tinit);
12452
+ tw.postMessage(threadInit(opts, program, tsab, process2.cred, thread));
12293
12453
  };
12294
12454
  worker.postMessage(
12295
12455
  processInit(opts, sab, process2.umask, copyCred(process2.cred), lane?.lane),
@@ -12314,7 +12474,9 @@ function spawnWasmProcess(opts) {
12314
12474
  signalMasks: () => process2.signalMasks(),
12315
12475
  umask: () => process2.umask,
12316
12476
  cred: () => copyCred(process2.cred),
12317
- writesBack: () => opts.program.abi === "wasi" && !done4
12477
+ writesBack: () => opts.program.abi === "wasi" && !done4,
12478
+ times: () => clocks.times(),
12479
+ running: () => clocks.running()
12318
12480
  };
12319
12481
  }
12320
12482
 
@@ -12787,7 +12949,7 @@ function serveFilesystem(port, handlers, capabilities = {}, options = {}) {
12787
12949
  (result) => {
12788
12950
  if (result instanceof Uint8Array) {
12789
12951
  const whole = result.byteOffset === 0 && result.byteLength === result.buffer.byteLength;
12790
- const bytes = options.owned && whole ? result : result.slice();
12952
+ const bytes = options.owned && whole ? result : fresh(result);
12791
12953
  reply({ id: data.id, result: bytes }, [bytes.buffer]);
12792
12954
  } else reply({ id: data.id, result: result ?? null });
12793
12955
  },
@@ -13275,14 +13437,14 @@ var RESTORE_AGAIN_MS = 3e4;
13275
13437
  function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
13276
13438
  const handles = /* @__PURE__ */ new Map();
13277
13439
  let nextFh = 0;
13278
- const fresh = (remote) => {
13440
+ const fresh2 = (remote) => {
13279
13441
  if (remote.client.token === remote.token && !remote.client.gone) return remote;
13280
13442
  throw fsError2("ESTALE", "the folder was granted again, so this open file is gone");
13281
13443
  };
13282
13444
  const live = (fh) => {
13283
13445
  const remote = handles.get(fh);
13284
13446
  if (!remote) throw fsError2("EBADF", `handle ${fh}`);
13285
- return fresh(remote);
13447
+ return fresh2(remote);
13286
13448
  };
13287
13449
  const call = (op, body = {}) => current().call(op, body);
13288
13450
  const same = (was, mode, seen) => seen?.mode === (mode | 128) && (was.ino !== void 0 && seen.ino !== void 0 ? seen.ino === was.ino : seen.size === was.size && seen.mtime === was.mtime);
@@ -13364,7 +13526,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
13364
13526
  if (!remote) throw fsError2("EBADF", `handle ${fh}`);
13365
13527
  handles.delete(fh);
13366
13528
  try {
13367
- await fresh(remote).client.call("release", { fh: remote.fh }, true);
13529
+ await fresh2(remote).client.call("release", { fh: remote.fh }, true);
13368
13530
  } finally {
13369
13531
  if (remote.restore) await settle(current(), remote.restore);
13370
13532
  }
@@ -14733,6 +14895,8 @@ var LAYOUT = [
14733
14895
  ["/usr", 493],
14734
14896
  ["/usr/local", 493]
14735
14897
  ];
14898
+ var LOAD_SAMPLE_MS = 5e3;
14899
+ var LOAD_WINDOWS_MS = [6e4, 3e5, 9e5];
14736
14900
  var encoder3 = new TextEncoder();
14737
14901
  var USERS_MODES = ["off", "report", "enforce"];
14738
14902
  function usersMode(mode = "off") {
@@ -15261,6 +15425,7 @@ var Launcher = class {
15261
15425
  return this.jobs.sessionOf(pgid);
15262
15426
  }
15263
15427
  start(req) {
15428
+ this.sampling();
15264
15429
  if (req.pgid !== void 0 && this.jobs.sessionOf(req.pgid) === void 0) {
15265
15430
  throw fsError("ESRCH", `no process group ${req.pgid}`);
15266
15431
  }
@@ -15301,7 +15466,12 @@ var Launcher = class {
15301
15466
  forker: this.forker(pid, req),
15302
15467
  kill: (target, sig) => this.kill(target, sig, this.credOf(pid), pid),
15303
15468
  writesBack: (target) => this.processes.get(target)?.writesBack() === true,
15304
- processes: () => ({ boot: this.boot, processes: this.list(this.credOf(pid)) }),
15469
+ processes: () => ({
15470
+ boot: this.boot,
15471
+ processes: this.list(this.credOf(pid)),
15472
+ load: this.loadAverage(),
15473
+ cpu: this.cpuTotal()
15474
+ }),
15305
15475
  sudo: (call, signal) => this.sudo(pid, call, signal),
15306
15476
  openFiles: this.openFiles,
15307
15477
  nodes: this.nodes,
@@ -15335,7 +15505,14 @@ var Launcher = class {
15335
15505
  started: Date.now()
15336
15506
  });
15337
15507
  void handle.exited.then(() => {
15508
+ const spent = handle.times?.();
15509
+ if (spent) this.spent.set(pid, spent);
15338
15510
  this.processes.delete(pid);
15511
+ if (this.processes.size === 0) {
15512
+ clearInterval(this.sampler);
15513
+ this.sampler = void 0;
15514
+ this.idleSince = Date.now();
15515
+ }
15339
15516
  this.settling.settled(pid);
15340
15517
  if (req.ppid === void 0 || this.orphans.delete(pid)) this.forget(pid);
15341
15518
  else this.zombies.add(pid);
@@ -15351,6 +15528,12 @@ var Launcher = class {
15351
15528
  forget(pid) {
15352
15529
  this.jobs.remove(pid);
15353
15530
  this.described.delete(pid);
15531
+ const spent = this.spent.get(pid);
15532
+ if (spent) {
15533
+ this.reapedTimes.utime += spent.utime;
15534
+ this.reapedTimes.stime += spent.stime;
15535
+ }
15536
+ this.spent.delete(pid);
15354
15537
  this.creds.delete(pid);
15355
15538
  this.tags.delete(pid);
15356
15539
  this.granted.delete(pid);
@@ -15359,6 +15542,70 @@ var Launcher = class {
15359
15542
  if (this.zombies.delete(pid)) this.forget(pid);
15360
15543
  else this.orphans.add(pid);
15361
15544
  }
15545
+ load = [0, 0, 0];
15546
+ sampler;
15547
+ reapedTimes = { utime: 0, stime: 0 };
15548
+ idleAt = { wall: this.boot, busy: 0, idle: 0 };
15549
+ cpuTotal(now = Date.now()) {
15550
+ const total = { ...this.reapedTimes };
15551
+ const add = (t) => {
15552
+ total.utime += t?.utime ?? 0;
15553
+ total.stime += t?.stime ?? 0;
15554
+ };
15555
+ for (const handle of this.processes.values()) add(handle.times?.());
15556
+ for (const t of this.spent.values()) add(t);
15557
+ const busy = total.utime + total.stime;
15558
+ const at = this.idleAt;
15559
+ at.idle += Math.max(0, now - at.wall - (busy - at.busy));
15560
+ at.wall = Math.max(at.wall, now);
15561
+ at.busy = Math.max(at.busy, busy);
15562
+ return { ...total, idle: at.idle };
15563
+ }
15564
+ loadAverage() {
15565
+ return this.load;
15566
+ }
15567
+ sampledAt = Date.now();
15568
+ sampleLoad(now = Date.now()) {
15569
+ let running = 0;
15570
+ for (const handle of this.processes.values()) running += handle.running?.() ?? 0;
15571
+ const elapsed = Math.max(LOAD_SAMPLE_MS, now - this.sampledAt);
15572
+ this.sampledAt = Math.max(this.sampledAt, now);
15573
+ this.load = this.load.map((avg, i) => {
15574
+ const window = LOAD_WINDOWS_MS[i];
15575
+ const fresh2 = 1 - Math.exp(-LOAD_SAMPLE_MS / window);
15576
+ return avg * Math.exp(-elapsed / window) + running * fresh2;
15577
+ });
15578
+ }
15579
+ idleSince;
15580
+ sampling() {
15581
+ if (this.sampler) return;
15582
+ if (this.idleSince !== void 0) {
15583
+ const empty = Math.max(0, Date.now() - this.idleSince);
15584
+ this.load = this.load.map(
15585
+ (avg, i) => avg * Math.exp(-empty / LOAD_WINDOWS_MS[i])
15586
+ );
15587
+ }
15588
+ this.sampledAt = Date.now();
15589
+ this.sampler = setInterval(() => this.sampleLoad(), LOAD_SAMPLE_MS);
15590
+ this.sampler.unref?.();
15591
+ }
15592
+ spent = /* @__PURE__ */ new Map();
15593
+ timesAt(pid) {
15594
+ return this.processes.get(pid)?.times?.() ?? this.spent.get(pid);
15595
+ }
15596
+ timesOf(member, members) {
15597
+ let utime = 0;
15598
+ let stime = 0;
15599
+ let timed2 = false;
15600
+ for (let at = member; at; at = members.get(at.execParent ?? -1)) {
15601
+ const t = this.timesAt(at.pid);
15602
+ if (!t) continue;
15603
+ timed2 = true;
15604
+ utime += t.utime;
15605
+ stime += t.stime;
15606
+ }
15607
+ return timed2 ? { utime, stime } : {};
15608
+ }
15362
15609
  list(viewer = ROOT) {
15363
15610
  const members = new Map(this.jobs.list().map((member) => [member.pid, member]));
15364
15611
  const rootOf = (member) => {
@@ -15390,6 +15637,7 @@ var Launcher = class {
15390
15637
  started: (this.described.get(root.pid) ?? described2).started,
15391
15638
  state: this.processes.has(member.pid) ? "S" : "Z",
15392
15639
  memory: this.processes.get(member.pid)?.memory() ?? 0,
15640
+ ...this.timesOf(member, members),
15393
15641
  umask: this.umaskOf(member.pid),
15394
15642
  cred,
15395
15643
  ...this.signalMasksOf(member.pid)
@@ -15649,8 +15897,8 @@ var Launcher = class {
15649
15897
  ];
15650
15898
  for (const [path, mode] of layout) {
15651
15899
  const real = await this.realLayoutPath(path);
15652
- const fresh = real !== void 0 && !await this.base.hasOwner?.(real);
15653
- if (fresh) await this.base.chown?.(real, { uid: 0, gid: 0, mode, follow: false });
15900
+ const fresh2 = real !== void 0 && !await this.base.hasOwner?.(real);
15901
+ if (fresh2) await this.base.chown?.(real, { uid: 0, gid: 0, mode, follow: false });
15654
15902
  }
15655
15903
  }
15656
15904
  async realLayoutPath(given) {
@@ -16395,7 +16643,7 @@ function dialStream(port, onClose) {
16395
16643
  const writable2 = new WritableStream({
16396
16644
  write(chunk2) {
16397
16645
  if (closed) throw errorWithCode("EPIPE: the connection is closed");
16398
- const copy = chunk2.slice();
16646
+ const copy = fresh(chunk2);
16399
16647
  const acked = new Promise((resolve, reject) => acks.push({ resolve, reject }));
16400
16648
  port.postMessage(copy, [copy.buffer]);
16401
16649
  return acked;