@davidwells/llrt-analyzer
v0.1.1
Published
Per-function analyzer that decides (statically + by actually running under LLRT) whether a Lambda function can switch to AWS LLRT for ~10x faster cold starts, and helps flip it.
Maintainers
Readme
@davidwells/llrt-analyzer
Decide — per Lambda function — whether it can run on AWS LLRT
(QuickJS, no JIT) for ~10x faster cold starts, and help flip it. LLRT is not a
Node drop-in (no node:http/https/tls, no worker_threads, partial
crypto/fs/stream, pre-bundled AWS SDK subset), and you can't tell
statically — so this tool actually bundles the handler and runs it under both
Node and the LLRT binary, then diffs behavior. If it runs identically, it
recommends switching.
Install
npm i -D @davidwells/llrt-analyzer # CLI command is `llrt-analyzer`CLI
# One function
llrt-analyzer services/mcp-gateway/src/app.ts --project services/mcp-gateway --fn api
# One function, definitive (runs it under the real LLRT binary)
llrt-analyzer services/mcp-gateway/src/app.ts --project services/mcp-gateway --fn api --local
# Whole service (discovers functions from serverless.yml) + machine output
llrt-analyzer services/mcp-gateway --local --out result.json
# Scaffold the serverless.yml snippet, and publish the LLRT layer (+ write SSM)
llrt-analyzer init
llrt-analyzer publish-layer --arch arm64 --region us-east-1 --ssm /my-svc/prod/llrt-layer-arnTiers
- Static (default, no binary) — resolves the transitive import graph and
flags
node:blockers, a per-run disallowed-API guard, a known-quirks scan (confirmed behavioral breaks — e.g.hono/corsemptying the body, ortimingSafeEqual— caught from source signatures, no run needed), the AWS-SDK bundle, known-bad npm, and CPU-heavy workloads — each with a concrete fix. - Local call-comparison (
--local, the decisive tier) — bundles for LLRT (aliasing@aws-sdk/@smithyto a recorder shim, no side effects), runs the handler under Node and LLRT withfetch+ SDK calls intercepted, and diffs semantically. LLRT crashes are attributed to the exact missing API. Also measures the cold-start win (node vs LLRT init) and stamps confidence + provenance (see below). Results are cached by built-bundle content, so repeat/CI runs are instant on unchanged inputs (--no-cacheto bypass). - Deploy-verify (opt-in, heavy) — ephemeral-stage deploy + integration tests + a real cold-start measurement. Gated; the local tier covers correctness without a deploy.
Verdict
switch · compatible-but-not-recommended (e.g. CPU-heavy) · incompatible
(with the exact blocker + fix) · unknown (static-ambiguous — run --local).
result.json.llrtCandidates[] is jq-able for CI.
Every verdict carries a confidence (high/medium/low) and
provenance: static-only is low; a local run on the host arch is
high, but if the host ≠ Lambda's target (e.g. verified on darwin, Lambda is
linux/arm64) it is downgraded to medium and the report says so — the tool
never silently passes off a host-arch result as a Lambda guarantee. Local runs
also report coldStartDeltaMs (node/llrt/ratio), the number that
justifies the flip.
Known-quirks DB
data/known-quirks.<version>.json holds confirmed behavioral LLRT breaks
that plain import/builtin analysis can't see, each with a source-detectable
signature so the static tier (and the deploy guard) flag them before a local
run or deploy. Add to it as new quirks are found — findings accrete into cheap
checks. The serverless plugin's guard runs the static tier, so it inherits these
automatically.
Serverless plugin (flip a function)
The Serverless Framework consumer ships as its own package,
serverless-llrt-analyzer (depends on this core):
plugins:
- serverless-llrt-analyzer
custom:
llrt:
layerArn: arn:aws:lambda:us-east-1:xxxx:layer:llrt-arm64:1 # your LLRT layer
verify: true # guard: fail deploy if a flagged fn is incompatible
functions:
api: { handler: src/app.handler, llrt: true } # -> provided.al2023 + arm64 + LLRT layer
warmer: { handler: src/warmer.warm, llrt: true }
processImg: { handler: src/img.handler } # stays nodejsReverting is one line (remove llrt: true). Mixed-runtime services are
first-class.
smart-ci integration
- Validator:
@davidwells/llrt-analyzer/src/smart-ci-checkreturns the canonical{ valid, errors:[{check:'llrt-compat',…}], verified }shape; wire it intosrc/validate/index.jsbehind achecks.llrtCompattoggle. - Persistence (
src/persist.js): cache the "llrtable" verdict keyed onhash(bundle + lockfile + llrtVersion); re-verify only on change. The Tier-1 disallowed-API scan still runs every CI run as a guard. - CI cadence (consumer-configurable): (a) on-demand/opt-in, (b) a nightly
audit that opens "X is now LLRT-ready" PRs, (c) every changed candidate. Fold
result.json.llrtCandidatesintodetect-changes.ymlviajq.
LLRT version pinning
One org-wide pinned version (ORG_PINNED_LLRT_VERSION, currently 0.8.1-beta).
scripts/refresh.js regenerates the compat table from the LLRT repo; bumping the
pin is deliberate and re-verifies all llrtable functions. The LLRT binary is
downloaded from GitHub releases and executed, so it is verified against a
pinned sha256 in data/llrt-binary-checksums.<version>.json — a mismatch aborts
(an unlisted asset is allowed but logs its observed hash to pin).
When NOT to use LLRT
Compute-heavy functions (big loops, large-data transforms, heavy hashing): LLRT
has no JIT and can be slower than Node even when compatible — the analyzer
warns (compatible-but-not-recommended).
