gpui: Record hang trigger class and set the release frame budget to 24ms (#63586)
Hang incidents come in two classes that consumers could only tell apart by comparing `stall_ms` against the hang threshold, which is not recorded in the event: a single event over the threshold, or an interval whose cumulative foreground spend reached the frame budget. This PR makes the class explicit and re-tunes the budget. - `HangIncident` and `SerializedHangIncident` gain `trigger: HangTrigger` (`threshold` | `budget`). - The `Hang Incidents` telemetry event reports `threshold_incidents` and `budget_incidents` alongside the existing `total_incidents` (kept for existing queries; it is now the sum of the two). - The release frame budget moves from 8 ms to 24 ms. At 8 ms almost every busy 60 Hz frame qualified, so the budget class dominated incident counts without saying much. 24 ms is above every refresh period, so a budget incident now means the interval dropped at least one frame on any display. Frame smoothness below that is measured per-frame by `Frame Duration Report`. `budget_incidents` is the signal for lowering it further as hangs get fixed. Debug builds already used 100 ms and are unchanged. Part of a series on hang-telemetry data quality; #63588 handles withheld-frame `dirty_at`, and marking OS prompt waits so they stop registering as task-poll hangs is next. Release Notes: - N/A
This commit is contained in:
1 parent
49f5fe7750
commit
265ea4e838
3 files changed
+59
-16
No files matched your search
@@ -40,13 +40,30 @@ pub struct HangIncident {
|
||||
/// The interval the hangs occurred in, including all non-hang foreground
|
||||
/// work recorded alongside them.
|
||||
pub snapshot: FrameSnapshot,
|
||||
/// The events that blocked the foreground for at least the detector's
|
||||
/// threshold, longest first. When the incident was triggered by the
|
||||
/// frame budget alone, no event crossed the threshold and this instead
|
||||
/// Which detection rule qualified the interval.
|
||||
pub trigger: HangTrigger,
|
||||
/// For [`HangTrigger::Threshold`], the events that blocked the foreground
|
||||
/// for at least the detector's threshold, longest first. For
|
||||
/// [`HangTrigger::Budget`], no event crossed the threshold and this instead
|
||||
/// holds every event in the interval, longest first.
|
||||
pub contributors: Vec<ForegroundEvent>,
|
||||
}
|
||||
|
||||
/// The detection rule that qualified an interval as a [`HangIncident`].
|
||||
///
|
||||
/// Recorded explicitly so consumers can separate the two classes without
|
||||
/// re-deriving them from `stall_ms`, which stops working whenever the
|
||||
/// detector's thresholds change.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum HangTrigger {
|
||||
/// A single event blocked the foreground for at least the hang threshold.
|
||||
Threshold,
|
||||
/// No single event crossed the threshold, but the interval's total
|
||||
/// foreground spend reached the frame budget.
|
||||
Budget,
|
||||
}
|
||||
|
||||
impl HangDetector {
|
||||
/// Creates a detector reporting single events at or above `threshold`
|
||||
/// and intervals whose total foreground spend reached `frame_budget`.
|
||||
@@ -98,6 +115,8 @@ pub struct SerializedHangIncident {
|
||||
/// newly drawn frame finished platform submission (see
|
||||
/// [`HangDetector::first_present_at`]), otherwise `"steady"`.
|
||||
pub phase: &'static str,
|
||||
/// `"threshold"` or `"budget"` (see [`HangTrigger`]).
|
||||
pub trigger: HangTrigger,
|
||||
/// When the incident's active window started, in milliseconds since app
|
||||
/// startup: the sealing frame's first invalidation, or the earliest
|
||||
/// contributor's start when nothing was pending a repaint. Foreground
|
||||
@@ -250,6 +269,7 @@ impl SerializedHangIncident {
|
||||
Some(first_present_at) if active_start >= first_present_at => "steady",
|
||||
_ => "startup",
|
||||
},
|
||||
trigger: incident.trigger,
|
||||
start_ms: since_startup(active_start),
|
||||
active_ms: as_millis(active),
|
||||
stall_ms: incident
|
||||
@@ -407,7 +427,7 @@ impl HangIncident {
|
||||
.filter(|event| event.duration() >= threshold)
|
||||
.copied()
|
||||
.collect();
|
||||
if contributors.is_empty() {
|
||||
let trigger = if contributors.is_empty() {
|
||||
if snapshot.journal_discontinuous {
|
||||
return None;
|
||||
}
|
||||
@@ -416,10 +436,14 @@ impl HangIncident {
|
||||
return None;
|
||||
}
|
||||
contributors = snapshot.events.clone();
|
||||
}
|
||||
HangTrigger::Budget
|
||||
} else {
|
||||
HangTrigger::Threshold
|
||||
};
|
||||
contributors.sort_by_key(|event| std::cmp::Reverse(event.duration()));
|
||||
Some(Self {
|
||||
snapshot,
|
||||
trigger,
|
||||
contributors,
|
||||
})
|
||||
}
|
||||
@@ -449,7 +473,9 @@ mod tests {
|
||||
InputTiming, IntervalBoundary, PollSummary, PresentedFrame, SmallPollFlush,
|
||||
install_test_foreground_journal, record_present,
|
||||
};
|
||||
use super::{HangDetector, HangIncident, SerializedHangContributor, SerializedHangIncident};
|
||||
use super::{
|
||||
HangDetector, HangIncident, HangTrigger, SerializedHangContributor, SerializedHangIncident,
|
||||
};
|
||||
|
||||
actions!(hang_test, [HangyAction]);
|
||||
|
||||
@@ -567,6 +593,7 @@ mod tests {
|
||||
let serialized = SerializedHangIncident::convert(startup, &incident, 1, Some(at(50)));
|
||||
|
||||
assert_eq!(serialized.phase, "steady");
|
||||
assert_eq!(serialized.trigger, HangTrigger::Threshold);
|
||||
// The frame's first invalidation anchors the active window, not the
|
||||
// interval start or the first contributor.
|
||||
assert_eq!(serialized.start_ms, 100.0);
|
||||
@@ -755,12 +782,14 @@ mod tests {
|
||||
|
||||
let incident = HangIncident::detect(snapshot, HANG_THRESHOLD, FRAME_BUDGET)
|
||||
.expect("foreground spend exceeded the frame budget");
|
||||
assert_eq!(incident.trigger, HangTrigger::Budget);
|
||||
assert_eq!(incident.contributors.len(), 3);
|
||||
assert_eq!(
|
||||
incident.contributors[0].duration(),
|
||||
Duration::from_millis(8)
|
||||
);
|
||||
let serialized = SerializedHangIncident::convert(startup, &incident, 8, Some(startup));
|
||||
assert_eq!(serialized.trigger, HangTrigger::Budget);
|
||||
assert_eq!(serialized.stall_ms, 8.0);
|
||||
assert_eq!(serialized.dirty_to_present_ms, Some(150.0));
|
||||
assert_eq!(serialized.sealed_by, "present");
|
||||
|
||||
Reference in new issue
Block a user