I investigated the execution environment of Lambda Web Functions

I investigated the execution environment of Lambda Web Functions

I investigated AWS Lambda Web Functions runtime. CPU is Graviton3 2 vCPU, OpenSSL performance equals c7g.large. Comparing with standard Lambda, MicroVMs, EC2, lower CPU usage means lower cost.
2026.10.16

This page has been translated by machine translation. View original

Introduction

AWS Lambda Web Functions, which became GA in October 2026, allocates 2 GB of memory and 2 vCPUs to a single execution environment.

https://docs.aws.amazon.com/lambda/latest/dg/web-functions-runtimes.html

In this article, I examined the CPU generation and OpenSSL benchmark performance within the range observable from the execution environment. I also compared it with traditional Lambda (Event Functions), Lambda MicroVMs, and EC2 c6g, c7g, and c8g instances.

What Was Tested

For testing, I used a diagnostic app running on Node.js 24 (arm64). It reads /proc and /sys from fixed endpoints, executes commands on an allowlist, tests writes to /tmp, and returns results as JSON. It does not retrieve environment variables. Diagnostic data was collected from a single execution environment in the Tokyo region. The code is included at the end of the article.

Execution Environment Specs

The information observable from the Lambda Web Functions execution environment is as follows.

CPU and Memory

Item Value
Node.js v24.21.0 (arm64)
Kernel 6.18.51-120.163.amzn2023.aarch64.microvm
OS Amazon Linux 2023.12.20260930
vCPU 2 (os.availableParallelism(), the count from /proc/cpuinfo, and Cpus_allowed_list all show 2 CPUs)
CPU CPU part 0xd40, CPU variant 0x1, BogoMIPS 2100.00
Memory (official docs) 2 GB
Memory (/proc/meminfo MemTotal) 2460024 kB (approx. 2.35 GiB). os.totalmem() is 2519064576 bytes, swap is 0
os.cpus() model unknown

While the official memory allocation is 2 GB, /proc/meminfo in the execution environment shows MemTotal as approximately 2.35 GiB.

Permissions and Sandbox

The permission-related lines from /proc/self/status:

Uid:        993     993     993     993
Gid:        990     990     990     990
CapInh:     0000000000000000
CapPrm:     0000000000000000
CapEff:     0000000000000000
CapBnd:     0000000000000000
CapAmb:     0000000000000000
NoNewPrivs: 1
Seccomp:    2
Seccomp_filters:    8

The app runs as a non-root regular user, and execution of dmesg was denied (described later). With ps -eo pid,ppid,user,comm, PID 1 was root's init and PID 2 was sbx_user…'s node. With lsns, I confirmed 8 namespaces: time, user, net, mnt, uts, ipc, pid, and cgroup.

Filesystem and /tmp

The df -h output from the execution environment:

Filesystem      Size  Used Avail Use% Mounted on
/dev/vde        1.6G  1.1G  436M  71% /
tmpfs            64M     0   64M   0% /dev
/dev/vdc        3.8M  3.8M     0 100% /var/task
/dev/vdd        525M   11M  503M   3% /tmp
/dev/root       9.8G  854M  8.9G   9% /etc/passwd
/dev/vdb        1.5G  1.3M  1.5G   1% /etc/resolv.conf

/ was mounted as ext4 with ro,nosuid,nodev, and /var/task was mounted as squashfs with ro,nosuid,nodev. /tmp was ext4 with rw,relatime and without noexec. /tmp is readable and writable, and writes from the app succeeded. /dev/shm does not exist and returns ENOENT when accessed.

Writing 64 MiB at a time to /tmp resulted in ENOSPC when the file reached 526,385,152 bytes (approximately 502 MiB). The available space before writing was 527,192,064 bytes, and it recovered to the same value after deleting the file. statfs reported the filesystem capacity as 525 MiB, but the practical usable amount for temporary files is approximately 502 MiB.

