Add ResourceUsage to report subprocess CPU time and memory consumption#226
Open
jakepetroules wants to merge 1 commit intoswiftlang:mainfrom
Open
Add ResourceUsage to report subprocess CPU time and memory consumption#226jakepetroules wants to merge 1 commit intoswiftlang:mainfrom
jakepetroules wants to merge 1 commit intoswiftlang:mainfrom
Conversation
0742246 to a8f9745 Compare Introduce a public ResourceUsage struct that exposes userTime, systemTime (as Duration), and maxRSS (in bytes) for every terminated subprocess. An ExecutionResult protocol provides common access to terminationStatus and resourceUsage across both ExecutionOutcome and ExecutionRecord. On Unix, resource data is collected via wait4 (BSD) or the Linux kernel's 5-argument waitid syscall, which populates a rusage struct alongside the termination status. A linux_waitid C shim is added because glibc and musl only expose the 4-parameter POSIX waitid that omits rusage. The raw rusage is available as a public property on non-Windows platforms, with maxRSS normalized from KiB to bytes on Linux, FreeBSD, and OpenBSD. On Windows, GetProcessTimes provides CPU time (converted from FILETIME 100ns units) and GetProcessMemoryInfo provides PeakWorkingSetSize (maxRSS). This functionality is particularly necessary as part of SwiftSubprocess because collecting rusage information from a terminated subprocess requires the ability to run code when a process has changed state from executing to zombie, but before its pid has been reaped - something not possible with the current Subprocess API. Further, it is notoriously difficult to collect this information across all OSes for an arbitrary PID anyways, at least in a way that doesn't also simultaneously reap the pid. User time, system time, and maxRSS are some of the most common metrics typically extracted from getrusage. Exposing the raw rusage struct provides access to the rest, and on Windows, callers can use DuplicateHandle to get a process descriptor that can outlive Subprocess's control of the process and collect any additional metrics from the process when it is known to be in a terminated state.
a8f9745 to 66ae4c3 Compare compnerd reviewed Mar 18, 2026
Comment on lines +82 to +90
| private static func duration(from ft: FILETIME) -> Duration { | ||
| let hundredNanos = UInt64(ft.dwHighDateTime) << 32 | UInt64(ft.dwLowDateTime) | ||
| let seconds = Int64(hundredNanos / 10_000_000) | ||
| let remainder = Int64(hundredNanos % 10_000_000) | ||
| return Duration( | ||
| secondsComponent: seconds, | ||
| attosecondsComponent: remainder * 100_000_000_000 | ||
| ) | ||
| } |
Member
There was a problem hiding this comment.
This feels like something that might be better to extract as:
extension Duration { internal init(_ ft: FILETIME) { let nanos = UInt64(ft.dwHighDateTime) << 32 | UInt64(ft.dwLowDateTime) let seconds = Int64(nanos / 10_000_000) let remainder = Int64(nanos % 10_000_000) self = Duration(secondsComponents: seconds, attosecondsComponent: remainder * 100_000_000_000) } } jakepetroules commented Mar 22, 2026
| ) | ||
| #if canImport(Darwin) | ||
| self.maxRSS = Int(usage.ru_maxrss) // bytes on Darwin | ||
| #else |
Contributor Author
There was a problem hiding this comment.
note to self: probably should explicitly check for Linux/BSD here and #error in other cases to avoid assumptions on new platform bringup and avoid cases where the unit might be incorrect until someone notices and then people become dependent on the behavior
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Introduce a public ResourceUsage struct that exposes userTime, systemTime (as Duration), and maxRSS (in bytes) for every terminated subprocess. An ExecutionResult protocol provides common access to terminationStatus and resourceUsage across both ExecutionOutcome and ExecutionRecord.
On Unix, resource data is collected via wait4 (BSD) or the Linux kernel's 5-argument waitid syscall, which populates a rusage struct alongside the termination status. A linux_waitid C shim is added because glibc and musl only expose the 4-parameter POSIX waitid that omits rusage. The raw rusage is available as a public property on non-Windows platforms, with maxRSS normalized from KiB to bytes on Linux, FreeBSD, and OpenBSD.
On Windows, GetProcessTimes provides CPU time (converted from FILETIME 100ns units) and GetProcessMemoryInfo provides PeakWorkingSetSize (maxRSS).
This functionality is particularly necessary as part of SwiftSubprocess because collecting rusage information from a terminated subprocess requires the ability to run code when a process has changed state from executing to zombie, but before its pid has been reaped - something not possible with the current Subprocess API. Further, it is notoriously difficult to collect this information across all OSes for an arbitrary PID anyways, at least in a way that doesn't also simultaneously reap the pid. User time, system time, and maxRSS are some of the most common metrics typically extracted from getrusage. Exposing the raw rusage struct provides access to the rest, and on Windows, callers can use DuplicateHandle to get a process descriptor that can outlive Subprocess's control of the process and collect any additional metrics from the process when it is known to be in a terminated state.