Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dd897c07 authored by Vanshika Chopra's avatar Vanshika Chopra
Browse files

Track SurfaceFlinger main thread slices

- Track metrics related to duration and count for important slices of CriticalWorkload track of Surface flinger process.
- The specific slices to be tracked are commit, composition, PostComposition, Refresh Rate Selection and Transaction Handling.

Bug: 433228398
Test: atest SysUiMetricsV2Test
Flag: EXEMPT METRICS
Change-Id: I7b759610c3fffd142252af8abbd64c268186b13d
parent 67b8b2ff
Loading
Loading
Loading
Loading
+8 −2
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ filegroup {
    name: "sysui_metrics_artifacts",
    srcs: [
        "android_bitmap_metric.textproto",
        "android_sf_critical_work_main_thread.textproto",
    ],
}

@@ -30,6 +31,11 @@ prebuilt_etc {
    src: "android_bitmap_metric.textproto",
}

prebuilt_etc {
    name: "android_sf_critical_work_main_thread.textproto",
    src: "android_sf_critical_work_main_thread.textproto",
}

python_library_host {
    name: "metrics-tests-utils",
    srcs: ["tests/utils/*.py"],
@@ -47,8 +53,8 @@ python_test_host {
    ],
    data: [
        ":trace_processor_shell_prebuilt",
        "android_bitmap_metric.textproto",
        "tests/data/android_bitmap_metric_output.txt",
        "*.textproto",
        "tests/data/*.txt",
    ],
    test_options: {
        unit_test: true,
+103 −0
Original line number Diff line number Diff line
# Copyright (C) 2025 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# The Surfaceflinger process consists of key slices: commit, composition, and postComposition,
# Refresh Rate Selection and Transaction Handling in the CriticalWorkload track.
# This template spec tracks metrics related to duration and count for these slices.
# The metrics are cuj-scoped.

# SQL query to retrieve details about various CUJs, including their unique IDs, names, timestamps
# and their durations.
query: {
    id: "cujs"
    table: {
        table_name: "android_jank_latency_cujs"
        module_name: "android.cujs.sysui_cujs"
        column_names: "cuj_name"
        column_names: "ts"
        column_names: "dur"
        column_names: "cuj_id"
    }
    select_columns: { column_name: "cuj_name" }
    select_columns: { column_name: "dur" }
    select_columns: { column_name: "cuj_id" alias: "id" }
    select_columns: { column_name: "ts" }
}

# SQL query that joins the slice and track table of the standard SQL library. Subsequently, the
# results are filtered based on the track name, process name, and specific slices of interest.

query: {
    id: "sf_critical_work_slices"
    simple_slices: {
      track_name_glob: "CriticalWorkload"
      process_name_glob: "/system/bin/surfaceflinger"
    }
    # filtering slices by slice name since few instantaneous events are also tracked in CriticalWorkload
    # track and they appear as slices in the stdlib slice table.
    filters: {
      column_name: "slice_name"
      op: EQUAL
      string_rhs: "Commit"
      string_rhs: "Composition"
      string_rhs: "Post Composition"
      string_rhs: "Transaction Handling"
      string_rhs: "Refresh Rate Selection"
    }
    select_columns: { column_name: "dur" }
    select_columns: { column_name: "slice_name" }
    select_columns: { column_name: "id" }
    select_columns: { column_name: "ts" }
}

# Define the TraceMetricV2TemplateSpec to generate these aggregates as 5 metrics with cuj_name
# and slice_name as dimensions.
# The spec uses interval_intersect to ensure the metrics are CUJ scoped.
metric_template_spec: {
    id_prefix: "android_sf_critical_work_main_thread_cuj"
    dimensions: "cuj_name"
    dimensions: "slice_name"
    value_columns: "max_dur"
    value_columns: "avg_dur"
    value_columns: "count"
    query: {
        interval_intersect: {
            base: {
                inner_query_id: "sf_critical_work_slices"
            }
            interval_intersect: {
                inner_query_id: "cujs"
            }
        }
        group_by: {
            column_names: "slice_name"
            column_names: "cuj_name"
            aggregates: {
              column_name: "dur"
              op: MAX
              result_column_name: "max_dur"
            }
            aggregates: {
              column_name: "dur"
              op: MEAN
              result_column_name: "avg_dur"
            }
            aggregates: {
              column_name: "slice_name"
              op: COUNT
              result_column_name: "count"
            }
        }
    }
}
+195 −0
Original line number Diff line number Diff line
metric_bundles {
  row {
    values {
      double_value: 12000000.0
    }
    values {
      double_value: 7000000.0
    }
    values {
      double_value: 2.0
    }
    dimension {
      string_value: "BACK_PANEL_ARROW"
    }
    dimension {
      string_value: "Commit"
    }
  }
  row {
    values {
      double_value: 4000000.0
    }
    values {
      double_value: 3500000.0
    }
    values {
      double_value: 2.0
    }
    dimension {
      string_value: "BACK_PANEL_ARROW"
    }
    dimension {
      string_value: "Composition"
    }
  }
  row {
    values {
      double_value: 3000000.0
    }
    values {
      double_value: 3000000.0
    }
    values {
      double_value: 1.0
    }
    dimension {
      string_value: "BACK_PANEL_ARROW"
    }
    dimension {
      string_value: "Post Composition"
    }
  }
  row {
    values {
      double_value: 3000000.0
    }
    values {
      double_value: 3000000.0
    }
    values {
      double_value: 1.0
    }
    dimension {
      string_value: "BACK_PANEL_ARROW"
    }
    dimension {
      string_value: "Refresh Rate Selection"
    }
  }
  row {
    values {
      double_value: 5000000.0
    }
    values {
      double_value: 5000000.0
    }
    values {
      double_value: 1.0
    }
    dimension {
      string_value: "BACK_PANEL_ARROW"
    }
    dimension {
      string_value: "Transaction Handling"
    }
  }
  specs {
    id: "android_sf_critical_work_main_thread_cuj_max_dur"
    dimensions: "cuj_name"
    dimensions: "slice_name"
    value: "max_dur"
    query {
      interval_intersect {
        base {
          inner_query_id: "sf_critical_work_slices"
        }
        interval_intersect {
          inner_query_id: "cujs"
        }
      }
      group_by {
        column_names: "slice_name"
        column_names: "cuj_name"
        aggregates {
          column_name: "dur"
          op: MAX
          result_column_name: "max_dur"
        }
        aggregates {
          column_name: "dur"
          op: MEAN
          result_column_name: "avg_dur"
        }
        aggregates {
          column_name: "slice_name"
          op: COUNT
          result_column_name: "count"
        }
      }
    }
    bundle_id: "android_sf_critical_work_main_thread_cuj"
  }
  specs {
    id: "android_sf_critical_work_main_thread_cuj_avg_dur"
    dimensions: "cuj_name"
    dimensions: "slice_name"
    value: "avg_dur"
    query {
      interval_intersect {
        base {
          inner_query_id: "sf_critical_work_slices"
        }
        interval_intersect {
          inner_query_id: "cujs"
        }
      }
      group_by {
        column_names: "slice_name"
        column_names: "cuj_name"
        aggregates {
          column_name: "dur"
          op: MAX
          result_column_name: "max_dur"
        }
        aggregates {
          column_name: "dur"
          op: MEAN
          result_column_name: "avg_dur"
        }
        aggregates {
          column_name: "slice_name"
          op: COUNT
          result_column_name: "count"
        }
      }
    }
    bundle_id: "android_sf_critical_work_main_thread_cuj"
  }
  specs {
    id: "android_sf_critical_work_main_thread_cuj_count"
    dimensions: "cuj_name"
    dimensions: "slice_name"
    value: "count"
    query {
      interval_intersect {
        base {
          inner_query_id: "sf_critical_work_slices"
        }
        interval_intersect {
          inner_query_id: "cujs"
        }
      }
      group_by {
        column_names: "slice_name"
        column_names: "cuj_name"
        aggregates {
          column_name: "dur"
          op: MAX
          result_column_name: "max_dur"
        }
        aggregates {
          column_name: "dur"
          op: MEAN
          result_column_name: "avg_dur"
        }
        aggregates {
          column_name: "slice_name"
          op: COUNT
          result_column_name: "count"
        }
      }
    }
    bundle_id: "android_sf_critical_work_main_thread_cuj"
  }
}
+13 −0
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@
# limitations under the License.

from metrics_specs.tests.utils import android_bitmap_metric_trace
from metrics_specs.tests.utils import android_sf_critical_work_main_thread_trace
from metrics_specs.tests.utils import test_helper
import unittest

@@ -34,5 +35,17 @@ class MetricsV2Test(unittest.TestCase):
            ]
        )

    def test_android_android_sf_critical_work_main_thread_metric(self):
        self.helper.verify_metric(
            spec_file="android_sf_critical_work_main_thread.textproto",
            trace_proto_bytes = android_sf_critical_work_main_thread_trace.get_proto(),
            expected_output_file = "android_sf_critical_work_main_thread_output.txt",
            metric_ids = [
                "android_sf_critical_work_main_thread_cuj_max_dur",
                "android_sf_critical_work_main_thread_cuj_avg_dur",
                "android_sf_critical_work_main_thread_cuj_count",
            ]
        )