Local files such as those in /tmp may persist while the execution environment is reused. However, the lifetime of the execution environment is not guaranteed.

https://docs.aws.amazon.com/lambda/latest/dg/web-functions-env-lifecycle.html

/proc and /sys

Path Result
/sys/class/dmi/id ENOENT
/sys/hypervisor ENOENT
/sys/fs/cgroup ENOENT
/proc/self/cgroup readable
/proc/self/mountinfo readable (7 mounts, no sysfs)
/proc/cpuinfo, /proc/meminfo, /proc/self/status, /proc/self/limits readable
/proc/cmdline EACCES

cgroup CPU and memory limits, DMI, and hypervisor information could not be obtained from these paths. CPU count and the memory information visible from the execution environment could be retrieved from /proc/cpuinfo, /proc/meminfo, and Node.js's os module.

External Commands

Collecting and Bundling Binaries

Of the commands used for diagnostics, only df, uname, and coreutils were present in the execution environment by default. All other commands were collected from an AL2023 arm64 container (public.ecr.aws/amazonlinux/amazonlinux:2023, linux/arm64), and the binaries and shared libraries were bundled into bin/ and lib/ of the deployment package. The bundled versions were util-linux 2.37.4, procps-ng 3.3.17, and coreutils 8.32.

Saved as collect-binaries.sh:

#!/bin/bash
# Collect binaries and dependent libraries for util-linux / procps-ng / coreutils into bin/ lib/ inside an AL2023 (linux/arm64) container.
set -euo pipefail
OUT="${1:-/out}"
mkdir -p "$OUT/bin" "$OUT/lib"
dnf install -y -q util-linux procps-ng openssl >/dev/null 2>&1 || dnf install -y util-linux procps-ng openssl
cat /etc/os-release | head -3
# df / uname are shebang scripts pointing to the multi-call binary (/usr/bin/coreutils), so collect the main binary
for c in lscpu lsblk lsmem findmnt lsns dmesg free ps coreutils openssl; do
  p=$(command -v "$c" || true)
  if [ -z "$p" ]; then echo "MISSING $c"; continue; fi
  cp -L "$p" "$OUT/bin/$c"
  { ldd "$p" 2>&1 || true; } | awk '/=> \//{print $3}' | while read -r so; do cp -Ln "$so" "$OUT/lib/" 2>/dev/null || true; done
  echo "OK $c $p"
done
rpm -q openssl openssl-libs util-linux procps-ng coreutils-single
# Place the loader itself in lib/
cp -L /lib/ld-linux-aarch64.so.1 "$OUT/lib/" 2>/dev/null || cp -L /lib64/ld-linux-aarch64.so.1 "$OUT/lib/"
ls -l "$OUT/bin" "$OUT/lib"
[ -n "${HOST_UID:-}" ] && chown -R "$HOST_UID:${HOST_GID:-$HOST_UID}" "$OUT" || true

Collection was performed with the following command. bin/ and lib/ were output to diag-bin.

docker run --rm --platform linux/arm64 -e HOST_UID=$(id -u) -e HOST_GID=$(id -g) -v "$PWD:/work" -v "$PWD/diag-bin:/out" public.ecr.aws/amazonlinux/amazonlinux:2023 bash /work/collect-binaries.sh /out

df and uname were shebang scripts whose actual bodies were the multi-call binary coreutils. I bundled coreutils and called it with --coreutils-prog=<name>. All other commands were executed in the following form, passing the library path to the bundled loader:

<DST>/lib/ld-linux-aarch64.so.1 --library-path <DST>/lib <DST>/bin/<cmd>

In the diagnostic app, <DST> is set to /tmp/diag-bin.

The 24 files in the zipped bin/ and lib/ were all extracted to /var/task with mode 755 intact. /var/task is not noexec, and /var/task/bin/lscpu could be executed directly. lscpu exited with code 1 with the same error whether run directly, via the loader, or after copying to /tmp. The following table shows the results when copied to /tmp and run via the loader.

