Skip to content

uefi/perf: Capture FPDT data from a QEMU boot - #212

Open
kat-perez wants to merge 10 commits into
OpenDevicePartnership:mainfrom
kat-perez:kat-perez/issue-119-fpdt-capture
Open

uefi/perf: Capture FPDT data from a QEMU boot#212
kat-perez wants to merge 10 commits into
OpenDevicePartnership:mainfrom
kat-perez:kat-perez/issue-119-fpdt-capture

Conversation

@kat-perez

Copy link
Copy Markdown
Contributor

Adds the capture half of the QEMU performance harness: boot the firmware, recover the firmware basic boot performance table (FBPT) it produced, and parse it.

Closes #119.

Stacked on #211 — base that first, or review only the second commit here.

What it does

  • make-fbpt-disk.sh builds a FAT image that boots to the UEFI Shell and runs FbptDump.efi, which writes the table back to the same image. The shell's output is redirected onto the disk, since the dump application reports through standard output rather than the debug console and the platform has no serial console in ConOut.
  • capture-fbpt.sh boots the firmware with that disk attached, waits for the guest to power itself off, copies the table out and parses it to XML and text.

Firmware built without PERF_TRACE_ENABLE boots normally and silently produces nothing, so the capture asserts on the configuration the firmware reports and fails with the flag to rebuild with.

The parser wrapper

fpdt_parser from edk2-pytool-extensions can read the live FPDT from a running Windows system. It therefore imports windll/WinError at module scope and constructs its Windows firmware-table accessor unconditionally in main, so it cannot even be imported on Linux — which is where CI runs.

None of that is used when parsing a captured binary: every call site is guarded on input_fbpt_bin is None. fpdt_parser_any_platform.py supplies the missing names and replaces the accessor, and raises if a Windows-only path is genuinely reached rather than silently misbehaving. Verified to produce identical output on Windows and Linux from the same binary (564 records, same XML length).

Verification

End-to-end from a clean state, against firmware built from patina-qemu v4.0.5 with BLD_*_PERF_TRACE_ENABLE=TRUE:

created /tmp/fbpt-e2e/dump-disk.img
captured table: /tmp/fbpt-e2e/out/FBPT.bin (31066 bytes)
CRITICAL - SUCCESS, 776 record(s) parsed
PASS: captured firmware performance data

Failure paths checked individually:

ok   perf-disabled firmware is rejected (exit 1)
FAIL: firmware did not enable performance measurement
      rebuild with 'BLD_*_PERF_TRACE_ENABLE=TRUE'
ok   capture unknown argument (exit 2)
ok   disk build missing binaries (exit 2)

Sample phase timings from a captured run, via perf_report_generator:

Measurement
PEI 264.30 ms
DXE 91.31 ms
BDS 167.47 ms
ReadyToBoot 46.93 ms
OS loader StartImage 2642 ms from reset

Notes

  • FbptDump.efi is not in a default build; the README records that UefiTestingPkg/PerfTests/FbptDump/FbptDump.inf has to be added to the platform description first.
  • No workflow calls this yet; CI integration is tracked separately.

Boots a Patina Q35 firmware pair under QEMU and reports whether it
reaches BDS, so boot behaviour can be exercised somewhere reproducible
instead of only on hardware. Distinguishes a firmware that failed to
reach BDS from a setup problem via separate exit codes, and keeps the
debug console log as the artifact to inspect on failure.

The README records how to obtain firmware, including why performance
tracing has to be selected at build time: the platform always publishes
the performance configuration HOB, and a disabled HOB outranks the DXE
Core default, so swapping only the DXE Core binary cannot enable it.

Shell scripts are pinned to LF, which autocrlf would otherwise break for
Linux CI and WSL.

Assisted-by: GitHub Copilot:claude-opus-5

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings September 4, 2026 16:02
@kat-perez
kat-perez requested a review from a team as a code owner September 4, 2026 16:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new shell scripts have confirmed robustness issues around option parsing and dependency preflight that can produce incorrect exit codes and unhelpful failures.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds the “capture” half of the UEFI/QEMU performance harness: building a dump disk that runs FbptDump.efi, booting Patina Q35 firmware under QEMU until the guest powers off, extracting the captured FBPT binary, and parsing it on non-Windows CI hosts via a small fpdt_parser wrapper.

Changes:

  • Add QEMU boot and capture scripts for producing FBPT.bin plus parsed fbpt.xml/fbpt.txt.
  • Add a cross-platform Python wrapper to run edk2toolext.perf.fpdt_parser on Linux.
  • Add docs and repo settings tweaks for shell-script EOLs and Python cache ignores.
File summaries
File Description
uefi/perf/qemu/run-q35-boot.sh Boots Patina Q35 firmware under QEMU and watches the debug console for the BDS marker.
uefi/perf/qemu/capture-fbpt.sh Boots with a dump disk, waits for guest poweroff, extracts FBPT, and runs the parser.
uefi/perf/qemu/make-fbpt-disk.sh Builds a FAT image that boots to UEFI Shell and runs FbptDump.efi, redirecting output to disk.
uefi/perf/qemu/fpdt_parser_any_platform.py Monkeypatch wrapper to make fpdt_parser import/run on non-Windows when parsing a captured binary.
uefi/perf/qemu/README.md Usage documentation for bringup + capture flow and parser/report tooling.
.gitattributes Forces LF for *.sh to avoid CRLF shebang issues under CI/WSL.
.gitignore Ignores Python __pycache__/ artifacts.
Review details

