Branch: refs/heads/main
  Home:   https://github.com/WebKit/WebKit
  Commit: 5c1c95db5e0ea336071ae634567cda9aaa80c181
      
https://github.com/WebKit/WebKit/commit/5c1c95db5e0ea336071ae634567cda9aaa80c181
  Author: Nikolas Zimmermann <[email protected]>
  Date:   2026-08-28 (Fri, 28 Aug 2026)

  Changed paths:
    M Tools/Scripts/webkit-sysprof/README.md
    M Tools/Scripts/webkit-sysprof/pyproject.toml
    M Tools/Scripts/webkit-sysprof/webkitsysprof/__main__.py
    M Tools/Scripts/webkit-sysprof/webkitsysprof/analyze/__init__.py
    A Tools/Scripts/webkit-sysprof/webkitsysprof/analyze/explanations.py
    A Tools/Scripts/webkit-sysprof/webkitsysprof/cycles/__init__.py
    M Tools/Scripts/webkit-sysprof/webkitsysprof/dump/__init__.py
    M Tools/Scripts/webkit-sysprof/webkitsysprof/histogram/__init__.py
    M Tools/Scripts/webkit-sysprof/webkitsysprof/parser/direct_parser.py
    M Tools/Scripts/webkit-sysprof/webkitsysprof/summary/__init__.py
    A Tools/Scripts/webkit-sysprof/webkitsysprof/tests/frame_cycles_unittest.py
    A Tools/Scripts/webkit-sysprof/webkitsysprof/tests/helpers.py
    M Tools/Scripts/webkit-sysprof/webkitsysprof/tests/subcommands_unittest.py
    A Tools/Scripts/webkit-sysprof/webkitsysprof/tests/utils_unittest.py
    M Tools/Scripts/webkit-sysprof/webkitsysprof/utils/__init__.py

  Log Message:
  -----------
  [webkit-sysprof] Add a frame cycle breakdown to analyze
https://bugs.webkit.org/show_bug.cgi?id=322589

Reviewed by Carlos Garcia Campos.

webkit-sysprof analyze reports 'theoretical FPS', the number of frames the
engine produced over the analyzed duration. That says nothing about how long
one frame took, nor where its time went.

Add a 'frame cycle' reconstruction: a cycle runs from one
LayerTreeHostRenderingUpdate of a process to the next, so the frame rate is
1 / cycle duration, and it splits into the rendering update, waiting for
compositing, compositing and idle. The report gives the phase medians, how
much of the analyzed duration the cycles cover, and the implied FPS while
rendering, so a rate falling short can be told from a phase.

Also add --explain, off by default, which prints what the report does and does
not measure, and reject a timespan of no length rather than reporting zeroes
for it.