Execution Results

Command Result of bundled version
lscpu exit 1: failed to determine number of CPUs: /sys/devices/system/cpu/possible: No such file or directory
lsblk exit 1: failed to access sysfs directory: /sys/dev/block: No such file or directory
lsmem exit 1: cannot open /sys/devices/system/memory: No such file or directory
findmnt exit 0
lsns exit 0
dmesg exit 1: read kernel buffer failed: Operation not permitted
free -m exit 0
ps exit 0
df -h exit 0 (identical output to /usr/bin/df in the execution environment)
uname -a exit 0 (identical output to /usr/bin/uname in the execution environment)

The failures were lscpu, lsblk, and lsmem, which read /sys, and dmesg, which requires elevated permissions. findmnt, lsns, free, and ps, which only need /proc, worked when bundled. /usr/bin in the execution environment had 175 entries, and the target commands other than df, uname, and coreutils were not found in /usr/bin, /bin, /usr/sbin, /sbin, or /usr/local/bin.

CPU Generation and Performance

Graviton3-Generation CPU

In the Linux kernel CPU part definitions, 0xd40 corresponds to Neoverse V1. In the AWS Graviton technical guide, the compile option for Graviton3(E) is -mcpu=neoverse-v1, Graviton2 is neoverse-n1 (0xd0c), and Graviton4 is neoverse-v2 (0xd4f).

https://aws.github.io/graviton/c-c++.html

CPU identification information comparing Web Function with EC2 c6g.large, c7g.large, and c8g.large:

Item c6g.large c7g.large c8g.large Web Function
CPU part 0xd0c 0xd40 0xd4f 0xd40
CPU variant / revision 0x3 / 1 0x1 / 1 0x0 / 1 0x1 / 1
BogoMIPS 243.75 2100.00 2000.00 2100.00
lscpu BIOS Model name AWS Graviton2 AWS Graviton3 AWS Graviton4 — (lscpu fails due to missing /sys)
SVE-related Features none sve, svebf16, svei8mm (no sve2) sve2, sveaes, svesha3, etc. sve, svebf16, svei8mm (no sve2)
OPENSSL_armcap 0xbd 0x13ffd 0x179fd 0x13ffd

The CPU part, CPU variant, BogoMIPS, Features, and OPENSSL_armcap of Web Function all matched c7g.large. It has SVE-related features not present in Graviton2, but lacks SVE2 which Graviton4 has. From this, it can be inferred that Web Function uses a Graviton3-generation CPU. However, Graviton3 and Graviton3E cannot be distinguished by CPU part alone. Also, since the model name string could not be obtained in Web Function, this judgment is based on matching values.

OpenSSL Speed Comparison

Using the bundled openssl 3.5.8, I ran openssl speed with the same arguments on Web Function (one execution environment) and three EC2 instances (one each). The same version was installed on EC2 via dnf install and run from AL2023 in ap-northeast-1a via SSM Run Command. The measurement command format is as follows:

openssl speed -seconds 2 -bytes {64,16384} [-multi 2] -evp {aes-128-gcm,aes-256-gcm,sha256,sha512,sha3-256,chacha20-poly1305}
openssl speed -seconds 2 [-multi 2] {rsa2048,ecdsap256,ecdhx25519}

Of the 17 items measured with a single process, here are 4 representative ones. Each value is the median of 3 rounds.

Item Web Function c6g.large c7g.large c8g.large
sha256 64B (kB/s) 287,673 225,301 287,460 367,102
aes-128-gcm 16KB (kB/s) 5,949,915 2,393,760 5,287,231 6,333,563
rsa2048 sign/s 1,032 294 1,045 1,187
ecdsap256 sign/s 41,629 18,754 41,980 48,936

Web Function results were close to c7g.large in most items.