if __name__ == '__main__':
    unittest.main()
+396 −0
Original line number Diff line number Diff line
#!/usr/bin/env python3
# Copyright (C) 2025 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from metrics_specs.tests.utils import trace_proto_builder
from perfetto.protos.perfetto.trace.perfetto_trace_pb2 import Trace

SYSUI_PID = 5000
SYSUI_UI_TID = 5020
SF_PID = 6000
SF_TID = 6020
RANDOM_PROCESS_PID = 7000
RANDOM_PROCESS_TID = 7020

SYSUI_PACKAGE = "com.android.systemui"
SF_PACKAGE = "/system/bin/surfaceflinger"
RANDOM_PROCESS_PACKAGE = "random_process"

SYSUI_UID = 10001
SF_UID = 10002
RANDOM_PROCESS_UID = 10003
SYSUI_RTID = 1555
SF_RTID = 1655
RANDOM_PROCESS_RTID = 1755

FIRST_CUJ = "J<BACK_PANEL_ARROW>"

TRACK_NAME_1 = "CriticalWorkload"
TRACK_ID_1 = 400
TRACK_NAME_2 = "random_track"
TRACK_ID_2 = 500
TRACK_ID_3 = 600

COMMIT_SLICE = "Commit"
COMPOSITION_SLICE = "Composition"
POST_COMPOSITION_SLICE = "Post Composition"
TRANSACTIONAL_HANDLING_SLICE = "Transaction Handling"
REFRESH_RATE_SELECTION_SLICE = "Refresh Rate Selection"