Suppressed comments (2)

uefi/perf/qemu/run-q35-boot.sh:102

  • If qemu-system-x86_64 is not installed/on PATH, this will currently fail with command not found (exit 127), even though the script’s contract says setup problems should be EXIT_USAGE (2). It’s better to preflight the dependency and fail with a clear message and consistent exit code.
: > "$boot_log"

qemu-system-x86_64 \

uefi/perf/qemu/capture-fbpt.sh:109

  • This script relies on external tools (qemu-system-x86_64, mtype/mdir/mcopy, and the selected --python interpreter). If any are missing, the script will currently fail with command not found (exit 127), even though setup problems are meant to use EXIT_USAGE (2). Preflighting dependencies keeps failures clear and exit codes consistent.
: > "$boot_log"

qemu-system-x86_64 \
  -debugcon "file:${boot_log}" \
  • Files reviewed: 6/7 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread uefi/perf/qemu/capture-fbpt.sh
Comment thread uefi/perf/qemu/make-fbpt-disk.sh
Comment thread uefi/perf/qemu/run-q35-boot.sh
A flag given without a value read an unset positional, so 'set -u'
aborted with a bash error and exit 1 -- the code reserved for firmware
that failed to reach BDS. The timeout was worse: it is only used in
arithmetic after QEMU has started, so a non-numeric value killed the run
mid-boot, and an empty one reported reaching no BDS "within s" when
nothing had actually been measured.

Require a value for every flag that takes one, and reject a timeout that
is not a positive whole number, both before any firmware runs.

Assisted-by: GitHub Copilot:claude-opus-5

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 4, 2026 18:34
@kat-perez
kat-perez force-pushed the kat-perez/issue-119-fpdt-capture branch from dbc2b09 to 0ed1c69 Compare September 4, 2026 18:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new scripts’ documented exit-code/behavior contract depends on external tools and Python deps, but missing prerequisites currently surface as generic shell failures instead of consistent setup/usage errors.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 6/7 changed files
  • Comments generated: 3
  • Review effort level: Lite

Comment thread uefi/perf/qemu/capture-fbpt.sh
Comment thread uefi/perf/qemu/run-q35-boot.sh
Comment thread uefi/perf/qemu/fpdt_parser_any_platform.py
A missing qemu-system-x86_64 exited 127, which callers cannot tell apart
from firmware that failed to reach BDS. Check the command up front and
report it as a setup problem instead.

Assisted-by: GitHub Copilot:claude-opus-5

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 4, 2026 19:46
@kat-perez
kat-perez force-pushed the kat-perez/issue-119-fpdt-capture branch from 0ed1c69 to d2deca2 Compare September 4, 2026 19:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The capture script’s current behavior has a misleading failure mode when extracting the dump log and it doesn’t yet directly emit ms-resolution timing output per the linked acceptance criteria.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 6/7 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread uefi/perf/qemu/capture-fbpt.sh Outdated
Comment thread uefi/perf/qemu/capture-fbpt.sh
kat-perez and others added 7 commits September 4, 2026 16:15
Assisted-by: GitHub Copilot:claude-opus-5

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the capture half of the harness: a disk image that boots to the
UEFI Shell and dumps the firmware basic boot performance table, and a
script that boots the firmware with it, recovers the table and parses
it. The guest powers itself off when the dump finishes, so a capture
ends on its own rather than on a timeout.

Firmware built without performance tracing still boots and silently
produces nothing, so the capture checks the configuration the firmware
reports and fails with the flag to rebuild with.

The packaged fpdt_parser reads the live table from a running Windows
system and so cannot even be imported elsewhere, though none of that is
used when parsing a captured binary. A wrapper supplies the missing
pieces so the same parser runs where CI does, and raises if a
Windows-only path is genuinely reached.

Assisted-by: GitHub Copilot:claude-opus-5

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Running the harness parser wrapper leaves a __pycache__ directory beside
it, which is the repository's first Python source.

Assisted-by: GitHub Copilot:claude-opus-5

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Same argument handling as the boot harness: a flag given without a value
read an unset positional and aborted under 'set -u' with exit 1, the
code that means the capture itself failed, and a non-numeric timeout was
only caught once QEMU was already running.

Assisted-by: GitHub Copilot:claude-opus-5

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A missing qemu, mtools or parser dependency exited 127 or failed partway
through a capture, which callers cannot tell apart from firmware that
produced no data. Check them up front and report a setup problem.

Locate the parser module rather than importing it. On Linux the import
itself raises on Windows-only ctypes names, which is exactly what the
wrapper works around, so importing would reject a working host.

Assisted-by: GitHub Copilot:claude-opus-5

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
WinError returns an exception rather than raising one, so binding it to
OSError handed back an instance the caller could ignore and carry on with
nonsense values. Raise instead, matching the windll stand-in.

Assisted-by: GitHub Copilot:claude-opus-5

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Print ms-resolution boot milestones from the ACPI basic boot record after
each capture, and install the dump log only once mtype succeeds so a failed
extraction is no longer reported as a missing table.

Assisted-by: GitHub Copilot:claude-opus-5

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 4, 2026 20:16
@kat-perez
kat-perez force-pushed the kat-perez/issue-119-fpdt-capture branch from d2deca2 to 170fe52 Compare September 4, 2026 20:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The capture flow, parser wrapper, and documentation are consistent with the stated behavior and add the required per-invocation ms boot-time output without introducing verified defects in the changed code.

Review details
  • Files reviewed: 7/8 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FPDT capture script for QEMU runs

2 participants