Next, the throughput of 2 processes (-multi 2) is shown as a multiplier relative to 1 process. Aggregated from 51 measurements of 17 items × 3 rounds.

Environment Median Min–Max
Web Function 1.84 1.809–1.856
c6g.large 2.000 1.954–2.016
c7g.large 1.997 1.861–2.022
c8g.large 1.998 1.801–2.003

All three EC2 instances were around 2.00x, while Web Function was 1.84x even with 2 vCPUs.

Comparison with Lambda MicroVMs and Traditional Lambda

I ran the same diagnostics on Web Functions, Lambda MicroVMs, and traditional Lambda. MicroVMs were configured with a baseline of 4 GB / 2 vCPU, and traditional Lambda was configured with arm64 / 3,538 MB.

Item Web Functions Lambda MicroVMs Traditional Lambda
Visible CPU count 2 8 (peak) 3
MemTotal 2460024 kB 16437604 kB (peak) 4510668 kB
Execution user and permissions uid 993, CapEff 0, 8 Seccomp filters uid 0, 14 CapEff capabilities, Seccomp 0 uid 993, CapEff 0, 6 Seccomp filters
/sys absent sysfs (read-only). No DMI or hypervisor absent
lscpu, lsblk, lsmem exit 1 exit 0 exit 1
dmesg denied exit 0 denied
/tmp ENOSPC at approx. 502 MiB could write up to 1 GiB (test limit) ENOSPC at approx. 495 MiB (default 512 MB)
CPU part 0xd40 0xd40 (no SVE features, 1 instance) 0xd0c and 0xd40
openssl speed scaling with 2 processes 1.84 2.00 1.69

MicroVMs, measured in a single execution environment, ran as root and could access /sys, so lscpu, lsblk, lsmem, and dmesg all succeeded. openssl speed scaled to 7.996x with 8 processes (median of 51 measurements).

Traditional Lambda had the same uid, capabilities, /sys visibility, and command success/failure behavior as Web Functions. The visible CPU count was 3, but the total openssl speed throughput plateaued at around 1.7x: 1.686x with 2 processes, 1.698x with 3 processes, and 1.696x with 4 processes. Even with the same configuration, traditional Lambda execution environments were split between Graviton2 and Graviton3. The Graviton3 execution environment was used for OpenSSL performance measurements.

Estimated Hourly Cost

These are estimated values calculated from listed unit prices, not actual billed amounts. The assumptions are as follows:

  • Unit prices: Tokyo region, Arm, USD, using values from the Price List API effective 2026-10-01. Only compute charges are included.
  • CPU utilization: CPU time used ÷ (2 vCPUs × 3,600 seconds). Assumes a 2 vCPU configuration running continuously for 1 hour.
  • Web Functions: Calculated assuming billing for vCPU usage time and memory usage. Since memory usage was not measured, 1 GB is assumed.
  • MicroVMs: Calculated within the baseline of 4 GB / 2 vCPU.
CPU Utilization EC2 c7g.large Web Functions (1 GB memory consumed) Lambda MicroVMs (4 GB / 2 vCPU)
100% $0.0910 $0.2475 $0.2936
75% $0.0910 $0.1895 $0.2936
50% $0.0910 $0.1314 $0.2936
25% $0.0910 $0.0734 $0.2936

When utilization falls below approximately 33%, the estimated cost for Web Functions drops below EC2.

Calculation formulas
  • Web Functions
vCPU  7,200 vCPU-seconds × $0.0000322421 = $0.2321
Memory 3,600 GB-seconds × $0.0000042688 = $0.0154
Total (100% utilization) = $0.2475 / hour

Utilization at which cost equals EC2
($0.0910 − $0.0154) ÷ $0.2321 = 0.326 (32.6%)
  • Traditional Lambda (3,538 MB memory) run for 1 hour ($0.1658)
