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

Commit da5e98d6 authored by Vanshika Chopra's avatar Vanshika Chopra Committed by Android (Google) Code Review
Browse files

Merge "Track SurfaceFlinger main thread slices" into main

parents 6080c7a1 dd897c07
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