@@ -179,6 +179,10 @@ if (typeof globalThis !== "undefined" && typeof globalThis.__agentOSWasiModule =
179179 const __agentOSKernelStdioSyncRpcEnabled = () =>
180180 process?.env?.AGENTOS_WASI_STDIO_SYNC_RPC === "1";
181181 const __agentOSWasiDebugEnabled = () => process?.env?.AGENTOS_WASM_WASI_DEBUG === "1";
182+ const __agentOSWasiSyscallCountersEnabled = () =>
183+ process?.env?.AGENTOS_WASI_SYSCALL_COUNTERS === "1";
184+ const __agentOSWasiNow = () =>
185+ typeof performance?.now === "function" ? performance.now() : Date.now();
182186 const __agentOSWasiDebug = (message) => {
183187 if (!__agentOSWasiDebugEnabled() || typeof process?.stderr?.write !== "function") {
184188 return;
@@ -208,6 +212,7 @@ if (typeof globalThis !== "undefined" && typeof globalThis.__agentOSWasiModule =
208212 [1, { kind: "stdout", fdFlags: 0 }],
209213 [2, { kind: "stderr", fdFlags: 0 }],
210214 ]);
215+ this.syscallCountersEnabled = __agentOSWasiSyscallCountersEnabled();
211216 for (const [guestPath, spec] of Object.entries(this.preopens)) {
212217 const normalized = this._normalizePreopenSpec(spec);
213218 if (!normalized) {
@@ -259,6 +264,70 @@ if (typeof globalThis !== "undefined" && typeof globalThis.__agentOSWasiModule =
259264 random_get: (...args) => this._randomGet(...args),
260265 sched_yield: (...args) => this._schedYield(...args),
261266 };
267+ this._installSyscallCounterWrappers();
268+ }
269+
270+ _recordWasiSyscallMetric(name, startedAt, details = {}) {
271+ if (!this.syscallCountersEnabled || typeof process?.stderr?.write !== "function") {
272+ return;
273+ }
274+ try {
275+ const elapsedMs = __agentOSWasiNow() - startedAt;
276+ process.stderr.write(
277+ \`__AGENTOS_WASI_SYSCALL_METRICS__:\${JSON.stringify({
278+ name,
279+ elapsedMs,
280+ ...details,
281+ })}\\n\`,
282+ );
283+ } catch {
284+ // Ignore metrics failures.
285+ }
286+ }
287+
288+ _installSyscallCounterWrappers() {
289+ if (!this.syscallCountersEnabled) {
290+ return;
291+ }
292+ for (const name of [
293+ "path_open",
294+ "path_filestat_get",
295+ "fd_filestat_get",
296+ "fd_write",
297+ ]) {
298+ const original = this.wasiImport[name];
299+ if (typeof original !== "function") {
300+ continue;
301+ }
302+ this.wasiImport[name] = (...args) => {
303+ const startedAt = __agentOSWasiNow();
304+ const result = original(...args);
305+ const details = {
306+ result,
307+ fd: Number(args[0]) >>> 0,
308+ iovsLen: name === "fd_write" ? Number(args[2]) >>> 0 : undefined,
309+ };
310+ if (name === "path_filestat_get") {
311+ try {
312+ const target = this._readString(args[2], args[3]);
313+ details.pathLen = target.length;
314+ details.pathKind = target.startsWith("/")
315+ ? "absolute"
316+ : target.includes("/")
317+ ? "relative-nested"
318+ : "relative-child";
319+ details.pathSample =
320+ target.length > 120 ? \`\${target.slice(0, 120)}...\` : target;
321+ } catch {
322+ // Leave path-shape fields absent if the guest pointer is invalid.
323+ }
324+ }
325+ this._recordWasiSyscallMetric(name, startedAt, {
326+ ...details,
327+ });
328+ return result;
329+ };
330+ }
262331 }
263332
264333 start(instance) {
@@ -987,6 +1056,22 @@ if (typeof globalThis !== "undefined" && typeof globalThis.__agentOSWasiModule =
9871056 );
9881057 }
9891058
1059+ _rootRelativeTargetIsWithinAbsoluteArg(target) {
1060+ const rootGuestPath = __agentOSPath().posix.resolve("/", target);
1061+ return this.args
1062+ .slice(1)
1063+ .some((arg) => {
1064+ if (typeof arg !== "string" || !arg.startsWith("/")) {
1065+ return false;
1066+ }
1067+ const normalizedArg = __agentOSPath().posix.normalize(arg);
1068+ return (
1069+ rootGuestPath === normalizedArg ||
1070+ rootGuestPath.startsWith(\`\${normalizedArg}/\`)
1071+ );
1072+ });
1073+ }
1074+
9901075 _resolveRootRelativePath(target, preferCreateParent = false) {
9911076 const rootGuestPath = __agentOSPath().posix.resolve("/", target);
9921077 const rootMapping = this._resolveHostMappingForGuestPath(rootGuestPath);
@@ -1066,6 +1151,44 @@ if (typeof globalThis !== "undefined" && typeof globalThis.__agentOSWasiModule =
10661151 return resolved?.hostPath ?? null;
10671152 }
10681153
1154+ _resolveDescriptorDirectStatPath(fd, target) {
1155+ if (
1156+ typeof target !== "string" ||
1157+ target.length === 0 ||
1158+ target === "." ||
1159+ target === ".." ||
1160+ target.startsWith("/")
1161+ ) {
1162+ return null;
1163+ }
1164+ const entry = this._descriptorEntry(fd);
1165+ if (
1166+ !entry ||
1167+ (entry.kind !== "directory" && entry.kind !== "preopen") ||
1168+ typeof entry.hostPath !== "string" ||
1169+ entry.hostPath.length === 0
1170+ ) {
1171+ return null;
1172+ }
1173+ const baseGuestPath = this._descriptorGuestPath(entry);
1174+ if (typeof baseGuestPath !== "string") {
1175+ return null;
1176+ }
1177+ if (entry.kind === "preopen" && baseGuestPath === "/") {
1178+ if (!this._rootRelativeTargetIsWithinAbsoluteArg(target)) {
1179+ return null;
1180+ }
1181+ } else if (target.includes("/")) {
1182+ return null;
1183+ }
1184+ return {
1185+ error: __agentOSWasiErrnoSuccess,
1186+ guestPath: __agentOSPath().posix.resolve(baseGuestPath, target),
1187+ hostPath: __agentOSPath().join(entry.hostPath, target),
1188+ readOnly: entry.readOnly === true,
1189+ };
1190+ }
1191+
10691192 _writeFilestat(statPtr, stats, fallbackType) {
10701193 try {
10711194 const view = this._memoryView();
@@ -1752,6 +1875,9 @@ if (typeof globalThis !== "undefined" && typeof globalThis.__agentOSWasiModule =
17521875 }
17531876
17541877 _fdReaddir(fd, bufPtr, bufLen, cookie, bufUsedPtr) {
1878+ const startedAt = __agentOSWasiNow();
1879+ const requestedCookie = Number(cookie) >>> 0;
1880+ const requestedBufLen = Number(bufLen) >>> 0;
17551881 try {
17561882 const entry = this._descriptorEntry(fd);
17571883 const fsPath = this._descriptorDirectoryFsPath(entry);
@@ -1762,19 +1888,53 @@ if (typeof globalThis !== "undefined" && typeof globalThis.__agentOSWasiModule =
17621888 ) {
17631889 return __agentOSWasiErrnoBadf;
17641890 }
1765- const dirents = __agentOSFs()
1766- .readdirSync(fsPath, { withFileTypes: true })
1767- .sort((left, right) => left.name.localeCompare(right.name));
1891+ let dirents =
1892+ requestedCookie > 0 && Array.isArray(entry.readdirCache)
1893+ ? entry.readdirCache
1894+ : null;
1895+ if (!dirents) {
1896+ dirents = __agentOSFs()
1897+ .readdirSync(fsPath, { withFileTypes: true })
1898+ .sort((left, right) => left.name.localeCompare(right.name));
1899+ entry.readdirCache = dirents;
1900+ }
17681901 const view = this._memoryView();
17691902 const memory = this._memoryBytes();
17701903 let offset = Number(bufPtr) >>> 0;
1771- const limit = offset + (Number(bufLen) >>> 0) ;
1904+ const limit = offset + requestedBufLen ;
17721905 let used = 0;
1773- for (let index = Number(cookie) >>> 0; index < dirents.length; index += 1) {
1906+ let recordsReturned = 0;
1907+ let stoppedRecordTooLarge = false;
1908+ for (let index = requestedCookie; index < dirents.length; index += 1) {
17741909 const dirent = dirents[index];
17751910 const nameBytes = Buffer.from(dirent.name, "utf8");
17761911 const recordLen = 24 + nameBytes.length;
17771912 if (offset + recordLen > limit) {
1913+ const remaining = Math.max(0, limit - offset);
1914+ if (remaining > 0) {
1915+ const record = Buffer.alloc(recordLen);
1916+ const recordView = new DataView(
1917+ record.buffer,
1918+ record.byteOffset,
1919+ record.byteLength,
1920+ );
1921+ recordView.setBigUint64(0, BigInt(index + 1), true);
1922+ recordView.setBigUint64(8, BigInt(index + 1), true);
1923+ recordView.setUint32(16, nameBytes.length, true);
1924+ recordView.setUint8(
1925+ 20,
1926+ dirent.isDirectory()
1927+ ? __agentOSWasiFiletypeDirectory
1928+ : dirent.isSymbolicLink()
1929+ ? __agentOSWasiFiletypeSymbolicLink
1930+ : __agentOSWasiFiletypeRegularFile,
1931+ );
1932+ record.set(nameBytes, 24);
1933+ memory.set(record.subarray(0, remaining), offset);
1934+ offset += remaining;
1935+ used += remaining;
1936+ }
1937+ stoppedRecordTooLarge = true;
17781938 break;
17791939 }
17801940 view.setBigUint64(offset, BigInt(index + 1), true);
@@ -1791,9 +1951,27 @@ if (typeof globalThis !== "undefined" && typeof globalThis.__agentOSWasiModule =
17911951 memory.set(nameBytes, offset + 24);
17921952 offset += recordLen;
17931953 used += recordLen;
1794- }
1795- return this._writeUint32(bufUsedPtr, used);
1954+ recordsReturned += 1;
1955+ }
1956+ const result = this._writeUint32(bufUsedPtr, used);
1957+ this._recordWasiSyscallMetric("fd_readdir", startedAt, {
1958+ result,
1959+ fd: Number(fd) >>> 0,
1960+ cookie: requestedCookie,
1961+ bufLen: requestedBufLen,
1962+ used,
1963+ recordsReturned,
1964+ totalDirentsRead: dirents.length,
1965+ stoppedRecordTooLarge,
1966+ });
1967+ return result;
17961968 } catch (error) {
1969+ this._recordWasiSyscallMetric("fd_readdir", startedAt, {
1970+ result: "error",
1971+ fd: Number(fd) >>> 0,
1972+ cookie: requestedCookie,
1973+ bufLen: requestedBufLen,
1974+ });
17971975 return this._mapFsError(error);
17981976 }
17991977 }
@@ -1997,7 +2175,10 @@ if (typeof globalThis !== "undefined" && typeof globalThis.__agentOSWasiModule =
19972175
19982176 _pathFilestatGet(fd, flags, pathPtr, pathLen, statPtr) {
19992177 try {
2000- const resolved = this._resolveDescriptorPath(fd, pathPtr, pathLen);
2178+ const target = this._readString(pathPtr, pathLen);
2179+ const resolved =
2180+ this._resolveDescriptorDirectStatPath(fd, target) ??
2181+ this._resolveDescriptorPath(fd, pathPtr, pathLen);
20012182 if (resolved.error !== __agentOSWasiErrnoSuccess) {
20022183 return resolved.error;
20032184 }
0 commit comments