LAYER_1 = "TX - first_layer#0"

def setup_trace():
    trace = trace_proto_builder.TraceProtoBuilder(Trace())
    add_process(trace, package_name=SYSUI_PACKAGE, uid=SYSUI_UID, pid=SYSUI_PID)
    add_process(trace, package_name=SF_PACKAGE, uid=SF_UID, pid=SF_PID)
    trace.add_ftrace_packet(cpu=0)
    return trace

def add_slices_and_track(trace):
    # Add CriticalWorkload and a random track to the SF process
    trace.add_process_track_descriptor(
        process_track=TRACK_ID_1,
        track_name=TRACK_NAME_1,
        pid=SF_PID,
        process_name=SF_PACKAGE,
    )
    trace.add_process_track_descriptor(
        process_track=TRACK_ID_2,
        track_name=TRACK_NAME_2,
        pid=SF_PID,
        process_name=SF_PACKAGE,
    )

    # Add slices within cuj duration to the CriticalWorkload track in SF process
    trace.add_track_event_slice(
        name=COMMIT_SLICE,
        ts=27_000_000,
        dur=12_000_000,
        track=TRACK_ID_1,
        trusted_sequence_id=100,
    )
    trace.add_track_event_slice(
        name=TRANSACTIONAL_HANDLING_SLICE,
        ts=27_500_000,
        dur=5_000_000,
        track=TRACK_ID_1,
        trusted_sequence_id=100,
    )
    trace.add_track_event_slice(
        name=REFRESH_RATE_SELECTION_SLICE,
        ts=34_000_000,
        dur=3_000_000,
        track=TRACK_ID_1,
        trusted_sequence_id=100,
    )
    trace.add_track_event_slice(
        name=COMPOSITION_SLICE,
        ts=39_000_000,
        dur=4_000_000,
        track=TRACK_ID_1,
        trusted_sequence_id=100,
    )
    trace.add_track_event_slice(
        name=POST_COMPOSITION_SLICE,
        ts=43_000_000,
        dur=3_000_000,
        track=TRACK_ID_1,
        trusted_sequence_id=100,
    )

    trace.add_track_event_slice(
        name=COMMIT_SLICE,
        ts=84_000_000,
        dur=2_000_000,
        track=TRACK_ID_1,
        trusted_sequence_id=100,
    )
    trace.add_track_event_slice(
        name=COMPOSITION_SLICE,
        ts=86_000_000,
        dur=3_000_000,
        track=TRACK_ID_1,
        trusted_sequence_id=100,
    )

    # Add Post Composition slice outside the CUJ duration ( should not be tracked )
    trace.add_track_event_slice(
        name=POST_COMPOSITION_SLICE,
        ts=89_000_000,
        dur=3_000_000,
        track=TRACK_ID_1,
        trusted_sequence_id=100,
    )

    # Add slices to the random track ( should not be tracked)
    trace.add_track_event_slice(
        name=COMMIT_SLICE,
        ts=27_000_000,
        dur=12_000_000,
        track=TRACK_ID_2,
        trusted_sequence_id=100,
    )

    # Add CriticalWorkload track to the random process
    trace.add_process_track_descriptor(
        process_track=TRACK_ID_3,
        track_name=TRACK_NAME_1,
        pid=RANDOM_PROCESS_PID,
        process_name=RANDOM_PROCESS_PACKAGE,
    )

    # Add slices within cuj duration to the CriticalWorkload track in random process ( should not be tracked )
    trace.add_track_event_slice(
        name=COMMIT_SLICE,
        ts=27_000_000,
        dur=12_000_000,
        track=TRACK_ID_3,
        trusted_sequence_id=100,
    )
    return trace