3,538 MB ÷ 1,024 = 3.455 GB
3.455 GB × 3,600 seconds = 12,438 GB-seconds
12,438 GB-seconds × $0.0000133334 = $0.1658

Summary

This verification targets a single execution environment of Lambda Web Functions. The confirmed environment runs on Amazon Linux 2023 with a 2 vCPU configuration, and from the CPU identification information and benchmark results, we can infer that a Graviton3 generation CPU is being used.

While this is not a comparison with matched conditions such as memory, web servers with many wait times for DB or external API responses may benefit from billing based on CPU usage.

Compared to conventional Lambda, there are currently limitations on available runtimes and configurations. On the other hand, it can be a strong option for web workloads with low CPU usage, so please give it a try.

Diagnostic app code (app-src/diag.ts)
import http from 'node:http';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { spawnSync } from 'node:child_process';

// Diagnostic app for a Lambda Web Function. Read-only except for /tmp.
// - Only fixed paths and a fixed command allowlist are used. No request data reaches a path or a command.
// - Environment variables and /proc/self/environ are never read or returned.
const port = Number(process.env.LOCAL_PORT ?? 3000);
const HOSTNAME = os.hostname();
const SECRET_NAME = /serial|uuid/i; // values of these files are replaced by their length

type FileResult = { path: string; ok: boolean; bytes?: number; text?: string; truncated?: boolean; error?: string };

const readCapped = (p: string, cap = 16384): FileResult => {
  let fd: number | undefined;
  try {
    fd = fs.openSync(p, 'r');
    const buf = Buffer.alloc(cap + 1);
    const n = fs.readSync(fd, buf, 0, cap + 1, null);
    const truncated = n > cap;
    const text = buf.subarray(0, Math.min(n, cap)).toString('utf8');
    if (SECRET_NAME.test(path.basename(p))) return { path: p, ok: true, bytes: n, text: `<redacted by app: ${n} bytes>` };
    return { path: p, ok: true, bytes: n, text, truncated };
  } catch (e) {
    return { path: p, ok: false, error: (e as NodeJS.ErrnoException).code ?? String(e) };
  } finally {
    if (fd !== undefined) try { fs.closeSync(fd); } catch { /* ignore */ }
  }
};

// Read the regular files directly under `dir`; list (but do not read) sub-entries. Optionally recurse.
const dumpDir = (dir: string, depth: number, readLimit = 200): unknown => {
  let names: string[];
  try { names = fs.readdirSync(dir).sort(); } catch (e) { return { dir, ok: false, error: (e as NodeJS.ErrnoException).code ?? String(e) }; }
  const files: FileResult[] = [];
  const subdirs: string[] = [];
  const links: Record<string, string> = {};
  const children: unknown[] = [];
  for (const name of names) {
    const full = path.join(dir, name);
    let st: fs.Stats;
    try { st = fs.lstatSync(full); } catch (e) { files.push({ path: full, ok: false, error: (e as NodeJS.ErrnoException).code ?? String(e) }); continue; }
    if (st.isSymbolicLink()) { try { links[name] = fs.readlinkSync(full); } catch { links[name] = '?'; } continue; }
    if (st.isDirectory()) {
      subdirs.push(name);
      if (depth > 0) children.push(dumpDir(full, depth - 1, readLimit));
      continue;
    }
    if (files.length < readLimit) files.push(readCapped(full, 4096));
  }
  return { dir, ok: true, subdirs, links, files, children };
};

const statfs = (p: string) => {
  try {
    const s = fs.statfsSync(p, { bigint: true });
    const bs = s.bsize;
    return {
      path: p, type: `0x${s.type.toString(16)}`, bsize: Number(bs), blocks: Number(s.blocks), bfree: Number(s.bfree), bavail: Number(s.bavail),
      files: Number(s.files), ffree: Number(s.ffree),
      totalBytes: Number(s.blocks * bs), freeBytes: Number(s.bfree * bs), availBytes: Number(s.bavail * bs),
    };
  } catch (e) { return { path: p, error: (e as NodeJS.ErrnoException).code ?? String(e) }; }
};

