Branch: refs/heads/main
Home: https://github.com/WebKit/WebKit
Commit: b89d3a3c7b883d6bfaf7cd95d80acb5f47c814f6
https://github.com/WebKit/WebKit/commit/b89d3a3c7b883d6bfaf7cd95d80acb5f47c814f6
Author: Nikolas Zimmermann <[email protected]>
Date: 2026-08-31 (Mon, 31 Aug 2026)
Changed paths:
M Tools/Scripts/webkit-sysprof/README.md
M Tools/Scripts/webkit-sysprof/webkitsysprof/__main__.py
A Tools/Scripts/webkit-sysprof/webkitsysprof/cycle_analysis/__init__.py
M Tools/Scripts/webkit-sysprof/webkitsysprof/cycles/__init__.py
A
Tools/Scripts/webkit-sysprof/webkitsysprof/tests/cycle_analysis_unittest.py
M Tools/Scripts/webkit-sysprof/webkitsysprof/tests/subcommands_unittest.py
M Tools/Scripts/webkit-sysprof/webkitsysprof/tests/utils_unittest.py
M Tools/Scripts/webkit-sysprof/webkitsysprof/utils/__init__.py
Log Message:
-----------
[webkit-sysprof] Add a cycle-analysis subcommand
https://bugs.webkit.org/show_bug.cgi?id=322848
Reviewed by Carlos Garcia Campos.
analyze reports the median frame cycle, which says a frame was slow but
not which one or why: a cycle stalled by layout and one stalled by a long
timer look the same in a median.
Add a cycle-analysis subcommand that draws every cycle as a bar of cells,
one cell per slice of time (0.2 ms by default), colored by the mark
covering that moment on the main thread. Marks are painted longest first,
so nested marks are drawn over their parents and a bar reads as the call
structure. A second lane counts overlapping PaintTile marks, which tells a
main thread waiting on parallel rasterization apart from one doing nothing
at all, and --no-tile-lane drops it.
A vertical marker crosses every bar at each multiple of the refresh
interval, so a bar reaching past the first one missed that refresh and the
display showed the previous frame again. Short bars are padded past that
marker to keep it visible, and no marker is drawn where an interval is
only a few cells wide or the capture holds no refreshes, which the header
says. A cycle longer than --max-cells is truncated and marked, though the
legend still counts the whole of it.
--order slowest and --min-duration pick the worst cycles. --color auto
colorizes only on a terminal, and without color each cell becomes a
letter, which the legend maps along with the average time per cycle and
the marks behind each activity.
Each bar is drawn from the marks of the process that rendered its cycle
alone, and names that process where more than one rendered. The marks are
indexed per process for that, which the phase walk of the frame cycle
report needs too, so its mark index moves to utils and both share one
lookup.
* Tools/Scripts/webkit-sysprof/README.md:
* Tools/Scripts/webkit-sysprof/webkitsysprof/__main__.py:
(build_parser):
* Tools/Scripts/webkit-sysprof/webkitsysprof/cycle_analysis/__init__.py: Added.
(analyze_frame_cycle):
(draw_frame_cycles):
(_nothing_to_draw):
(_check_drawing_options):
(_renderers_of):
(_select_cycles):
(_Renderer):
(_Renderer.__init__):
(_Renderer.render):
(_Renderer._size_labels):
(_Renderer._cycle_label):
(_Renderer._cells_spanning):
(_Renderer._cell_count):
(_Renderer._render_header):
(_Renderer._ruler):
(_Renderer._ruler_step):
(_Renderer._render_cycle):
(_Renderer._lane):
(_Renderer._tile_lane):
(_Renderer._close_lane):
(_Renderer._painted):
(_Renderer._is_vblank_boundary):
(_Renderer._paint_activities):
(_Renderer._paint_tile_concurrency):
(_Renderer._cell_range):
(_Renderer._colored):
(_Renderer._render_footer):
(_marks_of_activity):
(_process_suffix):
(_row_label):
(_collect_marks):
(_marks_overlapping):
* Tools/Scripts/webkit-sysprof/webkitsysprof/cycles/__init__.py:
(_compositing_spans):
(_own_compositing_end):
(_cycles_of_one_process):
(_MarkIndex): Deleted.
(_MarkIndex.__init__): Deleted.
(_MarkIndex.spans_within): Deleted.
(_MarkIndex.last_end_of_marks_beginning_within): Deleted.
* Tools/Scripts/webkit-sysprof/webkitsysprof/tests/cycle_analysis_unittest.py:
Added.
(CycleAnalysisTest):
(CycleAnalysisTest.setUp):
(CycleAnalysisTest._draw):
(CycleAnalysisTest._lanes):
(CycleAnalysisTest._ruler):
(CycleAnalysisTest.test_a_nested_mark_is_drawn_over_the_one_it_runs_inside):
(CycleAnalysisTest.test_the_tile_lane_counts_the_tiles_painted_at_once):
(CycleAnalysisTest.test_the_tile_lane_can_be_left_out):
(CycleAnalysisTest.test_a_bar_is_drawn_from_the_marks_of_its_own_process):
(CycleAnalysisTest.test_a_refresh_marker_crosses_every_bar_at_each_interval):
(CycleAnalysisTest.test_a_bar_padded_to_the_refresh_interval_still_shows_the_marker):
(CycleAnalysisTest.test_an_interval_of_too_few_cells_draws_no_marker):
(CycleAnalysisTest.test_a_capture_without_refreshes_says_the_interval_is_unknown):
(CycleAnalysisTest.test_the_slowest_cycles_are_the_ones_worth_drawing):
(CycleAnalysisTest.test_a_cycle_shorter_than_the_minimum_is_left_out):
(CycleAnalysisTest.test_a_bar_says_which_process_rendered_it):
(CycleAnalysisTest.test_one_renderer_needs_no_process_on_its_bars):
(CycleAnalysisTest.test_every_lane_starts_in_the_same_column_as_the_ruler):
(CycleAnalysisTest.test_the_ruler_labels_never_run_into_one_another):
(CycleAnalysisTest.test_the_padding_past_a_cycle_is_blank_in_colour_too):
(CycleAnalysisTest.test_a_run_of_one_colour_is_written_as_one_escape):
(CycleAnalysisTest.test_a_bar_longer_than_the_cell_limit_is_marked_as_truncated):
(CycleAnalysisTest.test_a_capture_without_cycles_says_which_marks_it_needs):
(CycleAnalysisTest.test_the_legend_counts_the_cycle_the_bar_could_not_fit):
(CycleAnalysisTest.test_padding_that_did_not_fit_is_no_truncated_cycle):
(CycleAnalysisTest.test_the_next_cycle_is_left_out_of_this_ones_last_cell):
(CycleAnalysisTest.test_a_bar_stays_in_its_column_however_long_the_cycle_is):
* Tools/Scripts/webkit-sysprof/webkitsysprof/tests/subcommands_unittest.py:
(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_cycle_analysis_draws_a_bar_per_cycle_through_the_command_line):
(SubcommandsTest):
(SubcommandsTest.test_cycle_analysis_says_so_where_there_is_nothing_to_draw):
(SubcommandsTest.test_cycle_analysis_says_so_where_the_selection_matches_nothing):
(SubcommandsTest.test_cycle_analysis_rejects_a_timespan_it_cannot_honour):
(SubcommandsTest.test_cycle_analysis_rejects_options_that_draw_nothing):
* Tools/Scripts/webkit-sysprof/webkitsysprof/tests/utils_unittest.py:
(_window):
(UtilsTest.test_the_marks_overlapping_a_window_are_the_ones_running_in_it):
(UtilsTest):
(UtilsTest.test_a_mark_touching_a_window_at_its_edge_does_not_overlap_it):
(UtilsTest.test_an_index_of_no_marks_has_none_overlapping_anything):
* Tools/Scripts/webkit-sysprof/webkitsysprof/utils/__init__.py:
(MarkIndex):
(MarkIndex.__init__):
(MarkIndex.spans_within):
(MarkIndex.last_end_of_marks_beginning_within):
(MarkIndex.marks_overlapping):
Canonical link: https://commits.webkit.org/320143@main
To unsubscribe from these emails, change your notification settings at
https://github.com/WebKit/WebKit/settings/notifications