def add_process(trace, package_name, uid, pid):
    trace.add_package_list(ts=0, name=package_name, uid=uid, version_code=1)
    trace.add_process(pid=pid, ppid=pid, cmdline=package_name, uid=uid)
    trace.add_thread(tid=pid, tgid=pid, cmdline="MainThread", name="MainThread")

def add_expected_surface_frame_events(trace, ts, dur, token, pid):
    trace.add_expected_surface_frame_start_event(
        ts=ts,
        cookie=100000 + token,
        token=token,
        display_frame_token=100 + token,
        pid=pid,
        layer_name="",
    )
    trace.add_frame_end_event(ts=ts + dur, cookie=100000 + token)

def add_actual_surface_frame_events(trace, ts, dur, token, layer, pid):
    cookie = token + 1
    trace.add_actual_surface_frame_start_event(
        ts=ts,
        cookie=100002 + cookie,
        token=token,
        display_frame_token=token + 100,
        pid=pid,
        present_type=1,
        on_time_finish=1,
        gpu_composition=0,
        jank_type=1,
        prediction_type=3,
        layer_name=layer,
    )
    trace.add_frame_end_event(ts=ts + dur, cookie=100002 + cookie)

def add_cuj(trace, cuj_name):
    # Add 2 CUJs in the trace with the specified cuj_name.

    trace.add_async_atrace_for_thread(
        ts=25_000_000,
        ts_end=77_000_000,
        buf=cuj_name,
        pid=SYSUI_PID,
        tid=SYSUI_UI_TID,
    )

    trace.add_async_atrace_for_thread(
        ts=83_000_000,
        ts_end=102_000_000,
        buf=cuj_name,
        pid=SYSUI_PID,
        tid=SYSUI_UI_TID,
    )

    trace.add_atrace_instant(
        ts=25_000_001,
        buf=cuj_name + "#UIThread",
        pid=SYSUI_PID,
        tid=SYSUI_UI_TID,
    )

    trace.add_atrace_instant(
        ts=83_000_001,
        buf=cuj_name + "#UIThread",
        pid=SYSUI_PID,
        tid=SYSUI_UI_TID,
    )

    trace.add_atrace_instant_for_track(
        ts=25_000_001,
        buf="FT#beginVsync#20",
        pid=SYSUI_PID,
        tid=SYSUI_UI_TID,
        track_name=cuj_name,
    )

    trace.add_atrace_instant_for_track(
        ts=25_000_010,
        buf="FT#layerId#0",
        pid=SYSUI_PID,
        tid=SYSUI_UI_TID,
        track_name=cuj_name,
    )

    trace.add_atrace_instant_for_track(
        ts=76_000_001,
        buf="FT#endVsync#30",
        pid=SYSUI_PID,
        tid=SYSUI_UI_TID,
        track_name=cuj_name,
    )

    trace.add_atrace_instant_for_track(
        ts=83_000_001,
        buf="FT#beginVsync#65",
        pid=SYSUI_PID,
        tid=SYSUI_UI_TID,
        track_name=cuj_name,
    )

    trace.add_atrace_instant_for_track(
        ts=83_000_010,
        buf="FT#layerId#0",
        pid=SYSUI_PID,
        tid=SYSUI_UI_TID,
        track_name=cuj_name,
    )

    trace.add_atrace_instant_for_track(
        ts=101_000_001,
        buf="FT#endVsync#70",
        pid=SYSUI_PID,
        tid=SYSUI_UI_TID,
        track_name=cuj_name,
    )

    # Add Choreographer#doFrame slices within CUJ boundary.
    trace.add_frame(
        vsync=20,
        ts_do_frame=26_000_000,
        ts_end_do_frame=32_000_000,
        tid=SYSUI_UI_TID,
        pid=SYSUI_PID,
    )

    trace.add_atrace_for_thread(
        ts=27_000_000,
        ts_end=28_000_000,
        buf="DrawFrames 20",
        tid=SYSUI_RTID,
        pid=SYSUI_PID,
    )

    trace.add_frame(
        vsync=22,
        ts_do_frame=43_000_000,
        ts_end_do_frame=49_000_000,
        tid=SYSUI_UI_TID,
        pid=SYSUI_PID,
    )

    trace.add_atrace_for_thread(
        ts=44_000_000,
        ts_end=45_000_000,
        buf="DrawFrames 22",
        tid=SYSUI_RTID,
        pid=SYSUI_PID,
    )

    trace.add_frame(
        vsync=24,
        ts_do_frame=60_000_000,
        ts_end_do_frame=65_000_000,
        tid=SYSUI_UI_TID,
        pid=SYSUI_PID,
    )

    trace.add_atrace_for_thread(
        ts=61_000_000,
        ts_end=62_000_000,
        buf="DrawFrames 24",
        tid=SYSUI_RTID,
        pid=SYSUI_PID,
    )

    trace.add_frame(
        vsync=65,
        ts_do_frame=84_000_000,
        ts_end_do_frame=89_000_000,
        tid=SYSUI_UI_TID,
        pid=SYSUI_PID,
    )

    trace.add_atrace_for_thread(
        ts=85_000_000,
        ts_end=86_000_000,
        buf="DrawFrames 65",
        tid=SYSUI_RTID,
        pid=SYSUI_PID,
    )

    # Add expected and actual frames.
    add_expected_surface_frame_events(
        trace, ts=27_000_000, dur=16_000_000, token=20, pid=SYSUI_PID
    )
    add_actual_surface_frame_events(
        trace,
        ts=27_000_000,
        dur=7_000_000,
        token=20,
        layer=LAYER_1,
        pid=SYSUI_PID,
    )

    add_expected_surface_frame_events(
        trace, ts=44_000_000, dur=16_000_000, token=22, pid=SYSUI_PID
    )
    add_actual_surface_frame_events(
        trace,
        ts=44_000_000,
        dur=7_000_000,
        token=22,
        layer=LAYER_1,
        pid=SYSUI_PID,
    )

    add_expected_surface_frame_events(
        trace, ts=61_000_000, dur=16_000_000, token=24, pid=SYSUI_PID
    )
    add_actual_surface_frame_events(
        trace,
        ts=61_000_000,
        dur=6_000_000,
        token=24,
        layer=LAYER_1,
        pid=SYSUI_PID,
    )

    add_expected_surface_frame_events(
        trace, ts=84_000_000, dur=10_000_000, token=65, pid=SYSUI_PID
    )
    add_actual_surface_frame_events(
        trace,
        ts=84_000_000,
        dur=6_000_000,
        token=65,
        layer=LAYER_1,
        pid=SYSUI_PID,
    )

def get_proto():
    trace = setup_trace()
    add_cuj(trace, FIRST_CUJ)
    builder = add_slices_and_track(trace)
    return builder.trace.SerializeToString()
Loading