const modeInfo = (p: string) => {
  try { const st = fs.lstatSync(p); return { path: p, mode: (st.mode & 0o7777).toString(8), uid: st.uid, gid: st.gid, size: st.size, symlink: st.isSymbolicLink() ? fs.readlinkSync(p) : undefined }; }
  catch (e) { return { path: p, error: (e as NodeJS.ErrnoException).code ?? String(e) }; }
};

// ---- A. files read directly by Node ----
const routeA = () => ({
  dmi: dumpDir('/sys/class/dmi/id', 0),
  hypervisor: dumpDir('/sys/hypervisor', 3),
  cpuinfo: readCapped('/proc/cpuinfo', 65536),
  cgroup: dumpDir('/sys/fs/cgroup', 0),
  cgroupSelf: readCapped('/proc/self/cgroup'),
  mountinfo: readCapped('/proc/self/mountinfo', 65536),
  status: readCapped('/proc/self/status'),
  extra: ['/proc/version', '/proc/meminfo', '/proc/self/limits', '/proc/self/uid_map', '/proc/self/gid_map', '/proc/sys/kernel/osrelease', '/etc/os-release', '/proc/cmdline', '/proc/sys/kernel/hostname']
    .map((p) => readCapped(p, 8192)),
});

// ---- C. what Node itself reports ----
const routeC = () => ({
  node: { version: process.version, arch: process.arch, platform: process.platform, versions: { v8: process.versions.v8, openssl: process.versions.openssl } },
  os: {
    arch: os.arch(), machine: os.machine(), platform: os.platform(), type: os.type(), release: os.release(), version: os.version(),
    totalmem: os.totalmem(), freemem: os.freemem(), uptimeSec: os.uptime(), loadavg: os.loadavg(),
    availableParallelism: os.availableParallelism(), cpuCount: os.cpus().length,
    cpus: os.cpus().map((c) => ({ model: c.model, speed: c.speed })),
    hostnameLength: HOSTNAME.length,
  },
  process: { pid: process.pid, uid: process.getuid?.(), gid: process.getgid?.(), groups: process.getgroups?.(), cwd: process.cwd(), dirname: __dirname, execPath: process.execPath, memoryUsage: process.memoryUsage() },
  statfs: ['/', '/tmp', '/var/tmp', '/dev/shm', __dirname].map(statfs),
  bundledModes: ['bin', 'lib'].flatMap((d) => { try { return fs.readdirSync(path.join(__dirname, d)).map((n) => modeInfo(path.join(__dirname, d, n))); } catch { return []; } }),
});

// ---- /tmp write test (only /tmp; file removed afterwards) ----
const routeTmpWrite = () => {
  const file = '/tmp/diag-fill.bin';
  const chunk = Buffer.alloc(64 * 1024 * 1024, 0x61);
  const capBytes = 2 * 1024 * 1024 * 1024;
  const before = statfs('/tmp');
  const started = Date.now();
  let written = 0;
  let stopped = 'cap';
  let error: string | undefined;
  try {
    const fd = fs.openSync(file, 'w');
    try {
      while (written < capBytes) {
        fs.writeSync(fd, chunk, 0, chunk.length);
        written += chunk.length;
      }
    } catch (e) { stopped = 'error'; error = (e as NodeJS.ErrnoException).code ?? String(e); }
    try { fs.fsyncSync(fd); } catch { /* ignore */ }
    fs.closeSync(fd);
  } catch (e) { stopped = 'open-error'; error = (e as NodeJS.ErrnoException).code ?? String(e); }
  let fileSize: number | undefined;
  try { fileSize = fs.statSync(file).size; } catch { /* ignore */ }
  const during = statfs('/tmp');
  try { fs.unlinkSync(file); } catch { /* ignore */ }
  return { capBytes, chunkBytes: chunk.length, chunksWritten: written / chunk.length, writtenBytes: written, fileSizeAfterBytes: fileSize, stopped, error, ms: Date.now() - started, statfsBefore: before, statfsDuringFileExists: during, statfsAfterDelete: statfs('/tmp') };
};