* Tools/Scripts/webkit-sysprof/README.md:
* Tools/Scripts/webkit-sysprof/pyproject.toml:
* Tools/Scripts/webkit-sysprof/webkitsysprof/__main__.py:
(_add_subcommand):
(build_parser):
(main):
* Tools/Scripts/webkit-sysprof/webkitsysprof/analyze/__init__.py:
(_frame_rendering_reason):
(_counted):
(analyze):
(_calculate_statistics):
(_prepare_report):
(_prepare_rendering_report):
(_covered_duration):
(_cycle_in_vblank_intervals):
(_prepare_frame_cycle_report):
(_calculate_frame_compositions_per_vblank):
(_calculate_vblanks_per_rendering_update):
(_render_text_report):
(_analyzed_duration_sec):
(_theoretical_fps_explanation):
(_refresh_rate_explanation):
(_percentile_strings):
(_analyzed_cycles):
(_render_frame_cycle_numbers):
(_statistics_to_strings):
(format_statistic):
(_sysprof_data_to_high_level_representation): Deleted.
(_statistics_to_strings.format_statistic): Deleted.
* Tools/Scripts/webkit-sysprof/webkitsysprof/analyze/explanations.py: Added.
* Tools/Scripts/webkit-sysprof/webkitsysprof/cycles/__init__.py: Added.
(_MarkIndex):
(_MarkIndex.__init__):
(_MarkIndex.spans_within):
(_MarkIndex.last_end_of_marks_beginning_within):
(_marks_beginning_within):
(within):
(_compositing_indices_by_process):
(_compositing_spans):
(_own_compositing_end):
(calculate_frame_cycles):
(_cycles_of_one_process):
* Tools/Scripts/webkit-sysprof/webkitsysprof/dump/__init__.py:
(dump):
(_marks_to_rows):
* Tools/Scripts/webkit-sysprof/webkitsysprof/histogram/__init__.py:
(delta_histogram):
(_delta_times_ms):
(_calculate_optimal_bins):
(_plot_delta_time_distribution):
(_calculate_delta_times_ms): Deleted.
* Tools/Scripts/webkit-sysprof/webkitsysprof/parser/direct_parser.py:
* Tools/Scripts/webkit-sysprof/webkitsysprof/summary/__init__.py:
(_print_document_summary):
* Tools/Scripts/webkit-sysprof/webkitsysprof/tests/frame_cycles_unittest.py: 
Added.
(frame_cycle_report):
(FrameCyclesTest):
(FrameCyclesTest.test_phases_partition_every_resolved_cycle):
(FrameCyclesTest.test_composition_ending_after_the_cycle_is_reported_as_an_overrun):
(FrameCyclesTest.test_a_cycle_after_an_overrunning_composition_still_resolves):
(FrameCyclesTest.test_cycles_without_compositing_marks_are_counted_but_not_analyzed):
(FrameCyclesTest.test_the_gap_between_two_rendering_periods_is_reported_as_a_long_cycle):
(FrameCyclesTest.test_the_compositing_mark_name_is_resolved_per_cycle):
(FrameCyclesTest.test_compositing_is_over_when_the_last_compositing_mark_is):
(FrameCyclesTest.test_compositing_overlapping_the_rendering_update_still_counts):
(FrameCyclesTest.test_a_cycle_reaching_past_the_timespan_is_left_out_of_it):
(FrameCyclesTest.test_a_capture_without_compositing_marks_keeps_its_cycles):
(FrameCyclesTest.test_coverage_is_the_share_of_the_window_the_analyzed_cycles_took):
(FrameCyclesTest.test_a_single_cycle_reports_no_percentiles_rather_than_failing):
(FrameCyclesTest.test_no_cycles_at_all):
(FrameCyclesTest.test_cycles_per_vblank_interval_is_unknown_without_vblank_marks):
(FrameCyclesTest.test_cycles_are_restricted_to_the_ones_lying_within_the_timespan):
(FrameCyclesTest.test_compositing_running_inside_the_rendering_update_still_resolves):
(FrameCyclesTest.test_a_cycle_never_runs_between_two_processes):
(FrameCyclesTest.test_a_cycle_spent_entirely_on_an_earlier_composition_is_analyzed):
(FrameCyclesTest.test_coverage_merges_the_cycles_of_two_processes):
(FrameCyclesTest.test_a_composition_begun_before_the_window_is_still_seen_in_it):
(FrameCyclesTest.test_a_stall_is_seen_past_a_shorter_mark_of_the_same_name):
(FrameCyclesTest.test_a_capture_of_negative_length_reports_no_rate):
(FrameCyclesTest.test_no_cycles_says_so_rather_than_blaming_a_zero_median):
(FrameCyclesTest.test_every_overrunning_composition_of_a_stall_is_counted):
(FrameCyclesTest.test_a_cycle_running_a_sliver_of_an_inherited_composition_is_measured):
(FrameCyclesTest.test_a_cycle_running_only_an_inherited_composition_is_measured):
(FrameCyclesTest.test_a_cycle_composited_beside_its_update_only_is_measured_as_zero):
(FrameCyclesTest.test_a_mark_ending_before_the_capture_keeps_its_own_end):
(FrameCyclesTest.test_a_gap_between_two_compositions_is_a_wait_not_compositing):
(FrameCyclesTest.test_two_renderers_of_one_kind_are_told_apart_by_their_process):
(FrameCyclesTest.test_two_updates_at_one_instant_belong_to_two_processes):
(FrameCyclesTest.test_a_report_without_a_rate_prints_no_rate):
(FrameCyclesTest.test_a_steady_slow_capture_keeps_its_cycles):
(FrameCyclesTest.test_the_composition_overrun_median_describes_the_overrunning_cycles):
(FrameCyclesTest.test_an_unknown_refresh_rate_is_not_reported_as_zero):
(FrameCyclesTest.test_a_zero_median_vblank_interval_is_not_reported_as_shared_timestamps):
* Tools/Scripts/webkit-sysprof/webkitsysprof/tests/helpers.py: Added.
(mark):
(sysprof_data):
(approx):
(approx.__init__):
(approx.__eq__):
(approx.__repr__):
(SysprofTestCase):
(SysprofTestCase.setUp):
(SysprofTestCase.stdout):
* Tools/Scripts/webkit-sysprof/webkitsysprof/tests/subcommands_unittest.py:
(SubcommandsTest):
(SubcommandsTest.test_summary):
(SubcommandsTest.test_dump_marks_csv):
(SubcommandsTest.test_dump_counters_csv):
(SubcommandsTest.test_dump_marks_json):
(SubcommandsTest.test_dump_counters_json):
(SubcommandsTest.test_analyze_text):
(SubcommandsTest.test_analyze_text_with_explanations):
(SubcommandsTest.test_analyze_json_percentiles_stay_within_the_data_range):
(SubcommandsTest.test_analyze_json):
(SubcommandsTest.test_analyze_json_frame_cycle_phases_add_up_to_cycle_duration):
(SubcommandsTest.test_analyze_json_statistics_cover_all_relevant_marks):
(SubcommandsTest.test_analyze_with_a_timespan_holding_vblanks_but_no_rendering_update):
(SubcommandsTest.test_analyze_json_leaves_out_a_cycle_reaching_past_the_timespan):
(SubcommandsTest.test_analyze_resolves_a_cycle_whose_compositing_marks_trimming_drops):
(SubcommandsTest.test_explain_is_rejected_for_the_json_format_by_the_module_api):
(SubcommandsTest.test_analyze_rejects_a_timespan_it_cannot_honour):
(SubcommandsTest.test_analyze_clamps_a_timespan_reaching_past_the_capture):
(SubcommandsTest.test_vblanks_per_rendering_update_does_not_depend_on_the_mark_order):
(SubcommandsTest.test_a_capture_of_no_length_reports_no_rate):
(SubcommandsTest.test_delta_histogram_deltas_come_from_the_requested_mark):
(SubcommandsTest.test_delta_histogram_honours_the_timespan):
(SubcommandsTest.test_analyze_json_counts_every_vblank_interval_of_the_timespan):
(SubcommandsTest.test_frame_compositions_outside_the_vblank_range_are_left_out):
(SubcommandsTest.test_frame_rendering_reasons_bucket_a_frame_that_named_none):
(SubcommandsTest.test_frame_rendering_reasons_bucket_a_frame_that_named_none.did_render_frame):
(SubcommandsTest.test_explain_reaches_the_report_through_the_command_line):
(SubcommandsTest.test_a_capture_of_its_own_broken_timespan_is_no_usage_error):
(SubcommandsTest.test_statistics_are_matched_by_wording_rather_than_word_position):
(SubcommandsTest.test_explaining_an_empty_frame_cycle_section_still_explains_it):
(SubcommandsTest.test_a_broken_capture_is_no_usage_error_even_with_a_timespan):
(SubcommandsTest.test_a_window_meeting_the_capture_at_one_point_is_rejected):
(SubcommandsTest.test_json_percentiles_keep_the_fiftieth):
(SubcommandsTest.test_delta_histogram_keeps_the_processes_apart):
(SubcommandsTest.test_composition_overrun_says_nothing_where_nothing_was_analyzed):
(SubcommandsTest.test_an_update_ending_on_the_first_refresh_spans_it):
(SubcommandsTest.test_refreshes_that_composited_nothing_are_samples_of_nothing):
(SubcommandsTest.test_dump_csv_carries_every_column_of_a_row):
(SubcommandsTest.test_a_window_without_refreshes_keeps_the_capture_interval):
(SubcommandsTest.test_analyze_with_custom_timespan):
(SubcommandsTest.test_analyze_with_custom_timespan_begin):
(SubcommandsTest.test_analyze_with_custom_timespan_end):
(_capture_stdout): Deleted.
* Tools/Scripts/webkit-sysprof/webkitsysprof/tests/utils_unittest.py: Added.
(UtilsTest):
(UtilsTest.test_spans_are_merged_where_they_overlap_or_touch):
(UtilsTest.test_a_timespan_bound_is_digits_and_nothing_else):
(UtilsTest.test_percentiles_outside_the_range_of_the_quantiles_are_rejected):
(UtilsTest.test_a_bare_bound_is_the_begin_of_the_timespan):
(UtilsTest.test_vblank_intervals_do_not_depend_on_the_order_of_the_marks):
(UtilsTest.test_reshaping_the_same_parsed_data_twice_yields_the_same_result):
* Tools/Scripts/webkit-sysprof/webkitsysprof/utils/__init__.py:
(msec_to_nsec):
(UsageError):
(mark_begin):
(parse_timespan_argument):
(parse_timespan_argument.parse_bound):
(sysprof_data_with_marks_by_name):
(check_timespan_holds_data):
(trim_marks_by_name_to_timespan):
(mark_in_timespan):
(merged_spans):
(sample_statistics):
(percentiles):
(mark_pid):
(marks_by_process):
(marks_in_time_order):
(intervals_between_marks):
(display_refreshes):
(median_vblank_interval):
(trim_sysprof_data_to_timespan): Deleted.

Canonical link: https://commits.webkit.org/320029@main



To unsubscribe from these emails, change your notification settings at 
https://github.com/WebKit/WebKit/settings/notifications

Reply via email to