#!/usr/bin/env python3 # Copyright (C) 2023 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 a # # 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 python.generators.diff_tests.testing import Path, DataPath, Metric from python.generators.diff_tests.testing import Csv, Json, TextProto from python.generators.diff_tests.testing import DiffTestBlueprint from python.generators.diff_tests.testing import TestSuite class Profiling(TestSuite): # Perf profiling tests. def test_profiler_smaps(self): return DiffTestBlueprint( trace=TextProto(r""" packet { process_tree { processes { pid: 1 ppid: 0 cmdline: "init" uid: 0 } processes { pid: 2 ppid: 1 cmdline: "system_server" uid: 1000 } } } packet { trusted_packet_sequence_id: 999 timestamp: 10 smaps_packet { pid: 2 entries { path: "/system/lib64/libc.so" size_kb: 20 private_dirty_kb: 4 swap_kb: 4 } entries { path: "[anon: libc_malloc]" size_kb: 30 private_dirty_kb: 10 swap_kb: 10 } } } """), query=""" SELECT id, upid, ts, path, size_kb, private_dirty_kb, swap_kb FROM profiler_smaps; """, out=Csv(""" "id","upid","ts","path","size_kb","private_dirty_kb","swap_kb" 0,2,10,"/system/lib64/libc.so",20,4,4 1,2,10,"[anon: libc_malloc]",30,10,10 """)) def test_profiler_smaps_metric(self): return DiffTestBlueprint( trace=TextProto(r""" packet { process_tree { processes { pid: 1 ppid: 0 cmdline: "init" uid: 0 } processes { pid: 2 ppid: 1 cmdline: "system_server" uid: 1000 } } } packet { trusted_packet_sequence_id: 999 timestamp: 10 smaps_packet { pid: 2 entries { path: "/system/lib64/libc.so" size_kb: 20 private_dirty_kb: 4 swap_kb: 4 } entries { path: "[anon: libc_malloc]" size_kb: 30 private_dirty_kb: 10 swap_kb: 10 } } } """), query=Metric('profiler_smaps'), out=TextProto(r""" profiler_smaps { instance { process { name: "system_server" uid: 1000 pid: 2 android_user_id: 0 is_kernel_task: false } mappings { path: "[anon: libc_malloc]" size_kb: 30 private_dirty_kb: 10 swap_kb: 10 } mappings { path: "/system/lib64/libc.so" size_kb: 20 private_dirty_kb: 4 swap_kb: 4 } } } """)) # Regression test for b/222297079: when cumulative size in a flamegraph # a signed 32-bit integer. def test_heap_graph_flamegraph_matches_objects(self): return DiffTestBlueprint( trace=Path('heap_graph_huge_size.textproto'), query=""" SELECT obj.upid AS upid, obj.graph_sample_ts AS ts, SUM(obj.self_size + obj.native_size) AS total_objects_size, ( SELECT SUM(cumulative_size) FROM experimental_flamegraph( 'graph', obj.graph_sample_ts, NULL, obj.upid, NULL, NULL ) WHERE depth = 0 -- only the roots ) AS total_flamegraph_size FROM heap_graph_object AS obj WHERE obj.reachable != 0 GROUP BY obj.upid, obj.graph_sample_ts; """, out=Csv(""" "upid","ts","total_objects_size","total_flamegraph_size" 1,10,3000000036,3000000036 """)) # TODO(b/153552977): Stop supporting legacy heap graphs. These never made it # a public release, so we should eventually stop supporting workarounds for def test_heap_graph_flamegraph(self): return DiffTestBlueprint( trace=Path('heap_graph_legacy.textproto'), query=""" SELECT id, depth, name, map_name, count, cumulative_count, size, cumulative_size, parent_id FROM experimental_flamegraph( 'graph', (SELECT max(graph_sample_ts) FROM heap_graph_object), NULL, (SELECT max(upid) FROM heap_graph_object), NULL, NULL ) LIMIT 10; """, out=Path('heap_graph_flamegraph.out')) def test_stack_profile_tracker_empty_callstack(self): return DiffTestBlueprint( trace=TextProto(r""" packet { clock_snapshot { clocks: { clock_id: 6 # BOOTTIME timestamp: 0 } clocks: { clock_id: 4 # MONOTONIC_COARSE timestamp: 0 } } } packet { previous_packet_dropped: true incremental_state_cleared: true trusted_packet_sequence_id: 1 timestamp: 0 interned_data { callstacks { iid: 1 } } } packet { trusted_packet_sequence_id: 1 timestamp: 0 profile_packet { index: 0 continued: false process_dumps { samples { callstack_id: 1 self_allocated: 1 alloc_count: 1 } samples { callstack_id: 1 self_allocated: 1 alloc_count: 1 } } } } """), query=""" SELECT count(1) AS count FROM heap_profile_allocation; """, out=Csv(""" "count" 0 """)) # perf_sample table (traced_perf) with android R and S trace inputs. def test_perf_sample_rvc(self): return DiffTestBlueprint( trace=DataPath('perf_sample.pb'), query=""" SELECT ps.ts, ps.cpu, ps.cpu_mode, ps.unwind_error, ps.perf_session_id, pct.name AS cntr_name, pct.is_timebase, thread.tid, spf.name FROM experimental_annotated_callstack eac JOIN perf_sample ps ON (eac.start_id = ps.callsite_id) JOIN perf_counter_track pct USING(perf_session_id, cpu) JOIN thread USING(utid) JOIN stack_profile_frame spf ON (eac.frame_id = spf.id) ORDER BY ps.ts ASC, eac.depth ASC; """, out=Path('perf_sample_rvc.out')) def test_perf_sample_sc(self): return DiffTestBlueprint( trace=DataPath('perf_sample_sc.pb'), query=""" SELECT ps.ts, ps.cpu, ps.cpu_mode, ps.unwind_error, ps.perf_session_id, pct.name AS cntr_name, pct.is_timebase, thread.tid, spf.name FROM experimental_annotated_callstack eac JOIN perf_sample ps ON (eac.start_id = ps.callsite_id) JOIN perf_counter_track pct USING(perf_session_id, cpu) JOIN thread USING(utid) JOIN stack_profile_frame spf ON (eac.frame_id = spf.id) ORDER BY ps.ts ASC, eac.depth ASC; """, out=Path('perf_sample_sc.out')) def test_annotations(self): return DiffTestBlueprint( trace=DataPath('perf_sample_annotations.pftrace'), query=""" select eac.depth, eac.annotation, spm.name as map_name, ifnull(demangle(spf.name), spf.name) as frame_name from experimental_annotated_callstack eac join stack_profile_frame spf on (eac.frame_id = spf.id) join stack_profile_mapping spm on (spf.mapping = spm.id) where eac.start_id = ( select spc.id as callsite_id from stack_profile_callsite spc join stack_profile_frame spf on (spc.frame_id = spf.id) where spf.name = "_ZN3art28ResolveFieldWithAccessChecksEPNS_6ThreadEPNS_11ClassLinkerEtPNS_9ArtMethodEbbm") and depth != 10 -- Skipped because cause symbolization issues on clang vs gcc due to llvm-demangle order by depth asc; """, out=Csv(""" "depth","annotation","map_name","frame_name" 0,"[NULL]","/apex/com.android.runtime/lib64/bionic/libc.so","__libc_init" 1,"[NULL]","/system/bin/app_process64","main" 2,"[NULL]","/system/lib64/libandroid_runtime.so","android::AndroidRuntime::start(char const*, android::Vector const&, bool)" 3,"[NULL]","/system/lib64/libandroid_runtime.so","_JNIEnv::CallStaticVoidMethod(_jclass*, _jmethodID*, ...)" 4,"[NULL]","/apex/com.android.art/lib64/libart.so","art::JNI::CallStaticVoidMethodV(_JNIEnv*, _jclass*, _jmethodID*, std::__va_list)" 5,"[NULL]","/apex/com.android.art/lib64/libart.so","art::JValue art::InvokeWithVarArgs<_jmethodID*>(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jmethodID*, std::__va_list)" 6,"[NULL]","/apex/com.android.art/lib64/libart.so","art_quick_invoke_static_stub" 7,"aot","/system/framework/arm64/boot-framework.oat","com.android.internal.os.ZygoteInit.main" 8,"aot","/system/framework/arm64/boot-framework.oat","com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run" 9,"common-frame","/system/framework/arm64/boot.oat","art_jni_trampoline" 11,"common-frame","/apex/com.android.art/lib64/libart.so","_jobject* art::InvokeMethod<(art::PointerSize)8>(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jobject*, _jobject*, unsigned long)" 12,"common-frame","/apex/com.android.art/lib64/libart.so","art_quick_invoke_static_stub" 13,"aot","/system/framework/arm64/boot-framework.oat","android.app.ActivityThread.main" 14,"aot","/system/framework/arm64/boot-framework.oat","android.os.Looper.loop" 15,"aot","/system/framework/arm64/boot-framework.oat","android.os.Looper.loopOnce" 16,"aot","/system/framework/arm64/boot-framework.oat","android.os.Handler.dispatchMessage" 17,"aot","/system/framework/arm64/boot-framework.oat","android.view.Choreographer$FrameDisplayEventReceiver.run" 18,"aot","/system/framework/arm64/boot-framework.oat","android.view.Choreographer.doFrame" 19,"aot","/system/framework/arm64/boot-framework.oat","android.view.Choreographer.doCallbacks" 20,"aot","/system/framework/arm64/boot-framework.oat","android.view.ViewRootImpl$TraversalRunnable.run" 21,"aot","/system/framework/arm64/boot-framework.oat","android.view.ViewRootImpl.doTraversal" 22,"aot","/system/framework/arm64/boot-framework.oat","android.view.ViewRootImpl.performTraversals" 23,"interp","/system/framework/framework.jar","android.view.ViewRootImpl.notifyDrawStarted" 24,"common-frame-interp","/apex/com.android.art/lib64/libart.so","nterp_op_iget_object_slow_path" 25,"common-frame-interp","/apex/com.android.art/lib64/libart.so","nterp_get_instance_field_offset" 26,"common-frame-interp","/apex/com.android.art/lib64/libart.so","NterpGetInstanceFieldOffset" 27,"common-frame","/apex/com.android.art/lib64/libart.so","art::ResolveFieldWithAccessChecks(art::Thread*, art::ClassLinker*, unsigned short, art::ArtMethod*, bool, bool, unsigned long)" """)) def test_annotations_switch_interpreter(self): return DiffTestBlueprint( trace=Path('perf_sample_switch_interp.textproto'), query=""" select eac.depth, eac.annotation, spm.name as map_name, ifnull(demangle(spf.name), spf.name) as frame_name from experimental_annotated_callstack eac join stack_profile_frame spf on (eac.frame_id = spf.id) join stack_profile_mapping spm on (spf.mapping = spm.id) where eac.start_id = (select callsite_id from perf_sample) order by depth asc; """, out=Csv(""" "depth","annotation","map_name","frame_name" 0,"interp","/example.vdex","com.example.managed.frame" 1,"common-frame-interp","/apex/com.android.art/lib64/libart.so","ExecuteSwitchImplAsm" 2,"common-frame-interp","/apex/com.android.art/lib64/libart.so","void art::interpreter::ExecuteSwitchImplCpp(art::interpreter::SwitchImplContext*)" 3,"common-frame-interp","/apex/com.android.art/lib64/libart.so","bool art::interpreter::DoCall(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, bool, art::JValue*)" 4,"common-frame","/apex/com.android.art/lib64/libart.so","art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)" """)) def test_perf_sample_followers(self): return DiffTestBlueprint( trace=TextProto(r""" packet { clock_snapshot { primary_trace_clock: BUILTIN_CLOCK_BOOTTIME clocks { clock_id: 6 timestamp: 273574904041306 } clocks { clock_id: 2 timestamp: 1737644264730557105 } clocks { clock_id: 4 timestamp: 106208706668231 } clocks { clock_id: 1 timestamp: 1737644264734590878 } clocks { clock_id: 3 timestamp: 106208710702167 } clocks { clock_id: 5 timestamp: 106208710702494 } } trusted_packet_sequence_id: 1 } packet { first_packet_on_sequence: true timestamp: 273574983771490 timestamp_clock_id: 6 sequence_flags: 1 trace_packet_defaults { timestamp_clock_id: 3 perf_sample_defaults { timebase { frequency: 100 counter: SW_CPU_CLOCK } followers { counter: HW_CPU_CYCLES } followers { counter: HW_INSTRUCTIONS } } } trusted_packet_sequence_id: 4 previous_packet_dropped: true } packet { interned_data { build_ids { iid: 0 str: "" } mapping_paths { iid: 0 str: "" } function_names { iid: 0 str: "" } } sequence_flags: 2 trusted_packet_sequence_id: 4 } packet { sequence_flags: 2 timestamp: 106208800213886 interned_data { callstacks { iid: 1 } } perf_sample { cpu: 0 pid: 0 tid: 0 cpu_mode: MODE_KERNEL timebase_count: 10020141 follower_counts: 4672142 follower_counts: 1144537 callstack_iid: 1 } trusted_packet_sequence_id: 4 } """), query=""" select c.ts, c.value, pct.cpu, pct.perf_session_id, pct.is_timebase from counter c join perf_counter_track pct on c.track_id = pct.id order by ts, c.id """, out=Csv(""" "ts","value","cpu","perf_session_id","is_timebase" 273574993553025,10020141.000000,0,0,1 273574993553025,4672142.000000,0,0,0 273574993553025,1144537.000000,0,0,0 """))