// ---- B. external commands (fixed allowlist) ----
const SPECS: Record<string, string[]> = {
  lscpu: [], lsblk: [], lsmem: [], findmnt: [], lsns: [], dmesg: [],
  free: ['-m'], ps: ['-eo', 'pid,ppid,user,comm'], df: ['-h'], uname: ['-a'],
};
const MULTICALL = new Set(['df', 'uname']); // coreutils-single: /usr/bin/coreutils --coreutils-prog=<name>
const SRC_BIN = path.join(__dirname, 'bin');
const SRC_LIB = path.join(__dirname, 'lib');
const DST = '/tmp/diag-bin';
const LOADER = 'ld-linux-aarch64.so.1';

const prepare = () => {
  const r: Record<string, unknown> = { dst: DST };
  try {
    fs.mkdirSync(path.join(DST, 'bin'), { recursive: true });
    fs.mkdirSync(path.join(DST, 'lib'), { recursive: true });
    for (const [src, sub] of [[SRC_BIN, 'bin'], [SRC_LIB, 'lib']] as const) {
      for (const n of fs.readdirSync(src)) {
        const to = path.join(DST, sub, n);
        fs.copyFileSync(path.join(src, n), to);
        fs.chmodSync(to, 0o755);
      }
    }
    r.ok = true;
  } catch (e) { r.ok = false; r.error = (e as NodeJS.ErrnoException).code ?? String(e); }
  return r;
};

const run = (variant: string, argv: string[]) => {
  const t = Date.now();
  const r = spawnSync(argv[0], argv.slice(1), { timeout: 30000, maxBuffer: 2 * 1024 * 1024, env: { PATH: '/usr/bin:/bin' }, encoding: 'utf8' });
  const cap = (s: string | null, n: number) => (s && s.length > n ? `${s.slice(0, n)}\n...[truncated ${s.length - n} chars]` : s ?? '');
  return {
    variant, argv: argv.map((a) => a.replace(DST, '<DST>').replace(__dirname, '<TASK>')),
    exitCode: r.status, signal: r.signal, spawnError: r.error ? `${(r.error as NodeJS.ErrnoException).code ?? ''} ${r.error.message}`.trim() : undefined,
    stdout: cap(r.stdout, 16384), stderr: cap(r.stderr, 4096), ms: Date.now() - t,
  };
};

// ---- openssl speed (fixed argument lists only; the number of processes is fixed per route) ----
const OSSL = path.join(DST, 'bin', 'openssl');
const osslArgv = (args: string[]) => [path.join(DST, 'lib', LOADER), '--library-path', path.join(DST, 'lib'), OSSL, ...args];
const cpuStat = () => (readCapped('/proc/stat', 4096).text ?? '').split('\n').filter((l) => /^cpu\d* /.test(l));
const BULK_ALGS = ['aes-128-gcm', 'aes-256-gcm', 'sha256', 'sha512', 'sha3-256', 'chacha20-poly1305'];
const BULK_SIZES = ['64', '16384'];

const osslSuite = (kind: 'bulk' | 'pk', multi: number) => {
  const prep = prepare();
  const multiArgs = multi > 1 ? ['-multi', String(multi)] : [];
  const argLists: string[][] = kind === 'bulk'
    ? BULK_ALGS.flatMap((a) => BULK_SIZES.map((b) => ['speed', '-seconds', '2', '-bytes', b, ...multiArgs, '-evp', a]))
    : ['rsa2048', 'ecdsap256', 'ecdhx25519'].map((a) => ['speed', '-seconds', '2', ...multiArgs, a]);
  const statBefore = cpuStat();
  const started = Date.now();
  const runs = argLists.map((args) => run(`openssl-${kind}-multi${multi}`, osslArgv(args)));
  return { prepare: prep, kind, multi, totalMs: Date.now() - started, cpuStatBefore: statBefore, cpuStatAfter: cpuStat(), runs };
};
const routeOsslInfo = () => {
  const prep = prepare();
  return { prepare: prep, version: run('openssl-version', osslArgv(['version', '-a'])), cpuStat: cpuStat() };
};

