Skip to content

Add ResourceUsage to report subprocess CPU time and memory consumption#226

Open
jakepetroules wants to merge 1 commit intoswiftlang:mainfrom
jakepetroules:eng/PR-resource-usage
Open

Add ResourceUsage to report subprocess CPU time and memory consumption#226
jakepetroules wants to merge 1 commit intoswiftlang:mainfrom
jakepetroules:eng/PR-resource-usage

Conversation

@jakepetroules
Copy link
Contributor

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.

@jakepetroules jakepetroules added enhancement New feature or request API Change labels Mar 17, 2026
@jakepetroules jakepetroules force-pushed the eng/PR-resource-usage branch from 0742246 to a8f9745 Compare March 17, 2026 08:06
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.
@jakepetroules jakepetroules force-pushed the eng/PR-resource-usage branch from a8f9745 to 66ae4c3 Compare March 17, 2026 17:39
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
)
}
Copy link
Member

Choose a reason for hiding this comment

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

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) } }
@iCharlesHu iCharlesHu added this to the Post 1.0 - Feature Release milestone Mar 19, 2026
)
#if canImport(Darwin)
self.maxRSS = Int(usage.ru_maxrss) // bytes on Darwin
#else
Copy link
Contributor Author

Choose a reason for hiding this comment

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

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

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

Labels

API Change enhancement New feature or request

3 participants