const routeWhich = () => {
  const names = [...Object.keys(SPECS), 'coreutils'];
  const dirs = ['/usr/bin', '/bin', '/usr/sbin', '/sbin', '/usr/local/bin'];
  const found: Record<string, unknown[]> = {};
  for (const n of names) found[n] = dirs.map((d) => path.join(d, n)).filter((p) => fs.existsSync(p)).map(modeInfo);
  let usrBinCount: number | undefined;
  try { usrBinCount = fs.readdirSync('/usr/bin').length; } catch { /* ignore */ }
  return { pathVar: process.env.PATH, dirs: dirs.map((d) => modeInfo(d)), usrBinCount, found, osRelease: readCapped('/etc/os-release', 4096) };
};

const routeCmds = () => {
  const prep = prepare();
  const results: unknown[] = [];
  for (const [name, args] of Object.entries(SPECS)) {
    const inner = MULTICALL.has(name)
      ? [path.join(DST, 'bin', 'coreutils'), `--coreutils-prog=${name}`, ...args]
      : [path.join(DST, 'bin', name), ...args];
    results.push(run('bundled', [path.join(DST, 'lib', LOADER), '--library-path', path.join(DST, 'lib'), ...inner]));
    for (const d of ['/usr/bin', '/bin', '/usr/sbin', '/sbin']) {
      const p = path.join(d, name);
      if (fs.existsSync(p)) { results.push(run(`builtin:${d}`, [p, ...args])); break; }
    }
  }
  // Does the executable bit survive the zip? Run one bundled command straight from the code directory.
  const inplace = run('bundled-inplace', [path.join(SRC_LIB, LOADER), '--library-path', SRC_LIB, path.join(SRC_BIN, 'lscpu')]);
  const inplaceDirect = run('bundled-inplace-direct', [path.join(SRC_BIN, 'lscpu')]);
  return { prepare: prep, results, inplace, inplaceDirect };
};

const ROUTES: Record<string, () => unknown> = { '/a': routeA, '/c': routeC, '/which': routeWhich, '/cmds': routeCmds, '/tmpwrite': routeTmpWrite,
  '/ossl-info': routeOsslInfo, '/ossl-bulk-1': () => osslSuite('bulk', 1), '/ossl-bulk-2': () => osslSuite('bulk', 2), '/ossl-pk-1': () => osslSuite('pk', 1), '/ossl-pk-2': () => osslSuite('pk', 2) };

const server = http.createServer((req, res) => {
  const p = (req.url ?? '/').split('?')[0];
  const started = Date.now();
  try {
    const handler = ROUTES[p];
    const body = handler ? handler() : { endpoints: Object.keys(ROUTES) };
    // Never print the host name (an identifier of the execution environment).
    const text = JSON.stringify({ path: p, elapsedMs: Date.now() - started, body }, null, 2).split(HOSTNAME.length >= 4 ? HOSTNAME : '\u0000').join('<HOSTNAME>');
    res.writeHead(200, { 'content-type': 'application/json' });
    res.end(text);
  } catch (e) {
    res.writeHead(500, { 'content-type': 'application/json' });
    res.end(JSON.stringify({ error: String((e as Error).name), message: String((e as Error).message) }));
  }
});

server.listen(port, () => console.log(`listening on port ${port}`));

Share this article

AWSのお困り事はクラスメソッドへ