summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-05-10 18:05:38 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2024-05-10 18:05:38 +0000
commit6f410082253f96e5159928efe93144cf13ccbe13 (patch)
tree9ea1c4097580c8fe5def8cd29c9bbbc40ea033ca
parented5e496663c1992edf93acd89dba88c14799b83e (diff)
parent2081e1bde2e620d4826d190688bda09a21d9c2e4 (diff)
downloadextras-busytown-mac-infra-release.tar.gz
Merge "Snap for 11819167 from 3496d6c5434dad561f3d4d74476d5b4984bd93db to busytown-mac-infra-release" into busytown-mac-infra-releasebusytown-mac-infra-release
-rwxr-xr-xboottime_tools/bootanalyze/bootanalyze.py151
-rwxr-xr-xboottime_tools/bootanalyze/bootanalyze.sh52
-rw-r--r--boottime_tools/bootanalyze/config.yaml1
-rw-r--r--boottime_tools/bootanalyze/stressfs/Android.bp1
-rw-r--r--boottime_tools/bootio/bootio_collector.cpp14
-rw-r--r--checkpoint_gc/checkpoint_gc.sh22
-rw-r--r--crypto-perf/Android.bp35
-rw-r--r--crypto-perf/NOTICE190
-rw-r--r--crypto-perf/crypto.cpp165
-rw-r--r--ext4_utils/Android.bp1
-rw-r--r--ext4_utils/ext4_utils.cpp13
-rw-r--r--ext4_utils/include/ext4_utils/ext4_utils.h12
-rw-r--r--f2fs_utils/f2fs_sparseblock.c39
-rwxr-xr-xf2fs_utils/mkf2fsuserimg.sh18
l---------kcmdlinectrl/.clang-format1
-rw-r--r--kcmdlinectrl/Android.bp (renamed from setuclamp/Android.bp)24
-rw-r--r--kcmdlinectrl/OWNERS1
-rw-r--r--kcmdlinectrl/kcmdlinectrl.cc156
-rw-r--r--kcmdlinectrl/kcmdlinectrl.rc22
-rw-r--r--libatrace_rust/Android.bp109
-rw-r--r--libatrace_rust/OWNERS3
-rw-r--r--libatrace_rust/README.md129
-rw-r--r--libatrace_rust/benchmark/Android.bp63
-rw-r--r--libatrace_rust/benchmark/README.md99
-rw-r--r--libatrace_rust/benchmark/src/atrace_benchmark.cc61
-rw-r--r--libatrace_rust/benchmark/src/atrace_benchmark.rs61
-rw-r--r--libatrace_rust/benchmark/src/atrace_benchmark_common.rs53
-rw-r--r--libatrace_rust/benchmark/src/trace_enabler.cc37
-rw-r--r--libatrace_rust/benchmark/src/trace_enabler.h36
-rw-r--r--libatrace_rust/benchmark/src/tracing_subscriber_benchmark.rs176
-rw-r--r--libatrace_rust/bindgen/cutils_trace.h1
-rw-r--r--libatrace_rust/bindgen/cutils_trace_wrap.c62
-rw-r--r--libatrace_rust/bindgen/cutils_trace_wrap.h35
-rw-r--r--libatrace_rust/example/Android.bp21
-rw-r--r--libatrace_rust/example/src/main.rs92
-rw-r--r--libatrace_rust/example/src/tracing_subscriber_sample.rs60
-rw-r--r--libatrace_rust/src/lib.rs1218
-rw-r--r--libatrace_rust/src/tracing_subscriber.rs699
-rw-r--r--libfec/fec_open.cpp7
-rw-r--r--libfec/fec_private.h3
-rw-r--r--libfec/fec_process.cpp63
-rw-r--r--libfec/test/fec_unittest.cpp10
-rw-r--r--libfscrypt/Android.bp1
-rw-r--r--libfscrypt/fscrypt.cpp26
-rw-r--r--libfscrypt/include/fscrypt/fscrypt.h3
-rw-r--r--libfscrypt/tests/Android.bp1
-rw-r--r--libfscrypt/tests/fscrypt_test.cpp21
-rw-r--r--libjsonpb/parse/include/jsonpb/error_or.h11
-rw-r--r--libjsonpb/parse/jsonpb.cpp6
-rw-r--r--libjsonpb/verify/Android.bp1
l---------memory_replay/.clang-format1
-rw-r--r--memory_replay/Alloc.cpp1
-rw-r--r--memory_replay/Android.bp31
-rw-r--r--memory_replay/FilterTrace.cpp210
-rw-r--r--memory_replay/NativeInfo.cpp19
-rw-r--r--memory_replay/NativeInfo.h3
-rw-r--r--memory_replay/Pointers.cpp8
-rw-r--r--memory_replay/Threads.cpp14
-rw-r--r--memory_replay/TraceBenchmark.cpp46
-rw-r--r--memory_replay/main.cpp65
-rw-r--r--mmap-perf/Android.bp1
-rw-r--r--mmap-perf/mmapPerf.cpp2
-rw-r--r--mtectrl/mtectrl.cc157
-rw-r--r--mtectrl/mtectrl.rc22
-rw-r--r--mtectrl/mtectrl_test.cc246
-rw-r--r--multinetwork/Android.bp7
-rw-r--r--[-rwxr-xr-x]pagecache/pagecache.py7
-rw-r--r--partition_tools/Android.bp15
-rw-r--r--partition_tools/dynamic_partitions_device_info.proto16
-rw-r--r--partition_tools/lpdump.cc13
-rw-r--r--partition_tools/lpdumpd.cc25
-rw-r--r--partition_tools/lpmake.cc4
-rw-r--r--partition_tools/lpunpack.cc116
-rw-r--r--perf2cfg/Android.bp6
-rwxr-xr-xperf2cfg/perf2cfg_test.py10
-rw-r--r--perf_tools/Android.bp (renamed from tests/memeater/Android.bp)36
-rwxr-xr-xperf_tools/bats/lcan.py692
-rw-r--r--perf_tools/config.yaml15
-rw-r--r--perf_tools/parse_timestamp.py55
-rw-r--r--perf_tools/parse_timing.py197
-rwxr-xr-xperf_tools/progress_report.py159
-rw-r--r--perf_tools/report.proto21
-rwxr-xr-xperf_tools/sbtpull.py562
-rw-r--r--pinner/Android.bp (renamed from tests/iptables/qtaguid/Android.bp)46
-rw-r--r--pinner/OWNERS2
-rw-r--r--pinner/README.md144
-rw-r--r--pinner/include/meminspect.h268
-rw-r--r--pinner/include/pin_utils.h116
-rw-r--r--pinner/meminspect.cpp324
-rw-r--r--pinner/pin_utils.cpp347
-rw-r--r--pinner/pintool.cpp343
-rw-r--r--pinner/sample_pinconfig.txt5
-rw-r--r--pinner/tests/Android.bp95
-rw-r--r--pinner/tests/TEST_MAPPING10
-rw-r--r--pinner/tests/meminspect_tests.cpp284
-rw-r--r--pinner/tests/pintool_tests.cpp137
-rw-r--r--profcollectd/OWNERS4
-rw-r--r--profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl3
-rw-r--r--profcollectd/libprofcollectd/Android.bp4
-rw-r--r--profcollectd/libprofcollectd/bindings/libflags/Android.bp51
-rw-r--r--profcollectd/libprofcollectd/bindings/libflags/get_flags.cpp25
-rw-r--r--profcollectd/libprofcollectd/bindings/libflags/get_flags.hpp21
-rw-r--r--profcollectd/libprofcollectd/bindings/libflags/lib.rs38
-rw-r--r--profcollectd/libprofcollectd/config.rs51
-rw-r--r--profcollectd/libprofcollectd/lib.rs10
-rw-r--r--profcollectd/libprofcollectd/logging_trace_provider.rs7
-rw-r--r--profcollectd/libprofcollectd/report.rs32
-rw-r--r--profcollectd/libprofcollectd/scheduler.rs24
-rw-r--r--profcollectd/libprofcollectd/service.rs5
-rw-r--r--profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs52
-rw-r--r--profcollectd/libprofcollectd/simpleperf_lbr_trace_provider.rs119
-rw-r--r--profcollectd/libprofcollectd/trace_provider.rs9
-rw-r--r--setuclamp/setuclamp.cpp75
-rw-r--r--simpleperf/Android.bp115
-rw-r--r--simpleperf/BranchListFile.cpp440
-rw-r--r--simpleperf/BranchListFile.h176
-rw-r--r--simpleperf/BranchListFile_test.cpp36
-rw-r--r--simpleperf/CallChainJoiner_test.cpp9
-rw-r--r--simpleperf/ETMConstants.h2
-rw-r--r--simpleperf/ETMDecoder.cpp27
-rw-r--r--simpleperf/ETMDecoder.h17
-rw-r--r--simpleperf/ETMRecorder.cpp38
-rw-r--r--simpleperf/ETMRecorder.h12
-rw-r--r--simpleperf/IOEventLoop.cpp20
-rw-r--r--simpleperf/IOEventLoop.h8
-rw-r--r--simpleperf/IOEventLoop_test.cpp47
-rw-r--r--simpleperf/JITDebugReader.cpp70
-rw-r--r--simpleperf/JITDebugReader.h121
-rw-r--r--simpleperf/JITDebugReader_test.cpp57
-rw-r--r--simpleperf/MapRecordReader_test.cpp4
-rw-r--r--simpleperf/OfflineUnwinder_test.cpp3
-rw-r--r--simpleperf/ProbeEvents.cpp12
-rw-r--r--simpleperf/ProbeEvents.h12
-rw-r--r--simpleperf/ProbeEvents_test.cpp1
-rw-r--r--simpleperf/RecordFilter.cpp292
-rw-r--r--simpleperf/RecordFilter.h66
-rw-r--r--simpleperf/RecordFilter_test.cpp145
-rw-r--r--simpleperf/RecordReadThread.cpp37
-rw-r--r--simpleperf/RecordReadThread.h12
-rw-r--r--simpleperf/RecordReadThread_test.cpp31
-rw-r--r--simpleperf/RegEx.cpp9
-rw-r--r--simpleperf/RegEx.h3
-rw-r--r--simpleperf/RegEx_test.cpp2
-rw-r--r--simpleperf/branch_list.proto96
-rw-r--r--simpleperf/cmd_api_test.cpp4
-rw-r--r--simpleperf/cmd_boot_record_test.cpp1
-rw-r--r--simpleperf/cmd_debug_unwind.cpp9
-rw-r--r--simpleperf/cmd_debug_unwind_test.cpp12
-rw-r--r--simpleperf/cmd_dumprecord.cpp92
-rw-r--r--simpleperf/cmd_dumprecord_test.cpp8
-rw-r--r--simpleperf/cmd_inject.cpp919
-rw-r--r--simpleperf/cmd_inject_impl.h28
-rw-r--r--simpleperf/cmd_inject_test.cpp95
-rw-r--r--simpleperf/cmd_kmem.cpp11
-rw-r--r--simpleperf/cmd_kmem_test.cpp7
-rw-r--r--simpleperf/cmd_list.cpp272
-rw-r--r--simpleperf/cmd_list_test.cpp6
-rw-r--r--simpleperf/cmd_merge.cpp8
-rw-r--r--simpleperf/cmd_merge_test.cpp2
-rw-r--r--simpleperf/cmd_monitor.cpp45
-rw-r--r--simpleperf/cmd_monitor_test.cpp11
-rw-r--r--simpleperf/cmd_record.cpp587
-rw-r--r--simpleperf/cmd_record_impl.h10
-rw-r--r--simpleperf/cmd_record_test.cpp280
-rw-r--r--simpleperf/cmd_report.cpp16
-rw-r--r--simpleperf/cmd_report_sample.cpp262
-rw-r--r--simpleperf/cmd_report_sample_test.cpp58
-rw-r--r--simpleperf/cmd_report_test.cpp89
-rw-r--r--simpleperf/cmd_stat.cpp130
-rw-r--r--simpleperf/cmd_stat_impl.h31
-rw-r--r--simpleperf/cmd_stat_test.cpp116
-rw-r--r--simpleperf/cmd_trace_sched.cpp15
-rw-r--r--simpleperf/cmd_trace_sched_test.cpp2
-rw-r--r--simpleperf/command.cpp39
-rw-r--r--simpleperf/command.h20
-rw-r--r--simpleperf/command_test.cpp5
-rw-r--r--simpleperf/cpu_hotplug_test.cpp2
-rw-r--r--simpleperf/doc/README.md216
-rw-r--r--simpleperf/doc/android_application_profiling.md6
-rw-r--r--simpleperf/doc/android_platform_profiling.md26
-rw-r--r--simpleperf/doc/collect_etm_data_for_autofdo.md77
-rw-r--r--simpleperf/doc/debug_dwarf_unwinding.md6
-rw-r--r--simpleperf/doc/executable_commands_reference.md21
-rw-r--r--simpleperf/doc/jit_symbols.md3
-rw-r--r--simpleperf/doc/sample_filter.md3
-rw-r--r--simpleperf/doc/scripts_reference.md38
-rw-r--r--simpleperf/doc/view_the_profile.md20
-rw-r--r--simpleperf/dso.cpp71
-rw-r--r--simpleperf/dso.h1
-rw-r--r--simpleperf/dso_test.cpp24
-rw-r--r--simpleperf/environment.cpp121
-rw-r--r--simpleperf/environment.h9
-rw-r--r--simpleperf/environment_test.cpp26
-rw-r--r--simpleperf/etm_branch_list.proto66
-rw-r--r--simpleperf/event_attr.cpp13
-rw-r--r--simpleperf/event_attr.h16
-rw-r--r--simpleperf/event_selection_set.cpp315
-rw-r--r--simpleperf/event_selection_set.h53
-rw-r--r--simpleperf/event_selection_set_test.cpp97
-rw-r--r--simpleperf/event_table.json1042
-rwxr-xr-xsimpleperf/event_table_generator.py253
-rw-r--r--simpleperf/event_type.cpp13
-rw-r--r--simpleperf/event_type.h1
-rw-r--r--simpleperf/event_type_table.h496
-rwxr-xr-xsimpleperf/generate_event_type_table.py305
-rw-r--r--simpleperf/get_test_data.h2
-rw-r--r--simpleperf/gtest_main.cpp3
-rw-r--r--simpleperf/include/simpleperf_profcollect.hpp11
-rw-r--r--simpleperf/kallsyms_test.cpp5
-rw-r--r--simpleperf/libsimpleperf_report_fuzzer.cpp7
-rw-r--r--simpleperf/main.cpp1
-rw-r--r--simpleperf/nonlinux_support/nonlinux_support.cpp12
-rw-r--r--simpleperf/perf_regs_test.cpp1
-rw-r--r--simpleperf/profcollect.cpp124
-rw-r--r--simpleperf/read_apk_test.cpp3
-rw-r--r--simpleperf/read_dex_file.cpp1
-rw-r--r--simpleperf/read_dex_file.h1
-rw-r--r--simpleperf/read_dex_file_test.cpp1
-rw-r--r--simpleperf/read_elf.cpp178
-rw-r--r--simpleperf/read_elf_test.cpp16
-rw-r--r--simpleperf/read_symbol_map.cpp13
-rw-r--r--simpleperf/read_symbol_map_test.cpp74
-rw-r--r--simpleperf/record.cpp75
-rw-r--r--simpleperf/record.h18
-rw-r--r--simpleperf/record_equal_test.h29
-rw-r--r--simpleperf/record_file.h24
-rw-r--r--simpleperf/record_file_format.h4
-rw-r--r--simpleperf/record_file_reader.cpp92
-rw-r--r--simpleperf/record_file_test.cpp34
-rw-r--r--simpleperf/record_file_writer.cpp6
-rw-r--r--simpleperf/record_lib_interface.cpp2
-rw-r--r--simpleperf/record_lib_test.cpp5
-rw-r--r--simpleperf/record_test.cpp50
-rw-r--r--simpleperf/report_lib_interface.cpp87
-rw-r--r--simpleperf/report_utils.cpp290
-rw-r--r--simpleperf/report_utils.h32
-rw-r--r--simpleperf/report_utils_test.cpp49
-rw-r--r--simpleperf/runtest/Android.bp6
-rw-r--r--simpleperf/rust/lib.rs74
-rw-r--r--simpleperf/sample_tree_test.cpp9
-rw-r--r--simpleperf/scripts/Android.bp1
-rwxr-xr-xsimpleperf/scripts/annotate.py8
-rwxr-xr-xsimpleperf/scripts/app_profiler.py41
-rwxr-xr-xsimpleperf/scripts/bin/android/arm/simpleperfbin2800556 -> 2888740 bytes
-rwxr-xr-xsimpleperf/scripts/bin/android/arm64/simpleperfbin3921272 -> 4192832 bytes
-rwxr-xr-xsimpleperf/scripts/bin/android/riscv64/simpleperfbin0 -> 3102232 bytes
-rwxr-xr-xsimpleperf/scripts/bin/android/x86/simpleperfbin4362584 -> 3833680 bytes
-rwxr-xr-xsimpleperf/scripts/bin/android/x86_64/simpleperfbin4202720 -> 3653408 bytes
-rwxr-xr-xsimpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylibbin12817056 -> 22352496 bytes
-rwxr-xr-xsimpleperf/scripts/bin/darwin/x86_64/simpleperfbin12757344 -> 22309472 bytes
-rwxr-xr-xsimpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.sobin6653504 -> 5335176 bytes
-rwxr-xr-xsimpleperf/scripts/bin/linux/x86_64/simpleperfbin6632096 -> 5313288 bytes
-rwxr-xr-xsimpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dllbin5354496 -> 0 bytes
-rwxr-xr-xsimpleperf/scripts/bin/windows/x86_64/libwinpthread-1.dllbin572009 -> 0 bytes
-rwxr-xr-xsimpleperf/scripts/bin/windows/x86_64/simpleperf.exebin4598272 -> 0 bytes
-rwxr-xr-xsimpleperf/scripts/binary_cache_builder.py24
-rwxr-xr-xsimpleperf/scripts/gecko_profile_generator.py103
-rwxr-xr-xsimpleperf/scripts/ipc.py137
-rwxr-xr-xsimpleperf/scripts/pprof_proto_generator.py11
-rw-r--r--simpleperf/scripts/report_html.js45
-rwxr-xr-xsimpleperf/scripts/report_html.py100
-rwxr-xr-xsimpleperf/scripts/report_sample.py6
-rw-r--r--simpleperf/scripts/report_sample_pb2.py46
-rwxr-xr-xsimpleperf/scripts/sample_filter.py130
-rw-r--r--simpleperf/scripts/simpleperf_report_lib.py300
-rw-r--r--simpleperf/scripts/simpleperf_utils.py125
-rwxr-xr-xsimpleperf/scripts/stackcollapse.py6
-rw-r--r--simpleperf/scripts/test/app_profiler_test.py9
-rw-r--r--simpleperf/scripts/test/app_test.py16
-rwxr-xr-xsimpleperf/scripts/test/do_test.py3
-rw-r--r--simpleperf/scripts/test/gecko_profile_generator_test.py32
-rw-r--r--simpleperf/scripts/test/java_app_test.py2
-rw-r--r--simpleperf/scripts/test/kotlin_app_test.py4
-rw-r--r--simpleperf/scripts/test/pprof_proto_generator_test.py9
-rw-r--r--simpleperf/scripts/test/report_html_test.py15
-rw-r--r--simpleperf/scripts/test/report_lib_test.py157
-rw-r--r--simpleperf/scripts/test/sample_filter_test.py42
-rw-r--r--simpleperf/scripts/test/script_testdata/display_bitmaps.proto_databin0 -> 385461 bytes
-rw-r--r--simpleperf/scripts/test/script_testdata/perf_test_vmlinux.databin0 -> 1529 bytes
-rw-r--r--simpleperf/scripts/test/script_testdata/perf_with_interpreter_frames.gecko.json15
-rw-r--r--simpleperf/scripts/test/script_testdata/perf_with_jit_symbol.gecko.json1487
-rw-r--r--simpleperf/scripts/test/script_testdata/perf_with_tracepoint_event.gecko.json550
-rw-r--r--simpleperf/scripts/test/script_testdata/vmlinuxbin0 -> 2331 bytes
-rw-r--r--simpleperf/scripts/test/tools_test.py113
-rwxr-xr-xsimpleperf/scripts/update.py26
-rw-r--r--simpleperf/simpleperf.rc10
-rw-r--r--simpleperf/test_util.cpp74
-rw-r--r--simpleperf/test_util.h9
-rw-r--r--simpleperf/testdata/DisplayBitmaps.apkbin2377615 -> 760019 bytes
-rw-r--r--simpleperf/testdata/DisplayBitmapsTest.apkbin2941948 -> 2861529 bytes
-rw-r--r--simpleperf/testdata/data/symfs_for_no_symbol_table_warning/elfbin6280 -> 0 bytes
-rw-r--r--simpleperf/testdata/etm/etm_test_loop_smallbin0 -> 4008 bytes
-rw-r--r--simpleperf/testdata/etm/perf_for_small_binary.databin0 -> 22680 bytes
-rw-r--r--simpleperf/testdata/etm/perf_inject.data1
-rw-r--r--simpleperf/testdata/etm/perf_inject_small.data32
-rw-r--r--simpleperf/testdata/etm/perf_with_missing_aux_data.databin0 -> 24552 bytes
-rw-r--r--simpleperf/testdata/lbr/inject_lbr.data14
-rw-r--r--simpleperf/testdata/lbr/perf_lbr.databin0 -> 23855 bytes
-rw-r--r--simpleperf/thread_tree.cpp6
-rw-r--r--simpleperf/thread_tree.h2
-rw-r--r--simpleperf/thread_tree_test.cpp7
-rw-r--r--simpleperf/tracing.cpp31
-rw-r--r--simpleperf/tracing.h10
-rw-r--r--simpleperf/tracing_test.cpp6
-rw-r--r--simpleperf/utils.cpp20
-rw-r--r--simpleperf/utils.h7
-rw-r--r--simpleperf/utils_test.cpp14
-rw-r--r--simpleperf/workload_test.cpp4
-rw-r--r--slideshow/Android.bp3
-rw-r--r--tests/audio/alsa/Android.bp1
-rw-r--r--tests/binder/benchmarks/Android.bp1
-rw-r--r--tests/bootloader/Android.mk19
-rw-r--r--tests/bootloader/bootloadertest.py9
-rw-r--r--tests/fstest/Android.bp1
-rw-r--r--tests/iptables/qtaguid/socketTag.cpp398
-rw-r--r--tests/kernel.config/OWNERS6
-rw-r--r--tests/memeater/NOTICE190
-rw-r--r--tests/memeater/memeater.c107
-rw-r--r--tests/tcp_nuke_addr/Android.bp1
-rw-r--r--tests/timetest/Android.bp1
-rw-r--r--toolchain-extras/Android.bp15
-rwxr-xr-xtools/check_elf_alignment.sh90
-rw-r--r--verity/Android.bp1
-rw-r--r--verity/build_verity_tree.cpp11
324 files changed, 19054 insertions, 4918 deletions
diff --git a/boottime_tools/bootanalyze/bootanalyze.py b/boottime_tools/bootanalyze/bootanalyze.py
index af166949..2b47a899 100755
--- a/boottime_tools/bootanalyze/bootanalyze.py
+++ b/boottime_tools/bootanalyze/bootanalyze.py
@@ -16,7 +16,7 @@
#
"""Tool to analyze logcat and dmesg logs.
-bootanalyze read logcat and dmesg loga and determines key points for boot.
+bootanalyze read logcat and dmesg logs and determines key points for boot.
"""
import argparse
@@ -28,12 +28,11 @@ import os
import re
import select
import subprocess
-import sys
import time
import threading
import yaml
-from datetime import datetime, date
+from datetime import datetime
TIME_DMESG = r"\[\s*(\d+\.\d+)\]"
TIME_LOGCAT = r"[0-9]+\.?[0-9]*"
@@ -41,7 +40,9 @@ KERNEL_TIME_KEY = "kernel"
BOOT_ANIM_END_TIME_KEY = "BootAnimEnd"
KERNEL_BOOT_COMPLETE = "BootComplete_kernel"
LOGCAT_BOOT_COMPLETE = "BootComplete"
+CARWATCHDOG_BOOT_COMPLETE = "CarWatchdogBootupProfilingComplete"
LAUNCHER_START = "LauncherStart"
+CARWATCHDOG_DUMP_COMMAND = "adb shell dumpsys android.automotive.watchdog.ICarWatchdog/default"
BOOT_TIME_TOO_BIG = 200.0
MAX_RETRIES = 5
DEBUG = False
@@ -50,6 +51,7 @@ ADB_CMD = "adb"
TIMING_THRESHOLD = 5.0
BOOT_PROP = r"\[ro\.boottime\.([^\]]+)\]:\s+\[(\d+)\]"
BOOTLOADER_TIME_PROP = r"\[ro\.boot\.boottime\]:\s+\[([^\]]+)\]"
+CARWATCHDOG_PARSER_CMD = 'perf_stats_parser'
max_wait_time = BOOT_TIME_TOO_BIG
@@ -102,12 +104,18 @@ def main():
if DEBUG_PATTERN:
print("search event:{} timing event:{}".format(search_events_pattern, timing_events_pattern))
+ now = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
+ boot_chart_file_path_prefix = "bootchart-" + now
+ systrace_file_path_prefix = "systrace-" + now
+
+ if args.output:
+ boot_chart_file_path_prefix = args.output + '/' + boot_chart_file_path_prefix
+ systrace_file_path_prefix = args.output + '/' + systrace_file_path_prefix
+
data_points = {}
kernel_timing_points = collections.OrderedDict()
logcat_timing_points = collections.OrderedDict()
boottime_points = collections.OrderedDict()
- boot_chart_file_name_prefix = "bootchart-" + datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
- systrace_file_name_prefix = "systrace-" + datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
shutdown_event_all = collections.OrderedDict()
shutdown_timing_event_all = collections.OrderedDict()
for it in range(0, args.iterate):
@@ -141,11 +149,16 @@ def main():
# Processing error
print("Failed to collect valid samples for run {0}".format(it))
continue
+
if args.bootchart:
- grab_bootchart(boot_chart_file_name_prefix + "_run_" + str(it))
+ grab_bootchart(boot_chart_file_path_prefix + "_run_" + str(it))
if args.systrace:
- grab_systrace(systrace_file_name_prefix + "_run_" + str(it))
+ grab_systrace(systrace_file_path_prefix + "_run_" + str(it))
+
+ if args.carwatchdog:
+ grab_carwatchdog_bootstats(args.output)
+
for k, v in processing_data.items():
if k not in data_points:
data_points[k] = []
@@ -332,6 +345,8 @@ def iterate(args, search_events_pattern, timings_pattern, shutdown_events_patter
logcat_stop_events = [LOGCAT_BOOT_COMPLETE, LAUNCHER_START]
if args.fs_check:
logcat_stop_events.append("FsStat")
+ if args.carwatchdog:
+ logcat_stop_events.append(CARWATCHDOG_BOOT_COMPLETE)
logcat_events, logcat_timing_events = collect_events(
search_events_pattern, ADB_CMD + ' logcat -b all -v epoch', timings_pattern,\
logcat_stop_events, False)
@@ -504,6 +519,8 @@ def init_arguments():
parser.add_argument('-r', '--reboot', dest='reboot',
action='store_true',
help='reboot device for measurement', )
+ parser.add_argument('-o', '--output', dest='output', type=str,
+ help='Output directory where results are stored')
parser.add_argument('-c', '--config', dest='config',
default='config.yaml', type=argparse.FileType('r'),
help='config file for the tool', )
@@ -543,6 +560,8 @@ def init_arguments():
parser.add_argument('-y', '--systrace', dest='systrace',
action='store_true',
help='collect systrace from the device. kernel trace should be already enabled', )
+ parser.add_argument('-W', '--carwatchdog', dest='carwatchdog', action='store_true',
+ help='collect carwatchdog boot stats')
parser.add_argument('-G', '--buffersize', dest='buffersize', action='store', type=str,
default=None,
help='set logcat buffersize')
@@ -677,6 +696,15 @@ def collect_events(search_events, command, timings, stop_events, disable_timing_
log_timeout(time_left, stop_events, events, timing_events)
break
polled_events = read_poll.poll(time_left * 1000.0)
+ # adb logcat subprocess is auto-terminated when the adb connection is lost.
+ # Thus, check for the subprocess return code and reconnect to the device if
+ # needed. Otherwise, the logcat events cannot be polled completely.
+ if process.poll() is not None:
+ print("adb might be disconnected?\nRetrying to connect.")
+ run_adb_cmd('wait-for-device')
+ print(" reconnected")
+ init = True
+ continue
if len(polled_events) == 0:
log_timeout(time_left, stop_events, events, timing_events)
break
@@ -830,14 +858,21 @@ def reboot(serial, use_stressfs, permissive, use_adb_reboot, adb_buffersize=None
if run_adb_cmd('logcat -G {}'.format(adb_buffersize)) != 0:
debug('Fail to set logcat buffer size as {}'.format(adb_buffersize))
-def run_adb_cmd(cmd):
- return subprocess.call(ADB_CMD + ' ' + cmd, shell=True)
+'''
+Runs adb command. If do_return_result is true then output of command is
+returned otherwise an empty string is returned.
+'''
+def run_adb_cmd(cmd, do_return_result=False):
+ if do_return_result:
+ return subprocess.check_output(ADB_CMD + ' ' + cmd, shell=True).decode('utf-8', 'ignore').strip()
+ subprocess.call(ADB_CMD + ' ' + cmd, shell=True)
+ return ""
-def run_adb_shell_cmd(cmd):
- return subprocess.call(ADB_CMD + ' shell ' + cmd, shell=True)
+def run_adb_shell_cmd(cmd, do_return_result=False):
+ return run_adb_cmd('shell ' + cmd, do_return_result)
-def run_adb_shell_cmd_as_root(cmd):
- return subprocess.call(ADB_CMD + ' shell su root ' + cmd, shell=True)
+def run_adb_shell_cmd_as_root(cmd, do_return_result=False):
+ return run_adb_shell_cmd('su root ' + cmd, do_return_result)
def logcat_time_func(offset_year):
def f(date_str):
@@ -856,21 +891,95 @@ def stddev(data):
variance = sq_diffs_sum / items_count
return math.sqrt(variance)
-def grab_bootchart(boot_chart_file_name):
- subprocess.call("$ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh", shell=True)
- print("Saving boot chart as " + boot_chart_file_name + ".tgz")
- subprocess.call('cp /tmp/android-bootchart/bootchart.tgz ./' + boot_chart_file_name + '.tgz',\
+def grab_bootchart(boot_chart_file_path):
+ subprocess.run("$ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh", shell=True,
+ stdout=subprocess.DEVNULL)
+ print("Saving boot chart as " + boot_chart_file_path + ".tgz")
+ subprocess.call('cp /tmp/android-bootchart/bootchart.tgz ' + boot_chart_file_path + '.tgz', \
shell=True)
- subprocess.call('cp ./bootchart.png ./' + boot_chart_file_name + '.png', shell=True)
+ subprocess.call('cp ./bootchart.png ' + boot_chart_file_path + '.png', shell=True)
-def grab_systrace(systrace_file_name):
- trace_file = systrace_file_name + "_trace.txt"
+def grab_systrace(systrace_file_path_prefix):
+ trace_file = systrace_file_path_prefix + "_trace.txt"
with open(trace_file, 'w') as f:
f.write("TRACE:\n")
run_adb_shell_cmd_as_root("cat /d/tracing/trace >> " + trace_file)
- html_file = systrace_file_name + ".html"
+ html_file = systrace_file_path_prefix + ".html"
subprocess.call("$ANDROID_BUILD_TOP/external/chromium-trace/systrace.py --from-file=" + trace_file + " -o " +\
html_file, shell=True)
+def capture_build_info(out_dir, build_info_file_name):
+ fingerprint = run_adb_shell_cmd('getprop ro.build.fingerprint', True)
+ brand = run_adb_shell_cmd('getprop ro.product.brand', True)
+ product = run_adb_shell_cmd('getprop ro.product.name', True)
+ device = run_adb_shell_cmd('getprop ro.product.device', True)
+ version_release = run_adb_shell_cmd('getprop ro.build.version.release', True)
+ id = run_adb_shell_cmd('getprop ro.build.id', True)
+ version_incremental = run_adb_shell_cmd('getprop ro.build.version.incremental', True)
+ type = run_adb_shell_cmd('getprop ro.build.type', True)
+ tags = run_adb_shell_cmd('getprop ro.build.tags', True)
+ sdk = run_adb_shell_cmd('getprop ro.build.version.sdk', True)
+ platform_minor = run_adb_shell_cmd('getprop ro.android.car.version.platform_minor', True)
+ codename = run_adb_shell_cmd('getprop ro.build.version.codename', True)
+ carwatchdog_collection_interval = run_adb_shell_cmd('getprop ro.carwatchdog.system_event_collection_interval', True)
+ carwatchdog_post_event_duration = run_adb_shell_cmd('getprop ro.carwatchdog.post_system_event_duration', True)
+ carwatchdog_top_n_category = run_adb_shell_cmd('getprop ro.carwatchdog.top_n_stats_per_category', True)
+ carwatchdog_top_n_subcategory = run_adb_shell_cmd('getprop ro.carwatchdog.top_n_stats_per_subcategory', True)
+
+ # TODO: Change file format to JSON to avoid custom parser
+ build_info = []
+ build_info.append('Build information: ')
+ build_info.append('-' * 20)
+ build_info.append('fingerprint: ' + fingerprint)
+ build_info.append('brand: ' + brand)
+ build_info.append('product: ' + product)
+ build_info.append('device: ' + device)
+ build_info.append('version.release: ' + version_release)
+ build_info.append('id: ' + id)
+ build_info.append('version.incremental: ' + version_incremental)
+ build_info.append('type: ' + type)
+ build_info.append('tags: ' + tags)
+ build_info.append('sdk: ' + sdk)
+ build_info.append('platform minor version: ' + platform_minor)
+ build_info.append('codename: ' + codename)
+ build_info.append('carwatchdog collection interval (s): ' + carwatchdog_collection_interval)
+ build_info.append('carwatchdog post event duration (s): ' + carwatchdog_post_event_duration)
+ build_info.append('carwatchdog top N packages: ' + carwatchdog_top_n_category)
+ build_info.append('carwatchdog top N processes: ' + carwatchdog_top_n_subcategory)
+
+ build_info_str = '\n'.join(build_info)
+
+ with open(out_dir + '/' + build_info_file_name, 'w') as f:
+ f.write(build_info_str)
+
+def generate_proto(dump_file, build_info_file, out_proto_file):
+ subprocess.run("{} -f {} -b {} -d {}".format(CARWATCHDOG_PARSER_CMD,
+ dump_file,
+ build_info_file,
+ out_proto_file),
+ shell=True, stdout=subprocess.DEVNULL)
+
+def grab_carwatchdog_bootstats(result_dir):
+ carwatchdog_state = run_adb_shell_cmd_as_root('getprop init.svc.carwatchdogd', True)
+ if carwatchdog_state != "running":
+ print('carwatchdog (-d) flag set but CarWatchdog is not running on device')
+ return
+ elif not result_dir:
+ print('carwatchdog needs the output directory to be specified.')
+ return
+ print("Capturing carwatchdog stats")
+ build_info_file_name = "device_build_info.txt"
+ capture_build_info(result_dir, build_info_file_name)
+
+ # Capture CW dump and save dump to txt
+ dump_file_name = result_dir + '/carwatchdog_dump.txt'
+ subprocess.call(CARWATCHDOG_DUMP_COMMAND + " > " + dump_file_name, shell=True)
+
+ # Generate proto from dump
+ build_info_file_path = result_dir + '/' + build_info_file_name
+ out_proto_file_path = result_dir + '/carwatchdog_perf_stats_out.pb'
+ generate_proto(dump_file_name, build_info_file_path, out_proto_file_path)
+
+
if __name__ == '__main__':
main()
diff --git a/boottime_tools/bootanalyze/bootanalyze.sh b/boottime_tools/bootanalyze/bootanalyze.sh
index 7fce2e1c..bada4656 100755
--- a/boottime_tools/bootanalyze/bootanalyze.sh
+++ b/boottime_tools/bootanalyze/bootanalyze.sh
@@ -16,13 +16,18 @@
readme() {
echo '
-Analyze boot-time & bootchart
+Analyze boot-time
e.g.
ANDROID_BUILD_TOP="$PWD" \
CONFIG_YMAL="$ANDROID_BUILD_TOP/system/extras/boottime_tools/bootanalyze/config.yaml" \
LOOPS=3 \
- RESULTS_DIR="$ANDROID_BUILD_TOP/bootAnalyzeResults" \
- $PWD/system/extras/boottime_tools/bootanalyze/bootanalyze.sh
+ RESULTS_DIR="$PWD/bootAnalyzeResults" \
+ $ANDROID_BUILD_TOP/system/extras/boottime_tools/bootanalyze/bootanalyze.sh
+
+Flags:
+-a : Uses "adb reboot" (instead of "adb shell su root svc power reboot") command to reboot
+-b : If set grabs bootchart
+-w : If set grabs carwatchdog perf stats
'
exit
}
@@ -48,6 +53,29 @@ fi
echo "RESULTS_DIR=$RESULTS_DIR"
mkdir -p $RESULTS_DIR
+ADB_REBOOT_FLAG=""
+BOOTCHART_FLAG=""
+CARWATCHDOG_FLAG=""
+
+while getopts 'abw' OPTION; do
+ case "$OPTION" in
+ a)
+ ADB_REBOOT_FLAG="-a"
+ ;;
+ b)
+ BOOTCHART_FLAG="-b"
+ ;;
+ w)
+ CARWATCHDOG_FLAG="-W"
+ ;;
+ ?)
+ echo 'Error: Invalid flag set'
+ readme
+ ;;
+ esac
+done
+shift "$(($OPTIND -1))"
+
adb shell 'touch /data/bootchart/enabled'
@@ -55,15 +83,25 @@ if [[ -z $LOOPS ]]; then
LOOPS=1
fi
echo "Analyzing boot-time for LOOPS=$LOOPS"
+BOOTCHART_TGZ="/tmp/android-bootchart/bootchart.tgz"
START=1
-SLEEP_SEC=30
+SLEEP_SEC=20
for (( l=$START; l<=$LOOPS; l++ )); do
- echo -n "Loop: $l"
+ echo "Loop: $l"
SECONDS=0
- $SCRIPT_DIR/bootanalyze.py -c $CONFIG_YMAL -G 4M -r -b > "$RESULTS_DIR/boot$l.txt"
+ mkdir $RESULTS_DIR/$l
+ $SCRIPT_DIR/bootanalyze.py -c $CONFIG_YMAL -G 4M -r \
+ $ADB_REBOOT_FLAG $BOOTCHART_FLAG $CARWATCHDOG_FLAG \
+ -o "$RESULTS_DIR/$l" 1> "$RESULTS_DIR/$l/boot.txt"
+ if [[ $? -ne 0 ]]; then
+ echo "bootanalyze.py failed"
+ exit 1
+ fi
echo "$SECONDS sec."
- cp /tmp/android-bootchart/bootchart.tgz "$RESULTS_DIR/bootchart$l.tgz"
+ if [ -f "$BOOTCHART_TGZ" ]; then
+ cp $BOOTCHART_TGZ "$RESULTS_DIR/$l/bootchart.tgz"
+ fi
echo "Sleep for $SLEEP_SEC sec."
sleep $SLEEP_SEC
done
diff --git a/boottime_tools/bootanalyze/config.yaml b/boottime_tools/bootanalyze/config.yaml
index 83c1bcd8..a41cfadd 100644
--- a/boottime_tools/bootanalyze/config.yaml
+++ b/boottime_tools/bootanalyze/config.yaml
@@ -63,6 +63,7 @@ events:
BootComplete_kernel: processing action \(sys\.boot_completed=1\)
LauncherStart: START.*HOME.*(NexusLauncherActivity|GEL|LensPickerTrampolineActivity|SetupWizard|CarLauncher|launcher.*Launcher)
FsStat: fs_stat, partition:userdata stat:(0x\S+)
+ CarWatchdogBootupProfilingComplete: Switching to PERIODIC_COLLECTION and PERIODIC_MONITOR
shutdown_events:
ShutdownStart: ShutdownThread:\sNotifying thread to start shutdown
ShutdownBroadcast: ShutdownThread:\sSending shutdown broadcast
diff --git a/boottime_tools/bootanalyze/stressfs/Android.bp b/boottime_tools/bootanalyze/stressfs/Android.bp
index c0dee353..ac8a6250 100644
--- a/boottime_tools/bootanalyze/stressfs/Android.bp
+++ b/boottime_tools/bootanalyze/stressfs/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/boottime_tools/bootio/bootio_collector.cpp b/boottime_tools/bootio/bootio_collector.cpp
index 037a3e2b..3c13055b 100644
--- a/boottime_tools/bootio/bootio_collector.cpp
+++ b/boottime_tools/bootio/bootio_collector.cpp
@@ -15,14 +15,20 @@
*/
#include "bootio_collector.h"
-#include <android-base/logging.h>
+
#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <dirent.h>
+#include <inttypes.h>
#include <log/log.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <unordered_map>
+
#include "protos.pb.h"
#include "time.h"
-#include <unordered_map>
-#include <inttypes.h>
-#include <dirent.h>
namespace android {
diff --git a/checkpoint_gc/checkpoint_gc.sh b/checkpoint_gc/checkpoint_gc.sh
index 10514b5f..26c808cb 100644
--- a/checkpoint_gc/checkpoint_gc.sh
+++ b/checkpoint_gc/checkpoint_gc.sh
@@ -42,16 +42,23 @@ if [ ! -f /dev/sys/fs/by-name/userdata/gc_urgent ]; then
exit 0
fi
+# If we have sufficient free segments, it doesn't matter how much extra
+# space is unusable, since we only need to make it to boot complete to
+# get that space back
+MIN_FREE_SEGMENT=500
+
# Ideally we want to track unusable, as it directly measures what we
# care about. If it's not present, dirty_segments is the best proxy.
if [ -f /dev/sys/fs/by-name/userdata/unusable ]; then
UNUSABLE=1
METRIC="unusable blocks"
THRESHOLD=25000
+ MAX_INCREASE=500
read START < /dev/sys/fs/by-name/userdata/unusable
else
METRIC="dirty segments"
THRESHOLD=200
+ MAX_INCREASE=5
read START < /dev/sys/fs/by-name/userdata/dirty_segments
fi
@@ -79,6 +86,7 @@ fi
CURRENT=${START}
TODO=$((${START}-${THRESHOLD}))
+CUTOFF=$((${START} + ${MAX_INCREASE}))
while [ ${CURRENT} -gt ${THRESHOLD} ]; do
log -pi -t checkpoint_gc ${METRIC}:${CURRENT} \(threshold:${THRESHOLD}\) mode:${GC_TYPE} GC_SLEEP:${GC_SLEEP}
PROGRESS=`echo "(${START}-${CURRENT})/${TODO}"|bc -l`
@@ -91,6 +99,20 @@ while [ ${CURRENT} -gt ${THRESHOLD} ]; do
else
read CURRENT < /dev/sys/fs/by-name/userdata/dirty_segments
fi
+
+ if [ ${CURRENT} -gt ${CUTOFF} ]; then
+ log -pw -t checkpoint_gc Garbage Collection is making no progress. Aborting checkpoint_gc attempt \(initial ${METRIC}: ${START}, now: ${CURRENT}\)
+ break
+ fi
+
+ read CURRENT_FREE_SEGMENTS < /dev/sys/fs/by-name/userdata/free_segments
+ read CURRENT_OVP < /dev/sys/fs/by-name/userdata/ovp_segments
+ CURRENT_FREE_SEG=$((${CURRENT_FREE_SEGMENTS}-${CURRENT_OVP}))
+ if [ ${CURRENT_FREE_SEG} -gt ${MIN_FREE_SEGMENT} ]; then
+ log -pi checkpoint_gc Sufficient free segments. Extra gc not needed.
+ break
+ fi
+
sleep ${SLEEP}
TIME=$((${TIME}+${SLEEP}))
if [ ${TIME} -gt ${MAX_TIME} ]; then
diff --git a/crypto-perf/Android.bp b/crypto-perf/Android.bp
deleted file mode 100644
index 8f575ca9..00000000
--- a/crypto-perf/Android.bp
+++ /dev/null
@@ -1,35 +0,0 @@
-package {
- default_applicable_licenses: ["system_extras_crypto-perf_license"],
-}
-
-// Added automatically by a large-scale-change
-// See: http://go/android-license-faq
-license {
- name: "system_extras_crypto-perf_license",
- visibility: [":__subpackages__"],
- license_kinds: [
- "SPDX-license-identifier-Apache-2.0",
- ],
- license_text: [
- "NOTICE",
- ],
-}
-
-cc_binary {
- name: "crypto",
-
- cflags: [
- "-O0",
- "-march=armv8-a+crypto",
- "-Wall",
- "-Werror",
- ],
- srcs: ["crypto.cpp"],
-
- enabled: false,
- arch: {
- arm64: {
- enabled: true,
- },
- },
-}
diff --git a/crypto-perf/NOTICE b/crypto-perf/NOTICE
deleted file mode 100644
index c77f135e..00000000
--- a/crypto-perf/NOTICE
+++ /dev/null
@@ -1,190 +0,0 @@
-
- Copyright (c) 2012, 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.
-
- 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.
-
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
diff --git a/crypto-perf/crypto.cpp b/crypto-perf/crypto.cpp
deleted file mode 100644
index a7228324..00000000
--- a/crypto-perf/crypto.cpp
+++ /dev/null
@@ -1,165 +0,0 @@
-#include <ctype.h>
-#include <sched.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/resource.h>
-#include <sys/time.h>
-#include <unistd.h>
-#define USEC_PER_SEC 1000000ULL
-#define MAX_COUNT 1000000000ULL
-#define NUM_INSTS_GARBAGE 18
-
-// Contains information about benchmark options.
-typedef struct {
- int cpu_to_lock;
- int locked_freq;
-} command_data_t;
-
-void usage() {
- printf("--------------------------------------------------------------------------------\n");
- printf("Usage:");
- printf(" crypto [--cpu_to_lock CPU] [--locked_freq FREQ_IN_KHZ]\n\n");
- printf("!!!!!!Lock the desired core to a desired frequency before invoking this benchmark.\n");
- printf("Hint: Set scaling_max_freq=scaling_min_freq=FREQ_IN_KHZ. FREQ_IN_KHZ "
- "can be obtained from scaling_available_freq\n");
- printf("--------------------------------------------------------------------------------\n");
-}
-
-int processOptions(int argc, char** argv, command_data_t* cmd_data) {
- // Initialize the command_flags.
- cmd_data->cpu_to_lock = 0;
- cmd_data->locked_freq = 1;
- for (int i = 1; i < argc; i++) {
- if (argv[i][0] == '-') {
- int* save_value = NULL;
- if (strcmp(argv[i], "--cpu_to_lock") == 0) {
- save_value = &cmd_data->cpu_to_lock;
- } else if (strcmp(argv[i], "--locked_freq") == 0) {
- save_value = &cmd_data->locked_freq;
- } else {
- printf("Unknown option %s\n", argv[i]);
- return -1;
- }
- if (save_value) {
- // Checking both characters without a strlen() call should be
- // safe since as long as the argument exists, one character will
- // be present (\0). And if the first character is '-', then
- // there will always be a second character (\0 again).
- if (i == argc - 1 || (argv[i + 1][0] == '-' && !isdigit(argv[i + 1][1]))) {
- printf("The option %s requires one argument.\n", argv[i]);
- return -1;
- }
- *save_value = (int)strtol(argv[++i], NULL, 0);
- }
- }
- }
- return 0;
-}
-/* Performs encryption on garbage values. In Cortex-A57 r0p1 and later
- * revisions, pairs of dependent AESE/AESMC and AESD/AESIMC instructions are
- * higher performance when adjacent, and in the described order below. */
-void garbage_encrypt() {
- __asm__ __volatile__(
- "aese v0.16b, v4.16b ;"
- "aesmc v0.16b, v0.16b ;"
- "aese v1.16b, v4.16b ;"
- "aesmc v1.16b, v1.16b ;"
- "aese v2.16b, v4.16b ;"
- "aesmc v2.16b, v2.16b ;"
- "aese v0.16b, v5.16b ;"
- "aesmc v0.16b, v0.16b ;"
- "aese v1.16b, v5.16b ;"
- "aesmc v1.16b, v1.16b ;"
- "aese v2.16b, v5.16b ;"
- "aesmc v2.16b, v2.16b ;"
- "aese v0.16b, v6.16b ;"
- "aesmc v0.16b, v0.16b ;"
- "aese v1.16b, v6.16b ;"
- "aesmc v1.16b, v1.16b ;"
- "aese v2.16b, v6.16b ;"
- "aesmc v2.16b, v2.16b ;");
-}
-
-void garbage_decrypt() {
- __asm__ __volatile__(
- "aesd v0.16b, v4.16b ;"
- "aesimc v0.16b, v0.16b ;"
- "aesd v1.16b, v4.16b ;"
- "aesimc v1.16b, v1.16b ;"
- "aesd v2.16b, v4.16b ;"
- "aesimc v2.16b, v2.16b ;"
- "aesd v0.16b, v5.16b ;"
- "aesimc v0.16b, v0.16b ;"
- "aesd v1.16b, v5.16b ;"
- "aesimc v1.16b, v1.16b ;"
- "aesd v2.16b, v5.16b ;"
- "aesimc v2.16b, v2.16b ;"
- "aesd v0.16b, v6.16b ;"
- "aesimc v0.16b, v0.16b ;"
- "aesd v1.16b, v6.16b ;"
- "aesimc v1.16b, v1.16b ;"
- "aesd v2.16b, v6.16b ;"
- "aesimc v2.16b, v2.16b ;");
-}
-
-int main(int argc, char** argv) {
- usage();
- command_data_t cmd_data;
-
- if (processOptions(argc, argv, &cmd_data) == -1) {
- usage();
- return -1;
- }
- unsigned long long count = 0;
- struct timeval begin_time, end_time, elapsed_time;
- cpu_set_t cpuset;
- CPU_ZERO(&cpuset);
- CPU_SET(cmd_data.cpu_to_lock, &cpuset);
- if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) {
- perror("sched_setaffinity failed");
- return 1;
- }
- gettimeofday(&begin_time, NULL);
- while (count < MAX_COUNT) {
- garbage_encrypt();
- count++;
- }
- gettimeofday(&end_time, NULL);
- timersub(&end_time, &begin_time, &elapsed_time);
- fprintf(stderr, "encrypt: %llu us\n",
- elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec);
- fprintf(stderr, "encrypt instructions: %llu\n", MAX_COUNT * NUM_INSTS_GARBAGE);
- fprintf(stderr, "encrypt instructions per second: %f\n",
- (float)(MAX_COUNT * NUM_INSTS_GARBAGE * USEC_PER_SEC) /
- (elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec));
- if (cmd_data.locked_freq != 0) {
- fprintf(stderr, "encrypt instructions per cycle: %f\n",
- (float)(MAX_COUNT * NUM_INSTS_GARBAGE * USEC_PER_SEC) /
- ((elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec) * 1000 *
- cmd_data.locked_freq));
- }
- printf("--------------------------------------------------------------------------------\n");
-
- count = 0;
- gettimeofday(&begin_time, NULL);
- while (count < MAX_COUNT) {
- garbage_decrypt();
- count++;
- }
- gettimeofday(&end_time, NULL);
- timersub(&end_time, &begin_time, &elapsed_time);
- fprintf(stderr, "decrypt: %llu us\n",
- elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec);
- fprintf(stderr, "decrypt instructions: %llu\n", MAX_COUNT * NUM_INSTS_GARBAGE);
- fprintf(stderr, "decrypt instructions per second: %f\n",
- (float)(MAX_COUNT * NUM_INSTS_GARBAGE * USEC_PER_SEC) /
- (elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec));
- if (cmd_data.locked_freq != 0) {
- fprintf(stderr, "decrypt instructions per cycle: %f\n",
- (float)(MAX_COUNT * NUM_INSTS_GARBAGE * USEC_PER_SEC) /
- ((elapsed_time.tv_sec * USEC_PER_SEC + elapsed_time.tv_usec) * 1000 *
- cmd_data.locked_freq));
- }
- return 0;
-}
diff --git a/ext4_utils/Android.bp b/ext4_utils/Android.bp
index ba2c8ac0..b28e84f3 100644
--- a/ext4_utils/Android.bp
+++ b/ext4_utils/Android.bp
@@ -32,6 +32,7 @@ cc_library {
cflags: [
"-Werror",
"-fno-strict-aliasing",
+ "-D_FILE_OFFSET_BITS=64",
],
export_include_dirs: ["include"],
shared_libs: [
diff --git a/ext4_utils/ext4_utils.cpp b/ext4_utils/ext4_utils.cpp
index 5fce61bc..dde75903 100644
--- a/ext4_utils/ext4_utils.cpp
+++ b/ext4_utils/ext4_utils.cpp
@@ -83,12 +83,9 @@ int ext4_bg_has_super_block(int bg) {
/* Function to read the primary superblock */
void read_sb(int fd, struct ext4_super_block* sb) {
- off64_t ret;
+ if (lseek(fd, 1024, SEEK_SET) < 0) critical_error_errno("failed to seek to superblock");
- ret = lseek64(fd, 1024, SEEK_SET);
- if (ret < 0) critical_error_errno("failed to seek to superblock");
-
- ret = read(fd, sb, sizeof(*sb));
+ ssize_t ret = read(fd, sb, sizeof(*sb));
if (ret < 0) critical_error_errno("failed to read superblock");
if (ret != sizeof(*sb)) critical_error("failed to read all of superblock");
}
@@ -277,17 +274,17 @@ static void read_block_group_descriptors(int fd) {
}
int read_ext(int fd, int verbose) {
- off64_t ret;
+ off_t ret;
struct ext4_super_block sb;
read_sb(fd, &sb);
ext4_parse_sb_info(&sb);
- ret = lseek64(fd, info.len, SEEK_SET);
+ ret = lseek(fd, info.len, SEEK_SET);
if (ret < 0) critical_error_errno("failed to seek to end of input image");
- ret = lseek64(fd, info.block_size * (aux_info.first_data_block + 1), SEEK_SET);
+ ret = lseek(fd, info.block_size * (aux_info.first_data_block + 1), SEEK_SET);
if (ret < 0) critical_error_errno("failed to seek to block group descriptors");
read_block_group_descriptors(fd);
diff --git a/ext4_utils/include/ext4_utils/ext4_utils.h b/ext4_utils/include/ext4_utils/ext4_utils.h
index 48f3ee78..d6bef68d 100644
--- a/ext4_utils/include/ext4_utils/ext4_utils.h
+++ b/ext4_utils/include/ext4_utils/ext4_utils.h
@@ -21,11 +21,6 @@
extern "C" {
#endif
-#ifndef _GNU_SOURCE
-#define _GNU_SOURCE
-#endif
-#define _FILE_OFFSET_BITS 64
-#define _LARGEFILE64_SOURCE 1
#include <sys/types.h>
#include <unistd.h>
@@ -38,13 +33,6 @@ extern "C" {
#include <string.h>
#include <sys/types.h>
-#if defined(__APPLE__) && defined(__MACH__)
-#define lseek64 lseek
-#define ftruncate64 ftruncate
-#define mmap64 mmap
-#define off64_t off_t
-#endif
-
#include "ext4_sb.h"
extern int force;
diff --git a/f2fs_utils/f2fs_sparseblock.c b/f2fs_utils/f2fs_sparseblock.c
index 234ea7e8..487dfa9d 100644
--- a/f2fs_utils/f2fs_sparseblock.c
+++ b/f2fs_utils/f2fs_sparseblock.c
@@ -34,6 +34,9 @@
#define sit_in_journal(jnl, i) ((jnl)->sit_j.entries[i].se)
+/* Default to 4K blocks. Will replace with actual blocksize when we read superblock */
+struct f2fs_configuration c = {.blksize = 4096, .blksize_bits = 12};
+
static void dbg_print_raw_sb_info(struct f2fs_super_block* sb) {
SLOGV("\n");
SLOGV("+--------------------------------------------------------+\n");
@@ -137,11 +140,11 @@ static void dbg_print_info_struct(struct f2fs_info* info) {
SLOGV("blocks_per_sit: %" PRIu64, info->blocks_per_sit);
SLOGV("sit_blocks loc: %p", info->sit_blocks);
SLOGV("sit_sums loc: %p", info->sit_sums);
- SLOGV("sit_sums num: %d", le16_to_cpu(info->sit_sums->journal.n_sits));
+ SLOGV("sit_sums num: %d", le16_to_cpu(F2FS_SUMMARY_BLOCK_JOURNAL(info->sit_sums)->n_sits));
unsigned int i;
- for (i = 0; i < (le16_to_cpu(info->sit_sums->journal.n_sits)); i++) {
+ for (i = 0; i < (le16_to_cpu(F2FS_SUMMARY_BLOCK_JOURNAL(info->sit_sums)->n_sits)); i++) {
SLOGV("entry %d in journal entries is for segment %d", i,
- le32_to_cpu(segno_in_journal(&info->sit_sums->journal, i)));
+ le32_to_cpu(segno_in_journal(F2FS_SUMMARY_BLOCK_JOURNAL(info->sit_sums), i)));
}
SLOGV("cp_blkaddr: %" PRIu64, info->cp_blkaddr);
@@ -297,6 +300,10 @@ fail_no_cp:
return -EINVAL;
}
+static inline struct f2fs_sit_block* get_sit_block(struct f2fs_info* info, uint64_t sit_block) {
+ return (struct f2fs_sit_block*)((char*)info->sit_blocks + sit_block * F2FS_BLKSIZE);
+}
+
static int gather_sit_info(int fd, struct f2fs_info* info) {
uint64_t num_segments =
(info->total_blocks - info->main_blkaddr + info->blocks_per_segment - 1) /
@@ -304,7 +311,7 @@ static int gather_sit_info(int fd, struct f2fs_info* info) {
uint64_t num_sit_blocks = (num_segments + SIT_ENTRY_PER_BLOCK - 1) / SIT_ENTRY_PER_BLOCK;
uint64_t sit_block;
- info->sit_blocks = malloc(num_sit_blocks * sizeof(struct f2fs_sit_block));
+ info->sit_blocks = malloc(num_sit_blocks * F2FS_BLKSIZE);
if (!info->sit_blocks) return -1;
for (sit_block = 0; sit_block < num_sit_blocks; sit_block++) {
@@ -313,8 +320,8 @@ static int gather_sit_info(int fd, struct f2fs_info* info) {
if (f2fs_test_bit(sit_block, info->sit_bmp)) address += info->blocks_per_sit;
SLOGV("Reading cache block starting at block %" PRIu64, address);
- if (read_structure(fd, address * F2FS_BLKSIZE, &info->sit_blocks[sit_block],
- sizeof(struct f2fs_sit_block))) {
+ if (read_structure(fd, address * F2FS_BLKSIZE, get_sit_block(info, sit_block),
+ F2FS_BLKSIZE)) {
SLOGE("Could not read sit block at block %" PRIu64, address);
free(info->sit_blocks);
info->sit_blocks = NULL;
@@ -338,7 +345,7 @@ static inline uint64_t sum_blk_addr(struct f2fs_checkpoint* cp, struct f2fs_info
static int get_sit_summary(int fd, struct f2fs_info* info, struct f2fs_checkpoint* cp) {
char buffer[F2FS_BLKSIZE];
- info->sit_sums = calloc(1, sizeof(struct f2fs_summary_block));
+ info->sit_sums = calloc(1, F2FS_BLKSIZE);
if (!info->sit_sums) return -1;
/* CURSEG_COLD_DATA where the journaled SIT entries are. */
@@ -346,7 +353,8 @@ static int get_sit_summary(int fd, struct f2fs_info* info, struct f2fs_checkpoin
if (read_structure_blk(fd, info->cp_valid_cp_blkaddr + le32_to_cpu(cp->cp_pack_start_sum),
buffer, 1))
return -1;
- memcpy(&info->sit_sums->journal.n_sits, &buffer[SUM_JOURNAL_SIZE], SUM_JOURNAL_SIZE);
+ memcpy(&F2FS_SUMMARY_BLOCK_JOURNAL(info->sit_sums)->n_sits, &buffer[SUM_JOURNAL_SIZE],
+ SUM_JOURNAL_SIZE);
} else {
uint64_t blk_addr;
if (is_set_ckpt_flags(cp, CP_UMOUNT_FLAG))
@@ -356,7 +364,7 @@ static int get_sit_summary(int fd, struct f2fs_info* info, struct f2fs_checkpoin
if (read_structure_blk(fd, blk_addr, buffer, 1)) return -1;
- memcpy(info->sit_sums, buffer, sizeof(struct f2fs_summary_block));
+ memcpy(info->sit_sums, buffer, F2FS_BLKSIZE);
}
return 0;
}
@@ -384,6 +392,8 @@ struct f2fs_info* generate_f2fs_info(int fd) {
free(sb);
return NULL;
}
+ c.blksize_bits = get_sb(log_blocksize);
+ c.blksize = 1 << c.blksize_bits;
dbg_print_raw_sb_info(sb);
info->cp_blkaddr = le32_to_cpu(sb->cp_blkaddr);
@@ -485,9 +495,10 @@ int run_on_used_blocks(uint64_t startblock, struct f2fs_info* info,
/* check the SIT entries in the journal */
found = 0;
- for (i = 0; i < le16_to_cpu(info->sit_sums->journal.n_sits); i++) {
- if (le32_to_cpu(segno_in_journal(&info->sit_sums->journal, i)) == segnum) {
- sit_entry = &sit_in_journal(&info->sit_sums->journal, i);
+ for (i = 0; i < le16_to_cpu(F2FS_SUMMARY_BLOCK_JOURNAL(info->sit_sums)->n_sits); i++) {
+ if (le32_to_cpu(segno_in_journal(F2FS_SUMMARY_BLOCK_JOURNAL(info->sit_sums), i)) ==
+ segnum) {
+ sit_entry = &sit_in_journal(F2FS_SUMMARY_BLOCK_JOURNAL(info->sit_sums), i);
found = 1;
break;
}
@@ -496,8 +507,8 @@ int run_on_used_blocks(uint64_t startblock, struct f2fs_info* info,
/* get SIT entry from SIT section */
if (!found) {
sit_block_num_cur = segnum / SIT_ENTRY_PER_BLOCK;
- sit_entry =
- &info->sit_blocks[sit_block_num_cur].entries[segnum % SIT_ENTRY_PER_BLOCK];
+ sit_entry = &get_sit_block(info, sit_block_num_cur)
+ ->entries[segnum % SIT_ENTRY_PER_BLOCK];
}
block_offset = (block - info->main_blkaddr) % info->blocks_per_segment;
diff --git a/f2fs_utils/mkf2fsuserimg.sh b/f2fs_utils/mkf2fsuserimg.sh
index 59f9eea5..4290c50b 100755
--- a/f2fs_utils/mkf2fsuserimg.sh
+++ b/f2fs_utils/mkf2fsuserimg.sh
@@ -9,7 +9,7 @@ ${0##*/} OUTPUT_FILE SIZE
[-S] [-C FS_CONFIG] [-f SRC_DIR] [-D PRODUCT_OUT]
[-s FILE_CONTEXTS] [-t MOUNT_POINT] [-T TIMESTAMP] [-B block_map]
[-L LABEL] [--prjquota] [--casefold] [--compression] [--readonly]
- [--sldc <num> [sload compression sub-options]]
+ [--sldc <num> [sload compression sub-options]] [-b <block_size>]
<num>: number of the sload compression args, e.g. -a LZ4 counts as 2
when sload compression args are not given, <num> must be 0,
and the default flags will be used.
@@ -130,6 +130,19 @@ if [[ "$1" == "--sldc" ]]; then
done
fi
+if [[ "$1" == "-b" ]]; then
+ shift
+ BLOCKSIZE=$1
+ case $BLOCKSIZE in
+ ''|*[!0-9]*)
+ echo "-b needs a number"
+ exit 3 ;;
+ esac
+ shift
+ MKFS_OPTS+=" -b $BLOCKSIZE"
+ MKFS_OPTS+=" -w $BLOCKSIZE"
+fi
+
if [ -z $SIZE ]; then
echo "Need size of filesystem"
exit 2
@@ -163,13 +176,14 @@ function _build()
SLOAD_F2FS_CMD="sload_f2fs $SLOAD_OPTS $OUTPUT_FILE"
echo $SLOAD_F2FS_CMD
- MB_SIZE=`$SLOAD_F2FS_CMD | grep "Max image size" | awk '{print $5}'`
+ SLOAD_LOG=`$SLOAD_F2FS_CMD`
# allow 1: Filesystem errors corrected
ret=$?
if [ $ret -ne 0 ] && [ $ret -ne 1 ]; then
rm -f $OUTPUT_FILE
exit 4
fi
+ MB_SIZE=`echo "$SLOAD_LOG" | grep "Max image size" | awk '{print $5}'`
SIZE=$(((MB_SIZE + 6) * 1024 * 1024))
}
diff --git a/kcmdlinectrl/.clang-format b/kcmdlinectrl/.clang-format
new file mode 120000
index 00000000..242a033c
--- /dev/null
+++ b/kcmdlinectrl/.clang-format
@@ -0,0 +1 @@
+../../core/.clang-format-2 \ No newline at end of file
diff --git a/setuclamp/Android.bp b/kcmdlinectrl/Android.bp
index 8aa0e768..745c824a 100644
--- a/setuclamp/Android.bp
+++ b/kcmdlinectrl/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2021 The Android Open Source Project
+// Copyright (C) 2022 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.
@@ -17,21 +17,11 @@ package {
}
cc_binary {
- name: "setuclamp",
- cflags: [
- "-Wall",
- "-Werror",
+ name: "kcmdlinectrl",
+ srcs: ["kcmdlinectrl.cc"],
+ shared_libs: [
+ "libbootloader_message",
+ "libbase",
],
- srcs: ["setuclamp.cpp"],
-}
-
-cc_binary {
- name: "setuclamp_vendor",
- cflags: [
- "-Wall",
- "-Werror",
- ],
- stem: "setuclamp",
- vendor: true,
- srcs: ["setuclamp.cpp"],
+ init_rc: ["kcmdlinectrl.rc"],
}
diff --git a/kcmdlinectrl/OWNERS b/kcmdlinectrl/OWNERS
new file mode 100644
index 00000000..ad8cee6c
--- /dev/null
+++ b/kcmdlinectrl/OWNERS
@@ -0,0 +1 @@
+aliceryhl@google.com
diff --git a/kcmdlinectrl/kcmdlinectrl.cc b/kcmdlinectrl/kcmdlinectrl.cc
new file mode 100644
index 00000000..aafdb9a8
--- /dev/null
+++ b/kcmdlinectrl/kcmdlinectrl.cc
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include <getopt.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <bootloader_message/bootloader_message.h>
+
+#include <functional>
+#include <iostream>
+
+void PrintUsage(const char* progname) {
+ std::cerr << "USAGE: " << progname << " get [PROPERTY]" << std::endl;
+ std::cerr << " " << progname << " store [PROPERTY] [VALUE]" << std::endl;
+ std::cerr << " " << progname << " update-props" << std::endl;
+}
+
+int UpdateProps() {
+ misc_kcmdline_message m = {.version = MISC_KCMDLINE_MESSAGE_VERSION,
+ .magic = MISC_KCMDLINE_MAGIC_HEADER};
+ std::string err;
+ if (!ReadMiscKcmdlineMessage(&m, &err)) {
+ LOG(ERROR) << "Failed to read from misc: " << err;
+ return 1;
+ }
+
+ // If invalid, treat it as-if all flags are zero.
+ if (m.magic != MISC_KCMDLINE_MAGIC_HEADER || m.version != MISC_KCMDLINE_MESSAGE_VERSION) {
+ m = {.version = MISC_KCMDLINE_MESSAGE_VERSION,
+ .magic = MISC_KCMDLINE_MAGIC_HEADER,
+ .kcmdline_flags = 0};
+ }
+
+ bool use_rust_binder = (m.kcmdline_flags & MISC_KCMDLINE_BINDER_RUST) != 0;
+ android::base::SetProperty("kcmdline.binder", use_rust_binder ? "rust" : "c");
+
+ android::base::SetProperty("kcmdline.loaded", "1");
+ return 0;
+}
+
+int PrintProperty(const char* property_name) {
+ misc_kcmdline_message m = {.version = MISC_KCMDLINE_MESSAGE_VERSION,
+ .magic = MISC_KCMDLINE_MAGIC_HEADER};
+
+ std::string err;
+ if (!ReadMiscKcmdlineMessage(&m, &err)) {
+ LOG(ERROR) << "Failed to read from misc: " << err;
+ return 1;
+ }
+
+ if (m.magic != MISC_KCMDLINE_MAGIC_HEADER || m.version != MISC_KCMDLINE_MESSAGE_VERSION) {
+ std::cout << "kcmdline message is invalid, treating all flags as zero" << std::endl;
+ m = {.version = MISC_KCMDLINE_MESSAGE_VERSION,
+ .magic = MISC_KCMDLINE_MAGIC_HEADER,
+ .kcmdline_flags = 0};
+ }
+
+ if (!strcmp(property_name, "binder")) {
+ bool use_rust_binder = (m.kcmdline_flags & MISC_KCMDLINE_BINDER_RUST) != 0;
+ const char* binder_value = use_rust_binder ? "rust" : "c";
+ std::cout << "binder=" << binder_value << std::endl;
+ return 0;
+ } else {
+ LOG(ERROR) << "Unknown property name: " << property_name;
+ return 1;
+ }
+}
+
+int StoreProperty(const char* property_name, const char* new_value) {
+ misc_kcmdline_message m = {.version = MISC_KCMDLINE_MESSAGE_VERSION,
+ .magic = MISC_KCMDLINE_MAGIC_HEADER};
+
+ std::string err;
+ if (!ReadMiscKcmdlineMessage(&m, &err)) {
+ LOG(ERROR) << "Failed to read from misc: " << err;
+ return 1;
+ }
+
+ if (m.magic != MISC_KCMDLINE_MAGIC_HEADER || m.version != MISC_KCMDLINE_MESSAGE_VERSION) {
+ std::cout << "kcmdline message is invalid, resetting it" << std::endl;
+ m = {.version = MISC_KCMDLINE_MESSAGE_VERSION,
+ .magic = MISC_KCMDLINE_MAGIC_HEADER,
+ .kcmdline_flags = 0};
+ }
+
+ if (!strcmp(property_name, "binder")) {
+ if (!strcmp(new_value, "rust")) {
+ m.kcmdline_flags |= MISC_KCMDLINE_BINDER_RUST;
+ } else if (!strcmp(new_value, "c")) {
+ m.kcmdline_flags &= !MISC_KCMDLINE_BINDER_RUST;
+ } else {
+ LOG(ERROR) << "Binder property can only be 'c' or 'rust', but got " << new_value;
+ return 1;
+ }
+ } else {
+ LOG(ERROR) << "Unknown property name: " << property_name;
+ return 1;
+ }
+
+ if (!WriteMiscKcmdlineMessage(m, &err)) {
+ LOG(ERROR) << "Failed to write to misc: " << err;
+ return 1;
+ }
+
+ return 0;
+}
+
+int main(int argc, char** argv) {
+ char *action, *property_name, *new_value;
+
+ if (argc == 2) {
+ action = argv[1];
+ property_name = NULL;
+ new_value = NULL;
+ } else if (argc == 3) {
+ action = argv[1];
+ property_name = argv[2];
+ new_value = NULL;
+ } else if (argc == 4) {
+ action = argv[1];
+ property_name = argv[2];
+ new_value = argv[3];
+ } else {
+ PrintUsage(*argv);
+ return 1;
+ }
+
+ if (!strcmp(action, "update-props") && property_name == NULL) {
+ return UpdateProps();
+ } else if (!strcmp(action, "get") && property_name != NULL && new_value == NULL) {
+ return PrintProperty(property_name);
+ } else if (!strcmp(action, "store") && property_name != NULL && new_value != NULL) {
+ return StoreProperty(property_name, new_value);
+ } else {
+ PrintUsage(*argv);
+ return 1;
+ }
+}
diff --git a/kcmdlinectrl/kcmdlinectrl.rc b/kcmdlinectrl/kcmdlinectrl.rc
new file mode 100644
index 00000000..e0a3e7f0
--- /dev/null
+++ b/kcmdlinectrl/kcmdlinectrl.rc
@@ -0,0 +1,22 @@
+# Copyright (C) 2022 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.
+
+# adbd gets initialized in init, so run before that. this makes sure that the
+# user does not change the value before we initialize it
+on early-boot
+ exec_background -- /system/bin/kcmdlinectrl update-props
+
+on property:kcmdline.binder=*
+ wait_for_prop kcmdline.loaded 1
+ exec -- /system/bin/kcmdlinectrl store binder ${kcmdline.binder:-c}
diff --git a/libatrace_rust/Android.bp b/libatrace_rust/Android.bp
new file mode 100644
index 00000000..443a8885
--- /dev/null
+++ b/libatrace_rust/Android.bp
@@ -0,0 +1,109 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "libatrace_tracing_subscriber_defaults",
+ srcs: ["src/tracing_subscriber.rs"],
+ rustlibs: [
+ "libatrace_rust",
+ "libtracing",
+ "libtracing_subscriber",
+ ],
+}
+
+rust_library {
+ name: "libatrace_tracing_subscriber",
+ crate_name: "atrace_tracing_subscriber",
+ defaults: ["libatrace_tracing_subscriber_defaults"],
+ // Host support is for unit tests.
+ host_supported: true,
+ product_available: true,
+ vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+}
+
+rust_test_host {
+ name: "libatrace_tracing_subscriber_inline_tests",
+ defaults: ["libatrace_tracing_subscriber_defaults"],
+ test_suites: ["general_tests"],
+ rustlibs: [
+ "libonce_cell",
+ "libthread_local",
+ ],
+}
+
+rust_defaults {
+ name: "libatrace_rust_defaults",
+ srcs: ["src/lib.rs"],
+ rustlibs: [
+ "libcutils_trace_bindgen",
+ "libstatic_assertions",
+ "libbitflags",
+ ],
+}
+
+rust_library {
+ name: "libatrace_rust",
+ crate_name: "atrace",
+ defaults: ["libatrace_rust_defaults"],
+ // Host support is for unit tests.
+ host_supported: true,
+ product_available: true,
+ vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+}
+
+rust_test_host {
+ name: "libatrace_rust_inline_tests",
+ defaults: ["libatrace_rust_defaults"],
+ test_suites: ["general_tests"],
+ rustlibs: [
+ "libonce_cell",
+ "libthread_local",
+ ],
+}
+
+rust_bindgen {
+ name: "libcutils_trace_bindgen",
+ crate_name: "cutils_trace_bindgen",
+ wrapper_src: "bindgen/cutils_trace.h",
+ source_stem: "cutils_trace",
+ bindgen_flags: [
+ "--allowlist-function=atrace_.*",
+ "--allowlist-var=ATRACE_.*",
+ "--allowlist-var=atrace_.*",
+ ],
+ shared_libs: ["libcutils"],
+ static_libs: ["libcutils_trace_bindgen_wrap"],
+ // Host support is for unit tests.
+ host_supported: true,
+ product_available: true,
+ vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+}
+
+// TODO: b/291544011 - Replace with autogenerated wrappers once they are supported.
+cc_library_static {
+ name: "libcutils_trace_bindgen_wrap",
+ srcs: ["bindgen/cutils_trace_wrap.c"],
+ visibility: [":__subpackages__"],
+ shared_libs: ["libcutils"],
+ // Host support is for unit tests.
+ host_supported: true,
+ product_available: true,
+ vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+}
diff --git a/libatrace_rust/OWNERS b/libatrace_rust/OWNERS
new file mode 100644
index 00000000..2830d9f3
--- /dev/null
+++ b/libatrace_rust/OWNERS
@@ -0,0 +1,3 @@
+nputikhin@google.com
+dextero@google.com
+vill@google.com \ No newline at end of file
diff --git a/libatrace_rust/README.md b/libatrace_rust/README.md
new file mode 100644
index 00000000..4d4fa898
--- /dev/null
+++ b/libatrace_rust/README.md
@@ -0,0 +1,129 @@
+# libatrace_rust - ATrace bindings for Rust
+
+Wrapper library for ATrace methods from libcutils.
+
+## Quick start
+
+### Using ATrace bindings directly
+
+Add the library to your `rustlibs` in `Android.bp`:
+
+```text
+rustlibs: [
+ ...
+ "libatrace_rust",
+ ...
+],
+```
+
+Call tracing methods:
+
+```rust
+fn important_function() {
+ // Use this macro to trace a function.
+ atrace::trace_method!(AtraceTag::App);
+
+ if condition {
+ // Use a scoped event to trace inside a scope.
+ let _event = atrace::begin_scoped_event(AtraceTag::App, "Inside a scope");
+ ...
+ }
+
+ // Or just use the wrapped API directly.
+ atrace::atrace_begin(AtraceTag::App, "My event");
+ ...
+ atrace::atrace_end(AtraceTag::App)
+}
+```
+
+See more in the [example](./example/src/main.rs).
+
+You're all set! Now you can collect a trace with your favorite tracing tool like
+[Perfetto](https://perfetto.dev/docs/data-sources/atrace).
+
+### Using the tracing crate
+
+You can use the ATrace layer for the [tracing](https://docs.rs/tracing/latest/tracing/) crate.
+Compared to using the bindings directly, it has better instrumentation points and customizability.
+The main drawback is lower performance. See the [Performance](#performance) section below for more
+information.
+
+Add the tracing libraries to your `rustlibs` in `Android.bp`:
+
+```text
+ rustlibs: [
+ ...
+ "libatrace_tracing_subscriber",
+ "libtracing_subscriber",
+ "libtracing",
+ ...
+ ],
+```
+
+[Initialize](https://docs.rs/tracing/latest/tracing/index.html#in-executables) the subscriber
+before calling the tracing methods, usually somewhere in the beginning of `main()`.
+
+```rust
+// Initialize the subscriber, panic if it fails.
+tracing_subscriber::registry()
+ .with(AtraceSubscriber::default().with_filter())
+ .init();
+```
+
+The subscriber defaults to `AtraceTag::App`. Use other tags by creating the subscriber with
+`AtraceSubscriber::new(tag: AtraceTag)`.
+
+You can combine the subscriber with other
+[layers](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/layer/index.html). In
+that case, omit `with_filter()` from the `AtraceSubscriber` initialization - it is an optimization
+that disables instrumentation points when ATrace is disabled and it would affect other layers as
+well.
+
+Now you can
+[record spans and events](https://docs.rs/tracing/latest/tracing/index.html#recording-spans-and-events):
+
+```rust
+// This macro would automatically create and enter a span with function name and arguments.
+#[tracing::instrument]
+fn important_function() {
+ if condition {
+ // Use span! to trace inside a scope.
+ let _entered = tracing::span!(tracing::Level::TRACE, "Inside a scope").entered();
+ ...
+ }
+
+ // Use event! to record an instant event.
+ // You can annotate spans and events with fields. They will be appended to the end of
+ // the Atrace event.
+ tracing::info!(field="value", "My event");
+}
+```
+
+See more in the [example](./example/src/tracing_subscriber_sample.rs) and check out the docs for
+the [tracing](https://docs.rs/tracing/latest/tracing/index.html) and
+[tracing-subscriber](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/index.html)
+crates.
+
+## Performance
+
+This section is an overview, you can find specific numbers in
+[benchmark/README.md](./benchmark/README.md).
+
+### ATrace bindings
+
+When tracing is enabled, you can expect 1-10 us per event - this is a significant cost that may
+affect the performance of hot high-frequency methods. When the events are disabled, calling them is
+cheap - on the order of 5-10 ns. There is a 10-20% overhead from the wrapper, mostly caused by
+string conversion when tracing is enabled.
+
+### Tracing subscriber
+
+The subscriber uses the bindings and adds its own overhead that depends on usage:
+
+* With tracing disabled and subscriber created `with_filter`, events cost around 30 ns. Not using
+ the filter brings the cost up to 100-400 ns per event.
+* Instant events (`event!`) add roughly 200 ns to the bindings - 1.5 vs 1.3 us.
+* Spans (`span!`) are roughly 400 ns slower - 2.8 vs 2.4 us.
+* Using [fields](https://docs.rs/tracing/latest/tracing/index.html#recording-fields) adds time
+ that depends on the amount of the fields and the cost of converting them to strings. Typically
+ it is around an extra 500 ns per event and an extra 1 us for a span.
diff --git a/libatrace_rust/benchmark/Android.bp b/libatrace_rust/benchmark/Android.bp
new file mode 100644
index 00000000..9ab9070b
--- /dev/null
+++ b/libatrace_rust/benchmark/Android.bp
@@ -0,0 +1,63 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_binary {
+ name: "libatrace_rust_benchmark",
+ srcs: ["src/atrace_benchmark.rs"],
+ rustlibs: [
+ "libatrace_rust",
+ "libatrace_rust_benchmark_common",
+ "libcriterion",
+ ],
+}
+
+rust_binary {
+ name: "libatrace_tracing_subscriber_benchmark",
+ srcs: ["src/tracing_subscriber_benchmark.rs"],
+ rustlibs: [
+ "libatrace_rust_benchmark_common",
+ "libatrace_tracing_subscriber",
+ "libcriterion",
+ "libtracing",
+ "libtracing_subscriber",
+ ],
+}
+
+rust_library {
+ name: "libatrace_rust_benchmark_common",
+ crate_name: "atrace_rust_benchmark_common",
+ srcs: ["src/atrace_benchmark_common.rs"],
+ rustlibs: [
+ "libcriterion",
+ ],
+ shared_libs: [
+ "libcutils",
+ "liblog",
+ ],
+ static_libs: [
+ "libatrace_benchmark_trace_enabler",
+ ],
+}
+
+cc_binary {
+ name: "libatrace_rust_benchmark_cc",
+ srcs: ["src/atrace_benchmark.cc"],
+ shared_libs: [
+ "libcutils",
+ "liblog",
+ ],
+ static_libs: [
+ "libgoogle-benchmark-main",
+ "libatrace_benchmark_trace_enabler",
+ ],
+}
+
+cc_library_static {
+ name: "libatrace_benchmark_trace_enabler",
+ srcs: ["src/trace_enabler.cc"],
+ shared_libs: [
+ "libcutils",
+ "liblog",
+ ],
+}
diff --git a/libatrace_rust/benchmark/README.md b/libatrace_rust/benchmark/README.md
new file mode 100644
index 00000000..7c9e64c1
--- /dev/null
+++ b/libatrace_rust/benchmark/README.md
@@ -0,0 +1,99 @@
+# libatrace_rust benchmarks
+
+Benchmarks to compare the performance of Rust ATrace bindings with directly calling the
+`libcutils` methods from C++.
+
+## Benchmarks
+
+### ATrace wrapper benchmarks
+
+There are two binaries implementing the same benchmarks:
+
+* `libatrace_rust_benchmark` (`atrace_benchmark.rs`) for Rust.
+* `libatrace_rust_benchmark_cc` (`atrace_benchmark.cc`) for C++.
+
+The benchmarks emit ATrace events with tracing off and tracing on. `atrace_begin` is measured
+with short and long event names to check if the string length affects timings. For example,
+`tracing_on_begin/1000` measures `atrace_begin` with a 1000-character name and tracing enabled.
+
+### ATrace tracing subscriber benchmark
+
+There is a benchmark for the tracing crate subscriber - `libatrace_tracing_subscriber_benchmark`.
+We use it to check overhead over the base `libatrace_rust`.
+
+Similarly to the wrapper benchmarks, the subscriber is measured with tracing off and on. There are
+cases with and without extra fields to measure the cost of formatting. Cases that start with
+`filtered_` measure the subscriber in filtering mode with tracing disabled.
+
+## Running the benchmarks
+
+To run the benchmarks, push the binaries to the device with `adb` and launch them via `adb shell`.
+You may need to push dynamic libraries they depend on as well if they're not present on device and
+run with `LD_LIBRARY_PATH`.
+
+Do not enable ATrace collectors. The benchmarks effectively emit events in a loop and will spam
+any trace and distort performance results.
+
+The benchmarks will override system properties to enable or disable events, specifically ATrace App
+event collection in `debug.atrace.app_number` and `debug.atrace.app_0`. After a successful execution
+the events will be disabled.
+
+## Results
+
+The timings are not representative of actual cost of fully enabling tracing, only of emitting
+events via API, since there's nothing receiving the events.
+
+The tests were done on a `aosp_cf_x86_64_phone-userdebug` Cuttlefish VM. Execution times on real
+device may be different but we expect similar relative performance between Rust wrappers and C.
+
+*If you notice that measurements with tracing off and tracing on have similar times, it might mean
+that enabling ATrace events failed and you need to debug the benchmark.*
+
+### ATrace wrapper
+
+Rust results from `libatrace_rust_benchmark 2>&1 | grep time`:
+
+```text
+tracing_off_begin/10 time: [6.0211 ns 6.0382 ns 6.0607 ns]
+tracing_off_begin/1000 time: [6.0119 ns 6.0418 ns 6.0823 ns]
+tracing_off_end time: [6.5417 ns 6.6801 ns 6.8131 ns]
+tracing_on_begin/10 time: [1.2847 µs 1.2929 µs 1.3044 µs]
+tracing_on_begin/1000 time: [1.5395 µs 1.5476 µs 1.5580 µs]
+tracing_on_end time: [1.1153 µs 1.1208 µs 1.1276 µs]
+```
+
+C++ results from `libatrace_rust_benchmark_cc`:
+
+```text
+------------------------------------------------------------------------
+Benchmark Time CPU Iterations
+------------------------------------------------------------------------
+BM_TracingOffAtraceBegin/10 4.00 ns 3.96 ns 175953732
+BM_TracingOffAtraceBegin/1000 4.05 ns 4.02 ns 176298494
+BM_TracingOffAtraceEnd 4.08 ns 4.05 ns 176422059
+BM_TracingOnAtraceBegin/10 1119 ns 1110 ns 640816
+BM_TracingOnAtraceBegin/1000 1151 ns 1142 ns 615781
+BM_TracingOnAtraceEnd 1076 ns 1069 ns 653646
+```
+
+### ATrace tracing subscriber
+
+The tracing subscriber time consists of the underlying `libatrace_rust` call plus the time spent in
+the subscriber itself.
+
+Results from `libatrace_tracing_subscriber_benchmark 2>&1 | grep time`:
+
+```text
+tracing_off_event time: [47.444 ns 47.945 ns 48.585 ns]
+filtered_event time: [26.852 ns 26.942 ns 27.040 ns]
+tracing_off_event_args time: [80.597 ns 80.997 ns 81.475 ns]
+filtered_event_args time: [26.680 ns 26.782 ns 26.887 ns]
+tracing_off_span time: [316.48 ns 317.72 ns 319.12 ns]
+filtered_span time: [27.900 ns 27.959 ns 28.018 ns]
+tracing_off_span_args time: [364.92 ns 367.57 ns 370.95 ns]
+filtered_span_args time: [27.625 ns 27.919 ns 28.207 ns]
+tracing_on_event time: [1.4639 µs 1.4805 µs 1.4954 µs]
+tracing_on_event_args time: [2.0088 µs 2.0197 µs 2.0314 µs]
+tracing_on_span time: [2.7907 µs 2.7996 µs 2.8103 µs]
+tracing_on_span_args time: [3.6846 µs 3.6992 µs 3.7168 µs]
+```
diff --git a/libatrace_rust/benchmark/src/atrace_benchmark.cc b/libatrace_rust/benchmark/src/atrace_benchmark.cc
new file mode 100644
index 00000000..fa21638c
--- /dev/null
+++ b/libatrace_rust/benchmark/src/atrace_benchmark.cc
@@ -0,0 +1,61 @@
+/*
+ * 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 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.
+ */
+
+#include <benchmark/benchmark.h>
+#include <cutils/trace.h>
+#include <string>
+
+#include "trace_enabler.h"
+
+static void BM_TracingOffAtraceBegin(benchmark::State& state) {
+ disable_app_atrace();
+ std::string name(state.range(0), '0');
+ for (auto _ : state) {
+ atrace_begin(ATRACE_TAG_APP, name.c_str());
+ }
+}
+
+static void BM_TracingOffAtraceEnd(benchmark::State& state) {
+ disable_app_atrace();
+ for (auto _ : state) {
+ atrace_end(ATRACE_TAG_APP);
+ }
+}
+
+static void BM_TracingOnAtraceBegin(benchmark::State& state) {
+ enable_atrace_for_single_app("*libatrace_rust_benchmark_cc");
+ std::string name(state.range(0), '0');
+ for (auto _ : state) {
+ atrace_begin(ATRACE_TAG_APP, name.c_str());
+ }
+ disable_app_atrace();
+}
+
+static void BM_TracingOnAtraceEnd(benchmark::State& state) {
+ enable_atrace_for_single_app("*libatrace_rust_benchmark_cc");
+ for (auto _ : state) {
+ atrace_end(ATRACE_TAG_APP);
+ }
+ disable_app_atrace();
+}
+
+// Register the function as a benchmark
+BENCHMARK(BM_TracingOffAtraceBegin)->Arg(10)->Arg(1000);
+BENCHMARK(BM_TracingOffAtraceEnd);
+BENCHMARK(BM_TracingOnAtraceBegin)->Arg(10)->Arg(1000);
+BENCHMARK(BM_TracingOnAtraceEnd);
+
+BENCHMARK_MAIN(); \ No newline at end of file
diff --git a/libatrace_rust/benchmark/src/atrace_benchmark.rs b/libatrace_rust/benchmark/src/atrace_benchmark.rs
new file mode 100644
index 00000000..3614569a
--- /dev/null
+++ b/libatrace_rust/benchmark/src/atrace_benchmark.rs
@@ -0,0 +1,61 @@
+// 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 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.
+
+//! Benchmark for ATrace bindings.
+
+use atrace::AtraceTag;
+use atrace_rust_benchmark_common::{new_criterion, turn_tracing_off, turn_tracing_on};
+use criterion::{BenchmarkId, Criterion};
+
+fn bench_tracing_off_begin(c: &mut Criterion, name_len: usize) {
+ turn_tracing_off();
+ let name = "0".repeat(name_len);
+ c.bench_with_input(BenchmarkId::new("tracing_off_begin", name_len), &name, |b, name| {
+ b.iter(|| atrace::atrace_begin(AtraceTag::App, name.as_str()))
+ });
+}
+
+fn bench_tracing_off_end(c: &mut Criterion) {
+ turn_tracing_off();
+ c.bench_function("tracing_off_end", |b| b.iter(|| atrace::atrace_end(AtraceTag::App)));
+}
+
+fn bench_tracing_on_begin(c: &mut Criterion, name_len: usize) {
+ turn_tracing_on();
+ let name = "0".repeat(name_len);
+ c.bench_with_input(BenchmarkId::new("tracing_on_begin", name_len), &name, |b, name| {
+ b.iter(|| atrace::atrace_begin(AtraceTag::App, name.as_str()))
+ });
+ turn_tracing_off();
+}
+
+fn bench_tracing_on_end(c: &mut Criterion) {
+ turn_tracing_on();
+ c.bench_function("tracing_on_end", |b| b.iter(|| atrace::atrace_end(AtraceTag::App)));
+ turn_tracing_off();
+}
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+ let mut criterion = new_criterion();
+
+ bench_tracing_off_begin(&mut criterion, 10);
+ bench_tracing_off_begin(&mut criterion, 1000);
+ bench_tracing_off_end(&mut criterion);
+
+ bench_tracing_on_begin(&mut criterion, 10);
+ bench_tracing_on_begin(&mut criterion, 1000);
+ bench_tracing_on_end(&mut criterion);
+
+ Ok(())
+}
diff --git a/libatrace_rust/benchmark/src/atrace_benchmark_common.rs b/libatrace_rust/benchmark/src/atrace_benchmark_common.rs
new file mode 100644
index 00000000..6078d164
--- /dev/null
+++ b/libatrace_rust/benchmark/src/atrace_benchmark_common.rs
@@ -0,0 +1,53 @@
+// 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 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.
+
+//! Utilities to benchmark ATrace in Rust.
+
+use criterion::Criterion;
+
+// We could use bindgen to generate these bindings automatically but the signatures are simple and
+// we don't expect them to change much (if at all). So we specify them manually and skip having an
+// intermediate target.
+extern "C" {
+ fn disable_app_atrace();
+ fn enable_atrace_for_single_app(name: *const std::os::raw::c_char);
+}
+
+/// Disables ATrace for all apps (ATRACE_TAG_APP).
+pub fn turn_tracing_off() {
+ // SAFETY: This call is always safe.
+ unsafe {
+ disable_app_atrace();
+ }
+}
+
+/// Enables ATrace for this app.
+pub fn turn_tracing_on() {
+ // ATrace uses command line for per-process tracing control, so env::current_exe won't work.
+ let procname = std::ffi::CString::new(std::env::args().next().unwrap()).unwrap();
+ // SAFETY: `procname` is a valid C string and the function doesn't store it after it returns.
+ unsafe {
+ enable_atrace_for_single_app(procname.as_ptr());
+ }
+}
+
+/// Creates a new configured instance of Criterion for benchmarking.
+pub fn new_criterion() -> Criterion {
+ let path = "/data/local/tmp/criterion/benchmarks";
+ std::fs::create_dir_all(path).unwrap_or_else(|e| {
+ panic!("The criterion folder should be possible to create at {}: {}", path, e)
+ });
+ std::env::set_var("CRITERION_HOME", path);
+ Criterion::default()
+}
diff --git a/libatrace_rust/benchmark/src/trace_enabler.cc b/libatrace_rust/benchmark/src/trace_enabler.cc
new file mode 100644
index 00000000..49daff9b
--- /dev/null
+++ b/libatrace_rust/benchmark/src/trace_enabler.cc
@@ -0,0 +1,37 @@
+/*
+ * 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 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.
+ */
+
+#include "trace_enabler.h"
+
+#include <cutils/properties.h>
+#include <cutils/trace.h>
+#include <log/log.h>
+
+void set_property_or_die(const char* key, const char* value) {
+ LOG_ALWAYS_FATAL_IF(property_set(key, value) < 0, "Failed to set %s", key);
+}
+
+void disable_app_atrace() {
+ set_property_or_die("debug.atrace.app_number", "");
+ set_property_or_die("debug.atrace.app_0", "");
+ atrace_update_tags();
+}
+
+void enable_atrace_for_single_app(const char* name) {
+ set_property_or_die("debug.atrace.app_number", "1");
+ set_property_or_die("debug.atrace.app_0", name);
+ atrace_update_tags();
+}
diff --git a/libatrace_rust/benchmark/src/trace_enabler.h b/libatrace_rust/benchmark/src/trace_enabler.h
new file mode 100644
index 00000000..532c590e
--- /dev/null
+++ b/libatrace_rust/benchmark/src/trace_enabler.h
@@ -0,0 +1,36 @@
+/*
+ * 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 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.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+// A library to enable tracing for benchmarks.
+// It only causes ftrace events to be emitted so that we can measure
+// performance and is not intended to enable tracing for meaningful results.
+
+__BEGIN_DECLS
+
+// Disable tracing for ATRACE_TAG_APP events.
+// Terminates the app on error and writes an error message to logd and stderr.
+void disable_app_atrace();
+
+// Enable ATRACE_TAG_APP events only for the specified app pattern.
+// ATrace uses fnmatch for the pattern.
+// Terminates the app on error and writes an error message to logd and stderr.
+void enable_atrace_for_single_app(const char* name);
+
+__END_DECLS \ No newline at end of file
diff --git a/libatrace_rust/benchmark/src/tracing_subscriber_benchmark.rs b/libatrace_rust/benchmark/src/tracing_subscriber_benchmark.rs
new file mode 100644
index 00000000..f5a3a3b4
--- /dev/null
+++ b/libatrace_rust/benchmark/src/tracing_subscriber_benchmark.rs
@@ -0,0 +1,176 @@
+// 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 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.
+
+//! Benchmark for ATrace tracing subscriber.
+
+use atrace_rust_benchmark_common::{new_criterion, turn_tracing_off, turn_tracing_on};
+use atrace_tracing_subscriber::AtraceSubscriber;
+use criterion::Criterion;
+use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
+
+fn make_example_vec() -> Vec<i32> {
+ Vec::from([1, 2, 3, 4])
+}
+
+fn bench_with_subscriber<F>(c: &mut Criterion, name: &str, mut f: F)
+where
+ F: FnMut(),
+{
+ let subscriber = tracing_subscriber::registry().with(AtraceSubscriber::default());
+ tracing::subscriber::with_default(subscriber, || {
+ c.bench_function(name, |b| b.iter(&mut f));
+ });
+}
+
+fn bench_with_filtering_subscriber<F>(c: &mut Criterion, name: &str, mut f: F)
+where
+ F: FnMut(),
+{
+ let subscriber = tracing_subscriber::registry().with(AtraceSubscriber::default().with_filter());
+ tracing::subscriber::with_default(subscriber, || {
+ c.bench_function(name, |b| b.iter(&mut f));
+ });
+}
+
+fn bench_tracing_off_event(c: &mut Criterion) {
+ turn_tracing_off();
+ bench_with_subscriber(c, "tracing_off_event", || tracing::info!("bench info event"));
+}
+
+fn bench_filtered_event(c: &mut Criterion) {
+ turn_tracing_off();
+ bench_with_filtering_subscriber(c, "filtered_event", || tracing::info!("bench info event"));
+}
+
+fn bench_tracing_off_event_args(c: &mut Criterion) {
+ turn_tracing_off();
+ let v = make_example_vec();
+ bench_with_subscriber(c, "tracing_off_event_args", || {
+ tracing::info!(debug_arg1 = 123,
+ debug_arg2 = "argument",
+ debug_arg3 = ?v,
+ debug_arg4 = "last",
+ "bench info event")
+ });
+}
+
+fn bench_filtered_event_args(c: &mut Criterion) {
+ turn_tracing_off();
+ let v = make_example_vec();
+ bench_with_filtering_subscriber(c, "filtered_event_args", || {
+ tracing::info!(debug_arg1 = 123,
+ debug_arg2 = "argument",
+ debug_arg3 = ?v,
+ debug_arg4 = "last",
+ "bench info event")
+ });
+}
+
+fn bench_tracing_off_span(c: &mut Criterion) {
+ turn_tracing_off();
+ bench_with_subscriber(c, "tracing_off_span", || {
+ let _entered = tracing::info_span!("bench info span").entered();
+ });
+}
+
+fn bench_filtered_span(c: &mut Criterion) {
+ turn_tracing_off();
+ bench_with_filtering_subscriber(c, "filtered_span", || {
+ let _entered = tracing::info_span!("bench info span").entered();
+ });
+}
+
+fn bench_tracing_off_span_args(c: &mut Criterion) {
+ turn_tracing_off();
+ let v = make_example_vec();
+ bench_with_subscriber(c, "tracing_off_span_args", || {
+ let _entered = tracing::info_span!("bench info span", debug_arg1 = 123,
+ debug_arg2 = "argument",
+ debug_arg3 = ?v,
+ debug_arg4 = "last")
+ .entered();
+ });
+}
+
+fn bench_filtered_span_args(c: &mut Criterion) {
+ turn_tracing_off();
+ let v = make_example_vec();
+ bench_with_filtering_subscriber(c, "filtered_span_args", || {
+ let _entered = tracing::info_span!("bench info span", debug_arg1 = 123,
+ debug_arg2 = "argument",
+ debug_arg3 = ?v,
+ debug_arg4 = "last")
+ .entered();
+ });
+}
+
+fn bench_tracing_on_event(c: &mut Criterion) {
+ turn_tracing_on();
+ bench_with_subscriber(c, "tracing_on_event", || tracing::info!("bench info event"));
+ turn_tracing_off();
+}
+
+fn bench_tracing_on_event_args(c: &mut Criterion) {
+ turn_tracing_on();
+ let v = make_example_vec();
+ bench_with_subscriber(c, "tracing_on_event_args", || {
+ tracing::info!(debug_arg1 = 123,
+ debug_arg2 = "argument",
+ debug_arg3 = ?v,
+ debug_arg4 = "last",
+ "bench info event")
+ });
+ turn_tracing_off();
+}
+
+fn bench_tracing_on_span(c: &mut Criterion) {
+ turn_tracing_on();
+ bench_with_subscriber(c, "tracing_on_span", || {
+ let _entered = tracing::info_span!("bench info span").entered();
+ });
+ turn_tracing_off();
+}
+
+fn bench_tracing_on_span_args(c: &mut Criterion) {
+ turn_tracing_on();
+ let v = make_example_vec();
+ bench_with_subscriber(c, "tracing_on_span_args", || {
+ let _entered = tracing::info_span!("bench info span", debug_arg1 = 123,
+ debug_arg2 = "argument",
+ debug_arg3 = ?v,
+ debug_arg4 = "last")
+ .entered();
+ });
+ turn_tracing_off();
+}
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+ let mut criterion = new_criterion();
+
+ bench_tracing_off_event(&mut criterion);
+ bench_filtered_event(&mut criterion);
+ bench_tracing_off_event_args(&mut criterion);
+ bench_filtered_event_args(&mut criterion);
+ bench_tracing_off_span(&mut criterion);
+ bench_filtered_span(&mut criterion);
+ bench_tracing_off_span_args(&mut criterion);
+ bench_filtered_span_args(&mut criterion);
+
+ bench_tracing_on_event(&mut criterion);
+ bench_tracing_on_event_args(&mut criterion);
+ bench_tracing_on_span(&mut criterion);
+ bench_tracing_on_span_args(&mut criterion);
+
+ Ok(())
+}
diff --git a/libatrace_rust/bindgen/cutils_trace.h b/libatrace_rust/bindgen/cutils_trace.h
new file mode 100644
index 00000000..f974ac67
--- /dev/null
+++ b/libatrace_rust/bindgen/cutils_trace.h
@@ -0,0 +1 @@
+#include "cutils_trace_wrap.h" \ No newline at end of file
diff --git a/libatrace_rust/bindgen/cutils_trace_wrap.c b/libatrace_rust/bindgen/cutils_trace_wrap.c
new file mode 100644
index 00000000..eb46087f
--- /dev/null
+++ b/libatrace_rust/bindgen/cutils_trace_wrap.c
@@ -0,0 +1,62 @@
+/*
+ * 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 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.
+ */
+
+#include "cutils_trace_wrap.h"
+
+void atrace_begin_wrap(uint64_t tag, const char* name) {
+ atrace_begin(tag, name);
+}
+
+void atrace_end_wrap(uint64_t tag) {
+ atrace_end(tag);
+}
+
+uint64_t atrace_is_tag_enabled_wrap(uint64_t tag) {
+ return atrace_is_tag_enabled(tag);
+}
+
+void atrace_async_begin_wrap(uint64_t tag, const char* name, int32_t cookie) {
+ atrace_async_begin(tag, name, cookie);
+}
+
+void atrace_async_end_wrap(uint64_t tag, const char* name, int32_t cookie) {
+ atrace_async_end(tag, name, cookie);
+}
+
+void atrace_async_for_track_begin_wrap(uint64_t tag, const char* track_name, const char* name,
+ int32_t cookie) {
+ atrace_async_for_track_begin(tag, track_name, name, cookie);
+}
+
+void atrace_async_for_track_end_wrap(uint64_t tag, const char* track_name, int32_t cookie) {
+ atrace_async_for_track_end(tag, track_name, cookie);
+}
+
+void atrace_instant_wrap(uint64_t tag, const char* name) {
+ atrace_instant(tag, name);
+}
+
+void atrace_instant_for_track_wrap(uint64_t tag, const char* track_name, const char* name) {
+ atrace_instant_for_track(tag, track_name, name);
+}
+
+void atrace_int_wrap(uint64_t tag, const char* name, int32_t value) {
+ atrace_int(tag, name, value);
+}
+
+void atrace_int64_wrap(uint64_t tag, const char* name, int64_t value) {
+ atrace_int64(tag, name, value);
+}
diff --git a/libatrace_rust/bindgen/cutils_trace_wrap.h b/libatrace_rust/bindgen/cutils_trace_wrap.h
new file mode 100644
index 00000000..34c92aa4
--- /dev/null
+++ b/libatrace_rust/bindgen/cutils_trace_wrap.h
@@ -0,0 +1,35 @@
+/*
+ * 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 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.
+ */
+
+#pragma once
+
+#include <cutils/trace.h>
+
+// Wrappers for static inline functions
+// TODO: b/291544011 - Replace with autogenerated wrappers once they are supported.
+
+void atrace_begin_wrap(uint64_t tag, const char* name);
+void atrace_end_wrap(uint64_t tag);
+uint64_t atrace_is_tag_enabled_wrap(uint64_t tag);
+void atrace_async_begin_wrap(uint64_t tag, const char* name, int32_t cookie);
+void atrace_async_end_wrap(uint64_t tag, const char* name, int32_t cookie);
+void atrace_async_for_track_begin_wrap(uint64_t tag, const char* track_name, const char* name,
+ int32_t cookie);
+void atrace_async_for_track_end_wrap(uint64_t tag, const char* track_name, int32_t cookie);
+void atrace_instant_wrap(uint64_t tag, const char* name);
+void atrace_instant_for_track_wrap(uint64_t tag, const char* track_name, const char* name);
+void atrace_int_wrap(uint64_t tag, const char* name, int32_t value);
+void atrace_int64_wrap(uint64_t tag, const char* name, int64_t value);
diff --git a/libatrace_rust/example/Android.bp b/libatrace_rust/example/Android.bp
new file mode 100644
index 00000000..fb1545e4
--- /dev/null
+++ b/libatrace_rust/example/Android.bp
@@ -0,0 +1,21 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_binary {
+ name: "libatrace_rust_example",
+ srcs: ["src/main.rs"],
+ rustlibs: [
+ "libatrace_rust",
+ ],
+}
+
+rust_binary {
+ name: "libatrace_tracing_subscriber_example",
+ srcs: ["src/tracing_subscriber_sample.rs"],
+ rustlibs: [
+ "libatrace_tracing_subscriber",
+ "libtracing_subscriber",
+ "libtracing",
+ ],
+}
diff --git a/libatrace_rust/example/src/main.rs b/libatrace_rust/example/src/main.rs
new file mode 100644
index 00000000..84e3e470
--- /dev/null
+++ b/libatrace_rust/example/src/main.rs
@@ -0,0 +1,92 @@
+// 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 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.
+
+//! Usage sample for libatrace_rust.
+
+use std::thread::JoinHandle;
+
+use atrace::AtraceTag;
+
+fn spawn_async_event() -> JoinHandle<()> {
+ // Unlike normal events, async events don't need to be nested.
+ // You need to use the same name and cookie (the last arg) to close the event.
+ // The cookie must be unique on the name level.
+ let unique_cookie = 12345;
+ atrace::atrace_async_begin(AtraceTag::App, "Async task", unique_cookie);
+ std::thread::spawn(move || {
+ std::thread::sleep(std::time::Duration::from_millis(500));
+ atrace::atrace_async_end(AtraceTag::App, "Async task", unique_cookie);
+ })
+}
+
+fn spawn_async_event_with_track() -> JoinHandle<()> {
+ // Same as `atrace_async_begin` but per track.
+ // Track name (not event name) and cookie are used to close the event.
+ // The cookie must be unique on the track level.
+ let unique_cookie = 12345;
+ atrace::atrace_async_for_track_begin(
+ AtraceTag::App,
+ "Async track",
+ "Task with track",
+ unique_cookie,
+ );
+ std::thread::spawn(move || {
+ std::thread::sleep(std::time::Duration::from_millis(600));
+ atrace::atrace_async_for_track_end(AtraceTag::App, "Async track", unique_cookie);
+ })
+}
+
+fn spawn_counter_thread() -> JoinHandle<()> {
+ std::thread::spawn(|| {
+ for i in 1..=10 {
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ // Counter events are available for int and int64 to trace values.
+ atrace::atrace_int(AtraceTag::App, "Count of i", i);
+ }
+ })
+}
+
+fn main() {
+ // This macro will create a scoped event with the function name used as the event name.
+ atrace::trace_method!(AtraceTag::App);
+
+ // The scoped event will be ended when the returned guard is dropped.
+ let _scoped_event = atrace::begin_scoped_event(AtraceTag::App, "Example main");
+
+ // Methods starting with atrace_* are direct wrappers of libcutils methods.
+ let enabled_tags = atrace::atrace_get_enabled_tags();
+ println!("Enabled tags: {:?}", enabled_tags);
+
+ println!("Spawning async trace events");
+ let async_event_handler = spawn_async_event();
+ let async_event_with_track_handler = spawn_async_event_with_track();
+ let counter_thread_handler = spawn_counter_thread();
+
+ // Instant events have no duration and don't need to be closed.
+ atrace::atrace_instant(AtraceTag::App, "Instant event");
+
+ println!("Calling atrace_begin and sleeping for 1 sec...");
+ // If you begin an event you need to close it with the same tag. If you're calling begin
+ // manually make sure you have a matching end. Or just use a scoped event.
+ atrace::atrace_begin(AtraceTag::App, "Hello tracing!");
+ std::thread::sleep(std::time::Duration::from_secs(1));
+ atrace::atrace_end(AtraceTag::App);
+
+ println!("Joining async events...");
+ async_event_handler.join().unwrap();
+ async_event_with_track_handler.join().unwrap();
+ counter_thread_handler.join().unwrap();
+
+ println!("Done!");
+}
diff --git a/libatrace_rust/example/src/tracing_subscriber_sample.rs b/libatrace_rust/example/src/tracing_subscriber_sample.rs
new file mode 100644
index 00000000..b08fcdb4
--- /dev/null
+++ b/libatrace_rust/example/src/tracing_subscriber_sample.rs
@@ -0,0 +1,60 @@
+// 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 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.
+
+//! Usage sample for a tracing subscriber in libatrace_rust.
+
+use tracing::{debug, error, event, info, span, trace, warn, Level};
+
+use atrace_tracing_subscriber::AtraceSubscriber;
+use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
+
+#[tracing::instrument]
+fn mul_by_100_instrumented(num: i32) -> i32 {
+ let result = num * 100;
+ std::thread::sleep(std::time::Duration::from_millis(300));
+ event!(Level::INFO, num, result);
+ result
+}
+
+fn events_and_spans_demo() {
+ let power_level = 8999;
+
+ event!(Level::INFO, foo = "bar", power_level, "This is a {} message", "formattable");
+ std::thread::sleep(std::time::Duration::from_millis(100));
+
+ let span = span!(Level::TRACE, "Span name", baz = "quux");
+ std::thread::sleep(std::time::Duration::from_millis(300));
+
+ let _span_guard = span.enter();
+
+ let _entered_span = span!(Level::TRACE, "Entered span").entered();
+ std::thread::sleep(std::time::Duration::from_millis(300));
+
+ trace!("test {} log {}", "VERBOSE", mul_by_100_instrumented(42));
+ debug!("test {} log", "DEBUG");
+ info!("test {} log", "INFO");
+ warn!("test {} log", "WARNING");
+ error!("test {} log", "ERROR");
+}
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+ tracing_subscriber::registry()
+ .with(AtraceSubscriber::default().with_filter())
+ .with(tracing_subscriber::fmt::layer())
+ .init();
+
+ events_and_spans_demo();
+
+ Ok(())
+}
diff --git a/libatrace_rust/src/lib.rs b/libatrace_rust/src/lib.rs
new file mode 100644
index 00000000..7eecbf88
--- /dev/null
+++ b/libatrace_rust/src/lib.rs
@@ -0,0 +1,1218 @@
+// 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 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.
+
+//! ATrace instrumentation methods from cutils.
+
+use std::ffi::CString;
+
+#[cfg(not(test))]
+use cutils_trace_bindgen as trace_bind;
+
+// Wrap tags into a mod to allow missing docs.
+// We have to use the mod for this because Rust won't apply the attribute to the bitflags macro
+// invocation.
+pub use self::tags::*;
+pub mod tags {
+ // Tag constants are not documented in libcutils, so we don't document them here.
+ #![allow(missing_docs)]
+
+ use bitflags::bitflags;
+ use static_assertions::const_assert_eq;
+
+ bitflags! {
+ /// The trace tag is used to filter tracing in userland to avoid some of the runtime cost of
+ /// tracing when it is not desired.
+ ///
+ /// Using `AtraceTag::Always` will result in the tracing always being enabled - this should
+ /// ONLY be done for debug code, as userland tracing has a performance cost even when the
+ /// trace is not being recorded. `AtraceTag::Never` will result in the tracing always being
+ /// disabled.
+ ///
+ /// `AtraceTag::Hal` should be bitwise ORed with the relevant tags for tracing
+ /// within a hardware module. For example a camera hardware module would use
+ /// `AtraceTag::Camera | AtraceTag::Hal`.
+ ///
+ /// Source of truth is `system/core/libcutils/include/cutils/trace.h`.
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+ pub struct AtraceTag: u64 {
+ const Never = cutils_trace_bindgen::ATRACE_TAG_NEVER as u64;
+ const Always = cutils_trace_bindgen::ATRACE_TAG_ALWAYS as u64;
+ const Graphics = cutils_trace_bindgen::ATRACE_TAG_GRAPHICS as u64;
+ const Input = cutils_trace_bindgen::ATRACE_TAG_INPUT as u64;
+ const View = cutils_trace_bindgen::ATRACE_TAG_VIEW as u64;
+ const Webview = cutils_trace_bindgen::ATRACE_TAG_WEBVIEW as u64;
+ const WindowManager = cutils_trace_bindgen::ATRACE_TAG_WINDOW_MANAGER as u64;
+ const ActivityManager = cutils_trace_bindgen::ATRACE_TAG_ACTIVITY_MANAGER as u64;
+ const SyncManager = cutils_trace_bindgen::ATRACE_TAG_SYNC_MANAGER as u64;
+ const Audio = cutils_trace_bindgen::ATRACE_TAG_AUDIO as u64;
+ const Video = cutils_trace_bindgen::ATRACE_TAG_VIDEO as u64;
+ const Camera = cutils_trace_bindgen::ATRACE_TAG_CAMERA as u64;
+ const Hal = cutils_trace_bindgen::ATRACE_TAG_HAL as u64;
+ const App = cutils_trace_bindgen::ATRACE_TAG_APP as u64;
+ const Resources = cutils_trace_bindgen::ATRACE_TAG_RESOURCES as u64;
+ const Dalvik = cutils_trace_bindgen::ATRACE_TAG_DALVIK as u64;
+ const Rs = cutils_trace_bindgen::ATRACE_TAG_RS as u64;
+ const Bionic = cutils_trace_bindgen::ATRACE_TAG_BIONIC as u64;
+ const Power = cutils_trace_bindgen::ATRACE_TAG_POWER as u64;
+ const PackageManager = cutils_trace_bindgen::ATRACE_TAG_PACKAGE_MANAGER as u64;
+ const SystemServer = cutils_trace_bindgen::ATRACE_TAG_SYSTEM_SERVER as u64;
+ const Database = cutils_trace_bindgen::ATRACE_TAG_DATABASE as u64;
+ const Network = cutils_trace_bindgen::ATRACE_TAG_NETWORK as u64;
+ const Adb = cutils_trace_bindgen::ATRACE_TAG_ADB as u64;
+ const Vibrator = cutils_trace_bindgen::ATRACE_TAG_VIBRATOR as u64;
+ const Aidl = cutils_trace_bindgen::ATRACE_TAG_AIDL as u64;
+ const Nnapi = cutils_trace_bindgen::ATRACE_TAG_NNAPI as u64;
+ const Rro = cutils_trace_bindgen::ATRACE_TAG_RRO as u64;
+ const Thermal = cutils_trace_bindgen::ATRACE_TAG_THERMAL as u64;
+ const Last = cutils_trace_bindgen::ATRACE_TAG_LAST as u64;
+ const NotReady = cutils_trace_bindgen::ATRACE_TAG_NOT_READY as u64;
+ const ValidMask = cutils_trace_bindgen::ATRACE_TAG_VALID_MASK as u64;
+ }
+ }
+
+ // Assertion to keep tags in sync. If it fails, it means there are new tags added to
+ // cutils/trace.h. Add them to the tags above and update the assertion.
+ const_assert_eq!(AtraceTag::Thermal.bits(), cutils_trace_bindgen::ATRACE_TAG_LAST as u64);
+}
+
+/// RAII guard to close an event with tag.
+pub struct ScopedEvent {
+ tag: AtraceTag,
+}
+
+impl Drop for ScopedEvent {
+ fn drop(&mut self) {
+ atrace_end(self.tag);
+ }
+}
+
+/// Begins an event via `atrace_begin` and returns a guard that calls `atrace_end` when dropped.
+pub fn begin_scoped_event(tag: AtraceTag, name: &str) -> ScopedEvent {
+ atrace_begin(tag, name);
+ ScopedEvent { tag }
+}
+
+/// Creates a scoped event with the current method name.
+#[macro_export]
+macro_rules! trace_method {
+ {$tag:expr} => {
+ let mut _atrace_trace_method_name: &'static str = "";
+ {
+ // Declares function f inside current function.
+ fn f() {}
+ fn type_name_of<T>(_: T) -> &'static str {
+ std::any::type_name::<T>()
+ }
+ // type name of f is struct_or_crate_name::calling_function_name::f
+ let name = type_name_of(f);
+ // Remove the third to last character ("::f")
+ _atrace_trace_method_name = &name[..name.len() - 3];
+ }
+ let _atrace_trace_method_guard = atrace::begin_scoped_event($tag, _atrace_trace_method_name);
+ };
+}
+
+/// Set whether tracing is enabled for the current process. This is used to prevent tracing within
+/// the Zygote process.
+pub fn atrace_set_tracing_enabled(enabled: bool) {
+ // SAFETY: No pointers are transferred.
+ unsafe {
+ trace_bind::atrace_set_tracing_enabled(enabled);
+ }
+}
+
+/// `atrace_init` readies the process for tracing by opening the trace_marker file.
+/// Calling any trace function causes this to be run, so calling it is optional.
+/// This can be explicitly run to avoid setup delay on first trace function.
+pub fn atrace_init() {
+ // SAFETY: Call with no arguments.
+ unsafe {
+ trace_bind::atrace_init();
+ }
+}
+
+/// Returns enabled tags as a bitmask.
+///
+/// The tag mask is converted into an `AtraceTag`, keeping flags that do not correspond to a tag.
+pub fn atrace_get_enabled_tags() -> AtraceTag {
+ // SAFETY: Call with no arguments that returns a 64-bit int.
+ unsafe { AtraceTag::from_bits_retain(trace_bind::atrace_get_enabled_tags()) }
+}
+
+/// Test if a given tag is currently enabled.
+///
+/// It can be used as a guard condition around more expensive trace calculations.
+pub fn atrace_is_tag_enabled(tag: AtraceTag) -> bool {
+ // SAFETY: No pointers are transferred.
+ unsafe { trace_bind::atrace_is_tag_enabled_wrap(tag.bits()) != 0 }
+}
+
+/// Trace the beginning of a context. `name` is used to identify the context.
+///
+/// This is often used to time function execution.
+pub fn atrace_begin(tag: AtraceTag, name: &str) {
+ if !atrace_is_tag_enabled(tag) {
+ return;
+ }
+
+ let name_cstr = CString::new(name.as_bytes()).expect("CString::new failed");
+ // SAFETY: The function does not accept the pointer ownership, only reads its contents.
+ // The passed string is guaranteed to be null-terminated by CString.
+ unsafe {
+ trace_bind::atrace_begin_wrap(tag.bits(), name_cstr.as_ptr());
+ }
+}
+
+/// Trace the end of a context.
+///
+/// This should match up (and occur after) a corresponding `atrace_begin`.
+pub fn atrace_end(tag: AtraceTag) {
+ // SAFETY: No pointers are transferred.
+ unsafe {
+ trace_bind::atrace_end_wrap(tag.bits());
+ }
+}
+
+/// Trace the beginning of an asynchronous event. Unlike `atrace_begin`/`atrace_end` contexts,
+/// asynchronous events do not need to be nested.
+///
+/// The name describes the event, and the cookie provides a unique identifier for distinguishing
+/// simultaneous events.
+///
+/// The name and cookie used to begin an event must be used to end it.
+pub fn atrace_async_begin(tag: AtraceTag, name: &str, cookie: i32) {
+ if !atrace_is_tag_enabled(tag) {
+ return;
+ }
+
+ let name_cstr = CString::new(name.as_bytes()).expect("CString::new failed");
+ // SAFETY: The function does not accept the pointer ownership, only reads its contents.
+ // The passed string is guaranteed to be null-terminated by CString.
+ unsafe {
+ trace_bind::atrace_async_begin_wrap(tag.bits(), name_cstr.as_ptr(), cookie);
+ }
+}
+
+/// Trace the end of an asynchronous event.
+///
+/// This should have a corresponding `atrace_async_begin`.
+pub fn atrace_async_end(tag: AtraceTag, name: &str, cookie: i32) {
+ if !atrace_is_tag_enabled(tag) {
+ return;
+ }
+
+ let name_cstr = CString::new(name.as_bytes()).expect("CString::new failed");
+ // SAFETY: The function does not accept the pointer ownership, only reads its contents.
+ // The passed string is guaranteed to be null-terminated by CString.
+ unsafe {
+ trace_bind::atrace_async_end_wrap(tag.bits(), name_cstr.as_ptr(), cookie);
+ }
+}
+
+/// Trace the beginning of an asynchronous event.
+///
+/// In addition to the name and a cookie as in `atrace_async_begin`/`atrace_async_end`, a track name
+/// argument is provided, which is the name of the row where this async event should be recorded.
+///
+/// The track name and cookie used to begin an event must be used to end it.
+///
+/// The cookie here must be unique on the track_name level, not the name level.
+pub fn atrace_async_for_track_begin(tag: AtraceTag, track_name: &str, name: &str, cookie: i32) {
+ if !atrace_is_tag_enabled(tag) {
+ return;
+ }
+
+ let name_cstr = CString::new(name.as_bytes()).expect("CString::new failed");
+ let track_name_cstr = CString::new(track_name.as_bytes()).expect("CString::new failed");
+ // SAFETY: The function does not accept the pointer ownership, only reads its contents.
+ // The passed strings are guaranteed to be null-terminated by CString.
+ unsafe {
+ trace_bind::atrace_async_for_track_begin_wrap(
+ tag.bits(),
+ track_name_cstr.as_ptr(),
+ name_cstr.as_ptr(),
+ cookie,
+ );
+ }
+}
+
+/// Trace the end of an asynchronous event.
+///
+/// This should correspond to a previous `atrace_async_for_track_begin`.
+pub fn atrace_async_for_track_end(tag: AtraceTag, track_name: &str, cookie: i32) {
+ if !atrace_is_tag_enabled(tag) {
+ return;
+ }
+
+ let track_name_cstr = CString::new(track_name.as_bytes()).expect("CString::new failed");
+ // SAFETY: The function does not accept the pointer ownership, only reads its contents.
+ // The passed string is guaranteed to be null-terminated by CString.
+ unsafe {
+ trace_bind::atrace_async_for_track_end_wrap(tag.bits(), track_name_cstr.as_ptr(), cookie);
+ }
+}
+
+/// Trace an instantaneous context. `name` is used to identify the context.
+///
+/// An "instant" is an event with no defined duration. Visually is displayed like a single marker
+/// in the timeline (rather than a span, in the case of begin/end events).
+///
+/// By default, instant events are added into a dedicated track that has the same name of the event.
+/// Use `atrace_instant_for_track` to put different instant events into the same timeline track/row.
+pub fn atrace_instant(tag: AtraceTag, name: &str) {
+ if !atrace_is_tag_enabled(tag) {
+ return;
+ }
+
+ let name_cstr = CString::new(name.as_bytes()).expect("CString::new failed");
+ // SAFETY: The function does not accept the pointer ownership, only reads its contents.
+ // The passed string is guaranteed to be null-terminated by CString.
+ unsafe {
+ trace_bind::atrace_instant_wrap(tag.bits(), name_cstr.as_ptr());
+ }
+}
+
+/// Trace an instantaneous context. `name` is used to identify the context. `track_name` is the name
+/// of the row where the event should be recorded.
+///
+/// An "instant" is an event with no defined duration. Visually is displayed like a single marker
+/// in the timeline (rather than a span, in the case of begin/end events).
+pub fn atrace_instant_for_track(tag: AtraceTag, track_name: &str, name: &str) {
+ if !atrace_is_tag_enabled(tag) {
+ return;
+ }
+
+ let name_cstr = CString::new(name.as_bytes()).expect("CString::new failed");
+ let track_name_cstr = CString::new(track_name.as_bytes()).expect("CString::new failed");
+ // SAFETY: The function does not accept the pointer ownership, only reads its contents.
+ // The passed string is guaranteed to be null-terminated by CString.
+ unsafe {
+ trace_bind::atrace_instant_for_track_wrap(
+ tag.bits(),
+ track_name_cstr.as_ptr(),
+ name_cstr.as_ptr(),
+ );
+ }
+}
+
+/// Traces an integer counter value. `name` is used to identify the counter.
+///
+/// This can be used to track how a value changes over time.
+pub fn atrace_int(tag: AtraceTag, name: &str, value: i32) {
+ if !atrace_is_tag_enabled(tag) {
+ return;
+ }
+
+ let name_cstr = CString::new(name.as_bytes()).expect("CString::new failed");
+ // SAFETY: The function does not accept the pointer ownership, only reads its contents.
+ // The passed string is guaranteed to be null-terminated by CString.
+ unsafe {
+ trace_bind::atrace_int_wrap(tag.bits(), name_cstr.as_ptr(), value);
+ }
+}
+
+/// Traces a 64-bit integer counter value. `name` is used to identify the counter.
+///
+/// This can be used to track how a value changes over time.
+pub fn atrace_int64(tag: AtraceTag, name: &str, value: i64) {
+ if !atrace_is_tag_enabled(tag) {
+ return;
+ }
+
+ let name_cstr = CString::new(name.as_bytes()).expect("CString::new failed");
+ // SAFETY: The function does not accept the pointer ownership, only reads its contents.
+ // The passed string is guaranteed to be null-terminated by CString.
+ unsafe {
+ trace_bind::atrace_int64_wrap(tag.bits(), name_cstr.as_ptr(), value);
+ }
+}
+
+#[cfg(test)]
+use self::tests::mock_atrace as trace_bind;
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use std::ffi::CStr;
+ use std::os::raw::c_char;
+
+ /// Utilities to mock ATrace bindings.
+ ///
+ /// Normally, for behavior-driven testing we focus on the outcomes of the functions rather than
+ /// calls into bindings. However, since the purpose of the library is to forward data into
+ /// the underlying implementation (which we assume to be correct), that's what we test.
+ pub mod mock_atrace {
+ use std::cell::RefCell;
+ use std::os::raw::c_char;
+
+ /// Contains logic to check binding calls.
+ /// Implement this trait in the test with mocking logic and checks in implemented functions.
+ /// Default implementations panic.
+ pub trait ATraceMocker {
+ fn atrace_set_tracing_enabled(&mut self, _enabled: bool) {
+ panic!("Unexpected call");
+ }
+ fn atrace_init(&mut self) {
+ panic!("Unexpected call");
+ }
+ fn atrace_get_enabled_tags(&mut self) -> u64 {
+ panic!("Unexpected call");
+ }
+ fn atrace_is_tag_enabled_wrap(&mut self, _tag: u64) -> u64 {
+ panic!("Unexpected call");
+ }
+ fn atrace_begin_wrap(&mut self, _tag: u64, _name: *const c_char) {
+ panic!("Unexpected call");
+ }
+ fn atrace_end_wrap(&mut self, _tag: u64) {
+ panic!("Unexpected call");
+ }
+ fn atrace_async_begin_wrap(&mut self, _tag: u64, _name: *const c_char, _cookie: i32) {
+ panic!("Unexpected call");
+ }
+ fn atrace_async_end_wrap(&mut self, _tag: u64, _name: *const c_char, _cookie: i32) {
+ panic!("Unexpected call");
+ }
+ fn atrace_async_for_track_begin_wrap(
+ &mut self,
+ _tag: u64,
+ _track_name: *const c_char,
+ _name: *const c_char,
+ _cookie: i32,
+ ) {
+ panic!("Unexpected call");
+ }
+ fn atrace_async_for_track_end_wrap(
+ &mut self,
+ _tag: u64,
+ _track_name: *const c_char,
+ _cookie: i32,
+ ) {
+ panic!("Unexpected call");
+ }
+ fn atrace_instant_wrap(&mut self, _tag: u64, _name: *const c_char) {
+ panic!("Unexpected call");
+ }
+ fn atrace_instant_for_track_wrap(
+ &mut self,
+ _tag: u64,
+ _track_name: *const c_char,
+ _name: *const c_char,
+ ) {
+ panic!("Unexpected call");
+ }
+ fn atrace_int_wrap(&mut self, _tag: u64, _name: *const c_char, _value: i32) {
+ panic!("Unexpected call");
+ }
+ fn atrace_int64_wrap(&mut self, _tag: u64, _name: *const c_char, _value: i64) {
+ panic!("Unexpected call");
+ }
+
+ /// This method should contain checks to be performed at the end of the test.
+ fn finish(&self) {}
+ }
+
+ struct DefaultMocker;
+ impl ATraceMocker for DefaultMocker {}
+
+ // Global mock object is thread-local, so that the tests can run safely in parallel.
+ thread_local!(static MOCKER: RefCell<Box<dyn ATraceMocker>> = RefCell::new(Box::new(DefaultMocker{})));
+
+ /// Sets the global mock object.
+ fn set_mocker(mocker: Box<dyn ATraceMocker>) {
+ MOCKER.with(|m| *m.borrow_mut() = mocker)
+ }
+
+ /// Calls the passed method `f` with a mutable reference to the global mock object.
+ /// Example:
+ /// ```
+ /// with_mocker(|mocker| mocker.atrace_begin_wrap(tag, name))
+ /// ```
+ fn with_mocker<F, R>(f: F) -> R
+ where
+ F: FnOnce(&mut dyn ATraceMocker) -> R,
+ {
+ MOCKER.with(|m| f(m.borrow_mut().as_mut()))
+ }
+
+ /// Finish the test and perform final checks in the mocker.
+ /// Calls `finish()` on the global mocker.
+ ///
+ /// Needs to be called manually at the end of each test that uses mocks.
+ ///
+ /// May panic, so it can not be called in `drop()` methods,
+ /// since it may result in double panic.
+ pub fn mocker_finish() {
+ with_mocker(|m| m.finish())
+ }
+
+ /// RAII guard that resets the mock to the default implementation.
+ pub struct MockerGuard;
+ impl Drop for MockerGuard {
+ fn drop(&mut self) {
+ set_mocker(Box::new(DefaultMocker {}));
+ }
+ }
+
+ /// Sets the mock object for the duration of the scope.
+ ///
+ /// Returns a RAII guard that resets the mock back to default on destruction.
+ pub fn set_scoped_mocker<T: ATraceMocker + 'static>(m: T) -> MockerGuard {
+ set_mocker(Box::new(m));
+ MockerGuard {}
+ }
+
+ // Wrapped functions that forward calls into mocker.
+ // The functions are marked as unsafe to match the binding interface, won't compile otherwise.
+ // The mocker methods themselves are not marked as unsafe.
+
+ pub unsafe fn atrace_set_tracing_enabled(enabled: bool) {
+ with_mocker(|m| m.atrace_set_tracing_enabled(enabled))
+ }
+ pub unsafe fn atrace_init() {
+ with_mocker(|m| m.atrace_init())
+ }
+ pub unsafe fn atrace_get_enabled_tags() -> u64 {
+ with_mocker(|m| m.atrace_get_enabled_tags())
+ }
+ pub unsafe fn atrace_is_tag_enabled_wrap(tag: u64) -> u64 {
+ with_mocker(|m| m.atrace_is_tag_enabled_wrap(tag))
+ }
+ pub unsafe fn atrace_begin_wrap(tag: u64, name: *const c_char) {
+ with_mocker(|m| m.atrace_begin_wrap(tag, name))
+ }
+ pub unsafe fn atrace_end_wrap(tag: u64) {
+ with_mocker(|m| m.atrace_end_wrap(tag))
+ }
+ pub unsafe fn atrace_async_begin_wrap(tag: u64, name: *const c_char, cookie: i32) {
+ with_mocker(|m| m.atrace_async_begin_wrap(tag, name, cookie))
+ }
+ pub unsafe fn atrace_async_end_wrap(tag: u64, name: *const c_char, cookie: i32) {
+ with_mocker(|m| m.atrace_async_end_wrap(tag, name, cookie))
+ }
+ pub unsafe fn atrace_async_for_track_begin_wrap(
+ tag: u64,
+ track_name: *const c_char,
+ name: *const c_char,
+ cookie: i32,
+ ) {
+ with_mocker(|m| m.atrace_async_for_track_begin_wrap(tag, track_name, name, cookie))
+ }
+ pub unsafe fn atrace_async_for_track_end_wrap(
+ tag: u64,
+ track_name: *const c_char,
+ cookie: i32,
+ ) {
+ with_mocker(|m| m.atrace_async_for_track_end_wrap(tag, track_name, cookie))
+ }
+ pub unsafe fn atrace_instant_wrap(tag: u64, name: *const c_char) {
+ with_mocker(|m| m.atrace_instant_wrap(tag, name))
+ }
+ pub unsafe fn atrace_instant_for_track_wrap(
+ tag: u64,
+ track_name: *const c_char,
+ name: *const c_char,
+ ) {
+ with_mocker(|m| m.atrace_instant_for_track_wrap(tag, track_name, name))
+ }
+ pub unsafe fn atrace_int_wrap(tag: u64, name: *const c_char, value: i32) {
+ with_mocker(|m| m.atrace_int_wrap(tag, name, value))
+ }
+ pub unsafe fn atrace_int64_wrap(tag: u64, name: *const c_char, value: i64) {
+ with_mocker(|m| m.atrace_int64_wrap(tag, name, value))
+ }
+ }
+
+ #[test]
+ fn forwards_set_tracing_enabled() {
+ #[derive(Default)]
+ struct CallCheck {
+ set_tracing_enabled_count: u32,
+ }
+
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_set_tracing_enabled(&mut self, enabled: bool) {
+ self.set_tracing_enabled_count += 1;
+ assert!(self.set_tracing_enabled_count < 2);
+ assert!(enabled);
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.set_tracing_enabled_count, 1);
+ }
+ }
+
+ let _guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ atrace_set_tracing_enabled(true);
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn forwards_atrace_init() {
+ #[derive(Default)]
+ struct CallCheck {
+ init_count: u32,
+ }
+
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_init(&mut self) {
+ self.init_count += 1;
+ assert!(self.init_count < 2);
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.init_count, 1);
+ }
+ }
+
+ let _guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ atrace_init();
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn forwards_atrace_get_enabled_tags() {
+ #[derive(Default)]
+ struct CallCheck {
+ get_enabled_tags_count: u32,
+ }
+
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_get_enabled_tags(&mut self) -> u64 {
+ self.get_enabled_tags_count += 1;
+ assert!(self.get_enabled_tags_count < 2);
+ (cutils_trace_bindgen::ATRACE_TAG_HAL | cutils_trace_bindgen::ATRACE_TAG_GRAPHICS)
+ as u64
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.get_enabled_tags_count, 1);
+ }
+ }
+
+ let _guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ let res = atrace_get_enabled_tags();
+ assert_eq!(res, AtraceTag::Hal | AtraceTag::Graphics);
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn forwards_trace_begin() {
+ #[derive(Default)]
+ struct CallCheck {
+ begin_count: u32,
+ }
+
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled_wrap(&mut self, _tag: u64) -> u64 {
+ 1
+ }
+ fn atrace_begin_wrap(&mut self, tag: u64, name: *const c_char) {
+ self.begin_count += 1;
+ assert!(self.begin_count < 2);
+ assert_eq!(tag, cutils_trace_bindgen::ATRACE_TAG_APP as u64);
+ // SAFETY: If the code under test is correct, the pointer is guaranteed to satisfy
+ // the requirements of `CStr::from_ptr`. If the code is not correct, this section is
+ // unsafe and will hopefully fail the test.
+ unsafe {
+ assert_eq!(CStr::from_ptr(name).to_str().expect("to_str failed"), "Test Name");
+ }
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.begin_count, 1);
+ }
+ }
+
+ let _guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ atrace_begin(AtraceTag::App, "Test Name");
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn trace_begin_not_called_with_disabled_tag() {
+ #[derive(Default)]
+ struct CallCheck {
+ is_tag_enabled_count: u32,
+ }
+
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled_wrap(&mut self, _tag: u64) -> u64 {
+ self.is_tag_enabled_count += 1;
+ assert!(self.is_tag_enabled_count < 2);
+ 0
+ }
+ fn atrace_begin_wrap(&mut self, _tag: u64, _name: *const c_char) {
+ panic!("Begin should not be called with disabled tag.")
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.is_tag_enabled_count, 1);
+ }
+ }
+
+ let _guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ atrace_begin(AtraceTag::App, "Ignore me");
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn forwards_trace_end() {
+ #[derive(Default)]
+ struct CallCheck {
+ end_count: u32,
+ }
+
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_end_wrap(&mut self, tag: u64) {
+ self.end_count += 1;
+ assert!(self.end_count < 2);
+ assert_eq!(tag, cutils_trace_bindgen::ATRACE_TAG_APP as u64);
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.end_count, 1);
+ }
+ }
+
+ let _guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ atrace_end(AtraceTag::App);
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn can_combine_tags() {
+ #[derive(Default)]
+ struct CallCheck {
+ begin_count: u32,
+ }
+
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled_wrap(&mut self, _tag: u64) -> u64 {
+ 1
+ }
+ fn atrace_begin_wrap(&mut self, tag: u64, _name: *const c_char) {
+ self.begin_count += 1;
+ assert!(self.begin_count < 2);
+ assert_eq!(
+ tag,
+ (cutils_trace_bindgen::ATRACE_TAG_HAL | cutils_trace_bindgen::ATRACE_TAG_CAMERA)
+ as u64
+ );
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.begin_count, 1);
+ }
+ }
+
+ let _guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ atrace_begin(AtraceTag::Hal | AtraceTag::Camera, "foo");
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn forwards_is_tag_enabled() {
+ #[derive(Default)]
+ struct CallCheck {
+ is_tag_enabled_count: u32,
+ }
+
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled_wrap(&mut self, tag: u64) -> u64 {
+ self.is_tag_enabled_count += 1;
+ assert!(self.is_tag_enabled_count < 2);
+ assert_eq!(tag, cutils_trace_bindgen::ATRACE_TAG_ADB as u64);
+ 1
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.is_tag_enabled_count, 1);
+ }
+ }
+
+ let _guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ let res = atrace_is_tag_enabled(AtraceTag::Adb);
+ assert!(res);
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn forwards_async_begin() {
+ #[derive(Default)]
+ struct CallCheck {
+ async_begin_count: u32,
+ }
+
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled_wrap(&mut self, _tag: u64) -> u64 {
+ 1
+ }
+ fn atrace_async_begin_wrap(&mut self, tag: u64, name: *const c_char, cookie: i32) {
+ self.async_begin_count += 1;
+ assert!(self.async_begin_count < 2);
+ assert_eq!(tag, cutils_trace_bindgen::ATRACE_TAG_APP as u64);
+ // SAFETY: If the code under test is correct, the pointer is guaranteed to satisfy
+ // the requirements of `CStr::from_ptr`. If the code is not correct, this section is
+ // unsafe and will hopefully fail the test.
+ unsafe {
+ assert_eq!(CStr::from_ptr(name).to_str().expect("to_str failed"), "Test Name");
+ }
+ assert_eq!(cookie, 123);
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.async_begin_count, 1);
+ }
+ }
+
+ let _guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ atrace_async_begin(AtraceTag::App, "Test Name", 123);
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn forwards_async_end() {
+ #[derive(Default)]
+ struct CallCheck {
+ async_end_count: u32,
+ }
+
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled_wrap(&mut self, _tag: u64) -> u64 {
+ 1
+ }
+ fn atrace_async_end_wrap(&mut self, tag: u64, name: *const c_char, cookie: i32) {
+ self.async_end_count += 1;
+ assert!(self.async_end_count < 2);
+ assert_eq!(tag, cutils_trace_bindgen::ATRACE_TAG_APP as u64);
+ // SAFETY: If the code under test is correct, the pointer is guaranteed to satisfy
+ // the requirements of `CStr::from_ptr`. If the code is not correct, this section is
+ // unsafe and will hopefully fail the test.
+ unsafe {
+ assert_eq!(CStr::from_ptr(name).to_str().expect("to_str failed"), "Test Name");
+ }
+ assert_eq!(cookie, 123);
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.async_end_count, 1);
+ }
+ }
+
+ let _guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ atrace_async_end(AtraceTag::App, "Test Name", 123);
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn forwards_async_for_track_begin() {
+ #[derive(Default)]
+ struct CallCheck {
+ async_for_track_begin_count: u32,
+ }
+
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled_wrap(&mut self, _tag: u64) -> u64 {
+ 1
+ }
+ fn atrace_async_for_track_begin_wrap(
+ &mut self,
+ tag: u64,
+ track_name: *const c_char,
+ name: *const c_char,
+ cookie: i32,
+ ) {
+ self.async_for_track_begin_count += 1;
+ assert!(self.async_for_track_begin_count < 2);
+ assert_eq!(tag, cutils_trace_bindgen::ATRACE_TAG_APP as u64);
+ // SAFETY: If the code under test is correct, the pointer is guaranteed to satisfy
+ // the requirements of `CStr::from_ptr`. If the code is not correct, this section is
+ // unsafe and will hopefully fail the test.
+ unsafe {
+ assert_eq!(
+ CStr::from_ptr(track_name).to_str().expect("to_str failed"),
+ "Track"
+ );
+ assert_eq!(CStr::from_ptr(name).to_str().expect("to_str failed"), "Test Name");
+ }
+ assert_eq!(cookie, 123);
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.async_for_track_begin_count, 1);
+ }
+ }
+
+ let _guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ atrace_async_for_track_begin(AtraceTag::App, "Track", "Test Name", 123);
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn forwards_async_for_track_end() {
+ #[derive(Default)]
+ struct CallCheck {
+ async_for_track_end_count: u32,
+ }
+
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled_wrap(&mut self, _tag: u64) -> u64 {
+ 1
+ }
+ fn atrace_async_for_track_end_wrap(
+ &mut self,
+ tag: u64,
+ track_name: *const c_char,
+ cookie: i32,
+ ) {
+ self.async_for_track_end_count += 1;
+ assert!(self.async_for_track_end_count < 2);
+ assert_eq!(tag, cutils_trace_bindgen::ATRACE_TAG_APP as u64);
+ // SAFETY: If the code under test is correct, the pointer is guaranteed to satisfy
+ // the requirements of `CStr::from_ptr`. If the code is not correct, this section is
+ // unsafe and will hopefully fail the test.
+ unsafe {
+ assert_eq!(
+ CStr::from_ptr(track_name).to_str().expect("to_str failed"),
+ "Track"
+ );
+ }
+ assert_eq!(cookie, 123);
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.async_for_track_end_count, 1);
+ }
+ }
+
+ let _guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ atrace_async_for_track_end(AtraceTag::App, "Track", 123);
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn forwards_trace_instant() {
+ #[derive(Default)]
+ struct CallCheck {
+ trace_instant_count: u32,
+ }
+
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled_wrap(&mut self, _tag: u64) -> u64 {
+ 1
+ }
+ fn atrace_instant_wrap(&mut self, tag: u64, name: *const c_char) {
+ self.trace_instant_count += 1;
+ assert!(self.trace_instant_count < 2);
+ assert_eq!(tag, cutils_trace_bindgen::ATRACE_TAG_APP as u64);
+ // SAFETY: If the code under test is correct, the pointer is guaranteed to satisfy
+ // the requirements of `CStr::from_ptr`. If the code is not correct, this section is
+ // unsafe and will hopefully fail the test.
+ unsafe {
+ assert_eq!(CStr::from_ptr(name).to_str().expect("to_str failed"), "Test Name");
+ }
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.trace_instant_count, 1);
+ }
+ }
+
+ let _guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ atrace_instant(AtraceTag::App, "Test Name");
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn forwards_trace_instant_for_track() {
+ #[derive(Default)]
+ struct CallCheck {
+ trace_instant_for_track_count: u32,
+ }
+
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled_wrap(&mut self, _tag: u64) -> u64 {
+ 1
+ }
+ fn atrace_instant_for_track_wrap(
+ &mut self,
+ tag: u64,
+ track_name: *const c_char,
+ name: *const c_char,
+ ) {
+ self.trace_instant_for_track_count += 1;
+ assert!(self.trace_instant_for_track_count < 2);
+ assert_eq!(tag, cutils_trace_bindgen::ATRACE_TAG_APP as u64);
+ // SAFETY: If the code under test is correct, the pointer is guaranteed to satisfy
+ // the requirements of `CStr::from_ptr`. If the code is not correct, this section is
+ // unsafe and will hopefully fail the test.
+ unsafe {
+ assert_eq!(
+ CStr::from_ptr(track_name).to_str().expect("to_str failed"),
+ "Track"
+ );
+ assert_eq!(CStr::from_ptr(name).to_str().expect("to_str failed"), "Test Name");
+ }
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.trace_instant_for_track_count, 1);
+ }
+ }
+
+ let _guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ atrace_instant_for_track(AtraceTag::App, "Track", "Test Name");
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn forwards_trace_int() {
+ #[derive(Default)]
+ struct CallCheck {
+ trace_int_count: u32,
+ }
+
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled_wrap(&mut self, _tag: u64) -> u64 {
+ 1
+ }
+ fn atrace_int_wrap(&mut self, tag: u64, name: *const c_char, value: i32) {
+ self.trace_int_count += 1;
+ assert!(self.trace_int_count < 2);
+ assert_eq!(tag, cutils_trace_bindgen::ATRACE_TAG_APP as u64);
+ // SAFETY: If the code under test is correct, the pointer is guaranteed to satisfy
+ // the requirements of `CStr::from_ptr`. If the code is not correct, this section is
+ // unsafe and will hopefully fail the test.
+ unsafe {
+ assert_eq!(CStr::from_ptr(name).to_str().expect("to_str failed"), "Test Name");
+ }
+ assert_eq!(value, 32);
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.trace_int_count, 1);
+ }
+ }
+
+ let _guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ atrace_int(AtraceTag::App, "Test Name", 32);
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn forwards_trace_int64() {
+ #[derive(Default)]
+ struct CallCheck {
+ trace_int64_count: u32,
+ }
+
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled_wrap(&mut self, _tag: u64) -> u64 {
+ 1
+ }
+ fn atrace_int64_wrap(&mut self, tag: u64, name: *const c_char, value: i64) {
+ self.trace_int64_count += 1;
+ assert!(self.trace_int64_count < 2);
+ assert_eq!(tag, cutils_trace_bindgen::ATRACE_TAG_APP as u64);
+ // SAFETY: If the code under test is correct, the pointer is guaranteed to satisfy
+ // the requirements of `CStr::from_ptr`. If the code is not correct, this section is
+ // unsafe and will hopefully fail the test.
+ unsafe {
+ assert_eq!(CStr::from_ptr(name).to_str().expect("to_str failed"), "Test Name");
+ }
+ assert_eq!(value, 64);
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.trace_int64_count, 1);
+ }
+ }
+
+ let _guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ atrace_int64(AtraceTag::App, "Test Name", 64);
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn scoped_event_starts_and_ends_in_order() {
+ #[derive(Default)]
+ struct CallCheck {
+ begin_count: u32,
+ end_count: u32,
+ instant_count: u32,
+ }
+
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled_wrap(&mut self, _tag: u64) -> u64 {
+ 1
+ }
+
+ fn atrace_begin_wrap(&mut self, tag: u64, name: *const c_char) {
+ assert_eq!(self.end_count, 0);
+ assert_eq!(self.instant_count, 0);
+
+ self.begin_count += 1;
+ assert!(self.begin_count < 2);
+ assert_eq!(tag, cutils_trace_bindgen::ATRACE_TAG_APP as u64);
+ // SAFETY: If the code under test is correct, the pointer is guaranteed to satisfy
+ // the requirements of `CStr::from_ptr`. If the code is not correct, this section is
+ // unsafe and will hopefully fail the test.
+ unsafe {
+ assert_eq!(
+ CStr::from_ptr(name).to_str().expect("to_str failed"),
+ "Scoped Event"
+ );
+ }
+ }
+
+ fn atrace_instant_wrap(&mut self, _tag: u64, _name: *const c_char) {
+ // We don't care about the contents of the event, we only use it to check begin/end ordering.
+ assert_eq!(self.begin_count, 1);
+ assert_eq!(self.end_count, 0);
+
+ self.instant_count += 1;
+ assert!(self.instant_count < 2);
+ }
+
+ fn atrace_end_wrap(&mut self, tag: u64) {
+ assert_eq!(self.begin_count, 1);
+ assert_eq!(self.instant_count, 1);
+
+ self.end_count += 1;
+ assert!(self.end_count < 2);
+ assert_eq!(tag, cutils_trace_bindgen::ATRACE_TAG_APP as u64);
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.begin_count, 1);
+ assert_eq!(self.end_count, 1);
+ assert_eq!(self.instant_count, 1);
+ }
+ }
+
+ let _guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ {
+ let _event_guard = begin_scoped_event(AtraceTag::App, "Scoped Event");
+ atrace_instant(AtraceTag::App, "Instant event called within scoped event");
+ }
+
+ mock_atrace::mocker_finish();
+ }
+
+ // Need to have this alias to make the macro work, since it calls atrace::begin_scoped_event.
+ use crate as atrace;
+ fn traced_method_for_test() {
+ trace_method!(AtraceTag::App);
+ atrace_instant(AtraceTag::App, "Instant event called within method");
+ }
+
+ #[test]
+ fn method_trace_starts_and_ends_in_order() {
+ #[derive(Default)]
+ struct CallCheck {
+ begin_count: u32,
+ end_count: u32,
+ instant_count: u32,
+ }
+
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled_wrap(&mut self, _tag: u64) -> u64 {
+ 1
+ }
+
+ fn atrace_begin_wrap(&mut self, tag: u64, name: *const c_char) {
+ assert_eq!(self.end_count, 0);
+ assert_eq!(self.instant_count, 0);
+
+ self.begin_count += 1;
+ assert!(self.begin_count < 2);
+ assert_eq!(tag, cutils_trace_bindgen::ATRACE_TAG_APP as u64);
+ // SAFETY: If the code under test is correct, the pointer is guaranteed to satisfy
+ // the requirements of `CStr::from_ptr`. If the code is not correct, this section is
+ // unsafe and will hopefully fail the test.
+ unsafe {
+ assert_eq!(
+ CStr::from_ptr(name).to_str().expect("to_str failed"),
+ "lib::tests::traced_method_for_test"
+ );
+ }
+ }
+
+ fn atrace_instant_wrap(&mut self, _tag: u64, _name: *const c_char) {
+ // We don't care about the contents of the event, we only use it to check begin/end ordering.
+ assert_eq!(self.begin_count, 1);
+ assert_eq!(self.end_count, 0);
+
+ self.instant_count += 1;
+ assert!(self.instant_count < 2);
+ }
+
+ fn atrace_end_wrap(&mut self, tag: u64) {
+ assert_eq!(self.begin_count, 1);
+ assert_eq!(self.instant_count, 1);
+
+ self.end_count += 1;
+ assert!(self.end_count < 2);
+ assert_eq!(tag, cutils_trace_bindgen::ATRACE_TAG_APP as u64);
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.begin_count, 1);
+ assert_eq!(self.end_count, 1);
+ assert_eq!(self.instant_count, 1);
+ }
+ }
+
+ let _guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ traced_method_for_test();
+
+ mock_atrace::mocker_finish();
+ }
+}
diff --git a/libatrace_rust/src/tracing_subscriber.rs b/libatrace_rust/src/tracing_subscriber.rs
new file mode 100644
index 00000000..76c20740
--- /dev/null
+++ b/libatrace_rust/src/tracing_subscriber.rs
@@ -0,0 +1,699 @@
+// 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 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.
+
+//! Tracing-subscriber layer for libatrace_rust.
+
+use ::atrace::AtraceTag;
+use std::fmt::Write;
+use tracing::span::Attributes;
+use tracing::span::Record;
+use tracing::{Event, Id, Subscriber};
+use tracing_subscriber::field::Visit;
+use tracing_subscriber::layer::{Context, Layer};
+use tracing_subscriber::registry::LookupSpan;
+
+/// Subscriber layer that forwards events to ATrace.
+pub struct AtraceSubscriber {
+ tag: AtraceTag,
+ should_record_fields: bool,
+ should_filter: bool,
+}
+
+impl Default for AtraceSubscriber {
+ fn default() -> Self {
+ Self::new(AtraceTag::App)
+ }
+}
+
+impl AtraceSubscriber {
+ /// Makes a new subscriber with tag.
+ pub fn new(tag: AtraceTag) -> AtraceSubscriber {
+ AtraceSubscriber { tag, should_filter: false, should_record_fields: true }
+ }
+
+ /// Enables event and span filtering. With filtering enabled, this layer would filter events for
+ /// all the layers of the subscriber.
+ /// Use this to speed up the subscriber if it's the only layer. Do not enable if you need other
+ /// layers to receive events when ATrace is disabled.
+ pub fn with_filter(self) -> AtraceSubscriber {
+ AtraceSubscriber { should_filter: true, ..self }
+ }
+
+ /// Disables recording of field values.
+ pub fn without_fields(self) -> AtraceSubscriber {
+ AtraceSubscriber { should_record_fields: false, ..self }
+ }
+}
+
+// Internal methods.
+impl AtraceSubscriber {
+ /// Checks that events and spans should be recorded in the span/event notification.
+ fn should_process_event(&self) -> bool {
+ // If `should_filter == true` we don't need to check the tag - it was already checked by
+ // the layer filter in the `Layer::enabled()` method.
+ // The checks are done in this order:
+ // * `Layer::register_callsite()` - once per callsite, the result is cached.
+ // * `Layer::enabled()` - once per span or event construction if the callsite is enabled.
+ // * `should_process_event()` - on every notification like new span, span enter/exit/record, event.
+ // The first two checks are global, i.e. affect other layers, and only enabled with `should_filter`.
+ // Read more:
+ // https://docs.rs/tracing-subscriber/latest/tracing_subscriber/layer/index.html#filtering-with-layers
+ self.should_filter || atrace::atrace_is_tag_enabled(self.tag)
+ }
+}
+
+impl<S: Subscriber + for<'lookup> LookupSpan<'lookup>> Layer<S> for AtraceSubscriber {
+ fn register_callsite(
+ &self,
+ _metadata: &'static tracing::Metadata<'static>,
+ ) -> tracing::subscriber::Interest {
+ if self.should_filter {
+ // When we return `Interest::sometimes()`, the `enabled()` method would get checked
+ // every time.
+ // We can't use callsite caching (`Interest::never()`) because there's no callback
+ // for when tracing gets enabled - we need to check it every time.
+ tracing::subscriber::Interest::sometimes()
+ } else {
+ // If we do not disable events in the layer, we always receive the notifications.
+ tracing::subscriber::Interest::always()
+ }
+ }
+
+ // When filtering in this layer is enabled, this method would get called on every event and span.
+ // This filter affects all layers, so if this method returns false, it would disable the event
+ // for others as well.
+ fn enabled(&self, _metadata: &tracing::Metadata<'_>, _ctx: Context<'_, S>) -> bool {
+ !self.should_filter || atrace::atrace_is_tag_enabled(self.tag)
+ }
+
+ fn on_new_span(&self, attrs: &Attributes, id: &Id, ctx: Context<S>) {
+ if !self.should_record_fields || attrs.fields().is_empty() || !self.should_process_event() {
+ return;
+ }
+
+ let span = ctx.span(id).unwrap();
+ let mut formatter = FieldFormatter::for_span(span.metadata().name());
+ attrs.record(&mut formatter);
+ span.extensions_mut().insert(formatter);
+ }
+
+ fn on_record(&self, span: &Id, values: &Record, ctx: Context<S>) {
+ if !self.should_record_fields || !self.should_process_event() {
+ return;
+ }
+
+ values
+ .record(ctx.span(span).unwrap().extensions_mut().get_mut::<FieldFormatter>().unwrap());
+ }
+
+ fn on_enter(&self, id: &Id, ctx: Context<S>) {
+ if !self.should_process_event() {
+ return;
+ }
+
+ let span = ctx.span(id).unwrap();
+ if span.fields().is_empty() || !self.should_record_fields {
+ atrace::atrace_begin(self.tag, span.metadata().name());
+ } else {
+ let span_extensions = span.extensions();
+ let formatter = span_extensions.get::<FieldFormatter>().unwrap();
+ atrace::atrace_begin(self.tag, formatter.as_str());
+ }
+ }
+
+ fn on_exit(&self, _id: &Id, _ctx: Context<S>) {
+ if !self.should_process_event() {
+ return;
+ }
+
+ atrace::atrace_end(self.tag);
+ }
+
+ fn on_event(&self, event: &Event, _ctx: Context<S>) {
+ if !self.should_process_event() {
+ return;
+ }
+
+ if self.should_record_fields {
+ let mut formatter = FieldFormatter::for_event();
+ event.record(&mut formatter);
+ atrace::atrace_instant(self.tag, formatter.as_str());
+ } else if let Some(field) = event.metadata().fields().field("message") {
+ struct MessageVisitor<'a> {
+ tag: AtraceTag,
+ field: &'a tracing::field::Field,
+ }
+ impl Visit for MessageVisitor<'_> {
+ fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
+ if field == self.field {
+ atrace::atrace_instant(self.tag, value);
+ }
+ }
+ fn record_debug(
+ &mut self,
+ field: &tracing::field::Field,
+ value: &dyn std::fmt::Debug,
+ ) {
+ if field == self.field {
+ atrace::atrace_instant(self.tag, &format!("{:?}", value));
+ }
+ }
+ }
+ event.record(&mut MessageVisitor { tag: self.tag, field: &field });
+ } else {
+ atrace::atrace_instant(
+ self.tag,
+ &format!("{} event", event.metadata().level().as_str()),
+ );
+ }
+ }
+}
+
+struct FieldFormatter {
+ is_event: bool,
+ s: String,
+}
+
+impl FieldFormatter {
+ fn new() -> FieldFormatter {
+ const DEFAULT_STR_CAPACITY: usize = 128; // Should fit most events without realloc.
+ FieldFormatter { is_event: true, s: String::with_capacity(DEFAULT_STR_CAPACITY) }
+ }
+
+ fn for_event() -> FieldFormatter {
+ FieldFormatter { is_event: true, ..FieldFormatter::new() }
+ }
+ fn for_span(span_name: &str) -> FieldFormatter {
+ let mut formatter = FieldFormatter { is_event: false, ..FieldFormatter::new() };
+ formatter.s.push_str(span_name);
+ formatter
+ }
+
+ fn as_str(&self) -> &str {
+ &self.s
+ }
+ fn add_delimeter_if_needed(&mut self) {
+ if !self.s.is_empty() {
+ self.s.push_str(", ");
+ }
+ }
+}
+
+impl Visit for FieldFormatter {
+ fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
+ self.add_delimeter_if_needed();
+ if self.is_event && field.name() == "message" {
+ self.s.push_str(value);
+ } else {
+ write!(&mut self.s, "{} = \"{}\"", field.name(), value).unwrap();
+ }
+ }
+ fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
+ self.add_delimeter_if_needed();
+ if self.is_event && field.name() == "message" {
+ write!(&mut self.s, "{:?}", value).unwrap();
+ } else {
+ write!(&mut self.s, "{} = {:?}", field.name(), value).unwrap();
+ }
+ }
+}
+
+#[cfg(test)]
+use self::tests::mock_atrace as atrace;
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use tracing::Level;
+ use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
+
+ pub mod mock_atrace {
+ use atrace::AtraceTag;
+ use std::cell::RefCell;
+
+ /// Contains logic to check binding calls.
+ /// Implement this trait in the test with mocking logic and checks in implemented functions.
+ /// Default implementations panic.
+ pub trait ATraceMocker {
+ fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
+ panic!("Unexpected call");
+ }
+
+ fn atrace_begin(&mut self, _tag: AtraceTag, _name: &str) {
+ panic!("Unexpected call");
+ }
+
+ fn atrace_end(&mut self, _tag: AtraceTag) {
+ panic!("Unexpected call");
+ }
+
+ fn atrace_instant(&mut self, _tag: AtraceTag, _name: &str) {
+ panic!("Unexpected call");
+ }
+
+ /// This method should contain checks to be performed at the end of the test.
+ fn finish(&self) {}
+ }
+
+ struct DefaultMocker;
+ impl ATraceMocker for DefaultMocker {}
+
+ // Global mock object is thread-local, so that the tests can run safely in parallel.
+ thread_local!(static MOCKER: RefCell<Box<dyn ATraceMocker>> = RefCell::new(Box::new(DefaultMocker{})));
+
+ /// Sets the global mock object.
+ fn set_mocker(mocker: Box<dyn ATraceMocker>) {
+ MOCKER.with(|m| *m.borrow_mut() = mocker)
+ }
+
+ /// Calls the passed method `f` with a mutable reference to the global mock object.
+ /// Example:
+ /// ```
+ /// with_mocker(|mocker| mocker.atrace_begin(tag, name))
+ /// ```
+ fn with_mocker<F, R>(f: F) -> R
+ where
+ F: FnOnce(&mut dyn ATraceMocker) -> R,
+ {
+ MOCKER.with(|m| f(m.borrow_mut().as_mut()))
+ }
+
+ /// Finish the test and perform final checks in the mocker.
+ /// Calls `finish()` on the global mocker.
+ ///
+ /// Needs to be called manually at the end of each test that uses mocks.
+ ///
+ /// May panic, so it can not be called in `drop()` methods,
+ /// since it may result in double panic.
+ pub fn mocker_finish() {
+ with_mocker(|m| m.finish())
+ }
+
+ /// RAII guard that resets the mock to the default implementation.
+ pub struct MockerGuard;
+ impl Drop for MockerGuard {
+ fn drop(&mut self) {
+ set_mocker(Box::new(DefaultMocker {}));
+ }
+ }
+
+ /// Sets the mock object for the duration of the scope.
+ ///
+ /// Returns a RAII guard that resets the mock back to default on destruction.
+ pub fn set_scoped_mocker<T: ATraceMocker + 'static>(m: T) -> MockerGuard {
+ set_mocker(Box::new(m));
+ MockerGuard {}
+ }
+
+ // Wrapped functions that forward calls into mocker.
+
+ pub fn atrace_is_tag_enabled(tag: AtraceTag) -> bool {
+ with_mocker(|m| m.atrace_is_tag_enabled(tag))
+ }
+ pub fn atrace_begin(tag: AtraceTag, name: &str) {
+ with_mocker(|m| m.atrace_begin(tag, name))
+ }
+
+ pub fn atrace_end(tag: AtraceTag) {
+ with_mocker(|m| m.atrace_end(tag))
+ }
+
+ pub fn atrace_instant(tag: AtraceTag, name: &str) {
+ with_mocker(|m| m.atrace_instant(tag, name))
+ }
+ }
+
+ #[test]
+ fn emits_span_begin() {
+ #[derive(Default)]
+ struct CallCheck {
+ begin_count: u32,
+ }
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
+ true
+ }
+ fn atrace_begin(&mut self, tag: AtraceTag, name: &str) {
+ self.begin_count += 1;
+ assert!(self.begin_count < 2);
+ assert_eq!(tag, AtraceTag::App);
+ assert_eq!(name, "test span");
+ }
+ fn atrace_end(&mut self, _tag: AtraceTag) {}
+
+ fn finish(&self) {
+ assert_eq!(self.begin_count, 1);
+ }
+ }
+ let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ let _subscriber_guard = tracing::subscriber::set_default(
+ tracing_subscriber::registry().with(AtraceSubscriber::default()),
+ );
+
+ let _span = tracing::info_span!("test span").entered();
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn emits_span_end() {
+ #[derive(Default)]
+ struct CallCheck {
+ end_count: u32,
+ }
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
+ true
+ }
+ fn atrace_begin(&mut self, _tag: AtraceTag, _name: &str) {}
+ fn atrace_end(&mut self, tag: AtraceTag) {
+ self.end_count += 1;
+ assert!(self.end_count < 2);
+ assert_eq!(tag, AtraceTag::App);
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.end_count, 1);
+ }
+ }
+ let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ let _subscriber_guard = tracing::subscriber::set_default(
+ tracing_subscriber::registry().with(AtraceSubscriber::default()),
+ );
+
+ {
+ let _span = tracing::info_span!("test span").entered();
+ }
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn span_begin_end_is_ordered() {
+ #[derive(Default)]
+ struct CallCheck {
+ begin_count: u32,
+ instant_count: u32,
+ end_count: u32,
+ }
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
+ true
+ }
+ fn atrace_begin(&mut self, _tag: AtraceTag, _name: &str) {
+ assert_eq!(self.end_count, 0);
+ assert_eq!(self.instant_count, 0);
+
+ self.begin_count += 1;
+ assert!(self.begin_count < 2);
+ }
+ fn atrace_instant(&mut self, _tag: AtraceTag, _name: &str) {
+ assert_eq!(self.begin_count, 1);
+ assert_eq!(self.end_count, 0);
+
+ self.instant_count += 1;
+ assert!(self.instant_count < 2);
+ }
+ fn atrace_end(&mut self, _tag: AtraceTag) {
+ assert_eq!(self.begin_count, 1);
+ assert_eq!(self.instant_count, 1);
+
+ self.end_count += 1;
+ assert!(self.end_count < 2);
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.begin_count, 1);
+ assert_eq!(self.end_count, 1);
+ assert_eq!(self.instant_count, 1);
+ }
+ }
+ let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ let _subscriber_guard = tracing::subscriber::set_default(
+ tracing_subscriber::registry().with(AtraceSubscriber::default()),
+ );
+
+ {
+ let _span = tracing::info_span!("span").entered();
+ tracing::info!("test info");
+ }
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn emits_instant_event() {
+ #[derive(Default)]
+ struct CallCheck {
+ instant_count: u32,
+ }
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
+ true
+ }
+ fn atrace_instant(&mut self, tag: AtraceTag, name: &str) {
+ self.instant_count += 1;
+ assert!(self.instant_count < 2);
+ assert_eq!(tag, AtraceTag::App);
+ assert_eq!(name, "test info");
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.instant_count, 1);
+ }
+ }
+ let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ let _subscriber_guard = tracing::subscriber::set_default(
+ tracing_subscriber::registry().with(AtraceSubscriber::default()),
+ );
+
+ tracing::info!("test info");
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn formats_event_without_message_with_fields_disabled() {
+ #[derive(Default)]
+ struct CallCheck {
+ instant_count: u32,
+ }
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
+ true
+ }
+ fn atrace_instant(&mut self, _tag: AtraceTag, name: &str) {
+ self.instant_count += 1;
+ assert!(self.instant_count < 2);
+ assert_eq!(name, "DEBUG event");
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.instant_count, 1);
+ }
+ }
+ let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ let _subscriber_guard = tracing::subscriber::set_default(
+ tracing_subscriber::registry().with(AtraceSubscriber::default().without_fields()),
+ );
+
+ tracing::debug!(foo = 1);
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn formats_event_without_message_with_fields_enabled() {
+ #[derive(Default)]
+ struct CallCheck {
+ instant_count: u32,
+ }
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
+ true
+ }
+ fn atrace_instant(&mut self, _tag: AtraceTag, name: &str) {
+ self.instant_count += 1;
+ assert!(self.instant_count < 2);
+ assert_eq!(name, "foo = 1");
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.instant_count, 1);
+ }
+ }
+ let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ let _subscriber_guard = tracing::subscriber::set_default(
+ tracing_subscriber::registry().with(AtraceSubscriber::default()),
+ );
+
+ tracing::debug!(foo = 1);
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn can_set_tag() {
+ #[derive(Default)]
+ struct CallCheck {
+ begin_count: u32,
+ instant_count: u32,
+ end_count: u32,
+ }
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
+ true
+ }
+ fn atrace_begin(&mut self, tag: AtraceTag, _name: &str) {
+ self.begin_count += 1;
+ assert!(self.begin_count < 2);
+ assert_eq!(tag, AtraceTag::WindowManager);
+ }
+ fn atrace_instant(&mut self, tag: AtraceTag, _name: &str) {
+ self.instant_count += 1;
+ assert!(self.instant_count < 2);
+ assert_eq!(tag, AtraceTag::WindowManager);
+ }
+ fn atrace_end(&mut self, tag: AtraceTag) {
+ self.end_count += 1;
+ assert!(self.end_count < 2);
+ assert_eq!(tag, AtraceTag::WindowManager);
+ }
+
+ fn finish(&self) {
+ assert_eq!(self.begin_count, 1);
+ assert_eq!(self.end_count, 1);
+ assert_eq!(self.instant_count, 1);
+ }
+ }
+ let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ let _subscriber_guard = tracing::subscriber::set_default(
+ tracing_subscriber::registry().with(AtraceSubscriber::new(AtraceTag::WindowManager)),
+ );
+
+ {
+ let _span = tracing::info_span!("span").entered();
+ tracing::info!("test info");
+ }
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn fields_ignored_when_disabled() {
+ #[derive(Default)]
+ struct CallCheck {
+ begin_count: u32,
+ instant_count: u32,
+ }
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
+ true
+ }
+ fn atrace_begin(&mut self, _tag: AtraceTag, name: &str) {
+ self.begin_count += 1;
+ assert!(self.begin_count < 2);
+ assert_eq!(name, "test span");
+ }
+ fn atrace_instant(&mut self, _tag: AtraceTag, name: &str) {
+ self.instant_count += 1;
+ assert!(self.instant_count < 2);
+ assert_eq!(name, "test info");
+ }
+ fn atrace_end(&mut self, _tag: AtraceTag) {}
+ fn finish(&self) {
+ assert_eq!(self.begin_count, 1);
+ assert_eq!(self.instant_count, 1);
+ }
+ }
+ let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ let _subscriber_guard = tracing::subscriber::set_default(
+ tracing_subscriber::registry().with(AtraceSubscriber::default().without_fields()),
+ );
+
+ let _span = tracing::info_span!("test span", bar = "foo").entered();
+ tracing::event!(Level::INFO, foo = "bar", "test info");
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn formats_instant_event_fields() {
+ #[derive(Default)]
+ struct CallCheck {
+ instant_count: u32,
+ }
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
+ true
+ }
+ fn atrace_instant(&mut self, _tag: AtraceTag, name: &str) {
+ self.instant_count += 1;
+ assert!(self.instant_count < 2);
+ assert_eq!(name, "test info, foo = \"bar\", baz = 5");
+ }
+ fn finish(&self) {
+ assert_eq!(self.instant_count, 1);
+ }
+ }
+ let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ let _subscriber_guard = tracing::subscriber::set_default(
+ tracing_subscriber::registry().with(AtraceSubscriber::default()),
+ );
+
+ tracing::event!(Level::INFO, foo = "bar", baz = 5, "test info");
+
+ mock_atrace::mocker_finish();
+ }
+
+ #[test]
+ fn formats_span_fields() {
+ #[derive(Default)]
+ struct CallCheck {
+ begin_count: u32,
+ }
+ impl mock_atrace::ATraceMocker for CallCheck {
+ fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
+ true
+ }
+ fn atrace_begin(&mut self, _tag: AtraceTag, name: &str) {
+ self.begin_count += 1;
+ assert!(self.begin_count < 2);
+ assert_eq!(name, "test span, foo = \"bar\", baz = 5");
+ }
+ fn atrace_end(&mut self, _tag: AtraceTag) {}
+ fn finish(&self) {
+ assert_eq!(self.begin_count, 1);
+ }
+ }
+ let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default());
+
+ let _subscriber_guard = tracing::subscriber::set_default(
+ tracing_subscriber::registry().with(AtraceSubscriber::default()),
+ );
+
+ let _span = tracing::info_span!("test span", foo = "bar", baz = 5).entered();
+
+ mock_atrace::mocker_finish();
+ }
+}
diff --git a/libfec/fec_open.cpp b/libfec/fec_open.cpp
index 175207b8..6825942b 100644
--- a/libfec/fec_open.cpp
+++ b/libfec/fec_open.cpp
@@ -418,8 +418,6 @@ int fec_close(struct fec_handle *f)
close(f->fd);
}
- pthread_mutex_destroy(&f->mutex);
-
reset_handle(f);
delete f;
@@ -528,11 +526,6 @@ int fec_open(struct fec_handle **handle, const char *path, int mode, int flags,
f->ecc.rsn = FEC_RSM - roots;
f->flags = flags;
- if (unlikely(pthread_mutex_init(&f->mutex, NULL) != 0)) {
- error("failed to create a mutex: %s", strerror(errno));
- return -1;
- }
-
f->fd = TEMP_FAILURE_RETRY(open(path, mode | O_CLOEXEC));
if (f->fd == -1) {
diff --git a/libfec/fec_private.h b/libfec/fec_private.h
index 199f8017..0c633223 100644
--- a/libfec/fec_private.h
+++ b/libfec/fec_private.h
@@ -135,8 +135,7 @@ struct fec_handle {
ecc_info ecc;
int fd;
int flags; /* additional flags passed to fec_open */
- int mode; /* mode for open(2) */
- pthread_mutex_t mutex;
+ int mode; /* mode for open(2) */
uint64_t errors;
uint64_t data_size;
uint64_t pos;
diff --git a/libfec/fec_process.cpp b/libfec/fec_process.cpp
index f11b8b2c..51290772 100644
--- a/libfec/fec_process.cpp
+++ b/libfec/fec_process.cpp
@@ -14,12 +14,13 @@
* limitations under the License.
*/
+#include <future>
#include "fec_private.h"
struct process_info {
int id;
- fec_handle *f;
- uint8_t *buf;
+ fec_handle* f;
+ uint8_t* buf;
size_t count;
uint64_t offset;
read_func func;
@@ -28,21 +29,15 @@ struct process_info {
};
/* thread function */
-static void * __process(void *cookie)
-{
- process_info *p = static_cast<process_info *>(cookie);
-
- debug("thread %d: [%" PRIu64 ", %" PRIu64 ")", p->id, p->offset,
- p->offset + p->count);
+static process_info* __process(process_info* p) {
+ debug("thread %d: [%" PRIu64 ", %" PRIu64 ")", p->id, p->offset, p->offset + p->count);
p->rc = p->func(p->f, p->buf, p->count, p->offset, &p->errors);
return p;
}
/* launches a maximum number of threads to process a read */
-ssize_t process(fec_handle *f, uint8_t *buf, size_t count, uint64_t offset,
- read_func func)
-{
+ssize_t process(fec_handle* f, uint8_t* buf, size_t count, uint64_t offset, read_func func) {
check(f);
check(buf);
check(func);
@@ -60,30 +55,27 @@ ssize_t process(fec_handle *f, uint8_t *buf, size_t count, uint64_t offset,
}
uint64_t start = (offset / FEC_BLOCKSIZE) * FEC_BLOCKSIZE;
- size_t blocks = fec_div_round_up(count, FEC_BLOCKSIZE);
-
- size_t count_per_thread = fec_div_round_up(blocks, threads) * FEC_BLOCKSIZE;
- size_t max_threads = fec_div_round_up(count, count_per_thread);
+ size_t blocks = fec_div_round_up(offset + count - start, FEC_BLOCKSIZE);
- if ((size_t)threads > max_threads) {
- threads = (int)max_threads;
+ /* start at most one thread per block we're accessing */
+ if ((size_t)threads > blocks) {
+ threads = (int)blocks;
}
+ size_t count_per_thread = fec_div_round_up(blocks, threads) * FEC_BLOCKSIZE;
size_t left = count;
uint64_t pos = offset;
uint64_t end = start + count_per_thread;
- debug("%d threads, %zu bytes per thread (total %zu)", threads,
- count_per_thread, count);
+ debug("max %d threads, %zu bytes per thread (total %zu spanning %zu blocks)", threads,
+ count_per_thread, count, blocks);
- std::vector<pthread_t> handles;
+ std::vector<std::future<process_info*>> handles;
process_info info[threads];
ssize_t rc = 0;
/* start threads to process queue */
- for (int i = 0; i < threads; ++i) {
- check(left > 0);
-
+ for (int i = 0; i < threads && left > 0; ++i) {
info[i].id = i;
info[i].f = f;
info[i].buf = &buf[pos - offset];
@@ -97,32 +89,19 @@ ssize_t process(fec_handle *f, uint8_t *buf, size_t count, uint64_t offset,
info[i].count = left;
}
- pthread_t thread;
-
- if (pthread_create(&thread, NULL, __process, &info[i]) != 0) {
- error("failed to create thread: %s", strerror(errno));
- rc = -1;
- } else {
- handles.push_back(thread);
- }
+ handles.push_back(std::async(std::launch::async, __process, &info[i]));
pos = end;
- end += count_per_thread;
+ end += count_per_thread;
left -= info[i].count;
}
- check(left == 0);
-
ssize_t nread = 0;
/* wait for all threads to complete */
- for (auto thread : handles) {
- process_info *p = NULL;
-
- if (pthread_join(thread, (void **)&p) != 0) {
- error("failed to join thread: %s", strerror(errno));
- rc = -1;
- } else if (!p || p->rc == -1) {
+ for (auto&& future : handles) {
+ process_info* p = future.get();
+ if (!p || p->rc == -1) {
rc = -1;
} else {
nread += p->rc;
@@ -130,7 +109,7 @@ ssize_t process(fec_handle *f, uint8_t *buf, size_t count, uint64_t offset,
}
}
- if (rc == -1) {
+ if (left > 0 || rc == -1) {
errno = EIO;
return -1;
}
diff --git a/libfec/test/fec_unittest.cpp b/libfec/test/fec_unittest.cpp
index 421eb501..1ced2d98 100644
--- a/libfec/test/fec_unittest.cpp
+++ b/libfec/test/fec_unittest.cpp
@@ -215,6 +215,16 @@ TEST_F(FecUnitTest, VerityImage_FecRead) {
ASSERT_EQ(1024, fec_pread(handle, read_data.data(), 1024, corrupt_offset));
ASSERT_EQ(std::vector<uint8_t>(1024, 255), read_data);
+
+ // Unaligned read that spans two blocks
+ ASSERT_EQ(678, fec_pread(handle, read_data.data(), 678, corrupt_offset - 123));
+ ASSERT_EQ(std::vector<uint8_t>(123, 254),
+ std::vector<uint8_t>(read_data.begin(), read_data.begin() + 123));
+ ASSERT_EQ(std::vector<uint8_t>(555, 255),
+ std::vector<uint8_t>(read_data.begin() + 123, read_data.begin() + 678));
+
+ std::vector<uint8_t> large_data(53388, 0);
+ ASSERT_EQ(53388, fec_pread(handle, large_data.data(), 53388, 385132));
}
TEST_F(FecUnitTest, LoadAvbImage_HashtreeFooter) {
diff --git a/libfscrypt/Android.bp b/libfscrypt/Android.bp
index 53091d8f..6c913160 100644
--- a/libfscrypt/Android.bp
+++ b/libfscrypt/Android.bp
@@ -29,7 +29,6 @@ cc_library {
shared_libs: [
"libbase",
"libcutils",
- "libkeyutils",
"liblogwrap",
],
}
diff --git a/libfscrypt/fscrypt.cpp b/libfscrypt/fscrypt.cpp
index 174cecad..4c756b18 100644
--- a/libfscrypt/fscrypt.cpp
+++ b/libfscrypt/fscrypt.cpp
@@ -29,6 +29,7 @@
#include <logwrap/logwrap.h>
#include <string.h>
#include <sys/stat.h>
+#include <sys/statvfs.h>
#include <sys/types.h>
#include <unistd.h>
#include <utils/misc.h>
@@ -161,6 +162,9 @@ bool OptionsToStringForApiLevel(unsigned int first_api_level, const EncryptionOp
if (options.use_hw_wrapped_key) {
*options_string += "+wrappedkey_v0";
}
+ if (options.dusize_4k) {
+ *options_string += "+dusize_4k";
+ }
EncryptionOptions options_check;
if (!ParseOptionsForApiLevel(first_api_level, *options_string, &options_check)) {
@@ -207,6 +211,7 @@ bool ParseOptionsForApiLevel(unsigned int first_api_level, const std::string& op
// Default to v2 after Q
options->version = first_api_level > __ANDROID_API_Q__ ? 2 : 1;
options->flags = 0;
+ options->dusize_4k = false;
options->use_hw_wrapped_key = false;
if (parts.size() > 2 && !parts[2].empty()) {
auto flags = android::base::Split(parts[2], "+");
@@ -221,6 +226,8 @@ bool ParseOptionsForApiLevel(unsigned int first_api_level, const std::string& op
options->flags |= FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32;
} else if (flag == "wrappedkey_v0") {
options->use_hw_wrapped_key = true;
+ } else if (flag == "dusize_4k") {
+ options->dusize_4k = true;
} else {
LOG(ERROR) << "Unknown flag: " << flag;
return false;
@@ -284,6 +291,15 @@ static std::string PolicyDebugString(const EncryptionPolicy& policy) {
return ss.str();
}
+static int GetFilesystemBlockSize(const std::string& path) {
+ struct statvfs info;
+ if (statvfs(path.c_str(), &info) == 0) {
+ return info.f_bsize;
+ }
+ PLOG(ERROR) << "Error retrieving filesystem information from " << path;
+ return getpagesize();
+}
+
bool EnsurePolicy(const EncryptionPolicy& policy, const std::string& directory) {
union {
fscrypt_policy_v1 v1;
@@ -317,6 +333,16 @@ bool EnsurePolicy(const EncryptionPolicy& policy, const std::string& directory)
kern_policy.v2.contents_encryption_mode = policy.options.contents_mode;
kern_policy.v2.filenames_encryption_mode = policy.options.filenames_mode;
kern_policy.v2.flags = policy.options.flags;
+ // Configure the data unit size if one was explicitly specified and it doesn't match the
+ // default data unit size of the filesystem.
+ //
+ // We don't configure a data unit size if one wasn't explicitly specified, since the
+ // kernel might not support it. We also don't configure a data unit size that's already
+ // the filesystem default, since this allows dusize_4k to be added to the fstab of an
+ // existing device using 4K filesystem blocks without changing the policy.
+ if (policy.options.dusize_4k && GetFilesystemBlockSize(directory) != 4096) {
+ kern_policy.v2.log2_data_unit_size = 12;
+ }
policy.key_raw_ref.copy(reinterpret_cast<char*>(kern_policy.v2.master_key_identifier),
FSCRYPT_KEY_IDENTIFIER_SIZE);
break;
diff --git a/libfscrypt/include/fscrypt/fscrypt.h b/libfscrypt/include/fscrypt/fscrypt.h
index 11f37119..11c3c04a 100644
--- a/libfscrypt/include/fscrypt/fscrypt.h
+++ b/libfscrypt/include/fscrypt/fscrypt.h
@@ -35,6 +35,7 @@ struct EncryptionOptions {
int filenames_mode;
int flags;
bool use_hw_wrapped_key;
+ bool dusize_4k;
// Ensure that "version" is not valid on creation and so must be explicitly set
EncryptionOptions() : version(0) {}
@@ -64,7 +65,7 @@ bool EnsurePolicy(const EncryptionPolicy& policy, const std::string& directory);
inline bool operator==(const EncryptionOptions& lhs, const EncryptionOptions& rhs) {
return (lhs.version == rhs.version) && (lhs.contents_mode == rhs.contents_mode) &&
(lhs.filenames_mode == rhs.filenames_mode) && (lhs.flags == rhs.flags) &&
- (lhs.use_hw_wrapped_key == rhs.use_hw_wrapped_key);
+ (lhs.use_hw_wrapped_key == rhs.use_hw_wrapped_key) && (lhs.dusize_4k == rhs.dusize_4k);
}
inline bool operator!=(const EncryptionOptions& lhs, const EncryptionOptions& rhs) {
diff --git a/libfscrypt/tests/Android.bp b/libfscrypt/tests/Android.bp
index c630fc6d..aed2b0d0 100644
--- a/libfscrypt/tests/Android.bp
+++ b/libfscrypt/tests/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_security_response",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "system_extras_libfscrypt_license"
diff --git a/libfscrypt/tests/fscrypt_test.cpp b/libfscrypt/tests/fscrypt_test.cpp
index 70eb1780..90297db4 100644
--- a/libfscrypt/tests/fscrypt_test.cpp
+++ b/libfscrypt/tests/fscrypt_test.cpp
@@ -63,6 +63,7 @@ TEST(fscrypt, ParseOptions) {
EXPECT_EQ(FSCRYPT_MODE_AES_256_XTS, options.contents_mode);
EXPECT_EQ(FSCRYPT_MODE_AES_256_CTS, options.filenames_mode);
EXPECT_EQ(FSCRYPT_POLICY_FLAGS_PAD_4, options.flags);
+ EXPECT_FALSE(options.dusize_4k);
}
for (const auto& d : defaults) {
TEST_STRING(30, d, "aes-256-xts:aes-256-cts:v2");
@@ -71,6 +72,7 @@ TEST(fscrypt, ParseOptions) {
EXPECT_EQ(FSCRYPT_MODE_AES_256_XTS, options.contents_mode);
EXPECT_EQ(FSCRYPT_MODE_AES_256_CTS, options.filenames_mode);
EXPECT_EQ(FSCRYPT_POLICY_FLAGS_PAD_16, options.flags);
+ EXPECT_FALSE(options.dusize_4k);
}
EXPECT_FALSE(ParseOptionsForApiLevel(29, "blah", &dummy_options));
@@ -176,6 +178,23 @@ TEST(fscrypt, ParseOptions) {
EXPECT_FALSE(ParseOptionsForApiLevel(30, "aes-256-xts:aes-256-cts:v2:foo", &dummy_options));
EXPECT_FALSE(ParseOptionsForApiLevel(30, "aes-256-xts:aes-256-cts:blah", &dummy_options));
EXPECT_FALSE(ParseOptionsForApiLevel(30, "aes-256-xts:aes-256-cts:vblah", &dummy_options));
+
+ {
+ TEST_STRING(34, ":aes-256-hctr2", "aes-256-xts:aes-256-hctr2:v2");
+ EXPECT_EQ(2, options.version);
+ EXPECT_EQ(FSCRYPT_MODE_AES_256_XTS, options.contents_mode);
+ EXPECT_EQ(FSCRYPT_MODE_AES_256_HCTR2, options.filenames_mode);
+ EXPECT_EQ(FSCRYPT_POLICY_FLAGS_PAD_16, options.flags);
+ }
+
+ {
+ TEST_STRING(34, "::dusize_4k", "aes-256-xts:aes-256-cts:v2+dusize_4k");
+ EXPECT_EQ(2, options.version);
+ EXPECT_EQ(FSCRYPT_MODE_AES_256_XTS, options.contents_mode);
+ EXPECT_EQ(FSCRYPT_MODE_AES_256_CTS, options.filenames_mode);
+ EXPECT_EQ(FSCRYPT_POLICY_FLAGS_PAD_16, options.flags);
+ EXPECT_TRUE(options.dusize_4k);
+ }
}
TEST(fscrypt, ComparePolicies) {
@@ -193,6 +212,7 @@ TEST(fscrypt, ComparePolicies) {
foo_options.filenames_mode = 1;
foo_options.flags = 1;
foo_options.use_hw_wrapped_key = true;
+ foo_options.dusize_4k = true;
foo.options = foo_options;
EXPECT_EQ(foo, foo);
TEST_INEQUALITY(foo, key_raw_ref, "bar");
@@ -201,4 +221,5 @@ TEST(fscrypt, ComparePolicies) {
TEST_INEQUALITY(foo, options.filenames_mode, 3);
TEST_INEQUALITY(foo, options.flags, 0);
TEST_INEQUALITY(foo, options.use_hw_wrapped_key, false);
+ TEST_INEQUALITY(foo, options.dusize_4k, false);
}
diff --git a/libjsonpb/parse/include/jsonpb/error_or.h b/libjsonpb/parse/include/jsonpb/error_or.h
index 3fd3e997..b012a1ea 100644
--- a/libjsonpb/parse/include/jsonpb/error_or.h
+++ b/libjsonpb/parse/include/jsonpb/error_or.h
@@ -17,6 +17,7 @@
#pragma once
#include <string>
+#include <utility>
#include <variant>
#include <android-base/logging.h>
@@ -49,22 +50,22 @@ struct ErrorOr {
return *std::get_if<0u>(&data_);
}
bool ok() const { return data_.index() != 0; }
- static ErrorOr<T> MakeError(const std::string& message) {
- return ErrorOr<T>(message, Tag::kDummy);
+ static ErrorOr<T> MakeError(std::string message) {
+ return ErrorOr<T>(std::move(message), Tag::kDummy);
}
private:
enum class Tag { kDummy };
static constexpr std::in_place_index_t<0> kIndex0{};
static constexpr std::in_place_index_t<1> kIndex1{};
- ErrorOr(const std::string& msg, Tag) : data_(kIndex0, msg) {}
+ ErrorOr(std::string msg, Tag) : data_(kIndex0, std::move(msg)) {}
std::variant<std::string, T> data_;
};
template <typename T>
-inline ErrorOr<T> MakeError(const std::string& message) {
- return ErrorOr<T>::MakeError(message);
+inline ErrorOr<T> MakeError(std::string message) {
+ return ErrorOr<T>::MakeError(std::move(message));
}
} // namespace jsonpb
diff --git a/libjsonpb/parse/jsonpb.cpp b/libjsonpb/parse/jsonpb.cpp
index e65a39d1..6c428282 100644
--- a/libjsonpb/parse/jsonpb.cpp
+++ b/libjsonpb/parse/jsonpb.cpp
@@ -40,7 +40,7 @@ ErrorOr<std::string> MessageToJsonString(const Message& message) {
std::unique_ptr<TypeResolver> resolver(
NewTypeResolverForDescriptorPool(kTypeUrlPrefix, DescriptorPool::generated_pool()));
- google::protobuf::util::JsonOptions options;
+ google::protobuf::util::JsonPrintOptions options;
options.add_whitespace = true;
std::string json;
@@ -48,7 +48,7 @@ ErrorOr<std::string> MessageToJsonString(const Message& message) {
&json, options);
if (!status.ok()) {
- return MakeError<std::string>(status.message().as_string());
+ return MakeError<std::string>(std::string(status.message()));
}
return ErrorOr<std::string>(std::move(json));
}
@@ -61,7 +61,7 @@ ErrorOr<std::monostate> JsonStringToMessage(const std::string& content, Message*
std::string binary;
auto status = JsonToBinaryString(resolver.get(), GetTypeUrl(*message), content, &binary);
if (!status.ok()) {
- return MakeError<std::monostate>(status.message().as_string());
+ return MakeError<std::monostate>(std::string(status.message()));
}
if (!message->ParseFromString(binary)) {
return MakeError<std::monostate>("Fail to parse.");
diff --git a/libjsonpb/verify/Android.bp b/libjsonpb/verify/Android.bp
index a6641855..be49b037 100644
--- a/libjsonpb/verify/Android.bp
+++ b/libjsonpb/verify/Android.bp
@@ -16,6 +16,7 @@
// using Protobuf as schema for JSON files. The reason is that the JSON parser that
// libprotobuf-cpp-full provides is relatively relaxed.
package {
+ default_team: "trendy_team_android_kernel",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/memory_replay/.clang-format b/memory_replay/.clang-format
new file mode 120000
index 00000000..fd0645fd
--- /dev/null
+++ b/memory_replay/.clang-format
@@ -0,0 +1 @@
+../.clang-format-2 \ No newline at end of file
diff --git a/memory_replay/Alloc.cpp b/memory_replay/Alloc.cpp
index a6247105..b2112188 100644
--- a/memory_replay/Alloc.cpp
+++ b/memory_replay/Alloc.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <malloc.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
diff --git a/memory_replay/Android.bp b/memory_replay/Android.bp
index bdcde1b2..e1f3b68a 100644
--- a/memory_replay/Android.bp
+++ b/memory_replay/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_native_tools_libraries",
default_applicable_licenses: ["system_extras_memory_replay_license"],
}
@@ -75,7 +76,6 @@ cc_defaults {
static_libs: [
"liballoc_parser",
- "libasync_safe",
],
}
@@ -85,6 +85,8 @@ cc_binary {
srcs: ["main.cpp"],
+ static_libs: ["liblog"],
+
multilib: {
lib32: {
suffix: "32",
@@ -95,6 +97,33 @@ cc_binary {
},
}
+cc_binary_host {
+ name: "filter_trace",
+
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ ],
+
+ shared_libs: [
+ "libziparchive",
+ ],
+
+ static_libs: [
+ "liballoc_parser",
+ "libbase",
+ "liblog",
+ ],
+
+ srcs: [
+ "Alloc.cpp",
+ "File.cpp",
+ "FilterTrace.cpp",
+ "Pointers.cpp",
+ ],
+}
+
cc_test {
name: "memory_replay_tests",
defaults: ["memory_replay_defaults"],
diff --git a/memory_replay/FilterTrace.cpp b/memory_replay/FilterTrace.cpp
new file mode 100644
index 00000000..27f1945b
--- /dev/null
+++ b/memory_replay/FilterTrace.cpp
@@ -0,0 +1,210 @@
+/*
+ * 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 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.
+ */
+
+#include <err.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <limits>
+#include <string_view>
+#include <unordered_map>
+
+#include <android-base/file.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+
+#include "AllocParser.h"
+#include "File.h"
+
+static std::string GetBaseExec() {
+ return android::base::Basename(android::base::GetExecutablePath());
+}
+
+static void Usage() {
+ fprintf(
+ stderr,
+ "Usage: %s [--min_size SIZE] [--max_size SIZE] [--print_trace_format] [--help] TRACE_FILE\n",
+ GetBaseExec().c_str());
+ fprintf(stderr, " --min_size SIZE\n");
+ fprintf(stderr, " Display all allocations that are greater than or equal to SIZE\n");
+ fprintf(stderr, " --max_size SIZE\n");
+ fprintf(stderr, " Display all allocations that are less than or equal to SIZE\n");
+ fprintf(stderr, " --print_trace_format\n");
+ fprintf(stderr, " Display all allocations from the trace in the trace format\n");
+ fprintf(stderr, " --help\n");
+ fprintf(stderr, " Display this usage message\n");
+ fprintf(stderr, " TRACE_FILE\n");
+ fprintf(stderr, " The name of the trace file to filter\n");
+ fprintf(stderr, "\n Display all of the allocations from the trace file that meet the filter\n");
+ fprintf(stderr, " criteria. By default, without changing the min size or max size, all\n");
+ fprintf(stderr, " allocations in the trace will be printed.\n");
+}
+
+static bool ParseOptions(int argc, char** argv, size_t& min_size, size_t& max_size,
+ bool& print_trace_format, std::string_view& trace_file) {
+ while (true) {
+ option options[] = {
+ {"min_size", required_argument, nullptr, 'i'},
+ {"max_size", required_argument, nullptr, 'x'},
+ {"print_trace_format", no_argument, nullptr, 'p'},
+ {"help", no_argument, nullptr, 'h'},
+ {nullptr, 0, nullptr, 0},
+ };
+ int option_index = 0;
+ int opt = getopt_long(argc, argv, "", options, &option_index);
+ if (opt == -1) {
+ break;
+ }
+
+ switch (opt) {
+ case 'i':
+ case 'x':
+ size_t value;
+ if (!android::base::ParseUint<size_t>(optarg, &value)) {
+ fprintf(stderr, "%s: option '--%s' is not valid: %s\n", GetBaseExec().c_str(),
+ options[option_index].name, optarg);
+ return false;
+ }
+ if (opt == 'i') {
+ min_size = value;
+ } else {
+ max_size = value;
+ }
+ break;
+ case 'p':
+ print_trace_format = true;
+ break;
+ case 'h':
+ default:
+ return false;
+ }
+ }
+ if (optind + 1 != argc) {
+ fprintf(stderr, "%s: only allows one argument.\n", GetBaseExec().c_str());
+ return false;
+ }
+ if (min_size > max_size) {
+ fprintf(stderr, "%s: min size(%zu) must be less than max size(%zu)\n", GetBaseExec().c_str(),
+ min_size, max_size);
+ return false;
+ }
+
+ trace_file = argv[optind];
+ return true;
+}
+
+static void PrintEntry(const AllocEntry& entry, size_t size, bool print_trace_format) {
+ if (print_trace_format) {
+ switch (entry.type) {
+ case REALLOC:
+ if (entry.u.old_ptr == 0) {
+ // Convert to a malloc since it is functionally the same.
+ printf("%d: malloc %p %zu\n", entry.tid, reinterpret_cast<void*>(entry.ptr), entry.size);
+ } else {
+ printf("%d: realloc %p %p %zu\n", entry.tid, reinterpret_cast<void*>(entry.ptr),
+ reinterpret_cast<void*>(entry.u.old_ptr), entry.size);
+ }
+ break;
+ case MALLOC:
+ printf("%d: malloc %p %zu\n", entry.tid, reinterpret_cast<void*>(entry.ptr), entry.size);
+ break;
+ case MEMALIGN:
+ printf("%d: memalign %p %zu %zu\n", entry.tid, reinterpret_cast<void*>(entry.ptr),
+ entry.u.align, entry.size);
+ break;
+ case CALLOC:
+ printf("%d: calloc %p %zu %zu\n", entry.tid, reinterpret_cast<void*>(entry.ptr),
+ entry.u.n_elements, entry.size);
+ break;
+ default:
+ errx(1, "Invalid entry type found %d\n", entry.type);
+ break;
+ }
+ } else {
+ printf("%s size %zu\n", entry.type == REALLOC && entry.u.old_ptr != 0 ? "realloc" : "alloc",
+ size);
+ }
+}
+
+static void ProcessTrace(const std::string_view& trace, size_t min_size, size_t max_size,
+ bool print_trace_format) {
+ AllocEntry* entries;
+ size_t num_entries;
+ GetUnwindInfo(trace.data(), &entries, &num_entries);
+
+ if (!print_trace_format) {
+ if (max_size != std::numeric_limits<size_t>::max()) {
+ printf("Scanning for allocations between %zu and %zu\n", min_size, max_size);
+ } else if (min_size != 0) {
+ printf("Scanning for allocations >= %zu\n", min_size);
+ } else {
+ printf("Scanning for all allocations\n");
+ }
+ }
+ size_t total_allocs = 0;
+ size_t total_reallocs = 0;
+ for (size_t i = 0; i < num_entries; i++) {
+ const AllocEntry& entry = entries[i];
+ switch (entry.type) {
+ case MALLOC:
+ case MEMALIGN:
+ case REALLOC:
+ if (entry.size >= min_size && entry.size <= max_size) {
+ PrintEntry(entry, entry.size, print_trace_format);
+ if (entry.type == REALLOC) {
+ total_reallocs++;
+ } else {
+ total_allocs++;
+ }
+ }
+ break;
+
+ case CALLOC:
+ if (size_t size = entry.u.n_elements * entry.size;
+ size >= min_size && entry.size <= max_size) {
+ PrintEntry(entry, size, print_trace_format);
+ }
+ break;
+
+ case FREE:
+ case THREAD_DONE:
+ default:
+ break;
+ }
+ }
+ if (!print_trace_format) {
+ printf("Total allocs: %zu\n", total_allocs);
+ printf("Total reallocs: %zu\n", total_reallocs);
+ }
+
+ FreeEntries(entries, num_entries);
+}
+
+int main(int argc, char** argv) {
+ size_t min_size = 0;
+ size_t max_size = std::numeric_limits<size_t>::max();
+ bool print_trace_format = false;
+ std::string_view trace_file;
+ if (!ParseOptions(argc, argv, min_size, max_size, print_trace_format, trace_file)) {
+ Usage();
+ return 1;
+ }
+
+ ProcessTrace(trace_file, min_size, max_size, print_trace_format);
+ return 0;
+}
diff --git a/memory_replay/NativeInfo.cpp b/memory_replay/NativeInfo.cpp
index 3439a29d..433b6b90 100644
--- a/memory_replay/NativeInfo.cpp
+++ b/memory_replay/NativeInfo.cpp
@@ -27,23 +27,12 @@
#include <unistd.h>
#include <android-base/unique_fd.h>
-#include <async_safe/log.h>
#include "NativeInfo.h"
-void NativePrintf(const char* fmt, ...) {
- va_list args;
- va_start(args, fmt);
- char buffer[512];
- int buffer_len = async_safe_format_buffer_va_list(buffer, sizeof(buffer), fmt, args);
- va_end(args);
-
- (void)write(STDOUT_FILENO, buffer, buffer_len);
-}
-
void NativeFormatFloat(char* buffer, size_t buffer_len, uint64_t value, uint64_t divisor) {
uint64_t hundreds = ((((value % divisor) * 1000) / divisor) + 5) / 10;
- async_safe_format_buffer(buffer, buffer_len, "%" PRIu64 ".%02" PRIu64, value / divisor, hundreds);
+ snprintf(buffer, buffer_len, "%" PRIu64 ".%02" PRIu64, value / divisor, hundreds);
}
// This function is not re-entrant since it uses a static buffer for
@@ -106,7 +95,7 @@ void NativePrintInfo(const char* preamble) {
android::base::unique_fd smaps_fd(open("/proc/self/smaps", O_RDONLY));
if (smaps_fd == -1) {
- err(1, "Cannot open /proc/self/smaps: %s\n", strerror(errno));
+ err(1, "Cannot open /proc/self/smaps");
}
NativeGetInfo(smaps_fd, &rss_bytes, &va_bytes);
@@ -114,7 +103,7 @@ void NativePrintInfo(const char* preamble) {
// Avoid any allocations, so use special non-allocating printfs.
char buffer[256];
NativeFormatFloat(buffer, sizeof(buffer), rss_bytes, 1024 * 1024);
- NativePrintf("%sNative RSS: %zu bytes %sMB\n", preamble, rss_bytes, buffer);
+ dprintf(STDOUT_FILENO, "%sNative RSS: %zu bytes %sMB\n", preamble, rss_bytes, buffer);
NativeFormatFloat(buffer, sizeof(buffer), va_bytes, 1024 * 1024);
- NativePrintf("%sNative VA Space: %zu bytes %sMB\n", preamble, va_bytes, buffer);
+ dprintf(STDOUT_FILENO, "%sNative VA Space: %zu bytes %sMB\n", preamble, va_bytes, buffer);
}
diff --git a/memory_replay/NativeInfo.h b/memory_replay/NativeInfo.h
index c91eec29..a33db027 100644
--- a/memory_replay/NativeInfo.h
+++ b/memory_replay/NativeInfo.h
@@ -20,8 +20,5 @@ void NativeGetInfo(int smaps_fd, size_t* rss_bytes, size_t* va_bytes);
void NativePrintInfo(const char* preamble);
-// Does not support any floating point specifiers.
-void NativePrintf(const char* fmt, ...) __printflike(1, 2);
-
// Fill buffer as if %0.2f was chosen for value / divisor.
void NativeFormatFloat(char* buffer, size_t buffer_len, uint64_t value, uint64_t divisor);
diff --git a/memory_replay/Pointers.cpp b/memory_replay/Pointers.cpp
index 6335dc2c..b04b9310 100644
--- a/memory_replay/Pointers.cpp
+++ b/memory_replay/Pointers.cpp
@@ -35,7 +35,7 @@ Pointers::Pointers(size_t max_allocs) {
void* memory =
mmap(nullptr, pointers_size_, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
if (memory == MAP_FAILED) {
- err(1, "Unable to allocate data for pointer hash: %zu total_allocs\n", max_allocs);
+ err(1, "Unable to allocate data for pointer hash: %zu total_allocs", max_allocs);
}
// Set all of the pointers to be empty.
memset(memory, 0, pointers_size_);
@@ -52,7 +52,7 @@ Pointers::~Pointers() {
void Pointers::Add(uintptr_t key_pointer, void* pointer) {
pointer_data* data = FindEmpty(key_pointer);
if (data == nullptr) {
- err(1, "No empty entry found for 0x%" PRIxPTR "\n", key_pointer);
+ errx(1, "No empty entry found for 0x%" PRIxPTR, key_pointer);
}
atomic_store(&data->key_pointer, key_pointer);
data->pointer = pointer;
@@ -60,12 +60,12 @@ void Pointers::Add(uintptr_t key_pointer, void* pointer) {
void* Pointers::Remove(uintptr_t key_pointer) {
if (key_pointer == 0) {
- err(1, "Illegal zero value passed to Remove\n");
+ errx(1, "Illegal zero value passed to Remove");
}
pointer_data* data = Find(key_pointer);
if (data == nullptr) {
- err(1, "No pointer value found for 0x%" PRIxPTR "\n", key_pointer);
+ errx(1, "No pointer value found for 0x%" PRIxPTR, key_pointer);
}
void* pointer = data->pointer;
diff --git a/memory_replay/Threads.cpp b/memory_replay/Threads.cpp
index 61f950ee..15fc69f1 100644
--- a/memory_replay/Threads.cpp
+++ b/memory_replay/Threads.cpp
@@ -54,8 +54,8 @@ Threads::Threads(Pointers* pointers, size_t max_threads)
void* memory = mmap(nullptr, data_size_, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
if (memory == MAP_FAILED) {
- err(1, "Failed to map in memory for Threads: map size %zu, max threads %zu\n",
- data_size_, max_threads_);
+ err(1, "Failed to map in memory for Threads: map size %zu, max threads %zu", data_size_,
+ max_threads_);
}
threads_ = new (memory) Thread[max_threads_];
@@ -71,18 +71,17 @@ Threads::~Threads() {
Thread* Threads::CreateThread(pid_t tid) {
if (num_threads_ == max_threads_) {
- err(1, "Too many threads created, current max %zu.\n", num_threads_);
+ errx(1, "Too many threads created, current max %zu", num_threads_);
}
Thread* thread = FindEmptyEntry(tid);
if (thread == nullptr) {
- err(1, "No empty entries found, current max %zu, num threads %zu\n",
- max_threads_, num_threads_);
+ errx(1, "No empty entries found, current max %zu, num threads %zu", max_threads_, num_threads_);
}
thread->tid_ = tid;
thread->pointers_ = pointers_;
thread->total_time_nsecs_ = 0;
if ((errno = pthread_create(&thread->thread_id_, nullptr, ThreadRunner, thread)) != 0) {
- err(1, "Failed to create thread %d: %s\n", tid, strerror(errno));
+ err(1, "Failed to create thread %d", tid);
}
num_threads_++;
@@ -136,8 +135,7 @@ Thread* Threads::FindEmptyEntry(pid_t tid) {
void Threads::Finish(Thread* thread) {
int ret = pthread_join(thread->thread_id_, nullptr);
if (ret != 0) {
- fprintf(stderr, "pthread_join failed: %s\n", strerror(ret));
- exit(1);
+ err(1, "pthread_join failed");
}
total_time_nsecs_ += thread->total_time_nsecs_;
thread->tid_ = 0;
diff --git a/memory_replay/TraceBenchmark.cpp b/memory_replay/TraceBenchmark.cpp
index 0a7dc90b..a3aad57a 100644
--- a/memory_replay/TraceBenchmark.cpp
+++ b/memory_replay/TraceBenchmark.cpp
@@ -142,7 +142,7 @@ static void GetTraceData(const std::string& filename, TraceDataType* trace_data)
}
void* map = mmap(nullptr, sizeof(void*) * trace_data->num_ptrs, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
if (map == MAP_FAILED) {
- err(1, "mmap failed\n");
+ err(1, "mmap failed");
}
trace_data->ptrs = reinterpret_cast<void**>(map);
@@ -277,10 +277,10 @@ static void BenchmarkTrace(benchmark::State& state, const char* filename, bool e
->Repetitions(4) \
->ReportAggregatesOnly(true)
-static void BM_angry_birds2(benchmark::State& state) {
+static void BM_angry_birds2_default(benchmark::State& state) {
BenchmarkTrace(state, "angry_birds2.zip", true);
}
-BENCHMARK(BM_angry_birds2)->BENCH_OPTIONS;
+BENCHMARK(BM_angry_birds2_default)->BENCH_OPTIONS;
#if defined(__BIONIC__)
static void BM_angry_birds2_no_decay(benchmark::State& state) {
@@ -289,10 +289,10 @@ static void BM_angry_birds2_no_decay(benchmark::State& state) {
BENCHMARK(BM_angry_birds2_no_decay)->BENCH_OPTIONS;
#endif
-static void BM_camera(benchmark::State& state) {
+static void BM_camera_default(benchmark::State& state) {
BenchmarkTrace(state, "camera.zip", true);
}
-BENCHMARK(BM_camera)->BENCH_OPTIONS;
+BENCHMARK(BM_camera_default)->BENCH_OPTIONS;
#if defined(__BIONIC__)
static void BM_camera_no_decay(benchmark::State& state) {
@@ -301,10 +301,10 @@ static void BM_camera_no_decay(benchmark::State& state) {
BENCHMARK(BM_camera_no_decay)->BENCH_OPTIONS;
#endif
-static void BM_candy_crush_saga(benchmark::State& state) {
+static void BM_candy_crush_saga_default(benchmark::State& state) {
BenchmarkTrace(state, "candy_crush_saga.zip", true);
}
-BENCHMARK(BM_candy_crush_saga)->BENCH_OPTIONS;
+BENCHMARK(BM_candy_crush_saga_default)->BENCH_OPTIONS;
#if defined(__BIONIC__)
static void BM_candy_crush_saga_no_decay(benchmark::State& state) {
@@ -313,10 +313,10 @@ static void BM_candy_crush_saga_no_decay(benchmark::State& state) {
BENCHMARK(BM_candy_crush_saga_no_decay)->BENCH_OPTIONS;
#endif
-void BM_gmail(benchmark::State& state) {
+void BM_gmail_default(benchmark::State& state) {
BenchmarkTrace(state, "gmail.zip", true);
}
-BENCHMARK(BM_gmail)->BENCH_OPTIONS;
+BENCHMARK(BM_gmail_default)->BENCH_OPTIONS;
#if defined(__BIONIC__)
void BM_gmail_no_decay(benchmark::State& state) {
@@ -325,10 +325,10 @@ void BM_gmail_no_decay(benchmark::State& state) {
BENCHMARK(BM_gmail_no_decay)->BENCH_OPTIONS;
#endif
-void BM_maps(benchmark::State& state) {
+void BM_maps_default(benchmark::State& state) {
BenchmarkTrace(state, "maps.zip", true);
}
-BENCHMARK(BM_maps)->BENCH_OPTIONS;
+BENCHMARK(BM_maps_default)->BENCH_OPTIONS;
#if defined(__BIONIC__)
void BM_maps_no_decay(benchmark::State& state) {
@@ -337,10 +337,10 @@ void BM_maps_no_decay(benchmark::State& state) {
BENCHMARK(BM_maps_no_decay)->BENCH_OPTIONS;
#endif
-void BM_photos(benchmark::State& state) {
+void BM_photos_default(benchmark::State& state) {
BenchmarkTrace(state, "photos.zip", true);
}
-BENCHMARK(BM_photos)->BENCH_OPTIONS;
+BENCHMARK(BM_photos_default)->BENCH_OPTIONS;
#if defined(__BIONIC__)
void BM_photos_no_decay(benchmark::State& state) {
@@ -349,10 +349,10 @@ void BM_photos_no_decay(benchmark::State& state) {
BENCHMARK(BM_photos_no_decay)->BENCH_OPTIONS;
#endif
-void BM_pubg(benchmark::State& state) {
+void BM_pubg_default(benchmark::State& state) {
BenchmarkTrace(state, "pubg.zip", true);
}
-BENCHMARK(BM_pubg)->BENCH_OPTIONS;
+BENCHMARK(BM_pubg_default)->BENCH_OPTIONS;
#if defined(__BIONIC__)
void BM_pubg_no_decay(benchmark::State& state) {
@@ -361,10 +361,10 @@ void BM_pubg_no_decay(benchmark::State& state) {
BENCHMARK(BM_pubg_no_decay)->BENCH_OPTIONS;
#endif
-void BM_surfaceflinger(benchmark::State& state) {
+void BM_surfaceflinger_default(benchmark::State& state) {
BenchmarkTrace(state, "surfaceflinger.zip", true);
}
-BENCHMARK(BM_surfaceflinger)->BENCH_OPTIONS;
+BENCHMARK(BM_surfaceflinger_default)->BENCH_OPTIONS;
#if defined(__BIONIC__)
void BM_surfaceflinger_no_decay(benchmark::State& state) {
@@ -373,10 +373,10 @@ void BM_surfaceflinger_no_decay(benchmark::State& state) {
BENCHMARK(BM_surfaceflinger_no_decay)->BENCH_OPTIONS;
#endif
-void BM_system_server(benchmark::State& state) {
+void BM_system_server_default(benchmark::State& state) {
BenchmarkTrace(state, "system_server.zip", true);
}
-BENCHMARK(BM_system_server)->BENCH_OPTIONS;
+BENCHMARK(BM_system_server_default)->BENCH_OPTIONS;
#if defined(__BIONIC__)
void BM_system_server_no_decay(benchmark::State& state) {
@@ -385,10 +385,10 @@ void BM_system_server_no_decay(benchmark::State& state) {
BENCHMARK(BM_system_server_no_decay)->BENCH_OPTIONS;
#endif
-void BM_systemui(benchmark::State& state) {
+void BM_systemui_default(benchmark::State& state) {
BenchmarkTrace(state, "systemui.zip", true);
}
-BENCHMARK(BM_systemui)->BENCH_OPTIONS;
+BENCHMARK(BM_systemui_default)->BENCH_OPTIONS;
#if defined(__BIONIC__)
void BM_systemui_no_decay(benchmark::State& state) {
@@ -397,10 +397,10 @@ void BM_systemui_no_decay(benchmark::State& state) {
BENCHMARK(BM_systemui_no_decay)->BENCH_OPTIONS;
#endif
-void BM_youtube(benchmark::State& state) {
+void BM_youtube_default(benchmark::State& state) {
BenchmarkTrace(state, "youtube.zip", true);
}
-BENCHMARK(BM_youtube)->BENCH_OPTIONS;
+BENCHMARK(BM_youtube_default)->BENCH_OPTIONS;
#if defined(__BIONIC__)
void BM_youtube_no_decay(benchmark::State& state) {
diff --git a/memory_replay/main.cpp b/memory_replay/main.cpp
index e610305e..7d7c5383 100644
--- a/memory_replay/main.cpp
+++ b/memory_replay/main.cpp
@@ -34,6 +34,9 @@
#include "Thread.h"
#include "Threads.h"
+#include <log/log.h>
+#include <log/log_read.h>
+
constexpr size_t kDefaultMaxThreads = 512;
static size_t GetMaxAllocs(const AllocEntry* entries, size_t num_entries) {
@@ -70,6 +73,42 @@ static size_t GetMaxAllocs(const AllocEntry* entries, size_t num_entries) {
return max_allocs;
}
+static void PrintLogStats(const char* log_name) {
+ logger_list* list =
+ android_logger_list_open(android_name_to_log_id(log_name), ANDROID_LOG_NONBLOCK, 0, getpid());
+ if (list == nullptr) {
+ printf("Failed to open log for %s\n", log_name);
+ return;
+ }
+ while (true) {
+ log_msg entry;
+ ssize_t retval = android_logger_list_read(list, &entry);
+ if (retval == 0) {
+ break;
+ }
+ if (retval < 0) {
+ if (retval == -EINTR) {
+ continue;
+ }
+ // EAGAIN means there is nothing left to read when ANDROID_LOG_NONBLOCK is set.
+ if (retval != -EAGAIN) {
+ printf("Failed to read log entry: %s\n", strerrordesc_np(retval));
+ }
+ break;
+ }
+ if (entry.msg() == nullptr) {
+ continue;
+ }
+ // Only print allocator tagged log entries.
+ std::string_view tag(entry.msg() + 1);
+ if (tag != "scudo" && tag != "jemalloc") {
+ continue;
+ }
+ printf("%s\n", &tag.back() + 2);
+ }
+ android_logger_list_close(list);
+}
+
static void ProcessDump(const AllocEntry* entries, size_t num_entries, size_t max_threads) {
// Do a pass to get the maximum number of allocations used at one
// time to allow a single mmap that can hold the maximum number of
@@ -78,15 +117,15 @@ static void ProcessDump(const AllocEntry* entries, size_t num_entries, size_t ma
Pointers pointers(max_allocs);
Threads threads(&pointers, max_threads);
- NativePrintf("Maximum threads available: %zu\n", threads.max_threads());
- NativePrintf("Maximum allocations in dump: %zu\n", max_allocs);
- NativePrintf("Total pointers available: %zu\n\n", pointers.max_pointers());
+ dprintf(STDOUT_FILENO, "Maximum threads available: %zu\n", threads.max_threads());
+ dprintf(STDOUT_FILENO, "Maximum allocations in dump: %zu\n", max_allocs);
+ dprintf(STDOUT_FILENO, "Total pointers available: %zu\n\n", pointers.max_pointers());
NativePrintInfo("Initial ");
for (size_t i = 0; i < num_entries; i++) {
if (((i + 1) % 100000) == 0) {
- NativePrintf(" At line %zu:\n", i + 1);
+ dprintf(STDOUT_FILENO, " At line %zu:\n", i + 1);
NativePrintInfo(" ");
}
const AllocEntry& entry = entries[i];
@@ -139,7 +178,15 @@ static void ProcessDump(const AllocEntry* entries, size_t num_entries, size_t ma
char buffer[256];
uint64_t total_nsecs = threads.total_time_nsecs();
NativeFormatFloat(buffer, sizeof(buffer), total_nsecs, 1000000000);
- NativePrintf("Total Allocation/Free Time: %" PRIu64 "ns %ss\n", total_nsecs, buffer);
+ dprintf(STDOUT_FILENO, "Total Allocation/Free Time: %" PRIu64 "ns %ss\n", total_nsecs, buffer);
+
+ // Send native allocator stats to the log
+ mallopt(M_LOG_STATS, 0);
+
+ // No need to avoid allocations at this point since all stats have been sent to the log.
+ printf("Native Allocator Stats:\n");
+ PrintLogStats("system");
+ PrintLogStats("main");
}
int main(int argc, char** argv) {
@@ -161,13 +208,13 @@ int main(int argc, char** argv) {
}
#if defined(__LP64__)
- NativePrintf("64 bit environment.\n");
+ dprintf(STDOUT_FILENO, "64 bit environment.\n");
#else
- NativePrintf("32 bit environment.\n");
+ dprintf(STDOUT_FILENO, "32 bit environment.\n");
#endif
#if defined(__BIONIC__)
- NativePrintf("Setting decay time to 1\n");
+ dprintf(STDOUT_FILENO, "Setting decay time to 1\n");
mallopt(M_DECAY_TIME, 1);
#endif
@@ -180,7 +227,7 @@ int main(int argc, char** argv) {
size_t num_entries;
GetUnwindInfo(argv[1], &entries, &num_entries);
- NativePrintf("Processing: %s\n", argv[1]);
+ dprintf(STDOUT_FILENO, "Processing: %s\n", argv[1]);
ProcessDump(entries, num_entries, max_threads);
diff --git a/mmap-perf/Android.bp b/mmap-perf/Android.bp
index d989e5fe..bbc65089 100644
--- a/mmap-perf/Android.bp
+++ b/mmap-perf/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_android_kernel",
default_applicable_licenses: ["system_extras_mmap-perf_license"],
}
diff --git a/mmap-perf/mmapPerf.cpp b/mmap-perf/mmapPerf.cpp
index d7627e28..e0f60529 100644
--- a/mmap-perf/mmapPerf.cpp
+++ b/mmap-perf/mmapPerf.cpp
@@ -14,7 +14,7 @@
#include <sys/mman.h>
using namespace std;
-static const size_t pageSize = PAGE_SIZE;
+static const size_t pageSize = getpagesize();
static size_t fsize = 1024 * (1ull << 20);
static size_t pagesTotal = fsize / pageSize;
diff --git a/mtectrl/mtectrl.cc b/mtectrl/mtectrl.cc
index bea12a6c..3032ad5f 100644
--- a/mtectrl/mtectrl.cc
+++ b/mtectrl/mtectrl.cc
@@ -47,6 +47,8 @@ bool UpdateProp(const char* prop_name, const misc_memtag_message& m) {
if (CheckAndUnset(mode, MISC_MEMTAG_MODE_MEMTAG_KERNEL_ONCE))
AddItem(&prop_str, "memtag-kernel-once");
if (CheckAndUnset(mode, MISC_MEMTAG_MODE_MEMTAG_OFF)) AddItem(&prop_str, "memtag-off");
+ if (CheckAndUnset(mode, MISC_MEMTAG_MODE_FORCED)) AddItem(&prop_str, "forced");
+ if (prop_str.empty()) prop_str = "none";
if (android::base::GetProperty(prop_name, "") != prop_str)
android::base::SetProperty(prop_name, prop_str);
if (mode) {
@@ -68,6 +70,7 @@ void PrintUsage(const char* progname) {
<< "USAGE: " << progname
<< "\n"
" [-s PROPERTY_NAME]\n"
+ " [-f PROPERTY_NAME]\n"
" [none,][memtag,][memtag-once,][memtag-kernel,][memtag-kernel-once,][memtag-off,]\n"
" [default|force_on|force_off]\n"
" [-t PATH_TO_FAKE_MISC_PARTITION]\n"
@@ -76,6 +79,9 @@ void PrintUsage(const char* progname) {
" -s PROPERTY_NAME\n"
" Sets the system property 'PROPERTY_NAME' to the new MTE mode (if provided), or to\n"
" the current value from the /misc partition.\n"
+ " -f PROPERTY_NAME\n"
+ " Used in combination with -s without a new MTE mode and sets the system property\n"
+ " 'PROPERTY_NAME' to 1 after reading the current value from the /misc partition\n"
" [none,][memtag,][memtag-once,][memtag-kernel,][memtag-kernel-once,][memtag-off,]\n"
" A set of MTE options to be applied, if provided. Multiple options may be\n"
" specified as a ','-delimited list, e.g. 'memtag,memtag-kernel'.\n"
@@ -89,6 +95,9 @@ void PrintUsage(const char* progname) {
" - memtag-kernel-once: MTE is enabled in the kernel, only for the next reboot.\n"
" - memtag-off: MTE is persistently disabled in both userspace and kernel upon \n"
" the next reboot.\n"
+ " - forced: the current state is the result of force_on or force_off in the next\n"
+ " argument. When the next argument is set back to \"default\", the\n"
+ " state will be cleared.\n"
" [default|force_on|force_off]\n"
" An alternative method of configuring the MTE options to be applied, if provided.\n"
" This control is generally to be used by device_config only, and it overwrites\n"
@@ -116,6 +125,8 @@ int StringToMode(const char* value) {
memtag_mode |= MISC_MEMTAG_MODE_MEMTAG_KERNEL_ONCE;
} else if (field == "memtag-off") {
memtag_mode |= MISC_MEMTAG_MODE_MEMTAG_OFF;
+ } else if (field == "forced") {
+ memtag_mode |= MISC_MEMTAG_MODE_FORCED;
} else if (field != "none") {
LOG(ERROR) << "Unknown value for mode: " << field;
return -1;
@@ -124,22 +135,132 @@ int StringToMode(const char* value) {
return memtag_mode;
}
+// Handles the override flag and applies it to the memtag message.
+// The logic is as follows:
+// If the override changes the configuration (i.e., if MTE was not enabled
+// through MODE_MEMTAG and the override is force_on, or MTE was not
+// disabled through MEMTAG_OFF and the override is force_off), the MTE
+// state is considered FORCED. In that case, if the override gets reset
+// to "default" (i.e. no override), the default state of memtag config
+// is restored. The theory for this is that disabling the override should
+// only keep the non-default state if it has been active throughout the
+// override, not restore it if it had been dormant for the duration of the
+// override.
+//
+// State machine diagrams of the MTE state and the effect of override below:
+//
+// default,force_off
+// ┌───┐
+// │ │
+// ┌──┴───▼───┐
+// │memtag-off│
+// └─────┬────┘
+// │
+// force_on │ ┌────┐
+// │ │ │ force_on
+// force_off┌───────▼───┴─┐ │
+// ┌────────┤memtag,forced│◄─┘
+// │ └▲─────────┬──┘
+// force_off │ │ │
+// ┌────┐ │ force_on│ │ default
+// │ │ │ │ │
+// │ ┌─┴────▼─────────┴┐ ┌▼──────┐
+// └─►│memtag-off,forced├───────►none │
+// └─────────────────┘default└───────┘
+//
+//
+//
+// default,force_on
+// ┌───┐
+// │ │
+// ┌──┴───▼───┐
+// │memtag │
+// └─────┬────┘
+// │
+// force_off│ ┌────┐
+// │ │ │ force_off
+// force_on ┌───────┴───────┴─┐ │
+// ┌────────┤memtag-off,forced◄──┘
+// │ └▲─────────┬──────┘
+// force_on │ │ │
+// ┌────┐ │force_off│ │ default
+// │ │ │ │ │
+// │ ┌─┴────▼─────────┴┐ ┌▼──────┐
+// └─►│memtag,forced ├───────►none │
+// └─────────────────┘default└───────┘
+//
+//
+//
+// default
+// ┌───┐
+// │ │
+// force_off ┌──┴───▼───┐
+// ┌─────────────┤none │
+// │ └─────┬────┘
+// │ │
+// │ force_on │ ┌────┐
+// │ │ │ │ force_on
+// │ force_off┌───────▼───┴─┐ │
+// │ ┌────────┤memtag,forced│◄─┘
+// │ │ └▲─────────┬──┘
+// force_off│ │ │ │
+// ┌────┐ │ │ force_on│ │ default
+// │ │ │ │ │ │
+// │ ┌─┴─▼──▼─────────┴┐ ┌▼──────┐
+// └─►│memtag-off,forced├───────►none │
+// └─────────────────┘default└───────┘
bool HandleOverride(const std::string& override_value, misc_memtag_message* m) {
if (override_value == "force_off") {
// If the force_off override is active, only allow MEMTAG_MODE_MEMTAG_ONCE.
+ if ((m->memtag_mode & MISC_MEMTAG_MODE_MEMTAG_OFF) == 0) {
+ m->memtag_mode |= MISC_MEMTAG_MODE_FORCED;
+ }
m->memtag_mode |= MISC_MEMTAG_MODE_MEMTAG_OFF;
m->memtag_mode &= ~MISC_MEMTAG_MODE_MEMTAG;
} else if (override_value == "force_on") {
+ if ((m->memtag_mode & MISC_MEMTAG_MODE_MEMTAG) == 0) {
+ m->memtag_mode |= MISC_MEMTAG_MODE_FORCED;
+ }
m->memtag_mode |= MISC_MEMTAG_MODE_MEMTAG;
m->memtag_mode &= ~MISC_MEMTAG_MODE_MEMTAG_OFF;
- } else if (!override_value.empty() && override_value != "default") {
+ } else if (override_value.empty() || override_value == "default") {
+ // The mode changed from forced_on or forced_off to default, which means we
+ // restore the normal state.
+ if (m->memtag_mode & MISC_MEMTAG_MODE_FORCED) {
+ m->memtag_mode &= ~MISC_MEMTAG_MODE_MEMTAG;
+ m->memtag_mode &= ~MISC_MEMTAG_MODE_MEMTAG_OFF;
+ m->memtag_mode &= ~MISC_MEMTAG_MODE_FORCED;
+ }
+ } else {
return false;
}
return true;
}
+int DoSetProp(const std::function<bool(misc_memtag_message*, std::string*)>& read_memtag_message,
+ const char* set_prop) {
+ // -s <property> is given on its own. This means we want to read the state
+ // of the misc partition into the property.
+ std::string err;
+ misc_memtag_message m = {};
+ if (!read_memtag_message(&m, &err)) {
+ LOG(ERROR) << "Failed to read memtag message: " << err;
+ return 1;
+ }
+ if (m.magic != MISC_MEMTAG_MAGIC_HEADER || m.version != MISC_MEMTAG_MESSAGE_VERSION) {
+ // This should not fail by construction.
+ CHECK(UpdateProp(set_prop, {}));
+ // This is an expected case, as the partition gets initialized to all zero.
+ return 0;
+ }
+ // Unlike above, setting the system property here can fail if the misc partition
+ // was corrupted by another program (e.g. the bootloader).
+ return UpdateProp(set_prop, m) ? 0 : 1;
+}
+
int main(int argc, char** argv) {
const char* set_prop = nullptr;
+ const char* flag_prop = nullptr;
int opt;
std::function<bool(misc_memtag_message*, std::string*)> read_memtag_message =
ReadMiscMemtagMessage;
@@ -147,7 +268,7 @@ int main(int argc, char** argv) {
WriteMiscMemtagMessage;
android::base::unique_fd fake_partition_fd;
- while ((opt = getopt(argc, argv, "s:t:")) != -1) {
+ while ((opt = getopt(argc, argv, "s:t:f:")) != -1) {
switch (opt) {
case 's':
// Set property in argument to state of misc partition. If given by
@@ -158,6 +279,9 @@ int main(int argc, char** argv) {
// state.
set_prop = optarg;
break;
+ case 'f':
+ flag_prop = optarg;
+ break;
case 't': {
// Use different fake misc partition for testing.
const char* filename = optarg;
@@ -184,35 +308,22 @@ int main(int argc, char** argv) {
const char* value = optind < argc ? argv[optind++] : nullptr;
const char* override_value = optind < argc ? argv[optind++] : nullptr;
- if (optind != argc) { // Unknown argument.
+ if ((optind != argc) || // Unknown argument.
+ (value && flag_prop) || // -f is only valid when no value given
+ (!value && !set_prop)) { // value must be given if -s is not
PrintUsage(argv[0]);
return 1;
}
if (!value && set_prop) {
- // -s <property> is given on its own. This means we want to read the state
- // of the misc partition into the property.
- std::string err;
- misc_memtag_message m = {};
- if (!read_memtag_message(&m, &err)) {
- LOG(ERROR) << "Failed to read memtag message: " << err;
- return 1;
- }
- if (m.magic != MISC_MEMTAG_MAGIC_HEADER || m.version != MISC_MEMTAG_MESSAGE_VERSION) {
- // This should not fail by construction.
- CHECK(UpdateProp(set_prop, {}));
- // This is an expected case, as the partition gets initialized to all zero.
- return 0;
+ int ret = DoSetProp(read_memtag_message, set_prop);
+ if (flag_prop) {
+ android::base::SetProperty(flag_prop, "1");
}
- // Unlike above, setting the system property here can fail if the misc partition
- // was corrupted by another program (e.g. the bootloader).
- return UpdateProp(set_prop, m) ? 0 : 1;
+ return ret;
}
- if (!value) {
- PrintUsage(argv[0]);
- return 1;
- }
+ CHECK(value);
misc_memtag_message m = {.version = MISC_MEMTAG_MESSAGE_VERSION,
.magic = MISC_MEMTAG_MAGIC_HEADER};
diff --git a/mtectrl/mtectrl.rc b/mtectrl/mtectrl.rc
index 699b56ec..3d03d0b8 100644
--- a/mtectrl/mtectrl.rc
+++ b/mtectrl/mtectrl.rc
@@ -12,16 +12,22 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-on property:arm64.memtag.bootctl=*
- exec -- /system/bin/mtectrl ${arm64.memtag.bootctl:-none} ${persist.device_config.memory_safety_native_boot.bootloader_override:-default}
+on property:arm64.memtag.bootctl=* && property:ro.arm64.memtag.bootctl_supported=1
+ wait_for_prop arm64.memtag.bootctl_loaded 1
+ exec -- /system/bin/mtectrl ${arm64.memtag.bootctl:-none} ${persist.device_config.runtime_native_boot.bootloader_override:-default}
-on property:persist.device_config.memory_safety_native_boot.bootloader_override=*
- exec -- /system/bin/mtectrl ${arm64.memtag.bootctl:-none} ${persist.device_config.memory_safety_native_boot.bootloader_override:-default}
+on property:persist.device_config.runtime_native_boot.bootloader_override=* && property:ro.arm64.memtag.bootctl_supported=1
+ wait_for_prop arm64.memtag.bootctl_loaded 1
+ exec -- /system/bin/mtectrl ${arm64.memtag.bootctl:-none} ${persist.device_config.runtime_native_boot.bootloader_override:-default}
# adbd gets initialized in init, so run before that. this makes sure that the
# user does not change the value before we initialize it
-on early-init && property:ro.arm64.memtag.bootctl_supported=1
- exec -- /system/bin/mtectrl -s arm64.memtag.bootctl
+on early-boot && property:ro.arm64.memtag.bootctl_supported=1
+ exec_background -- /system/bin/mtectrl -s arm64.memtag.bootctl -f arm64.memtag.bootctl_loaded
-on shutdown && property:ro.arm64.memtag.bootctl_supported=1
- exec -- /system/bin/mtectrl ${arm64.memtag.bootctl:-none} ${persist.device_config.memory_safety_native_boot.bootloader_override:-default}
+on shutdown && property:ro.arm64.memtag.bootctl_supported=1 && property:arm64.memtag.bootctl_loaded=*
+ # This doesn't use wait_for_prop to not stall the shutdown.
+ exec -- /system/bin/mtectrl ${arm64.memtag.bootctl:-none} ${persist.device_config.runtime_native_boot.bootloader_override:-default}
+
+on property:persist.device_config.runtime_native_boot.bootloader_override=force_on
+ setprop persist.sys.mte.permissive 1
diff --git a/mtectrl/mtectrl_test.cc b/mtectrl/mtectrl_test.cc
index 5fe77f87..aadd9d6e 100644
--- a/mtectrl/mtectrl_test.cc
+++ b/mtectrl/mtectrl_test.cc
@@ -23,16 +23,55 @@
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <bootloader_message/bootloader_message.h>
+#include <string_view>
namespace {
using ::testing::StartsWith;
-int mtectrl(const char* arg) {
+int mtectrl(std::string_view arg) {
std::string cmd = "mtectrl -t /data/local/tmp/misc_memtag ";
cmd += arg;
return system(cmd.c_str());
}
+int RunMteCtrl() {
+ CHECK(android::base::GetIntProperty("arm64.memtag.test_bootctl_loaded", 0) == 1);
+ std::string arg = android::base::GetProperty("arm64.memtag.test_bootctl", "none");
+ arg += " ";
+ arg += android::base::GetProperty("arm64.memtag.test_bootctl_override", "default");
+ return mtectrl(arg);
+}
+
+void Boot(misc_memtag_message m) {
+ std::string m_str(reinterpret_cast<char*>(&m), sizeof(m));
+ android::base::WriteStringToFile(m_str, "/data/local/tmp/misc_memtag");
+ mtectrl("-s arm64.memtag.test_bootctl -f arm64.memtag.test_bootctl_loaded");
+ // arm64.memtag.test_bootctl got updated, so we trigger ourselves.
+ RunMteCtrl();
+}
+
+void Reboot() {
+ android::base::SetProperty("arm64.memtag.test_bootctl", "INVALID");
+ android::base::SetProperty("arm64.memtag.test_bootctl_loaded", "0");
+ std::string m_str;
+ ASSERT_TRUE(android::base::ReadFileToString("/data/local/tmp/misc_memtag", &m_str));
+ misc_memtag_message m;
+ ASSERT_EQ(m_str.size(), sizeof(m));
+ memcpy(&m, m_str.c_str(), sizeof(m));
+ m.memtag_mode &= ~MISC_MEMTAG_MODE_MEMTAG_ONCE;
+ Boot(m);
+}
+
+void SetMemtagProp(const std::string& s) {
+ android::base::SetProperty("arm64.memtag.test_bootctl", s);
+ RunMteCtrl();
+}
+
+void SetOverrideProp(const std::string& s) {
+ android::base::SetProperty("arm64.memtag.test_bootctl_override", s);
+ RunMteCtrl();
+}
+
std::string GetMisc() {
std::string data;
CHECK(android::base::ReadFileToString("/data/local/tmp/misc_memtag", &data, false));
@@ -42,6 +81,9 @@ std::string GetMisc() {
std::string TestProperty() {
return android::base::GetProperty("arm64.memtag.test_bootctl", "");
}
+std::string TestFlag() {
+ return android::base::GetProperty("arm64.memtag.test_bootctl_loaded", "");
+}
} // namespace
class MteCtrlTest : public ::testing::Test {
@@ -52,6 +94,8 @@ class MteCtrlTest : public ::testing::Test {
CHECK(ftruncate(fd, sizeof(misc_memtag_message)) != -1);
close(fd);
android::base::SetProperty("arm64.memtag.test_bootctl", "INVALID");
+ android::base::SetProperty("arm64.memtag.test_bootctl_override", "");
+ android::base::SetProperty("arm64.memtag.test_bootctl_loaded", "0");
}
void TearDown() override {
CHECK(unlink("/data/local/tmp/misc_memtag") == 0);
@@ -64,84 +108,208 @@ TEST_F(MteCtrlTest, invalid) {
}
TEST_F(MteCtrlTest, set_once) {
- ASSERT_EQ(mtectrl("memtag-once"), 0);
+ Boot({});
+ SetMemtagProp("memtag-once");
EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x02"));
}
TEST_F(MteCtrlTest, set_once_kernel) {
- ASSERT_EQ(mtectrl("memtag-once,memtag-kernel"), 0);
+ Boot({});
+ SetMemtagProp("memtag-once,memtag-kernel");
EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x06"));
}
-TEST_F(MteCtrlTest, set_memtag) {
- ASSERT_EQ(mtectrl("memtag"), 0);
- EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x01"));
-}
-
-TEST_F(MteCtrlTest, set_memtag_force_off) {
- ASSERT_EQ(mtectrl("memtag force_off"), 0);
- EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x10"));
-}
-
TEST_F(MteCtrlTest, read_memtag) {
- ASSERT_EQ(mtectrl("memtag"), 0);
- ASSERT_EQ(mtectrl("-s arm64.memtag.test_bootctl"), 0);
+ Boot({});
+ SetMemtagProp("memtag");
+ Reboot();
EXPECT_EQ(TestProperty(), "memtag");
+ EXPECT_EQ(TestFlag(), "1");
}
TEST_F(MteCtrlTest, read_invalid_memtag_message) {
misc_memtag_message m = {.version = 1, .magic = 0xffff, .memtag_mode = MISC_MEMTAG_MODE_MEMTAG};
- std::string m_str(reinterpret_cast<char*>(&m), sizeof(m));
- android::base::WriteStringToFile(m_str, "/data/local/tmp/misc_memtag");
- ASSERT_EQ(mtectrl("-s arm64.memtag.test_bootctl"), 0);
- EXPECT_EQ(TestProperty(), "");
+ Boot(m);
+ EXPECT_EQ(TestProperty(), "none");
+ EXPECT_EQ(TestFlag(), "1");
}
TEST_F(MteCtrlTest, read_invalid_memtag_mode) {
misc_memtag_message m = {.version = MISC_MEMTAG_MESSAGE_VERSION,
.magic = MISC_MEMTAG_MAGIC_HEADER,
.memtag_mode = MISC_MEMTAG_MODE_MEMTAG | 1u << 31};
- std::string m_str(reinterpret_cast<char*>(&m), sizeof(m));
- android::base::WriteStringToFile(m_str, "/data/local/tmp/misc_memtag");
- ASSERT_NE(mtectrl("-s arm64.memtag.test_bootctl"), 0);
+ Boot(m);
EXPECT_EQ(TestProperty(), "memtag");
+ EXPECT_EQ(TestFlag(), "1");
+}
+
+TEST_F(MteCtrlTest, set_read_force_off) {
+ Boot({});
+ SetMemtagProp("memtag,memtag-once");
+ SetOverrideProp("force_off");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "memtag-off,forced");
+ SetOverrideProp("default");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "none");
+}
+
+TEST_F(MteCtrlTest, set_read_force_off_none) {
+ Boot({});
+ SetMemtagProp("none");
+ SetOverrideProp("force_off");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "memtag-off,forced");
+ SetOverrideProp("default");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "none");
}
-TEST_F(MteCtrlTest, set_read_memtag) {
- ASSERT_EQ(mtectrl("-s arm64.memtag.test_bootctl memtag"), 0);
+TEST_F(MteCtrlTest, set_read_force_off_and_on) {
+ Boot({});
+ SetMemtagProp("memtag,memtag-once");
+ SetOverrideProp("force_off");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "memtag-off,forced");
+ SetOverrideProp("default");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "none");
+ SetOverrideProp("force_on");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "memtag,forced");
+}
+
+TEST_F(MteCtrlTest, set_read_force_off_already) {
+ Boot({});
+ SetMemtagProp("memtag-off,memtag-once");
+ SetOverrideProp("force_off");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "memtag-off");
+ SetOverrideProp("default");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "memtag-off");
+}
+
+TEST_F(MteCtrlTest, set_read_force_off_and_on_already) {
+ Boot({});
+ SetMemtagProp("memtag-off,memtag-once");
+ SetOverrideProp("force_off");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "memtag-off");
+ SetOverrideProp("default");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "memtag-off");
+ SetOverrideProp("force_on");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "memtag,forced");
+}
+
+TEST_F(MteCtrlTest, set_read_force_on) {
+ Boot({});
+ SetMemtagProp("memtag-once");
+ SetOverrideProp("force_on");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "memtag,forced");
+ SetOverrideProp("default");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "none");
+}
+
+TEST_F(MteCtrlTest, set_read_force_on_none) {
+ Boot({});
+ SetMemtagProp("none");
+ SetOverrideProp("force_on");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "memtag,forced");
+ SetOverrideProp("default");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "none");
+}
+
+TEST_F(MteCtrlTest, set_read_force_on_and_off) {
+ Boot({});
+ SetMemtagProp("memtag-once");
+ SetOverrideProp("force_on");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "memtag,forced");
+ SetOverrideProp("default");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "none");
+ SetOverrideProp("force_off");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "memtag-off,forced");
+}
+
+TEST_F(MteCtrlTest, set_read_force_on_already) {
+ Boot({});
+ SetMemtagProp("memtag,memtag-once");
+ SetOverrideProp("force_on");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "memtag");
+ SetOverrideProp("default");
+ Reboot();
EXPECT_EQ(TestProperty(), "memtag");
}
-TEST_F(MteCtrlTest, set_read_force_off) {
- ASSERT_EQ(mtectrl("-s arm64.memtag.test_bootctl memtag,memtag-once force_off"), 0);
- EXPECT_EQ(TestProperty(), "memtag-once,memtag-off");
+TEST_F(MteCtrlTest, set_read_force_on_and_off_already) {
+ Boot({});
+ SetMemtagProp("memtag,memtag-once");
+ SetOverrideProp("force_on");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "memtag");
+ SetOverrideProp("default");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "memtag");
+ SetOverrideProp("force_off");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "memtag-off,forced");
}
TEST_F(MteCtrlTest, override) {
- ASSERT_EQ(mtectrl("memtag"), 0);
- ASSERT_EQ(mtectrl("memtag-once"), 0);
+ Boot({});
+ SetMemtagProp(("memtag"));
+ SetMemtagProp(("memtag-once"));
EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x02"));
}
TEST_F(MteCtrlTest, read_empty) {
- ASSERT_EQ(mtectrl("-s arm64.memtag.test_bootctl"), 0);
- EXPECT_EQ(TestProperty(), "");
+ Boot({});
+ EXPECT_EQ(TestProperty(), "none");
+ EXPECT_EQ(TestFlag(), "1");
}
TEST_F(MteCtrlTest, force_off_invalid_mode) {
- mtectrl("-s arm64.memtag.test_bootctl memtag-invalid force_off");
- EXPECT_EQ(TestProperty(), "memtag-off");
- EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x10"));
+ Boot({});
+ SetMemtagProp("memtag-invalid");
+ SetOverrideProp("force_off");
+ EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x30"));
+ Reboot();
+ EXPECT_EQ(TestProperty(), "memtag-off,forced");
+ SetOverrideProp("default");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "none");
}
TEST_F(MteCtrlTest, force_on_invalid_mode) {
- mtectrl("-s arm64.memtag.test_bootctl memtag-invalid force_on");
- EXPECT_EQ(TestProperty(), "memtag");
- EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x01"));
+ Boot({});
+ SetMemtagProp("memtag-invalid");
+ SetOverrideProp("force_on");
+ EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x21"));
+ Reboot();
+ EXPECT_EQ(TestProperty(), "memtag,forced");
+ SetOverrideProp("default");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "none");
}
TEST_F(MteCtrlTest, mode_invalid_override) {
- mtectrl("-s arm64.memtag.test_bootctl memtag force_invalid");
- EXPECT_EQ(TestProperty(), "memtag");
+ Boot({});
+ SetMemtagProp("memtag");
+ SetOverrideProp("force_invalid");
EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x01"));
+ Reboot();
+ EXPECT_EQ(TestProperty(), "memtag");
+ SetOverrideProp("default");
+ Reboot();
+ EXPECT_EQ(TestProperty(), "memtag");
}
diff --git a/multinetwork/Android.bp b/multinetwork/Android.bp
index 06c4b346..33a38981 100644
--- a/multinetwork/Android.bp
+++ b/multinetwork/Android.bp
@@ -21,13 +21,6 @@ cc_defaults {
"libandroid",
"libbase",
],
-
- product_variables: {
- // The PDK build does not have access to frameworks/native elements.
- pdk: {
- enabled: false,
- },
- },
}
// Sample util binaries.
diff --git a/pagecache/pagecache.py b/pagecache/pagecache.py
index c822ff00..3f96a5d1 100755..100644
--- a/pagecache/pagecache.py
+++ b/pagecache/pagecache.py
@@ -229,7 +229,7 @@ class AdbUtils():
def parse_atrace_line(line, pagecache_stats, app_name):
# Find a mm_filemap_add_to_page_cache entry
- m = re.match('.* (mm_filemap_add_to_page_cache|mm_filemap_delete_from_page_cache): dev (\d+):(\d+) ino ([0-9a-z]+) page=([0-9a-z]+) pfn=\d+ ofs=(\d+).*', line)
+ m = re.match('.* (mm_filemap_add_to_page_cache|mm_filemap_delete_from_page_cache): dev (\d+):(\d+) ino ([0-9a-z]+) page=([0-9a-z]+) pfn=([0-9a-z]+) ofs=(\d+).*', line)
if m != None:
# Get filename
device_number = int(m.group(2)) << 8 | int(m.group(3))
@@ -261,8 +261,9 @@ def get_inode_data(datafile, dumpfile, adb_serial):
else:
# Build inode maps if we were tracing page cache
print('Downloading inode data from device')
- stat_dump = AdbUtils.do_preprocess_adb_cmd('find /system /data /vendor ' +
- '-exec stat -c "%d %i %s %n" {} \;', adb_serial)
+ stat_dump = AdbUtils.do_preprocess_adb_cmd(
+ 'find /apex /system /system_ext /product /data /vendor ' +
+ '-exec stat -c "%d %i %s %n" {} \;', adb_serial)
if stat_dump is None:
print 'Could not retrieve inode data from device.'
sys.exit(1)
diff --git a/partition_tools/Android.bp b/partition_tools/Android.bp
index c2800601..e483cea0 100644
--- a/partition_tools/Android.bp
+++ b/partition_tools/Android.bp
@@ -147,17 +147,27 @@ cc_binary {
cc_binary {
name: "lpdumpd",
- defaults: ["lp_defaults"],
+ defaults: [
+ "lp_defaults",
+ "libsnapshot_cow_defaults",
+ "libsnapshot_hal_deps",
+ ],
init_rc: ["lpdumpd.rc"],
shared_libs: [
"libbase",
"libbinder",
+ "libfs_mgr_binder",
"liblog",
"liblp",
"liblpdump",
"liblpdump_interface-cpp",
+ "libprotobuf-cpp-lite",
+ "libsnapshot",
"libutils",
],
+ static_libs: [
+ "update_metadata-protos",
+ ],
srcs: [
"lpdumpd.cc",
],
@@ -172,6 +182,9 @@ cc_binary_host {
"liblp",
"libsparse",
],
+ static_libs: [
+ "libc++fs",
+ ],
srcs: [
"lpunpack.cc",
],
diff --git a/partition_tools/dynamic_partitions_device_info.proto b/partition_tools/dynamic_partitions_device_info.proto
index e53b40e2..82d20098 100644
--- a/partition_tools/dynamic_partitions_device_info.proto
+++ b/partition_tools/dynamic_partitions_device_info.proto
@@ -20,12 +20,12 @@ package android;
// Keep in sync with proto files on EDI backend. Otherwise, new fields will
// go ignored.
-// Next: 6
+// Next: 7
message DynamicPartitionsDeviceInfoProto {
bool enabled = 1;
bool retrofit = 2;
- // Next: 7
+ // Next: 8
message Partition {
string name = 1;
string group_name = 2 [json_name = "group_name"];
@@ -36,6 +36,8 @@ message DynamicPartitionsDeviceInfoProto {
uint64 fs_size = 5 [json_name = "fs_size"];
/** Used space of the filesystem. */
uint64 fs_used = 6 [json_name = "fs_used"];
+ /** Name of the filesystem. */
+ string fs_type = 7 [json_name = "fs_type"];
}
repeated Partition partitions = 3;
@@ -55,4 +57,14 @@ message DynamicPartitionsDeviceInfoProto {
uint64 alignment_offset = 5 [json_name = "alignment_offset"];
}
repeated BlockDevice block_devices = 5 [json_name = "block_devices"];
+
+ // Next: 4
+ message SuperDevice {
+ string name = 1;
+ /** Used space in bytes */
+ uint64 used_size = 2 [json_name = "used_size"];
+ /** Total size of the super in bytes */
+ uint64 total_size = 3 [json_name = "total_size"];
+ }
+ SuperDevice super_device = 6 [json_name = "super_device"];
}
diff --git a/partition_tools/lpdump.cc b/partition_tools/lpdump.cc
index 047b5ee0..4c1fe954 100644
--- a/partition_tools/lpdump.cc
+++ b/partition_tools/lpdump.cc
@@ -173,6 +173,12 @@ static bool MergeMetadata(const LpMetadata* metadata,
block_device_proto->set_alignment(info.alignment);
block_device_proto->set_alignment_offset(info.alignment_offset);
}
+
+ auto super_device_proto = proto->mutable_super_device();
+ super_device_proto->set_name(GetSuperPartitionName());
+ super_device_proto->set_used_size(builder->UsedSpace());
+ super_device_proto->set_total_size(GetTotalSuperPartitionSize(*metadata));
+
return true;
}
@@ -229,6 +235,13 @@ static bool MergeFsUsage(DynamicPartitionsDeviceInfoProto* proto,
partition_proto->set_is_dynamic(false);
}
partition_proto->set_fs_size((uint64_t)vst.f_blocks * vst.f_frsize);
+
+ if (!entry.fs_type.empty()) {
+ partition_proto->set_fs_type(entry.fs_type);
+ } else {
+ partition_proto->set_fs_type("UNKNOWN");
+ }
+
if (vst.f_bavail <= vst.f_blocks) {
partition_proto->set_fs_used((uint64_t)(vst.f_blocks - vst.f_bavail) * vst.f_frsize);
}
diff --git a/partition_tools/lpdumpd.cc b/partition_tools/lpdumpd.cc
index 7717e115..86f0b45d 100644
--- a/partition_tools/lpdumpd.cc
+++ b/partition_tools/lpdumpd.cc
@@ -20,12 +20,14 @@
#include <vector>
#include <android-base/logging.h>
+#include <android-base/properties.h>
#include <android-base/strings.h>
#include <android/lpdump/BnLpdump.h>
#include <android/lpdump/ILpdump.h>
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
+#include <libsnapshot/snapshot.h>
int LpdumpMain(int argc, char* argv[], std::ostream&, std::ostream&);
@@ -50,15 +52,24 @@ class Lpdump : public BnLpdump {
std::stringstream error;
int ret = LpdumpMain((int)local_argv.size(), local_argv.data(), output, error);
std::string error_str = error.str();
- if (ret == 0) {
- if (!error_str.empty()) {
- LOG(WARNING) << error_str;
- }
- *aidl_return = output.str();
- return Status::ok();
- } else {
+ if (ret != 0) {
return Status::fromServiceSpecificError(ret, error_str.c_str());
}
+
+ if (android::base::GetBoolProperty("ro.virtual_ab.enabled", false)) {
+ if (auto sm = android::snapshot::SnapshotManager::New()) {
+ output << "---------------\n";
+ output << "Snapshot state:\n";
+ output << "---------------\n";
+ sm->Dump(output);
+ }
+ }
+
+ if (!error_str.empty()) {
+ LOG(WARNING) << error_str;
+ }
+ *aidl_return = output.str();
+ return Status::ok();
}
};
diff --git a/partition_tools/lpmake.cc b/partition_tools/lpmake.cc
index 16dfec52..d7085222 100644
--- a/partition_tools/lpmake.cc
+++ b/partition_tools/lpmake.cc
@@ -489,6 +489,10 @@ int main(int argc, char* argv[]) {
}
}
+ // unlink before writing, in case it is being used by an emulator or other program,
+ // we don't want to break that program by changing the data it is accessing.
+ unlink(output_path.c_str());
+
std::unique_ptr<LpMetadata> metadata = builder->Export();
if (!images.empty() || force_full_image) {
if (block_devices.size() == 1) {
diff --git a/partition_tools/lpunpack.cc b/partition_tools/lpunpack.cc
index 1f870c5d..696d3f25 100644
--- a/partition_tools/lpunpack.cc
+++ b/partition_tools/lpunpack.cc
@@ -21,6 +21,7 @@
#include <sys/types.h>
#include <unistd.h>
+#include <filesystem>
#include <iostream>
#include <limits>
#include <string>
@@ -34,11 +35,12 @@
using namespace android::fs_mgr;
using android::base::unique_fd;
+using android::base::borrowed_fd;
using SparsePtr = std::unique_ptr<sparse_file, decltype(&sparse_file_destroy)>;
class ImageExtractor final {
public:
- ImageExtractor(unique_fd&& image_fd, std::unique_ptr<LpMetadata>&& metadata,
+ ImageExtractor(std::vector<unique_fd>&& image_fds, std::unique_ptr<LpMetadata>&& metadata,
std::unordered_set<std::string>&& partitions, const std::string& output_dir);
bool Extract();
@@ -48,7 +50,7 @@ class ImageExtractor final {
bool ExtractPartition(const LpMetadataPartition* partition);
bool ExtractExtent(const LpMetadataExtent& extent, int output_fd);
- unique_fd image_fd_;
+ std::vector<unique_fd> image_fds_;
std::unique_ptr<LpMetadata> metadata_;
std::unordered_set<std::string> partitions_;
std::string output_dir_;
@@ -59,16 +61,15 @@ class ImageExtractor final {
// file format.
class SparseWriter final {
public:
- SparseWriter(int output_fd, int image_fd, uint32_t block_size);
+ SparseWriter(borrowed_fd output_fd, uint32_t block_size);
- bool WriteExtent(const LpMetadataExtent& extent);
+ bool WriteExtent(borrowed_fd image_fd, const LpMetadataExtent& extent);
bool Finish();
private:
bool WriteBlock(const uint8_t* data);
- int output_fd_;
- int image_fd_;
+ borrowed_fd output_fd_;
uint32_t block_size_;
off_t hole_size_ = 0;
};
@@ -81,7 +82,14 @@ static int usage(int /* argc */, char* argv[]) {
"Usage:\n"
" %s [options...] SUPER_IMAGE [OUTPUT_DIR]\n"
"\n"
+ "The SUPER_IMAGE argument is mandatory and expected to contain\n"
+ "the metadata. Additional super images are referenced with '-i' as needed to extract\n"
+ "the desired partition[s].\n"
+ "Default OUTPUT_DIR is '.'.\n"
+ "\n"
"Options:\n"
+ " -i, --image=IMAGE Use the given file as an additional super image.\n"
+ " This can be specified multiple times.\n"
" -p, --partition=NAME Extract the named partition. This can\n"
" be specified multiple times.\n"
" -S, --slot=NUM Slot number (default is 0).\n",
@@ -92,6 +100,7 @@ static int usage(int /* argc */, char* argv[]) {
int main(int argc, char* argv[]) {
// clang-format off
struct option options[] = {
+ { "image", required_argument, nullptr, 'i' },
{ "partition", required_argument, nullptr, 'p' },
{ "slot", required_argument, nullptr, 'S' },
{ nullptr, 0, nullptr, 0 },
@@ -100,6 +109,7 @@ int main(int argc, char* argv[]) {
uint32_t slot_num = 0;
std::unordered_set<std::string> partitions;
+ std::vector<std::string> image_files;
int rv, index;
while ((rv = getopt_long_only(argc, argv, "+p:sh", options, &index)) != -1) {
@@ -116,6 +126,9 @@ int main(int argc, char* argv[]) {
return usage(argc, argv);
}
break;
+ case 'i':
+ image_files.push_back(optarg);
+ break;
case 'p':
partitions.emplace(optarg);
break;
@@ -126,66 +139,78 @@ int main(int argc, char* argv[]) {
std::cerr << "Missing super image argument.\n";
return usage(argc, argv);
}
- std::string super_path = argv[optind++];
+ image_files.emplace(image_files.begin(), argv[optind++]);
std::string output_dir = ".";
if (optind + 1 <= argc) {
output_dir = argv[optind++];
}
- if (optind < argc) {
- std::cerr << "Unrecognized command-line arguments.\n";
- return usage(argc, argv);
- }
+ std::unique_ptr<LpMetadata> metadata;
+ std::vector<unique_fd> fds;
- // Done reading arguments; open super.img. PartitionOpener will decorate
- // relative paths with /dev/block/by-name, so get an absolute path here.
- std::string abs_super_path;
- if (!android::base::Realpath(super_path, &abs_super_path)) {
- std::cerr << "realpath failed: " << super_path << ": " << strerror(errno) << "\n";
- return EX_OSERR;
- }
+ for (std::size_t index = 0; index < image_files.size(); ++index) {
+ std::string super_path = image_files[index];
- unique_fd fd(open(super_path.c_str(), O_RDONLY | O_CLOEXEC));
- if (fd < 0) {
- std::cerr << "open failed: " << abs_super_path << ": " << strerror(errno) << "\n";
- return EX_OSERR;
- }
+ // Done reading arguments; open super.img. PartitionOpener will decorate
+ // relative paths with /dev/block/by-name, so get an absolute path here.
+ std::string abs_super_path;
+ if (!android::base::Realpath(super_path, &abs_super_path)) {
+ std::cerr << "realpath failed: " << super_path << ": " << strerror(errno) << "\n";
+ return EX_OSERR;
+ }
+
+ unique_fd fd(open(super_path.c_str(), O_RDONLY | O_CLOEXEC));
+ if (fd < 0) {
+ std::cerr << "open failed: " << abs_super_path << ": " << strerror(errno) << "\n";
+ return EX_OSERR;
+ }
- auto metadata = ReadMetadata(abs_super_path, slot_num);
- if (!metadata) {
SparsePtr ptr(sparse_file_import(fd, false, false), sparse_file_destroy);
if (ptr) {
- std::cerr << "This image appears to be a sparse image. It must be "
- "unsparsed to be"
- << " unpacked.\n";
+ std::cerr << "The image file '"
+ << super_path
+ << "' appears to be a sparse image. It must be unsparsed to be unpacked.\n";
return EX_USAGE;
}
- std::cerr << "Image does not appear to be in super-partition format.\n";
- return EX_USAGE;
+
+ if (!metadata) {
+ metadata = ReadMetadata(abs_super_path, slot_num);
+ if (!metadata) {
+ std::cerr << "Could not read metadata from the super image file '"
+ << super_path
+ << "'.\n";
+ return EX_USAGE;
+ }
+ }
+
+ fds.emplace_back(std::move(fd));
}
- ImageExtractor extractor(std::move(fd), std::move(metadata), std::move(partitions), output_dir);
+ // Now do actual extraction.
+ ImageExtractor extractor(std::move(fds), std::move(metadata), std::move(partitions), output_dir);
if (!extractor.Extract()) {
return EX_SOFTWARE;
}
return EX_OK;
}
-ImageExtractor::ImageExtractor(unique_fd&& image_fd, std::unique_ptr<LpMetadata>&& metadata,
+ImageExtractor::ImageExtractor(std::vector<unique_fd>&& image_fds, std::unique_ptr<LpMetadata>&& metadata,
std::unordered_set<std::string>&& partitions,
const std::string& output_dir)
- : image_fd_(std::move(image_fd)),
+ : image_fds_(std::move(image_fds)),
metadata_(std::move(metadata)),
partitions_(std::move(partitions)),
output_dir_(output_dir) {}
bool ImageExtractor::Extract() {
+ std::filesystem::create_directories(output_dir_);
if (!BuildPartitionList()) {
return false;
}
for (const auto& [name, info] : partition_map_) {
+ std::cout << "Attempting to extract partition '" << name << "'...\n";
if (!ExtractPartition(info)) {
return false;
}
@@ -217,13 +242,14 @@ bool ImageExtractor::ExtractPartition(const LpMetadataPartition* partition) {
for (uint32_t i = 0; i < partition->num_extents; i++) {
uint32_t index = partition->first_extent_index + i;
const LpMetadataExtent& extent = metadata_->extents[index];
+ std::cout << " Dealing with extent " << i << " from target source " << extent.target_source << "...\n";
if (extent.target_type != LP_TARGET_TYPE_LINEAR) {
std::cerr << "Unsupported target type in extent: " << extent.target_type << "\n";
return false;
}
- if (extent.target_source != 0) {
- std::cerr << "Split super devices are not supported.\n";
+ if (extent.target_source >= image_fds_.size()) {
+ std::cerr << "Insufficient number of super images passed, need at least " << extent.target_source + 1 << ".\n";
return false;
}
total_size += extent.num_sectors * LP_SECTOR_SIZE;
@@ -237,28 +263,28 @@ bool ImageExtractor::ExtractPartition(const LpMetadataPartition* partition) {
return false;
}
- SparseWriter writer(output_fd, image_fd_, metadata_->geometry.logical_block_size);
+ SparseWriter writer(output_fd, metadata_->geometry.logical_block_size);
// Extract each extent into output_fd.
for (uint32_t i = 0; i < partition->num_extents; i++) {
uint32_t index = partition->first_extent_index + i;
const LpMetadataExtent& extent = metadata_->extents[index];
- if (!writer.WriteExtent(extent)) {
+ if (!writer.WriteExtent(image_fds_[extent.target_source], extent)) {
return false;
}
}
return writer.Finish();
}
-SparseWriter::SparseWriter(int output_fd, int image_fd, uint32_t block_size)
- : output_fd_(output_fd), image_fd_(image_fd), block_size_(block_size) {}
+SparseWriter::SparseWriter(borrowed_fd output_fd, uint32_t block_size)
+ : output_fd_(output_fd), block_size_(block_size) {}
-bool SparseWriter::WriteExtent(const LpMetadataExtent& extent) {
+bool SparseWriter::WriteExtent(borrowed_fd image_fd, const LpMetadataExtent& extent) {
auto buffer = std::make_unique<uint8_t[]>(block_size_);
off_t super_offset = extent.target_data * LP_SECTOR_SIZE;
- if (lseek(image_fd_, super_offset, SEEK_SET) < 0) {
+ if (lseek(image_fd.get(), super_offset, SEEK_SET) < 0) {
std::cerr << "image lseek failed: " << strerror(errno) << "\n";
return false;
}
@@ -269,7 +295,7 @@ bool SparseWriter::WriteExtent(const LpMetadataExtent& extent) {
std::cerr << "extent is not block-aligned\n";
return false;
}
- if (!android::base::ReadFully(image_fd_, buffer.get(), block_size_)) {
+ if (!android::base::ReadFully(image_fd, buffer.get(), block_size_)) {
std::cerr << "read failed: " << strerror(errno) << "\n";
return false;
}
@@ -297,7 +323,7 @@ bool SparseWriter::WriteBlock(const uint8_t* data) {
}
if (hole_size_) {
- if (lseek(output_fd_, hole_size_, SEEK_CUR) < 0) {
+ if (lseek(output_fd_.get(), hole_size_, SEEK_CUR) < 0) {
std::cerr << "lseek failed: " << strerror(errno) << "\n";
return false;
}
@@ -312,12 +338,12 @@ bool SparseWriter::WriteBlock(const uint8_t* data) {
bool SparseWriter::Finish() {
if (hole_size_) {
- off_t offset = lseek(output_fd_, 0, SEEK_CUR);
+ off_t offset = lseek(output_fd_.get(), 0, SEEK_CUR);
if (offset < 0) {
std::cerr << "lseek failed: " << strerror(errno) << "\n";
return false;
}
- if (ftruncate(output_fd_, offset + hole_size_) < 0) {
+ if (ftruncate(output_fd_.get(), offset + hole_size_) < 0) {
std::cerr << "ftruncate failed: " << strerror(errno) << "\n";
return false;
}
diff --git a/perf2cfg/Android.bp b/perf2cfg/Android.bp
index e6b5505e..6e4efe97 100644
--- a/perf2cfg/Android.bp
+++ b/perf2cfg/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_art_mainline",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -48,4 +49,9 @@ python_test_host {
test_options: {
unit_test: true,
},
+ version: {
+ py3: {
+ embedded_launcher: true,
+ },
+ },
}
diff --git a/perf2cfg/perf2cfg_test.py b/perf2cfg/perf2cfg_test.py
index 90d757f4..8989a64a 100755
--- a/perf2cfg/perf2cfg_test.py
+++ b/perf2cfg/perf2cfg_test.py
@@ -16,12 +16,10 @@
import unittest
import os.path
-def load_tests(loader, standard_tests, _pattern):
- this_dir = os.path.dirname(__file__)
- perf2cfg_tests = loader.discover(start_dir=os.path.join(this_dir, 'tests'),
- pattern='test*.py')
- standard_tests.addTests(perf2cfg_tests)
- return standard_tests
+from tests.test_edit import *
+from tests.test_events import *
+from tests.test_parse import *
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/tests/memeater/Android.bp b/perf_tools/Android.bp
index dfeaea70..26b93511 100644
--- a/tests/memeater/Android.bp
+++ b/perf_tools/Android.bp
@@ -1,4 +1,5 @@
-// Copyright (C) 2017 The Android Open Source Project
+//
+// 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.
@@ -11,28 +12,27 @@
// 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.
-// Copyright The Android Open Source Project
+//
package {
// See: http://go/android-license-faq
- default_applicable_licenses: ["system_extras_tests_memeater_license"],
+ default_applicable_licenses: ["Android-Apache-2.0"],
}
-license {
- name: "system_extras_tests_memeater_license",
- visibility: [":__subpackages__"],
- license_kinds: [
- "SPDX-license-identifier-Apache-2.0",
- ],
- license_text: [
- "NOTICE",
- ],
+python_binary_host {
+ name: "progress_report",
+ srcs: ["progress_report.py"],
+ libs: [ "report_proto",],
+ version: {
+ py3: { embedded_launcher: true },
+ },
+ main: "progress_report.py",
}
-cc_binary {
- name: "memeater",
- srcs: ["memeater.c"],
- cflags: [
- "-Wno-unused-parameter",
- ],
+python_library_host {
+ name: "report_proto",
+ srcs: ["*.proto"],
+ proto: {
+ canonical_path_from_root: false,
+ },
}
diff --git a/perf_tools/bats/lcan.py b/perf_tools/bats/lcan.py
new file mode 100755
index 00000000..cfe381a4
--- /dev/null
+++ b/perf_tools/bats/lcan.py
@@ -0,0 +1,692 @@
+#!/usr/bin/python3
+# -------------------------------------------
+# logcat analysis
+# -------------------------------------------
+from ast import keyword
+from curses import keyname
+import argparse
+import os
+from string import digits
+from sbtaTools import TextFile
+import datetime
+import re
+import shlex
+import glob
+
+class LCItem:
+ def __init__(self, lCTimeProcessor):
+ self.dateTime = 0
+ self.relativeTime = 0
+ self.key = ""
+ self.moduleName = ""
+ self.keyword = ""
+ self.valueMsec = 0
+ self.lCTimeProcessor = lCTimeProcessor
+ self.lines = []
+
+ def set(self, dateTime, moduleName, keyText, valueMsec):
+ try:
+ self.dateTime = dateTime
+ self.relativeTime = self.lCTimeProcessor.toRelativeTime(self.dateTime)
+ self.moduleName = moduleName
+ self.keyword = keyText
+ self.key = moduleName+":" + keyText
+ self.valueMsec = valueMsec
+ except Exception as e:
+ errLine = "LCItem:set() ERROR Failed: " + str(e)
+ assert False, errLine
+
+ def copy(self, otherLCItem):
+ self.dateTime = otherLCItem.dataTime
+ self.relativeTime = otherLCItem.relativeTime
+ self.key = otherLCItem.key
+ self.moduleName = otherLCItem.moduleName
+ self.keyword = otherLCItem.keyword
+ self.valueMsec = otherLCItem.valueMsec
+ self.lCTimeProcessor = otherLCItem.lcTimeProcessor
+ self.lines = otherLCItem.lines
+
+ def appendLine(self, line):
+ self.lines.append(line)
+
+ def keyEqual(self, otherItem):
+ if self.key != otherItem.key:
+ return False
+ return True
+
+ def add(self, otherItem):
+ assert(self.key==otherItem.key)
+ self.lines.extend(otherItem.lines)
+ self.valueMsec = self.valueMsec + otherItem.valueMsec
+ return True
+
+ def addValue(self, otherLCItem):
+ if self.key=="":
+ self.copy(otherLCItem)
+ else:
+ assert(self.key==otherLCItem.key)
+ self.valueMsec = self.valueMsec + otherLCItem.valueMsec
+ return True
+
+ def divideValue(self, number): # scaler divide
+ self.valueMsec = self.valueMsec / number
+ return True
+
+ def key(self):
+ return self.key
+
+ def print(self):
+ #systemTime = self.lCTimeProcessor.toSystemTime(self.dateTime)
+ #relativeTime = self.lCTimeProcessor.toRelativeTime(self.dateTime)
+ newTimeString = str(self.relativeTime)
+ if (len(self.lines)>0):
+ print("{} {}: {} {:.4f} - {}".format(newTimeString, self.moduleName, self.keyword, self.valueMsec, self.lines[0]))
+ else:
+ print("{} {}: {} {:.4f} -".format(newTimeString, self.moduleName, self.keyword, self.valueMsec))
+
+ def printLines(self, prefix, min):
+ if (len(self.lines)<min):
+ return
+ for line in self.lines:
+ print(" {}{}".format(prefix, line))
+
+ def findModuleName(self, lineTextWords):
+ colonIndex = -1
+ try:
+ colonIndex = lineTextWords.index(":")
+ # address case of colon with no space
+ moduleName = lineTextWords[colonIndex-1]
+ except:
+ moduleName = ""
+ if colonIndex==-1:
+ for word in reversed(lineTextWords):
+ index = word.find(":")
+ if index!=-1:
+ moduleName = word[:index]
+ break
+ moduleName = moduleName.strip()
+ return colonIndex, moduleName
+
+ def parseLineWithTook(self, line):
+ maxLineLength = 100
+ stage = 0
+ try:
+ words = line.split(" ")
+ dataTimeO = self.lCTimeProcessor.parseTimeStamp(line)
+ if line.find("took to complete") != -1:
+ stage = 1
+ tookIndex = line.find(" took to complete:")
+ uptoEnd= line[:tookIndex]
+ lineTextWords = uptoEnd.split()
+ colonIndex, moduleName = self.findModuleName(lineTextWords)
+ keyword = " ".join([lineTextWords[6]])
+ value = re.findall(r'\d+', line[tookIndex:])[-1]
+ value = float(value)
+
+ elif line.find("took") != -1:
+ stage = 2
+ tookIndex = line.find(" took")
+ uptoEnd= line[:tookIndex]
+ uptoBracket = uptoEnd.rfind("(")
+ if uptoBracket != -1:
+ uptoEnd = uptoEnd[:uptoBracket]
+ #uptoEnd = uptoEnd.replace(":", "")
+ lineTextWords = shlex.split(uptoEnd)
+ colonIndex, moduleName = self.findModuleName(lineTextWords)
+ # if there is colon only take words after it
+ if colonIndex!=-1:
+ lineTextWords = lineTextWords[colonIndex+1:]
+ numWords = len(lineTextWords)
+ keyword = ""
+ stage = 3
+ try:
+ for i in range(max(numWords-3, 0), numWords, 1):
+ keyword = keyword + " " + lineTextWords[i]
+ except Exception as e:
+ errLine = "LCItem:parseLineWithTook() ERROR Failed to parse1: " + str(e)
+ print(errLine)
+ assert False, errLine
+
+ # reduce length
+ keyword = keyword[:maxLineLength]
+ keyword = keyword.strip()
+ # using regex expression to replace all numbers
+ keyword = re.sub(r'\d', "_", keyword)
+ value = 0
+ stage = 4
+ try:
+ multplier = 1
+ tookSubstring = line[tookIndex:]
+ secondsIndex = tookSubstring.find("seconds")
+ msIndex = tookSubstring.find("ms")
+ if (secondsIndex!=-1):
+ tookSubstring = tookSubstring[:secondsIndex]
+ multiplier = 1000
+ elif msIndex != -1:
+ tookSubstring = tookSubstring[:msIndex]
+ else:
+ # known exception
+ if tookSubstring.find("properties")==-1:
+ errLine = "LCItem:parseLineWithTook() ERROR invalid took in substring 1B {}".format(line)
+ print(errLine)
+ assert False, errLine
+ return False
+
+ values = re.findall(r'[\d\.\d]+', tookSubstring)
+ while "." in values:
+ values.remove(".")
+ value = float(values[-1])
+ if line.find("seconds") != -1:
+ value = value * multiplier
+ except Exception as e:
+ errLine = "LCItem:parseLineWithTook() ERROR Failed to parse2: " + str(e)
+ print(errLine)
+ assert False, errLine
+ stage = 5
+
+ else:
+ return False
+
+ stage = 6
+ self.set(dataTimeO, moduleName, keyword, value)
+ stage = 7
+ self.lines.append(line)
+
+ return True
+
+ except Exception as e:
+ errLine = "LCItem:parseLineWithTook() ERROR Failed to parse3:" + str(e)
+ print(errLine, stage)
+ assert False, errLine
+
+ def parseLine(self, line):
+ try:
+ words = line.split(" ")
+ dateTimeO = self.lCTimeProcessor.parseTimeStamp(line)
+ if (dateTimeO!=None):
+ #lcItem = LCItem(self.lCTimeProcessor)
+ newLine = line[19:].rstrip()
+ self.set(dateTimeO, "", newLine, 0)
+ #self.print()
+ return
+ else:
+ return None
+
+ except Exception as e:
+ errLine = "LCItem:parseLine() ERROR Failed to parse3:" + str(e)
+ print(errLine)
+ assert False, errLine
+
+ def find(self, keyword):
+ if self.key.find(keyword)!=-1:
+ return True
+ for line in self.lines:
+ if line.find(keyword)!=-1:
+ return True
+
+ def createLogLine(self):
+ line = ""
+ msecs = self.dateTime.strftime("%f")
+ timeString = self.dateTime.strftime("%m-%d %H:%M:%S.")
+ #timeString = timeString + msecs[]
+ return line
+
+class LCItemSet:
+ def __init__(self, item1, item2):
+ self.item1 = item1
+ self.item2 = item2
+ if (item1.key != "" and item2.key != ""):
+ assert(item1.key == item2.key)
+ if (item1.key!=""):
+ self.key = item1.key
+ else:
+ self.key = item2.key
+ self.diff = item2.valueMsec - item1.valueMsec
+
+ def __gt__(self, other):
+ if(self.diff>other.diff):
+ return True
+ else:
+ return False
+
+ def add(item):
+ assert(False)
+
+ def print(self, min, printAll):
+ self.diff = self.item2.valueMsec - self.item1.valueMsec
+ if abs(self.diff)<min:
+ return
+ flag = "12"
+ if self.item1.key=="":
+ flag = "-2"
+
+ if self.item2.key=="":
+ flag = "1-"
+
+ print("{}, {}, {}, {}, {}".format(self.key, self.item1.valueMsec, self.item2.valueMsec, self.diff, flag))
+ if printAll:
+ self.item1.printLines("1> ", 1)
+ self.item2.printLines("2> ", 1)
+
+class LCItemMap:
+ def __init__(self):
+ self.map = {}
+
+ def put(self, newItem):
+ item = self.map.get(newItem.key)
+ if item==None:
+ self.map[newItem.key] = newItem
+ else:
+ item.add(newItem)
+
+ def print(self):
+ for key in self.map:
+ self.map[key].print()
+
+ def find(self, keyword):
+ lCItems = []
+ for index, lCItem in self.map:
+ if lCItem.find(keyword):
+ lCItems.append(lCItem)
+ return lCItems
+
+ def addValues(self, other):
+ for index, item in other.map.items():
+ if item.key in self.map:
+ self.map[item.key].addValue(item)
+ else:
+ self.map[item.key] = item
+
+ def divideValue(self, number):
+ for index, item in self.map:
+ item.divideValue(number)
+
+class LCItemSetMap:
+ def __init__(self):
+ self.map = {}
+
+ def put(self, itemSet):
+ item = self.map.get(itemSet.key)
+ if item==None:
+ self.map[itemSet.key] = itemSet
+ else:
+ item.add(itemSet)
+
+ def printSorted(self, printAll):
+ a = sorted(self.map.items(), key=lambda x: (x[1], x[0]), reverse=True)
+ cumDif = 0
+ print("Key, Value1, Value2, diff")
+ for item in a:
+ item[1].print(1, printAll)
+ cumDif = cumDif + item[1].diff
+ print("CUMULATIVE DIFF: {}".format(cumDif))
+
+class LCTimeProcessor:
+ def __init__(self):
+ self.firstKernelTimeStamp = 0
+ self.lastKernelTimeStamp = 0
+ self.firstSystemTimesStamp = 0
+ self.lastTimeStamp = 0
+ self.zeroRelativeTime = 0
+ today = datetime.datetime.now()
+ year = str(today.year)
+ self.currentYear = year[-2:] # 2022/2023
+
+ def parseTimeStamp(self, line):
+ try:
+ if len(line)<19:
+ return None
+ currentYear = self.currentYear # 22
+ words = line.split(" ")
+ timeString = words[0]
+ #timeString = re.sub("[^0-9: -.]", "", timeString)
+ timeString = timeString.strip()
+ timeString = timeString[:18]
+ timeString = currentYear + "-" + timeString
+ dataTimeO = datetime.datetime.strptime(timeString, "%Y-%m-%d %H:%M:%S.%f")
+ return dataTimeO
+ except Exception as e:
+ # If no time stamp on this line
+ if line.find("beginning of")!=-1:
+ return None
+ errLine = "LCItem:parseTimeStamp() ERROR Failed to parse:" + str(e)
+ print(errLine)
+ assert False, errLine
+ return None
+
+
+ def process(self, line):
+ timeStamp = self.parseTimeStamp(line)
+ if timeStamp==None:
+ return False
+
+ if self.firstKernelTimeStamp==0:
+ self.firstKernelTimeStamp = timeStamp
+ else:
+ if timeStamp < self.firstKernelTimeStamp:
+ return False
+
+ timeChange = timeStamp - self.lastTimeStamp
+ if (timeChange.total_seconds() > 68*5):
+ if self.firstSystemTimesStamp ==0:
+ self.firstSystemTimesStamp = timeStamp
+ self.lastKernelTimeStamp = self.lastTimeStamp
+ self.zeroRelativeTime = self.toSystemTime(self.firstKernelTimeStamp)
+
+ self.lastTimeStamp = timeStamp
+ return True
+
+ def toSystemTime(self, timeStamp):
+ try:
+ # if no systemTime is found, it must all be system time
+ if self.firstSystemTimesStamp==0:
+ self.firstSystemTimesStamp = self.firstKernelTimeStamp
+ self.lastKernelTimeStamp = self.lastTimeStamp
+ self.zeroRelativeTime = self.firstKernelTimeStamp
+ return timeStamp
+ if timeStamp >= self.firstSystemTimesStamp:
+ return timeStamp
+ else:
+ timeChange = timeStamp - self.lastKernelTimeStamp
+ systemTime = self.firstSystemTimesStamp + timeChange
+ return systemTime
+ except Exception as e:
+ errLine = "LogLine:parseLine() ERROR Failed to parse3:" + str(e)
+ print(errLine)
+ assert False, errLine
+
+ def toRelativeTime(self, timeStamp):
+ systemTime = self.toSystemTime(timeStamp)
+ relativeTime = systemTime - self.zeroRelativeTime
+ return relativeTime
+
+ if timeStamp< self.firstSystemTimesStamp:
+ timeChange = timeStamp - self.lastKernelTimeStamp
+ systemTime = self.firstSystemTimesStamp + timeChange
+ return systemTime
+ else:
+ return timeStamp
+
+ def toString(self, timeStamp):
+ return timeStamp.strftime("%Y-%m-%d %H:%M:%S.%f")
+
+class LCLogLine:
+ def __init__(self, lCTimeProcessor):
+ self.dateTime = 0
+ self.relativeTime = 0
+ self.lineText = ""
+ self.lCTimeProcessor = lCTimeProcessor
+
+ def set(self, dateTime, lineText):
+ self.dateTime = dateTime
+ self.relativeTime = self.lCTimeProcessor.toRelativeTime(self.dateTime)
+ self.lineText = lineText
+
+ def print(self):
+ newTimeString = str(self.relativeTime)
+ print("{}{}".format(newTimeString, self.lineText))
+
+ def parseLine(self, line):
+ try:
+ dateTimeO = self.lCTimeProcessor.parseTimeStamp(line)
+ if (dateTimeO!=None):
+ lineText = line[19:].rstrip()
+ self.set(dateTimeO, lineText)
+ return
+ else:
+ return None
+
+ except Exception as e:
+ errLine = "LogLine:parseLine() ERROR Failed to parse3:" + str(e)
+ print(errLine)
+ assert False, errLine
+
+ def find(self, word):
+ if (self.lineText.find(word)!=-1):
+ return True
+ else:
+ return False
+
+ def findAll(self, words):
+ for word in words:
+ if (self.lineText.find(word)==-1):
+ return False
+ return True
+
+class LCLogFile(TextFile):
+ priorTimeStamp = 0.0
+ def __init__(self, _fileName = ""):
+ super(LCLogFile, self).__init__(_fileName)
+ self.linesWithTook = []
+ self.linesWithTookToComplete = []
+ self.linesWithoutTookToComplete = []
+ self.firstKernelTimeStamp = 0
+ self.lastKernelTimeStamp = 0
+ self.firstSystemTimesStamp = 0
+ self.lastTimeStamp = 0
+ self.lCTimeProcessor = LCTimeProcessor()
+ self.dumpLinesBeforeBeginning()
+
+ def dumpLinesBeforeBeginning(self):
+ # start from --------- beginning of kernel
+ beginningFound = False
+ _lines = []
+ for line in self.lines:
+ if beginningFound==True:
+ _lines.append(line)
+ self.lCTimeProcessor.process(line)
+
+ elif line.find("beginning of kernel") != -1:
+ beginningFound = True
+
+ self.lines = _lines
+
+
+ def scanTook(self):
+ lCItemMap = LCItemMap()
+ foundBeginning = False
+ for line in self.lines:
+ # start at beginning
+ if not foundBeginning:
+ if line.find("beginning of kernel=1") != -1:
+ foundBeginning = True
+ continue
+
+ # stop if boot complete
+ if line.find("sys.boot_completed=1") != -1:
+ break
+
+ if line.find("took") != -1:
+ self.linesWithTook.append(line.rstrip())
+
+ for line in self.linesWithTook:
+ lCItem = LCItem(self.lCTimeProcessor)
+ if lCItem.parseLineWithTook(line)==True:
+ lCItemMap.put(lCItem)
+
+ return lCItemMap
+
+ def print(self, numItems=None):
+ self.scanTook()
+
+ def convert(self, numItems=None):
+ lcLogLines = []
+ for line in self.lines:
+ lcLogLine = LCLogLine(self.lCTimeProcessor)
+ lcLogLine.parseLine(line)
+ lcLogLines.append(lcLogLine)
+ return lcLogLines
+'''
+ def createLCFile(self, fileName):
+ # create LCTimeProcessor
+ # create LCItem
+ # create LCLogLine
+ # write LCLogLine to file
+'''
+class ScanFile:
+ def __init__(self):
+ self.fileName = "none"
+
+ def scanKeyWords(self, fileName):
+ print("Scanning {}".format(fileName))
+ cmd = "grep \"apexd: wait for '\/dev\/loop-control'\" {}".format(fileName)
+ x = os.system(cmd)
+ cmd = "grep \"Service 'apexd-bootstrap\" {}".format(fileName)
+ x = os.system(cmd)
+ cmd = "grep apexd.status=activated {}".format(fileName)
+ x = os.system(cmd)
+ cmd = "grep \"Service 'bpfloader'\" {}".format(fileName)
+ x = os.system(cmd)
+ cmd = "grep \"sys.boot_completed=1\" {} | head -n 1".format(fileName)
+ x = os.system(cmd)
+
+ def scanTook(self, fileName):
+ lCLogFile = LCLogFile(fileName)
+ lCItemMap = lCLogFile.scanTook()
+
+ def convert(self, fileName):
+ lCLogFile = LCLogFile(fileName)
+ lcItems = lCLogFile.convert()
+ for lcItem in lcItems:
+ lcItem.print()
+
+ def phases(self, fileName):
+ keywordFile = TextFile("keywords")
+ #keywords = ['init first', 'init second', "Starting phase 200", "boot_completed"]
+
+ lCLogFile = LCLogFile(fileName)
+ keywordSets = []
+ for line in keywordFile.lines:
+ line = line.strip()
+ keywordSet = line.split(", ")
+ keywordSets.append(keywordSet)
+
+ lcLogLines = lCLogFile.convert()
+ for keywordSet in keywordSets:
+ for lcLogLine in lcLogLines:
+ if lcLogLine.findAll(keywordSet)==True:
+ lcLogLine.print()
+ break
+
+class Compare:
+ def __init__(self):
+ self.fileName = "none"
+
+ def compareLCItemMaps(self, lCItemMap1, lCItemMap2):
+ lCItemSetMap = LCItemSetMap()
+
+ for item1key in lCItemMap1.map:
+ found = False
+ for item2key in lCItemMap2.map:
+ if item2key==item1key:
+ lcItemSet = LCItemSet(lCItemMap1.map[item1key], lCItemMap2.map[item2key])
+ lCItemSetMap.put(lcItemSet)
+ found = True
+ break
+ # if item1Key is not in ItemMap2, add a null item
+ if found==False:
+ lCTimeProcessor = LCTimeProcessor()
+ nullLCItem = LCItem(lCTimeProcessor)
+ lcItemSet = LCItemSet(nullLCItem, lCItemMap1.map[item1key])
+ lCItemSetMap.put(lcItemSet)
+ found = True
+
+ lCItemSetMap.printSorted(printAll)
+ return lCItemSetMap
+
+ def compareFiles(self, fileName1, fileName2, printAll):
+ print("---------------------------------------------------------------")
+ print("lcan.py -cmp {} {}".format(fileName1, fileName2))
+ print("---------------------------------------------------------------")
+ lCLogFile1 = LCLogFile(fileName1)
+ lCItemMap1 = lCLogFile1.scanTook()
+ lCLogFile2 = LCLogFile(fileName2)
+ lCItemMap2 = lCLogFile2.scanTook()
+
+ lCItemSetMap = LCItemSetMap()
+
+ for item1key in lCItemMap1.map:
+ found = False
+ for item2key in lCItemMap2.map:
+ if item2key==item1key:
+ lcItemSet = LCItemSet(lCItemMap1.map[item1key], lCItemMap2.map[item2key])
+ lCItemSetMap.put(lcItemSet)
+ found = True
+ break
+ # if item1Key is not in ItemMap2, add a null item
+ if found==False:
+ lCTimeProcessor = LCTimeProcessor()
+ nullLCItem = LCItem(lCTimeProcessor)
+ lcItemSet = LCItemSet(nullLCItem, lCItemMap1.map[item1key])
+ lCItemSetMap.put(lcItemSet)
+ found = True
+
+ lCItemSetMap.printSorted(printAll)
+ return lCItemSetMap
+
+ def getAverageOfDir(self, buildId):
+ #get average values for build1
+ dirList = glob.glob("{}/LC-{}*.txt".format(buildId, buildId))
+ numFiles = len(dirList)
+ #iterate in numerical order
+ lCItemMapS = LCItemMap()
+ for index in range(numFiles):
+ fileName = "{}/LC-{}-{}.txt".format(buildId, buildId, index)
+ #for index, fileName in enumerate(dirList):
+ lCLogFile = LCLogFile(fileName)
+ lCItemMap = lCLogFile.scanTook()
+ lCItemMapS.addValues(lCItemMap)
+ lCItemMapS.divideValue(numFiles)
+ return lCItemMapS
+
+ def compareDirs(self, buildId1, buildId2, printAll):
+ print("---------------------------------------------------------------")
+ print("lcan.py -cmpd {} {} {}".format(buildId1, buildId2, printAll))
+ print("---------------------------------------------------------------")
+
+ #get average values for build1
+ lCItemMap1 = self.getAverageOfDir(buildId1)
+ lCItemMap2 = self.getAverageOfDir(buildId2)
+ self.compareLCItemMaps(self, lCItemMap1, lCItemMap2)
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument("-c", nargs=1, metavar=('<fileName>'), help="convert Logcat output to start from boot with converted timeStamps")
+parser.add_argument("-k", nargs=1, metavar=('<fileName>'), help="summary on keywords")
+parser.add_argument("-a", nargs=1, metavar=('<fileName>'), help="analyze file")
+parser.add_argument("-cmp", nargs=3, metavar=('<fileName1>', '<fileName2>', '<brief/all>'), help="compare logcat files")
+parser.add_argument("-cmpd", nargs=3, metavar=('<dirName1>', '<dirName2>', '<brief/all>'), help="compare logcat files")
+parser.add_argument("-p", nargs=1, metavar=('<fileName1>'), help="phase report on log files")
+args = parser.parse_args()
+
+if args.k!=None:
+ scanFile = ScanFile()
+ scanFile.scanKeyWords(args.k[0])
+
+if args.a!=None:
+ scanFile = ScanFile()
+ scanFile.scanTook(args.a[0])
+
+if args.c!=None:
+ scanFile = ScanFile()
+ scanFile.convert(args.c[0])
+
+if args.p!=None:
+ scanFile = ScanFile()
+ scanFile.phases(args.p[0])
+
+if args.cmp!=None:
+ printAll = False
+ compare = Compare()
+ if (len(args.cmp)>2):
+ if (args.cmp[2].find("all")!=-1):
+ printAll = True
+ compare.compareFiles(args.cmp[0], args.cmp[1], printAll)
+
+if args.cmpd!=None:
+ printAll = False
+ compare = Compare()
+ if (len(args.cmpd)>2):
+ if (args.cmpd[2].find("all")!=-1):
+ printAll = True
+ compare.compareDirs(args.cmpd[0], args.cmpd[1], printAll)
diff --git a/perf_tools/config.yaml b/perf_tools/config.yaml
new file mode 100644
index 00000000..4372ce20
--- /dev/null
+++ b/perf_tools/config.yaml
@@ -0,0 +1,15 @@
+# This file is used to store the keywords to be extracted
+# from the logcat.
+---
+- boot_progress_start
+- boot_progress_preload_start
+- boot_progress_preload_end
+- boot_progress_system_run
+- boot_progress_pms_start
+- boot_progress_pms_system_scan_start
+- boot_progress_pms_data_scan_start
+- boot_progress_pms_scan_end
+- boot_progress_pms_ready
+- boot_progress_ams_ready
+- boot_progress_enable_screen
+- car_helper_boot_phase
diff --git a/perf_tools/parse_timestamp.py b/perf_tools/parse_timestamp.py
new file mode 100644
index 00000000..bfac3f71
--- /dev/null
+++ b/perf_tools/parse_timestamp.py
@@ -0,0 +1,55 @@
+import sys
+import os
+from datetime import datetime
+
+# Usage:
+# replace_timestamp.py input.txt output.txt timestamp_string
+#
+# Description:
+# Replace timestamp in the input.txt with the difference timestamp to timestamp_string.
+#
+# Example: replace_timestamp.py input.txt output.txt "01-28 18:12:30.339".
+#
+def main():
+ filepath = sys.argv[1]
+ if not os.path.isfile(filepath):
+ print("File path {} does not exist. Exiting...".format(filepath))
+ sys.exit()
+
+ output_filepath = sys.argv[2]
+
+ timestamp_str = sys.argv[3]
+ date_time_obj = datetime.strptime(timestamp_str, '%m-%d %H:%M:%S.%f')
+
+ output_fp = open(output_filepath, 'w')
+ i = 1
+ with open(filepath, 'r', errors = 'ignore') as fp:
+ for line in fp:
+ newline = replace_timestamp_abs(line, timestamp_str, date_time_obj)
+ output_fp.write(newline)
+ i = i + 1
+ fp.close()
+ output_fp.close()
+
+
+def replace_timestamp_abs(line, timestamp_str, date_time_obj0):
+ if line[:5] != timestamp_str[:5]:
+ return line
+
+ index = line.find(" ", 6)
+ if index <= 0:
+ return line
+ substr0 = line[:index]
+ substr1 = line[index:]
+
+ try:
+ date_time_obj = datetime.strptime(substr0, '%m-%d %H:%M:%S.%f')
+ except ValueError:
+ return line
+
+ date_time_delta = date_time_obj - date_time_obj0
+ date_time_delta_str = str(date_time_delta)
+ return date_time_delta_str + substr1
+
+if __name__ == '__main__':
+ main()
diff --git a/perf_tools/parse_timing.py b/perf_tools/parse_timing.py
new file mode 100644
index 00000000..1ba23142
--- /dev/null
+++ b/perf_tools/parse_timing.py
@@ -0,0 +1,197 @@
+import sys
+import os
+from datetime import datetime
+
+# Usage:
+# python3 parse_timing.py logcat.txt "08-23 23:10:32.555" 10 200
+#
+# Description: extract events and timing in the log that start from timestamp "08-23 23:10:32.555"
+# till 10 seconds
+#
+# Usage:
+# python3 parse_timing.py logcat1.txt logcat2.txt 10 ts1 ts1 200
+#
+# Description: report the timing that the differences are bigger than 200
+#
+# Example:
+# python3 log_processing/parse_timing.py 8976224/logcat.txt 8879724/logcat.txt
+# "08-23 23:10:32.555" "07-29 06:39:06.254" 200
+def main():
+ if len(sys.argv) == 5:
+ process_one_log()
+ elif len(sys.argv) == 6:
+ compair_two_log()
+ else:
+ print("wrong number of arguments")
+
+def compair_two_log():
+ filepath1 = sys.argv[1]
+ print(filepath1)
+ if not os.path.isfile(filepath1):
+ print("File path {} does not exist. Exiting...".format(filepath1))
+ sys.exit()
+
+ filepath2 = sys.argv[2]
+ print(filepath2)
+ if not os.path.isfile(filepath2):
+ print("File path {} does not exist. Exiting...".format(filepath2))
+ sys.exit()
+
+ ts1 = datetime.strptime(sys.argv[3], '%m-%d %H:%M:%S.%f')
+ ts2 = datetime.strptime(sys.argv[4], '%m-%d %H:%M:%S.%f')
+ duration = float(sys.argv[5])*1000
+
+ # 1: took to complete 1000ms
+ # 2: took 33ms
+ # 3: took 33 ms or took 0.3 seconds
+ file1_events = [{}, {}, {}]
+ file2_events = [{}, {}, {}]
+
+ extract_events(filepath1, file1_events, ts1, duration)
+ extract_events(filepath2, file2_events, ts2, duration)
+
+
+ sum_events_timing(file1_events)
+ sum_events_timing(file2_events)
+
+ sum_all_events_timing_diff(file1_events, file2_events)
+
+ sys.exit()
+
+
+def process_one_log():
+ filepath = sys.argv[1]
+ print(filepath)
+ if not os.path.isfile(filepath):
+ print("File path {} does not exist. Exiting...".format(filepath))
+ sys.exit()
+
+ # 1: took to complete 1000ms
+ # 2: took 33ms
+ # 3: took 33 ms or took 0.3 seconds
+ events = [{}, {}, {}]
+ ts = datetime.strptime(sys.argv[2], '%m-%d %H:%M:%S.%f')
+ duration = float(sys.argv[3])*1000
+ extract_events(filepath, events, ts, duration)
+
+ timing = float(sys.argv[3])
+ print_sorted_all_events(events, timing)
+
+ sys.exit()
+
+def print_sorted_all_events(file_events, timing):
+ for i in range(len(file_events)):
+ print_sorted_events(file_events[i], timing)
+
+def print_sorted_events(events, timing):
+ for word in sorted(events, key=events.get, reverse=True):
+ if (events[word]) > timing:
+ print("event:{} \ttiming:{} ".format(word, events[word]))
+
+def sum_events_timing(events):
+ total_sum = 0;
+ for i in range(len(events)):
+ sum = 0
+ print("start summary for type {}".format(i))
+ for event in events[i]:
+ sum += events[i][event]
+ #print("event {} timing {} ".format(event, events[i][event]))
+ print("sum events type {} {} : timing {}".format(i, len(events), sum))
+ total_sum += sum
+ print("sum all type events timing {}\n".format(total_sum))
+
+def sum_events_timing_diff(type, file1_events, file2_events):
+ sum_diff = 0
+ max_diff = 0
+ regression_events = {}
+ print("start summary for type {}".format(type))
+ for event in file1_events:
+ val = file2_events.get(event)
+ if val != None:
+ diff = file1_events[event] - val
+ if diff > 100 and val > 100:
+ # print("regression event {} \t{}: {} \t{}: {} \tdiff: {}"
+ # .format(event, "case1", file1_events[event], "case2", val, diff))
+ regression_events[event] = diff
+ sum_diff += diff
+ max_diff = max(max_diff, diff)
+ print("\nsummary for timing type {} sum_diff {} max_diff {}".format(type, sum_diff, max_diff))
+ print_events(regression_events, file1_events, file2_events)
+
+def sum_all_events_timing_diff(file1_events, file2_events):
+ for i in range(len(file1_events)):
+ sum_events_timing_diff(i, file1_events[i], file2_events[i])
+
+def print_events(events, file1_events, file2_events):
+ for word in sorted(events, key=events.get, reverse=True):
+ if (events[word]) > 10:
+ print("{} \tdiff {} \t{} \t{}".format(word, events[word],file1_events[word], file2_events[word]))
+
+def find_took(words):
+ for i in range(len(words)):
+ if words[i] == 'took' or words[i] == "took:":
+ return i
+
+def extract_time(line, events):
+ if not "took" in line:
+ return
+
+ # 1: took to complete 1000ms
+ # 2: took 33ms
+ # 3: took 33 ms or took 0.3 seconds
+ words = line.strip().split(' ')
+ i = find_took(words)
+ index = 0;
+ str1 = " "
+ key = str1.join(words[8:i])
+
+ if words[i+1] == 'to' and words[i+2] == 'complete:':
+ index = 0;
+ val = float(words[i+3][:-2]);
+ elif words[i+1][-2:] == 'ms':
+ index = 1
+ val = float(words[i+1][:-2]);
+ elif len(words) > i+2:
+ index = 2
+ if words[i+2] == 'seconds':
+ val = float(words[i+1])*1000;
+ elif words[i+2] == 'ms':
+ val = float(words[i+1])
+ else:
+ return True
+
+ # print("index: {} key: {} val: {}".format(index, key, val));
+
+ if events[index].get(key) == None:
+ events[index][key] = val
+ return True
+ else:
+ # print("duplicate key: " + key + " line: " + line)
+ return True
+
+def check_time_range(line, ts, duration):
+ index = line.find(" ", 6)
+ if index <= 0:
+ return False
+
+ try:
+ current_time = datetime.strptime(line[:index], '%m-%d %H:%M:%S.%f')
+ except ValueError:
+ return False
+
+ deltatime = current_time - ts
+ if deltatime.total_seconds()*1000 < 0 or deltatime.total_seconds() > duration:
+ return False
+ return True
+
+def extract_events(filepath, events, ts, duration):
+ with open(filepath, errors='ignore') as fp:
+ for line in fp:
+ if check_time_range(line, ts, duration) == False:
+ continue
+ if extract_time(line, events) == False:
+ return
+
+
+if __name__ == '__main__':
+ main()
diff --git a/perf_tools/progress_report.py b/perf_tools/progress_report.py
new file mode 100755
index 00000000..f6a1c430
--- /dev/null
+++ b/perf_tools/progress_report.py
@@ -0,0 +1,159 @@
+#!/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 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.
+#
+
+import argparse
+from datetime import datetime
+import yaml
+import os
+import report_pb2
+import sys
+import traceback
+
+# Usage: python3 progress_report.py --logcat logcat.txt --config config.yaml --output_dir report_dir
+#
+# logcat.txt should contain the "boot_progress_start" and "boot_progress_enable_screen"".
+# config.yaml contains all the keywords to be extracted.
+# report_dir will contain three generated files:
+#
+# timestamp_log.txt: contains the same content as logcat.txt, but the timestamp is replaced
+# with relative time with boot_progress_start time.
+#
+# report_proto.txt: contains the report for the events related to the keywords.
+#
+# report.txt: contains logcat messages corresponding to the events captured in report_proto.txt
+
+def init_arguments():
+ parser = argparse.ArgumentParser(
+ prog = 'progrocess_report.py',
+ description='Extract timing information and generate a report.')
+ parser.add_argument(
+ '--logcat', type=str, required=True,
+ help = 'logcat file name')
+ parser.add_argument(
+ '--config', type=str, required=True,
+ help = 'configuration file for keywords')
+ parser.add_argument(
+ '--output_dir', type= str, required=True,
+ help = 'directory name to store the generated files')
+ return parser.parse_args()
+
+# Find boot_progress_start line and boot_progress_enable_screen find the time difference
+# return the start time string
+def find_boot_progress_start_end(fp):
+ start = ""
+ end = ""
+ for line in fp:
+ if "boot_progress_start" in line:
+ start = line
+ if "boot_progress_enable_screen" in line and len(start):
+ end = line
+ break
+
+ missing_error = ""
+ if start == "":
+ missing_error = "******logcat file missing boot_progress_start\n"
+ elif end == "":
+ missing_error += "******logcat file missing boot_progress_end "
+ if missing_error != "":
+ sys.exit("Missing required message in the logcat:\n" + missing_error)
+ return [start, end]
+
+# TODO(b/262259622): passing a tuple of (startDate, endDate)
+def replace_timestamp_abs(line, timestamp_str, date_time_obj0):
+ index = line.find(" ", 6)
+ if index <= 0:
+ return line
+ substr0 = line[:index]
+ substr1 = line[index:]
+
+ try:
+ date_time_obj = datetime.strptime(substr0, '%m-%d %H:%M:%S.%f')
+ except ValueError:
+ return line
+
+ date_time_delta = date_time_obj - date_time_obj0
+ date_time_delta_str = str(date_time_delta)
+ return date_time_delta_str + substr1
+
+def in_time_range(start, end, line):
+ try:
+ current_time = datetime.strptime(line[:18], '%m-%d %H:%M:%S.%f')
+ except ValueError:
+ return False
+
+ if current_time >= start and current_time <= end:
+ return True
+
+ return False
+
+# Here is an example of event we would like extract:
+# 09-15 16:04:15.655 root 991 991 I boot_progress_preload_start: 5440
+# for each event, it is a tuple of(timestamp, event_name, timing)
+def extract_event(line, keywords):
+ words = line.split(" ")
+ for keyword in keywords:
+ if keyword in words[-2]:
+ return (words[0], words[-2], words[-1])
+ return ()
+
+def write_to_new_file(timestamps, keywords, logcat_fp, timestamp_fixed_logcat_fp, report_fp,
+ report_proto_fp):
+ start_timestamp_obj = datetime.strptime(timestamps[0][:18], '%m-%d %H:%M:%S.%f')
+ end_timestamp_obj = datetime.strptime(timestamps[1][:18], '%m-%d %H:%M:%S.%f')
+ report = report_pb2.Report()
+ for line in logcat_fp:
+ ts_fixed_line = replace_timestamp_abs(line, timestamps[0][:18], start_timestamp_obj)
+ timestamp_fixed_logcat_fp.write(ts_fixed_line)
+ if in_time_range(start_timestamp_obj, end_timestamp_obj, line):
+ event = extract_event(ts_fixed_line, keywords)
+ if len(event) == 0:
+ continue
+
+ report_fp.write(ts_fixed_line)
+ record = report.record.add()
+ record.timestamp = event[0]
+ record.event = event[1]
+ record.timing = int(event[2])
+ report_proto_fp.write(str(report))
+
+def main():
+ args = init_arguments()
+
+ keywords = []
+ with open(args.config, 'r') as file:
+ keywords = yaml.safe_load(file)
+
+ if not os.path.isdir(args.output_dir):
+ os.mkdir(args.output_dir)
+ timestamp_fixed_logcat_fp = open(os.path.join(args.output_dir, "timestamp_fixed_log.txt"), 'w')
+ report_fp = open(os.path.join(args.output_dir, "report.txt"), 'w')
+ report_proto_fp = open(os.path.join(args.output_dir, "report_proto.txt"), 'w')
+ try:
+ with open(args.logcat, 'r', errors = 'ignore') as logcat_fp:
+ timestamps = find_boot_progress_start_end(logcat_fp)
+ logcat_fp.seek(0)
+ write_to_new_file(timestamps, keywords, logcat_fp, timestamp_fixed_logcat_fp, report_fp, report_proto_fp)
+ except Exception as e:
+ traceresult = traceback.format_exc()
+ print("Caught an exception: {}".format(traceback.format_exc()))
+
+ timestamp_fixed_logcat_fp.close()
+ report_fp.close()
+ report_proto_fp.close()
+
+if __name__ == '__main__':
+ main()
diff --git a/perf_tools/report.proto b/perf_tools/report.proto
new file mode 100644
index 00000000..fb9e839b
--- /dev/null
+++ b/perf_tools/report.proto
@@ -0,0 +1,21 @@
+syntax = "proto2";
+
+package report;
+
+message Keyword {
+ required string value = 1;
+}
+
+message Keywords {
+ repeated Keyword keyword = 1;
+}
+
+message Record {
+ required string timestamp = 1;
+ required string event = 2;
+ required int64 timing = 3;
+}
+
+message Report {
+ repeated Record record = 1;
+}
diff --git a/perf_tools/sbtpull.py b/perf_tools/sbtpull.py
new file mode 100755
index 00000000..c6af55ce
--- /dev/null
+++ b/perf_tools/sbtpull.py
@@ -0,0 +1,562 @@
+#!/usr/bin/python3
+#from calendar import c
+import sys
+import os
+import copy
+import argparse
+import statistics
+import glob
+import subprocess
+import re
+import time
+
+from string import digits
+
+class LogLine:
+ remove_digits = str.maketrans('', '', digits)
+ def __init__(self):
+ self.lineNum = 0
+ self.timeStamp = 0
+ self.delta = 0
+ self.deltaDiff = 0
+ self.text = "none"
+ self.textKey = "none"
+
+ def parse(self, index, line, priorTimeStamp):
+ _line = line.strip()
+ words = _line.split("]", 1)
+ timeString = words[0].strip(" [")
+ self.lineNum = index
+ self.timeStamp = float(timeString)
+ self.delta = self.timeStamp - priorTimeStamp
+ self.text = words[1][:150]
+ self.textKey = self.text.translate(self.remove_digits)
+ priorTimeStamp = self.timeStamp
+ return self
+
+ def getTextKey(self):
+ textKey = self.textKey
+ return textKey
+
+ def print(self):
+ print("I, {:5d}, T, {:8.4f}, D, {: .4f}, DD, ({: .4f}) {}".format(self.lineNum, self.timeStamp, self.delta, self.deltaDiff, self.text))
+
+ def toString(self):
+ return "I, {:5d}, T, {:8.4f}, D, {: .4f}, DD, ({: .4f}) {}".format(self.lineNum, self.timeStamp, self.delta, self.deltaDiff, self.text)
+
+def sortByDelta(item):
+ return item.delta
+
+def sortByTimeStamp(item):
+ return item.timeStamp
+
+class LogLineListStats:
+ def __init__(self):
+ self.numItems = 0
+ self.firstTimeStamp = 0
+ self.lastTimeStamp = 0
+ self.deltaSum = 0
+ self.deltaDiffSum = 0
+ self.status = "unknown"
+ self.name = "unknown"
+
+ def print(self):
+ print("Name {:25} NumItems {:4d} FirstTimeStamp {:.3f}, lastTimeStamp {:.3f}, deltaTime {:.3f} deltaSum {:.3f}, deltaDiffSum {:.3f} Status {}".format(self.name, self.numItems, self.firstTimeStamp, self.lastTimeStamp, (self.lastTimeStamp - self.firstTimeStamp), self.deltaSum, self.deltaDiffSum, self.status))
+
+ def add(self, _other):
+ if (_other.firstTimeStamp< self.firstTimeStamp):
+ self.firstTimeStamp = _other.firstTimeStamp
+
+ if (_other.lastTimeStamp > self.lastTimeStamp):
+ self.lastTimeStamp = _other.lastTimeStamp
+ self.deltaSum += _other.deltaSum
+
+
+# ------------------------------------------------------
+
+class LogLineList:
+
+ def __init__(self, _name= ""):
+ self.list = []
+ self.name = _name
+
+ def clear(self):
+ self.list.clear()
+
+ def append(self, item):
+ self.list.append(item)
+
+ def print(self, numItems=None):
+ printLineNum = 0
+ timeStart = 0
+ sumDelta = 0
+ sumDeltaDiff = 0
+ print("List: {}", self.name)
+ for item in self.list:
+ if (timeStart==0):
+ timeStart = item.timeStamp
+ timeOffset = item.timeStamp - timeStart
+ sumDelta += item.delta
+ sumDeltaDiff += item.deltaDiff
+ printLineNum += 1
+ printLine = "{:4d} {:.4f} {: .4f} ({: .4f}) | {} ".format(printLineNum, timeOffset, sumDelta, sumDeltaDiff, item.toString())
+ print(printLine)
+ if (numItems!=None):
+ numItems -= 1
+ if (numItems<=0):
+ break
+
+ def find(self, word):
+ itemList = []
+ for item in self.list:
+ if item.text.find(word) != -1:
+ itemList.append(item)
+ return itemList
+ def findFirst(self, word):
+ itemList = self.find(word)
+ if (itemList!=None):
+ if (len(itemList)>0):
+ return itemList[0]
+ return None
+
+ def findTextKey(self, textKey):
+ itemList = []
+ for item in self.list:
+ if item.getTextKey()==textKey:
+ itemList.append(item)
+ if (len(itemList)==0):
+ return None
+ return itemList[0]
+
+ def findItem(self, item):
+ textKey = item.getTextKey()
+ return self.findTextKey(textKey)
+
+ def findExactItem(self, item):
+ text = item.text
+ return self.find(text)
+
+ def filter(self, startKeyWord, endKeyWord, delta=0):
+ resultsList = LogLineList()
+ startTime = self.findFirst(startKeyWord).timeStamp
+ endTime = self.findFirst(endKeyWord).timeStamp
+ for item in self.list:
+ if ((item.timeStamp >= startTime) and (item.timeStamp<=endTime)):
+ if (item.timeStamp == startTime):
+ item.delta = 0
+ if ((item.delta > delta) or ((item.timeStamp == startTime))):
+ resultsList.append(item)
+ resultsList.name = self.name
+ return resultsList
+
+
+ def findCommon(self, otherList):
+ commonList = LogLineList()
+ commonList.name = self.name + "_common"
+ notCommonList = LogLineList()
+ notCommonList.name = self.name + "_notCommon"
+ numFoundItems = 0
+ numNotFoundItems = 0
+ for item in self.list:
+ dm1 = otherList.findExactItem(item)
+ _item = copy.deepcopy(item)
+ if dm1!=None:
+ commonList.append(_item)
+ numFoundItems += 1
+ else:
+ notCommonList.append(_item)
+ numNotFoundItems += 1
+ print("FindCommon {} {} {} {}".format(len(self.list), len(otherList.list), numFoundItems, numNotFoundItems ))
+ return commonList, notCommonList
+
+ def difference(self, otherList):
+ diffList = LogLineList()
+ diffList.name = otherList.name + "Diff"
+ for item in self.list:
+ thisItem = copy.deepcopy(item)
+ otherItem = otherList.findItem(item)
+ if (item.text.find("EXT4-fs (sda11): recovery complete")!=-1):
+ print("here")
+ if otherItem==None:
+ print("LogLineList::difference() !Error, other does not have {}".format(item.text))
+ else:
+ thisItem.deltaDiff = otherItem.delta - item.delta
+
+ diffList.append(thisItem)
+ return diffList
+
+ def analyze(self, checkPeriod = True, includeFirst = True):
+ numItems = 0
+ firstTimeStamp = 0
+ firstDelta = 0
+ lastTimeStamp = 0
+ deltaSum = 0
+ deltaDiffSum = 0
+ for item in self.list:
+ numItems += 1
+ deltaSum += item.delta
+ deltaDiffSum += item.deltaDiff
+ if firstTimeStamp==0:
+ firstTimeStamp = item.timeStamp
+ firstDelta = item.delta
+ deltaSum = 0
+ deltaDiffSum = 0
+ if (item.timeStamp<firstTimeStamp):
+ firstTimeStamp = item.timeStamp
+ firstDelta = item.delta
+
+ if (item.timeStamp > lastTimeStamp):
+ lastTimeStamp = item.timeStamp
+ timePeriod = lastTimeStamp - firstTimeStamp
+ status = "pass"
+ if (checkPeriod):
+ diff = timePeriod - deltaSum
+ if (abs(diff)>0.0001):
+ print("LogLineList::Analyze() {} ERROR! TimePeriod:{}, CumulativeDelta: {} ".format(self.name, timePeriod, deltaSum))
+ status = "ERROR"
+ logLineListStats = LogLineListStats()
+ logLineListStats.numItems = numItems
+ logLineListStats.firstTimeStamp = firstTimeStamp
+ logLineListStats.lastTimeStamp = lastTimeStamp
+ logLineListStats.deltaSum = deltaSum
+ logLineListStats.deltaDiffSum = deltaDiffSum
+ logLineListStats.status = status
+ logLineListStats.name = self.name
+ return logLineListStats
+
+ def addList(self, otherList):
+ self.list.extend(otherList.list)
+ self.list = sorted(self.list, key=sortByTimeStamp)
+
+
+class LogFile:
+ priorTimeStamp = 0.0
+ def __init__(self, _fileName = ""):
+ self.logLineList = LogLineList()
+ if (_fileName!=""):
+ self.load(_fileName)
+
+ def loadLines(self, lines):
+ logLineList = LogLineList()
+ for index, line in enumerate(lines):
+ logLine = LogLine().parse(index, line, self.priorTimeStamp)
+ self.priorTimeStamp = logLine.timeStamp
+ logLineList.append(logLine)
+ return logLineList
+
+ def load(self, _fileName):
+ self.name = _fileName
+ try:
+ file = open(_fileName, 'r')
+ lines = file.readlines()
+ self.logLineList = self.loadLines(lines)
+ file.close()
+ except:
+ print("Error, file '{}' does not exist".format(self.name))
+
+ def print(self, numItems=None):
+ self.logLineList.print(numItems)
+
+# -----------------------------------------------------
+
+class MetricSet:
+ def __init__(self, _names):
+ self.columnNames = _names
+ self.rowColArray = []
+ self.rowSum = []
+ self.rowMax = []
+ self.rowMin = []
+ self.rowStd = []
+ self.rowMedian = []
+ for col in self.columnNames:
+ self.rowSum.append(0)
+ self.rowMax.append(0)
+ self.rowMin.append(sys.maxsize)
+ self.rowStd.append(0)
+ self.rowMedian.append(0)
+
+ def appendSet(self, values):
+ self.rowColArray.append(values)
+
+ def print(self):
+ print("{}".format(" Line#"), end='')
+ for words in self.columnNames:
+ print(", '{}'".format(words), end='')
+ print("")
+
+ for row, values in enumerate(self.rowColArray):
+ print("{:6d}".format(row), end='')
+ for col, value in enumerate(values):
+ print(", {:.3f}".format(value), end='')
+ print("")
+
+ print("{}".format(" MAX"), end='')
+ for value in self.rowMax:
+ print(", {:.3f}".format(value), end='')
+ print("")
+
+
+ print("{}".format(" AVE"), end='')
+ for value in self.rowSum:
+ print(", {:.3f}".format(value), end='')
+ print("")
+
+ print("{}".format(" MIN"), end='')
+ for value in self.rowMin:
+ print(", {:2.3f}".format(value), end='')
+ print("")
+
+ print("{}".format(" STD"), end='')
+ for value in self.rowStd:
+ print(", {:2.3f}".format(value), end='')
+ print("")
+
+ print("{}".format("MEDIAN"), end='')
+ for value in self.rowMedian:
+ print(", {:2.3f}".format(value), end='')
+ print("")
+
+ def analyze(self):
+ stdCols = []
+ numCols = len(self.columnNames)
+ numRows = len(self.rowColArray)
+ for col in range(numCols):
+ stdCols.append([])
+
+ # compute sum
+ for row, values in enumerate(self.rowColArray):
+ for col, value in enumerate(values):
+ self.rowSum[col] += value
+ if value > self.rowMax[col]:
+ self.rowMax[col] = value
+ if value < self.rowMin[col]:
+ self.rowMin[col] = value
+
+ # compute std
+ for col in range(numCols):
+ for row in range(numRows):
+ try:
+ val = self.rowColArray[row][col]
+ stdCols[col].append(val)
+ except IndexError:
+ i = 3
+ for col, colList in enumerate(stdCols):
+ stdValue = 0
+ if (len(colList)>0):
+ stdValue = statistics.pstdev(colList)
+ stdMedian = statistics.median(colList)
+ self.rowStd[col] = stdValue
+ self.rowMedian[col] = stdMedian
+
+ #compute average
+ for col, value in enumerate(self.rowSum):
+ if numRows > 0:
+ self.rowSum[col] = self.rowSum[col] / numRows
+ else:
+ self.rowSum[col] = 0
+
+class AnalyzeFile:
+ initFirstTime = 0
+ initSecondTime = 0
+
+ def __init__(self, _fileName, _keyWords = ["init first", "init second", "boot_completed"]):
+ self.fileName = _fileName
+ self.logFile = LogFile(_fileName)
+ self.workingList = []
+ self.keyWords = _keyWords
+
+ def report(self):
+ print("-----------------------")
+ print("Reporting on '{}'".format(self.fileName))
+ for word in self.keyWords:
+ item = self.logFile.logLineList.findFirst(word)
+ item.print()
+ print("-----------------------")
+
+ def getMetrics(self, metricsSet):
+ values = []
+ for word in self.keyWords:
+ item = self.logFile.logLineList.findFirst(word)
+ if item is not None:
+ values.append(item.timeStamp)
+ else:
+ print("Did not find {} ".format(word))
+ metricsSet.appendSet(values)
+
+ def keyWordReport(self, keyword):
+ numItems = 0
+ cumd = 0
+ items = self.logFile.logLineList.find(keyword)
+ for item in items:
+ item.print()
+ numItems += 1
+ cumd += item.delta
+ print("Num {} found = {}, Sum delay = {:.2f} ".format(keyword, numItems, cumd))
+
+ for item in items:
+ lineKeywords = item.text.split(" ")
+ if (len(lineKeywords)>2):
+ if lineKeywords[2] == "Service":
+ tookIndex = item.text.find("took")
+ if (tookIndex!=None):
+ tookTime = item.text[tookIndex:tookIndex+10]
+ print("{} took {}".format(lineKeywords[3], tookTime))
+
+
+class Analyzer:
+ def __init__(self):
+ self.fileName = []
+
+ def rebootAndRunCmdToFile(self, fileNamePrefix, msgPrefix, Cmd, numTimes, startIndex):
+ captured = False
+ error = False
+ filenameNum = ""
+ for i in range(numTimes):
+ postfix = str(i+startIndex)
+ filenameNum = fileNamePrefix + "-" + postfix + ".txt"
+ print(msgPrefix + " to {}".format(filenameNum))
+ # try 5 times to capure status 'boot_completed'
+ for i in range(5):
+ captured = False
+ rebootCmd = "adb shell su root reboot"
+ fullCmd = Cmd + " > {}".format(filenameNum)
+ x = os.system(rebootCmd)
+ if (x!=0):
+ print("Error")
+ error = True
+ break
+ time.sleep(45)
+ x = os.system(fullCmd)
+ if (x!=0):
+ print("Error")
+ error = True
+ break
+ # check for boot complete
+ try:
+ checkBootComplete = "grep boot_complete {}".format(filenameNum)
+ output = subprocess.check_output(checkBootComplete, shell=True)
+ captured = True
+ break
+ except:
+ captured = False
+ print("trying again for {}".format(filenameNum))
+ if not captured:
+ print("ERROR - failed to capture {}".format(filenameNum))
+ if error:
+ os.system("rm {}".format(filenameNum))
+ return captured
+
+ def getBuildID(self):
+ buildIDCmd = "adb shell su root getprop ro.build.version.incremental"
+ buildString = subprocess.check_output(buildIDCmd, shell = True)
+ numberList = re.findall(r'\d+', buildString.decode('ISO-8859-1') )
+ if (numberList==None): return 0
+ if (len(numberList)==0): return 0
+ buildID = numberList[0]
+ return buildID
+
+ def pullDmesgLogs(self, BuildID, numTimes, startIndex):
+ fileNamePrefix = BuildID
+ msgPrefix = "Pulling Kernel dmesg logs"
+ cmd = "adb shell su root dmesg"
+ return self.rebootAndRunCmdToFile(fileNamePrefix, msgPrefix, cmd, numTimes, startIndex)
+
+ def pullLogcatLogs(self, BuildID, numTimes, startIndex):
+ fileNamePrefix = "LC-"+BuildID
+ msgPrefix = "Pulling Kernel Logcat"
+ cmd = "adb logcat -b all -d"
+ return self.rebootAndRunCmdToFile(fileNamePrefix, msgPrefix, cmd, numTimes, startIndex)
+
+ def runBootAnalyze(self, filename, numTimes, startIndex):
+ ABT = os.environ["ANDROID_BUILD_TOP"]
+ if (len(ABT)<=0):
+ print("ERROR - ANDROID_BUILD_TOP not set")
+ BAFILE = "BA-" + filename + "-" + str(numTimes + startIndex) + ".txt"
+ BACmd = ABT + "/system/extras/boottime_tools/bootanalyze/bootanalyze.py -c " + ABT + "/system/extras/boottime_tools/bootanalyze/config.yaml -n 20 -r -t > " + BAFILE
+ print(BACmd)
+ x = os.system(BACmd)
+ if (x!=0):
+ print("ERROR running bootanalze")
+ return False
+ return True
+
+ def pullAll(self):
+ BuildID = self.getBuildID()
+ Cmd = "adb bugreport bugreport-{}".format(BuildID)
+ print(Cmd)
+ x = os.system(Cmd)
+ if (x!=0):
+ print("ERROR Pulling all data")
+ return False
+ self.pullDmesgLogs(BuildID, 20, 0)
+ self.pullLogcatLogs(BuildID, 2, 0)
+ self.runBootAnalyze(BuildID, 20, 0)
+ self.summaryReportOnDmesgLogFiles(BuildID, 20)
+
+ def summaryReportOnDmesgLogFiles(self, BuildID, numFiles):
+ metricKeyWords = ["init first", "init second", "boot_completed"]
+ metricSet = MetricSet(metricKeyWords)
+ print("Summary report on log files with build ID {}".format(BuildID))
+ dirList = glob.glob("{}*.txt".format(BuildID))
+ numFilesAnalyzed = 0
+ for index, file in enumerate(dirList):
+ analyzeFile = AnalyzeFile(file, metricKeyWords)
+ #check it's a kernel log file
+ item = analyzeFile.logFile.logLineList.findFirst("build.fingerprint")
+ if (item!=None):
+ #check if it has the correct build ID
+ if (item.text.find(BuildID)==-1):
+ continue
+ else:
+ print("BuildID {} not found in file {} fingerprint {}".format(BuildID, file, item))
+ continue
+ analyzeFile.getMetrics(metricSet)
+ numFilesAnalyzed += 1
+ if ((index+1)>=numFiles):
+ break
+ if (numFilesAnalyzed>0):
+ metricSet.analyze()
+ metricSet.print()
+ else:
+ print("No files criteria {}* and build.fingerprint with {}".format(BuildID, BuildID))
+
+ def rename(self, BuildID1, BuildID2, fileType):
+ print("Summary report on log files with build ID {}".format(BuildID1))
+ dirList = glob.glob("*{}*".format(BuildID1))
+ for index, file in enumerate(dirList):
+ findRes = file.find(BuildID1)
+ if (findRes!=-1):
+ newFile = file.replace(BuildID1, BuildID2, 1)
+ newFile += fileType
+ os.system("mv {} {}".format(file, newFile))
+
+
+parser = argparse.ArgumentParser(description='pull all data files from seahawk and run dmesg summary report. The data files will be prefixed with the build ID')
+
+parser.add_argument("-plc", nargs=3, metavar=('<BuildID>', '<numTimes>', '<startIndex>'), help="pull logcat numTimes from seahawk")
+parser.add_argument("-pdm", nargs=3, metavar=('<BuildID>', '<numTimes>', '<startIndex>'), help="pull dmesg logs numTimes from seahawk")
+parser.add_argument("-pba", nargs=2, metavar=('<BuildID>', '<numTimes>'), help="pull bootanalyze numTimes from seahawk")
+parser.add_argument("-rd", nargs=2, metavar=('<BuildID>', '<numFiles>'), help="summary report on <numFiles> dmesg log files named <BuildID>-*.txt in current directory")
+parser.add_argument("-pA", action='store_true', help="pull all data from seahawk a default number of times")
+parser.add_argument("-t", nargs="*", help="test - do not use")
+args = parser.parse_args()
+
+
+if args.pdm!=None:
+ Analyzer().pullDmesgLogs(args.pdm[0], int(args.pdm[1]), int(args.pdm[2]))
+
+if args.plc!=None:
+ Analyzer().pullLogcatLogs(args.plc[0], int(args.plc[1]), int(args.plc[2]))
+
+if args.pba!=None:
+ Analyzer().runBootAnalyze(args.pba[0], int(args.pba[1]), 0)
+
+if args.pA!=None:
+ Analyzer().pullAll()
+
+if args.rd!=None:
+ Analyzer().summaryReportOnDmesgLogFiles(args.rd[0], int(args.rd[1]))
+
+if args.t!=None:
+ Analyzer().getBuildID()
+
diff --git a/tests/iptables/qtaguid/Android.bp b/pinner/Android.bp
index 935c0b41..40197ba2 100644
--- a/tests/iptables/qtaguid/Android.bp
+++ b/pinner/Android.bp
@@ -1,5 +1,5 @@
//
-// Copyright (C) 2011 The Android Open Source Project
+// Copyright (C) 2017 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.
@@ -15,27 +15,41 @@
//
package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "system_extras_tests_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["system_extras_tests_license"],
+ default_applicable_licenses: ["Android-Apache-2.0"],
}
-cc_test {
- name: "socketTag",
- srcs: ["socketTag.cpp"],
+cc_library_static {
+ name: "libmeminspect",
+ srcs: [
+ "meminspect.cpp",
+ "pin_utils.cpp"
+ ],
+ shared_libs: [
+ "libbase",
+ "libziparchive",
+ ],
+ export_shared_lib_headers: ["libziparchive"],
+ export_include_dirs: ["include"],
+ cppflags: [
+ "-g",
+ "-Wall",
+ "-Werror",
+ ],
+}
+
+cc_binary {
+ name: "pintool",
+ srcs: ["pintool.cpp"],
shared_libs: [
- "libcutils",
- "libutils",
- "liblog",
"libbase",
+ "libziparchive",
+ ],
+ static_libs: [
+ "libmeminspect",
],
- static_libs: ["libtestUtil"],
- cflags: [
+ cppflags: [
+ "-g",
"-Wall",
"-Werror",
- "-fno-strict-aliasing",
],
}
diff --git a/pinner/OWNERS b/pinner/OWNERS
new file mode 100644
index 00000000..0636f908
--- /dev/null
+++ b/pinner/OWNERS
@@ -0,0 +1,2 @@
+edgararriaga@google.com
+shayba@google.com \ No newline at end of file
diff --git a/pinner/README.md b/pinner/README.md
new file mode 100644
index 00000000..2115e809
--- /dev/null
+++ b/pinner/README.md
@@ -0,0 +1,144 @@
+# Pin Tool
+
+This tool is currently used mainly for:
+
+1) Inspecting resident memory and locality
+2) Generating and inspecting pinlist.meta used by Android's PinnerService
+
+For memory inspection, it allows probing live memory and providing the memory
+locations that are resident which can be used for diagnosis or as part of PGO
+optimizations.
+
+## Build and Install the tool
+
+To build and push the binary to device
+```
+mm pintool
+and push $ANDROID_REPO/out/target/product/<lunchtarget>/system/bin/pintool /data/local/tmp/pintool
+adb shell
+cd /data/local/tmp/pintool
+```
+
+
+## How to use the tool to generate pinner files
+
+Here are some sample use cases for this tool.
+
+### Sample Use Case 1: Probe Resident Memory for a mapped file or library and dump to console
+
+Executing the following command and providing the path to a library mapped by a process
+it will dump the resident memory ranges.
+
+```
+./pintool file <path_to_your_library> --gen-probe --dump
+```
+
+Note: you can use any kind of mapped file such as .so, .apk, etc.
+
+### Sample Use Case 2: Probe per-file Resident Memory for a mapped apk file and dump to console
+
+Executing this command will inspect resident memory for a zip file and dump
+per-file breakdowns.
+
+```
+./pintool file <path_to_myfile.apk> --zip --gen-probe --dump
+```
+
+### Sample Use Case 3: Probe per-file resident memory for a mapped apk, dump and generate pinlist.meta
+```
+./pintool file <path_to_myfile.apk> --zip --gen-probe --dump -o pinlist.meta
+```
+
+### Sample Use Case 4: Probe per-file resident memory and filter it with a provided pinconfig
+```
+./pintool file <path_to_myfile.apk> --zip --gen-probe --pinconfig pinconfig.txt --dump -o pinlist.meta
+```
+
+### Sample Use Case 5: Dump contents of a provided pinlist.meta file
+```
+./pintool pinlist pinlist.meta --dump -v
+```
+
+### Sample Use Case 6: Read a zip and filter based on a pinconfig file and generate pinlist.meta without probing
+
+This will skip doing any probing and it will just apply filtering based on the pinconfig.txt, this is helpful
+in cases where you do not intend to do any kind of PGO probing and know exactly what ranges you want to pin within your file
+
+```
+./pintool file <path_to_myfile.apk> --zip --pinconfig pinconfig.txt --dump -o pinlist.meta
+```
+
+### Sample Use Case 7: Load an existing zip probe and inspect its per-file contents
+
+```
+./pintool file /data/app/~~tmTrs5_XINwbpYWroRu5rA==/org.chromium.trichromelibrary_602300034-EFoOwMgVNBbwkMnp9zcWbg==/base.apk --zip --use-probe pinlist.meta --dump
+```
+
+
+## Pinconfig File Structure
+
+Pinconfig files specify a custom filter to be applied on top of a generated or provided memory probe
+it should specify a subset of files and optionally ranges within those files to be matched against
+and subsequently kept in case a pinlist.meta is generated.
+
+A `pinconfig.txt` is just a list of files with a key value pair separated by a newline.
+
+`pinconfig.txt` structure pattern:
+```
+(file <file>
+[offset <value>
+length <value>])*
+```
+where:
+<file>
+ Filename as a string, the parser will do a contains like operation (e.g. GLOB(*<file>*)) to match against files
+ within the zip file and stop on first match.
+<value>
+ Unsigned integer value
+
+Note: `offset` and `length` tokens are optional and if ommited, the whole file will be considered desired.
+
+
+Example `pinconfig.txt`:
+```
+file lib/arm64-v8a/libcrashpad_handler_trampoline.so
+file libmonochrome_64.so
+offset 1000
+len 50000
+file libarcore_sdk_c.so
+```
+
+## Pinlist.meta files
+
+"pinlist.meta" files are consumed by PinnerService.
+
+These files specify a list of memory ranges to be pinned (mlocked).
+If Android's PinnerService allows your app pinning, it will read the pinlist.meta
+file from inside your apk's assets folder (assets/pinlist.meta) and pin based
+on the specified ranges.
+
+Note: The PinnerService will need to support pinning your apk in order for the
+pinlist.meta file to be used.
+
+A pinlist.meta file is a binary file with a set of tuples of OFFSET and LENGTH
+stored in Big Endian format.
+
+4 byte: OFFSET
+4 byte: LEN
+
+pinlist.meta
+```
+OFFSET LEN*
+```
+
+So to read those files, it is usually helpful to use the `pintool`.
+
+## Other potential uses
+
+Outside of pinner service, the tool can be used to inspect resident memory for
+any file in memory.
+
+## Extra information
+
+the pinlist.meta depends on the apk contents and needs to be regenrated if
+you are pushing a new version of your apk. \ No newline at end of file
diff --git a/pinner/include/meminspect.h b/pinner/include/meminspect.h
new file mode 100644
index 00000000..a2e09ec2
--- /dev/null
+++ b/pinner/include/meminspect.h
@@ -0,0 +1,268 @@
+#pragma once
+
+#include <android-base/stringprintf.h>
+#include <fcntl.h>
+#include <sys/endian.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <iostream>
+#include <string>
+#include <vector>
+#include "ziparchive/zip_archive.h"
+
+#define MEMINSPECT_FAIL_OPEN 1
+#define MEMINSPECT_FAIL_FSTAT 2
+#define MEMINSPECT_FAIL_MINCORE 3
+
+#define DEFAULT_PAGES_PER_MINCORE 1
+
+/**
+ * This class stores an offset defined vma which exists
+ * relative to another memory address.
+ */
+class VmaRange {
+ public:
+ uint32_t offset;
+ uint32_t length;
+
+ VmaRange() {}
+ VmaRange(uint32_t off, uint32_t len) : offset(off), length(len) {}
+
+ bool is_empty() const;
+
+ /**
+ * @brief Compute the intersection of this range with another range
+ *
+ * Intersection Operation:
+ *
+ * Example 1:
+ * [ Range A ]
+ * [ Range B ]
+ * Intersection:
+ * [ C ]
+ *
+ * Example 2:
+ * [ Range A ] [ Range B ]
+ * No Intersection
+ *
+ * @param target range to test against
+ * @return the intersection range, if none is found, empty range is returned.
+ */
+ VmaRange intersect(const VmaRange& target) const;
+
+ /**
+ * @brief Merges the current range with a target range using a union operation
+ * that is only successful when overlapping ranges occur.
+ * A visual explanation can be seen as:
+ *
+ * Union-merge Operation:
+ *
+ * Example 1:
+ * [ Range A ]
+ * [ Range B ]
+ * Merged:
+ * [ Range C ]
+ *
+ * Example 2:
+ * [ Range A ] [ Range B ]
+ * Fails, no merge available.
+ *
+ * @param target The range to test against.
+ * @param result Upon successfully merging, contains the resulting range.
+ * @return the merged range, if none is found, empty range is returned.
+ */
+ VmaRange union_merge(const VmaRange& target) const;
+
+ uint32_t end_offset() const;
+};
+
+/**
+ * Represents a set of memory ranges
+ */
+struct VmaRangeGroup {
+ std::vector<VmaRange> ranges;
+
+ /**
+ * Compute intersection coverage between |range| and |this->ranges|
+ * and append it to |out_memres|
+ */
+ void compute_coverage(const VmaRange& range, VmaRangeGroup& out_memres) const;
+
+ /**
+ * Apply an offset to all existing |ranges|.
+ */
+ void apply_offset(uint64_t offset);
+
+ /**
+ * Computes total resident bytes from existing set of memory ranges.
+ */
+ uint64_t compute_total_size();
+};
+
+/**
+ * Represents useful immutable metadata for zip entry
+ */
+struct ZipEntryInfo {
+ std::string name;
+ uint64_t offset_in_zip;
+ uint64_t file_size_bytes;
+ uint64_t uncompressed_size;
+};
+
+/**
+ * Represents the resident memory coverage for a zip entry within a zip file.
+ */
+struct ZipEntryCoverage {
+ ZipEntryInfo info;
+
+ /**
+ * Contains all the coverage ranges if any have been computed with |compute_coverage|
+ * and their offsets will be the absolute global offset from the zip file start.
+ */
+ VmaRangeGroup coverage;
+
+ /**
+ * Computes the intersection coverage for the current zip file entry
+ * resident memory against a provided |probe| representing another set
+ * of ranges.
+ */
+ ZipEntryCoverage compute_coverage(const VmaRangeGroup& probe) const;
+};
+
+// Class used for inspecting resident memory for entries within a zip file
+class ZipMemInspector {
+ /**
+ * Stored probe of resident ranges either computed or provided by user.
+ */
+ VmaRangeGroup* probe_resident_ = nullptr;
+
+ /**
+ * List of file entries within zip file.
+ */
+ std::vector<ZipEntryInfo> entry_infos_;
+
+ /**
+ * Path to zip file.
+ */
+ std::string filename_;
+
+ /**
+ * Result of computing coverage operations.
+ */
+ std::vector<ZipEntryCoverage> entry_coverages_;
+
+ /**
+ * Handle that allows reading the zip entries.
+ */
+ ZipArchiveHandle handle_;
+
+ public:
+ ZipMemInspector(std::string filename) : filename_(filename) {}
+ ~ZipMemInspector();
+
+ /**
+ * Reads zip file and computes resident memory coverage per zip entry if
+ * a probe is provided, if no probe is provided, then whole file coverage
+ * will be assumed.
+ *
+ * Note: If any zip entries have been manually added via |add_file_info|
+ * then coverage will be only computed against manually added entries.
+ *
+ * @return 0 on success and 1 on error
+ */
+ int compute_per_file_coverage();
+
+ /**
+ * Computes resident memory for the entire zip file.
+ *
+ * @return 0 on success, 1 on failure
+ */
+ int probe_resident();
+
+ /**
+ * Retrieves the currently set probe if any exists.
+ */
+ VmaRangeGroup* get_probe();
+
+ /**
+ * Sets probe data in case you decide to pass a previously taken probe instead of a live taken
+ * one.
+ */
+ void set_existing_probe(VmaRangeGroup* probe);
+
+ /**
+ * Returns the result of memory coverage of each file if any has been computed via
+ * |compute_per_file_coverage|.
+ */
+ std::vector<ZipEntryCoverage>& get_file_coverages();
+
+ /**
+ * Returns the file information for each zip entry.
+ */
+ std::vector<ZipEntryInfo>& get_file_infos();
+
+ /**
+ * Add a zip entry manually.
+ *
+ * Note: Zip entries are usually retrieved by reading the |filename_| so
+ * this method is mostly used for cases where client wants control of
+ * zip file reading or for testing.
+ */
+ void add_file_info(ZipEntryInfo& file);
+
+ /**
+ * Computes the intersection coverage between provided |files| and |probe|.
+ *
+ * @return result of coverage computation
+ */
+ static std::vector<ZipEntryCoverage> compute_coverage(
+ const std::vector<ZipEntryCoverage>& files, VmaRangeGroup* probe);
+
+ private:
+ /**
+ * Read files and zip relative offsets for them.
+ *
+ * @return 0 on success, 1 on failure.
+ */
+ int read_files_and_offsets();
+};
+
+/**
+ * Retrieve file size in bytes for |file|
+ *
+ * @return positive value with file size on success, otherwise, returns -1 on error.
+ */
+int64_t get_file_size(const std::string& file);
+
+/**
+ * @brief Probe resident memory for a currently opened file in the system.
+ *
+ * @param probed_file File to probe as defined by its path.
+ * @param out_resident_mem Inspection result. This is populated when called.
+ * @param pages_per_mincore Size of mincore window used, bigger means more memory used
+ * during operation but slightly faster.
+ * @return 0 on success or on failure a non-zero error code from the following list:
+ * MEMINSPECT_FAIL_OPEN, MEMINSPECT_FAIL_FSTAT, MEMINSPECT_FAIL_MINCORE
+ */
+int probe_resident_memory(std::string probed_file, VmaRangeGroup& out_resident_mem,
+ int pages_per_mincore = DEFAULT_PAGES_PER_MINCORE);
+
+/**
+ * @brief Align vma ranges to a certain page size
+ *
+ * @param ranges vma ranges that have to be aligned
+ * @param alignment Desired alignment, this is usually the page size.
+ */
+void align_ranges(std::vector<VmaRange>& ranges, unsigned int alignment);
+
+/**
+ * @brief Merges a list of ranges following a union-like merge which
+ * means that two ranges that overlap will avoid double accounting for
+ * overlaps.
+ *
+ * @param ranges vma ranges that need to be merged.
+ * @return new vector with ranges merged.
+ */
+std::vector<VmaRange> merge_ranges(const std::vector<VmaRange>& ranges); \ No newline at end of file
diff --git a/pinner/include/pin_utils.h b/pinner/include/pin_utils.h
new file mode 100644
index 00000000..b6066d63
--- /dev/null
+++ b/pinner/include/pin_utils.h
@@ -0,0 +1,116 @@
+#pragma once
+
+#include <list>
+#include "meminspect.h"
+
+struct PinConfigFile {
+ std::string filename;
+
+ // File relative offsets
+ std::vector<VmaRange> ranges;
+
+ ZipEntryCoverage to_zipfilemem(const ZipEntryInfo& info);
+};
+
+struct PinConfig {
+ std::list<PinConfigFile> files_;
+
+ int parse(std::string filename, bool verbose = false);
+};
+
+/**
+ * @brief Generate a pinlist file from a given list of vmas containing a list of 4-byte pairs
+ * representing (4-byte offset, 4-byte len) contiguous in memory and they are stored in big endian
+ * format.
+ *
+ * @param output_file Output file to write pinlist
+ * @param vmas_to_pin Set of vmas to write into pinlist file.
+ * @param write_quota Specifies a maximum amount o bytes to be written to the pinlist file
+ * or -1 means no limit.
+ * @return 0 on success, non-zero on failure
+ */
+int write_pinlist_file(const std::string& output_file, const std::vector<VmaRange>& vmas_to_pin,
+ int64_t write_quota = -1);
+
+/**
+ * @brief This method is the counter part of @see write_pinlist_file(). It will read an existing
+ * pinlist file.
+ *
+ * @param pinner_file Input pinlist file
+ * @param pinranges Vmas read from pinlist file. This is populated on call.
+ * @return 0 on success, non-zero on failure
+ */
+int read_pinlist_file(const std::string& pinner_file, /*out*/ std::vector<VmaRange>& pinranges);
+
+enum ProbeType {
+ UNSET, // No probe setup
+ GENERATE, // Generate a probe
+ CUSTOM // User generated probe
+};
+
+class PinTool {
+ public:
+ enum DumpType { PROBE, FILE_COVERAGE, FILTERED };
+
+ private:
+ std::string input_file_;
+ std::string custom_probe_file_;
+ PinConfig* pinconfig_;
+ std::vector<ZipEntryCoverage> filtered_files_;
+ bool verbose_;
+ ZipMemInspector* zip_inspector_ = nullptr;
+
+ public:
+ PinTool(const std::string& input_file) : input_file_(input_file) {
+ zip_inspector_ = new ZipMemInspector(input_file_);
+ }
+
+ ~PinTool() {
+ delete zip_inspector_;
+ delete pinconfig_;
+ }
+
+ void set_verbose_output(bool verbose);
+
+ // Read |probe_file| which should be a pinlist.meta style
+ // file and use it as current probe.
+ void read_probe_from_pinlist(std::string probe_file);
+
+ // Compute a resident memory probe for |input_file_|
+ int probe_resident();
+
+ // Compute coverage for each zip entry contained within
+ // |input_file_|.
+ // Note: It only works for zip files
+ void compute_zip_entry_coverages();
+
+ /**
+ * Filter coverages based on a provided pinconfig style file
+ * See README.md for sample structure of pinconfig file.
+ *
+ * Note: It only works for zip files, for non zip files, this will be
+ * a no-op.
+ */
+ void filter_zip_entry_coverages(const std::string& pinconfig_file);
+
+ void filter_zip_entry_coverages(PinConfig* pinconfig);
+
+ /**
+ * Dumps output of existing coverages to console for |type|.
+ */
+ void dump_coverages(DumpType type);
+
+ /**
+ * Writes coverages into a pinlist.meta style file.
+ *
+ * @param write_quota Maximum bytes allowed to be written to file.
+ */
+ void write_coverages_as_pinlist(std::string output_pinlist, int64_t write_quota = -1);
+
+ std::vector<ZipEntryCoverage> get_filtered_zip_entries();
+
+ /**
+ * Sets a user defined inspector, currently only used for testing.
+ */
+ void set_custom_zip_inspector(ZipMemInspector* inspector);
+}; \ No newline at end of file
diff --git a/pinner/meminspect.cpp b/pinner/meminspect.cpp
new file mode 100644
index 00000000..84e00928
--- /dev/null
+++ b/pinner/meminspect.cpp
@@ -0,0 +1,324 @@
+#include "meminspect.h"
+#include <android-base/unique_fd.h>
+#include "ziparchive/zip_archive.h"
+
+using namespace std;
+using namespace android::base;
+using namespace ::android::base;
+
+const static VmaRange VMA_RANGE_EMPTY = VmaRange(0, 0);
+
+uint32_t VmaRange::end_offset() const {
+ return offset + length;
+}
+
+uint64_t VmaRangeGroup::compute_total_size() {
+ uint64_t total_size = 0;
+ for (auto&& range : ranges) {
+ total_size += range.length;
+ }
+ return total_size;
+}
+
+void VmaRangeGroup::apply_offset(uint64_t offset) {
+ for (auto&& range : ranges) {
+ range.offset += offset;
+ }
+}
+
+void VmaRangeGroup::compute_coverage(const VmaRange& range, VmaRangeGroup& out_memres) const {
+ for (auto&& resident_range : ranges) {
+ VmaRange intersect_res = resident_range.intersect(range);
+ if (!intersect_res.is_empty()) {
+ out_memres.ranges.push_back(intersect_res);
+ }
+ }
+}
+
+bool VmaRange::is_empty() const {
+ return length == 0;
+}
+
+VmaRange VmaRange::intersect(const VmaRange& target) const {
+ // First check if the slice is outside our range
+ if (target.end_offset() <= this->offset) {
+ return VMA_RANGE_EMPTY;
+ }
+ if (target.offset >= this->end_offset()) {
+ return VMA_RANGE_EMPTY;
+ }
+ VmaRange result;
+ // the slice should now be inside the range so compute the intersection.
+ result.offset = std::max(target.offset, this->offset);
+ uint32_t res_end = std::min(target.end_offset(), end_offset());
+ result.length = res_end - result.offset;
+
+ return result;
+}
+
+VmaRange VmaRange::union_merge(const VmaRange& target) const {
+ VmaRange result = intersect(target);
+ if (result.is_empty()) {
+ // Disjointed ranges, no merge.
+ return VMA_RANGE_EMPTY;
+ }
+
+ // Since there is an intersection, merge ranges between lowest
+ // and highest value.
+ result.offset = std::min(offset, target.offset);
+ uint32_t res_end = std::max(target.end_offset(), end_offset());
+ result.length = res_end - result.offset;
+ return result;
+}
+
+void align_ranges(std::vector<VmaRange>& vmas_to_align, unsigned int alignment) {
+ for (auto&& vma_to_align : vmas_to_align) {
+ uint32_t unaligned_offset = vma_to_align.offset % alignment;
+ vma_to_align.offset -= unaligned_offset;
+ vma_to_align.length += unaligned_offset;
+ }
+}
+
+bool compare_range(VmaRange& a, VmaRange& b) {
+ return a.offset < b.offset;
+}
+
+std::vector<VmaRange> merge_ranges(const std::vector<VmaRange>& ranges) {
+ if (ranges.size() <= 1) {
+ // Not enough ranges to perform a merge.
+ return ranges;
+ }
+
+ std::vector<VmaRange> to_merge_ranges = ranges;
+ std::vector<VmaRange> merged_ranges;
+ // Sort the ranges to make a slightly more efficient merging.
+ std::sort(to_merge_ranges.begin(), to_merge_ranges.end(), compare_range);
+
+ // The first element will always start as-is, then start merging with subsequent elements.
+ merged_ranges.push_back(to_merge_ranges[0]);
+ for (int iMerged = 0, iTarget = 1; iTarget < to_merge_ranges.size(); ++iTarget) {
+ VmaRange merged = merged_ranges[iMerged].union_merge(to_merge_ranges[iTarget]);
+ if (!merged.is_empty()) {
+ // Merge was successful, swallow range.
+ merged_ranges[iMerged] = merged;
+ } else {
+ // Merge failed, add disjointed range.
+ merged_ranges.push_back(to_merge_ranges[iTarget]);
+ ++iMerged;
+ }
+ }
+
+ return merged_ranges;
+}
+
+int64_t get_file_size(const std::string& file) {
+ unique_fd file_ufd(open(file.c_str(), O_RDONLY));
+ int fd = file_ufd.get();
+ if (fd == -1) {
+ return -1;
+ }
+
+ struct stat fstat_res;
+ int res = fstat(fd, &fstat_res);
+ if (res == -1) {
+ return -1;
+ }
+
+ return fstat_res.st_size;
+}
+
+int probe_resident_memory(string probed_file,
+ /*out*/ VmaRangeGroup& resident_ranges, int pages_per_mincore) {
+ unique_fd probed_file_ufd(open(probed_file.c_str(), O_RDONLY));
+ int probe_fd = probed_file_ufd.get();
+ if (probe_fd == -1) {
+ return MEMINSPECT_FAIL_OPEN;
+ }
+
+ int64_t total_bytes = get_file_size(probed_file);
+ if (total_bytes < 0) {
+ return MEMINSPECT_FAIL_FSTAT;
+ }
+
+ char* base_address =
+ (char*)mmap(0, (uint64_t)total_bytes, PROT_READ, MAP_SHARED, probe_fd, /*offset*/ 0);
+
+ // this determines how many pages to inspect per mincore syscall
+ unsigned char* window = new unsigned char[pages_per_mincore];
+
+ unsigned int page_size = sysconf(_SC_PAGESIZE);
+ unsigned long bytes_inspected = 0;
+
+ // total bytes in inspection window
+ unsigned long window_bytes = page_size * pages_per_mincore;
+
+ char* window_base;
+ bool started_vma_range = false;
+ uint32_t resident_vma_start_offset = 0;
+ for (window_base = base_address; bytes_inspected < total_bytes;
+ window_base += window_bytes, bytes_inspected += window_bytes) {
+ int res = mincore(window_base, window_bytes, window);
+ if (res != 0) {
+ if (errno == ENOMEM) {
+ // Did not find page, maybe it's a hole.
+ continue;
+ }
+ return MEMINSPECT_FAIL_MINCORE;
+ }
+ // Inspect the provided mincore window result sequentially
+ // and as soon as a change in residency happens a range is
+ // created or finished.
+ for (int iWin = 0; iWin < pages_per_mincore; ++iWin) {
+ if ((window[iWin] & (unsigned char)1) != 0) {
+ // Page is resident
+ if (!started_vma_range) {
+ // End of range
+ started_vma_range = true;
+ uint32_t window_offset = iWin * page_size;
+ resident_vma_start_offset = window_base + window_offset - base_address;
+ }
+ } else {
+ // Page is not resident
+ if (started_vma_range) {
+ // Start of range
+ started_vma_range = false;
+ uint32_t window_offset = iWin * page_size;
+ uint32_t resident_vma_end_offset = window_base + window_offset - base_address;
+ uint32_t resident_len = resident_vma_end_offset - resident_vma_start_offset;
+ VmaRange vma_range(resident_vma_start_offset, resident_len);
+ resident_ranges.ranges.push_back(vma_range);
+ }
+ }
+ }
+ }
+ // This was the last window, so close any opened vma range
+ if (started_vma_range) {
+ started_vma_range = false;
+ uint32_t in_memory_vma_end = window_base - base_address;
+ uint32_t resident_len = in_memory_vma_end - resident_vma_start_offset;
+ VmaRange vma_range(resident_vma_start_offset, resident_len);
+ resident_ranges.ranges.push_back(vma_range);
+ }
+
+ return 0;
+}
+
+ZipMemInspector::~ZipMemInspector() {
+ CloseArchive(handle_);
+ delete probe_resident_;
+}
+
+ZipEntryCoverage ZipEntryCoverage::compute_coverage(const VmaRangeGroup& probe) const {
+ ZipEntryCoverage file_coverage;
+ file_coverage.info = info;
+
+ // Compute coverage for each range in file against probe which represents a set of ranges.
+ for (auto&& range : coverage.ranges) {
+ probe.compute_coverage(range, file_coverage.coverage);
+ }
+
+ return file_coverage;
+}
+
+std::vector<ZipEntryCoverage> ZipMemInspector::compute_coverage(
+ const std::vector<ZipEntryCoverage>& files, VmaRangeGroup* probe) {
+ if (probe == nullptr) {
+ // No probe to calculate coverage against, so coverage is zero.
+ return std::vector<ZipEntryCoverage>();
+ }
+
+ std::vector<ZipEntryCoverage> file_coverages;
+ // Find the file coverage against provided probe.
+ for (auto&& file : files) {
+ // For each file, compute coverage against the probe which represents a list of ranges.
+ ZipEntryCoverage file_coverage = file.compute_coverage(*probe);
+ file_coverages.push_back(file_coverage);
+ }
+
+ return file_coverages;
+}
+
+void ZipMemInspector::add_file_info(ZipEntryInfo& file) {
+ entry_infos_.push_back(file);
+}
+
+int ZipMemInspector::compute_per_file_coverage() {
+ if (entry_infos_.empty()) {
+ // We haven't read the file information yet, so do it now.
+ if (read_files_and_offsets()) {
+ cerr << "Could not read zip entries to compute coverages." << endl;
+ return 1;
+ }
+ }
+
+ // All existing files should consider their whole memory as present by default.
+ std::vector<ZipEntryCoverage> entry_coverages;
+ for (auto&& entry_info : entry_infos_) {
+ ZipEntryCoverage entry_coverage;
+ entry_coverage.info = entry_info;
+ VmaRange file_vma_range(entry_info.offset_in_zip, entry_info.file_size_bytes);
+ entry_coverage.coverage.ranges.push_back(file_vma_range);
+ entry_coverage.coverage.compute_total_size();
+ entry_coverages.push_back(entry_coverage);
+ }
+
+ if (probe_resident_ != nullptr) {
+ // We decided to compute coverage based on a probe
+ entry_coverages_ = compute_coverage(entry_coverages, probe_resident_);
+ } else {
+ // No probe means whole file coverage
+ entry_coverages_ = entry_coverages;
+ }
+
+ return 0;
+}
+
+VmaRangeGroup* ZipMemInspector::get_probe() {
+ return probe_resident_;
+}
+
+void ZipMemInspector::set_existing_probe(VmaRangeGroup* probe) {
+ this->probe_resident_ = probe;
+}
+
+std::vector<ZipEntryCoverage>& ZipMemInspector::get_file_coverages() {
+ return entry_coverages_;
+}
+
+int ZipMemInspector::probe_resident() {
+ probe_resident_ = new VmaRangeGroup();
+ int res = probe_resident_memory(filename_, *probe_resident_);
+ if (res != 0) {
+ // Failed to probe
+ return res;
+ }
+
+ return 0;
+}
+
+std::vector<ZipEntryInfo>& ZipMemInspector::get_file_infos() {
+ return entry_infos_;
+}
+
+int ZipMemInspector::read_files_and_offsets() {
+ if (OpenArchive(filename_.c_str(), &handle_) < 0) {
+ return 1;
+ }
+ void* cookie;
+ int res = StartIteration(handle_, &cookie);
+ if (res != 0) {
+ return 1;
+ }
+
+ ZipEntry64 entry;
+ string name;
+ while (Next(cookie, &entry, &name) == 0) {
+ ZipEntryInfo file;
+ file.name = name;
+ file.offset_in_zip = entry.offset;
+ file.file_size_bytes = entry.compressed_length;
+ file.uncompressed_size = entry.uncompressed_length;
+ entry_infos_.push_back(file);
+ }
+ return 0;
+}
diff --git a/pinner/pin_utils.cpp b/pinner/pin_utils.cpp
new file mode 100644
index 00000000..7c746bb0
--- /dev/null
+++ b/pinner/pin_utils.cpp
@@ -0,0 +1,347 @@
+#include "pin_utils.h"
+#include <android-base/parseint.h>
+#include <algorithm>
+#include <fstream>
+#include <map>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+using namespace std;
+using namespace android::base;
+
+int write_pinlist_file(const std::string& output_file,
+ const std::vector<ZipEntryCoverage>& files_to_write, int64_t write_quota) {
+ std::vector<VmaRange> ranges;
+ for (auto&& file : files_to_write) {
+ ranges.insert(ranges.end(), file.coverage.ranges.begin(), file.coverage.ranges.end());
+ }
+ return write_pinlist_file(output_file, ranges, write_quota);
+}
+
+int write_pinlist_file(const std::string& output_file, const std::vector<VmaRange>& vmas_to_write,
+ int64_t write_quota) {
+ ofstream pinlist_file(output_file);
+ if (pinlist_file.fail()) {
+ return 1;
+ }
+ int64_t total_written = 0;
+ unsigned int page_size = sysconf(_SC_PAGESIZE);
+ const bool has_quota = write_quota > 0;
+ bool reached_quota = false;
+
+ // The PinnerService does not require aligned offsets, however, aligning
+ // allows our summary results to be accurate and avoids over-accounting
+ // of pinning in PinnerService.
+ std::vector<VmaRange> processed_vmas_to_write = vmas_to_write;
+ align_ranges(processed_vmas_to_write, page_size);
+
+ // When we page-align the ranges, we may cause overlaps between ranges
+ // as we elongate the begin offset to match the page the previous
+ // range may end up overlapping the current one.
+ processed_vmas_to_write = merge_ranges(processed_vmas_to_write);
+
+ for (auto&& processed_vma_to_write : processed_vmas_to_write) {
+ uint32_t vma_start_offset = processed_vma_to_write.offset;
+ uint32_t vma_length = processed_vma_to_write.length;
+ if (has_quota && (total_written + vma_length > write_quota)) {
+ // We would go beyond quota, set the maximum allowed write and exit.
+ vma_length = write_quota - total_written;
+ reached_quota = true;
+ }
+ // Transform to BigEndian as PinnerService requires that endianness for reading.
+ uint32_t vma_start_offset_be = htobe32(vma_start_offset);
+ uint32_t vma_length_be = htobe32(vma_length);
+ cout << "Pinlist Writing start=" << vma_start_offset << " bytes=" << vma_length << endl;
+ pinlist_file.write(reinterpret_cast<char*>(&vma_start_offset_be),
+ sizeof(vma_start_offset_be));
+ if (pinlist_file.fail()) {
+ return 1;
+ }
+ pinlist_file.write(reinterpret_cast<char*>(&vma_length_be), sizeof(vma_length_be));
+ total_written += vma_length;
+ if (pinlist_file.fail()) {
+ return 1;
+ }
+
+ if (reached_quota) {
+ break;
+ }
+ }
+ return 0;
+}
+
+int read_pinlist_file(const std::string& pinner_file, /*out*/ std::vector<VmaRange>& pinranges) {
+ ifstream pinlist_file(pinner_file);
+ if (pinlist_file.fail()) {
+ return 1;
+ }
+
+ uint32_t vma_start;
+ uint32_t vma_length;
+ while (!pinlist_file.eof()) {
+ pinlist_file.read(reinterpret_cast<char*>(&vma_start), sizeof(vma_start));
+ pinlist_file.read(reinterpret_cast<char*>(&vma_length), sizeof(vma_length));
+ if (pinlist_file.fail()) {
+ return 1;
+ }
+ vma_start = betoh32(vma_start);
+ vma_length = betoh32(vma_length);
+ pinranges.push_back(VmaRange(vma_start, vma_length));
+ }
+
+ return 0;
+}
+
+ZipEntryCoverage PinConfigFile::to_zipfilemem(const ZipEntryInfo& info) {
+ ZipEntryCoverage file;
+ file.info = info;
+
+ if (ranges.empty()) {
+ cout << "No ranges found for file " << info.name << " creating entire file range" << endl;
+ // Any file coming from pinconfig without explicit
+ // ranges will be assumed to be wanted in its entirety
+ ranges.push_back(VmaRange(0, info.file_size_bytes));
+ }
+
+ file.coverage.ranges = ranges;
+
+ // Offsets specified in pinconfig file are relative to the file
+ // so transform to zip global offsets which are used for coverage
+ // computations.
+ file.coverage.apply_offset(info.offset_in_zip);
+
+ file.coverage.compute_total_size();
+ return file;
+}
+
+int PinConfig::parse(std::string config_file, bool verbose) {
+ ifstream file(config_file);
+ string file_in_zip;
+ if (verbose) {
+ cout << "Parsing file: " << config_file << endl;
+ }
+ string token;
+ file >> token;
+ while (!file.eof()) {
+ if (token == "file") {
+ file >> file_in_zip;
+ PinConfigFile pin_config_file;
+ pin_config_file.filename = file_in_zip;
+ file >> token;
+ while (token != "file" && !file.eof()) {
+ VmaRange range;
+ // Inner parsing loop for per file config.
+ if (token == "offset") {
+ file >> token;
+ android::base::ParseUint(token, &range.offset);
+ file >> token;
+ if (token != "len") {
+ cerr << "Malformed file, expected 'len' after offset" << endl;
+ return 1;
+ }
+ file >> token;
+ android::base::ParseUint(token, &range.length);
+ pin_config_file.ranges.push_back(range);
+ }
+ file >> token;
+ }
+ files_.push_back(pin_config_file);
+ } else {
+ cerr << "Unexpected token: " << token << ". Exit read" << endl;
+ return 1;
+ }
+ }
+
+ if (files_.empty()) {
+ cerr << "Failed parsing pinconfig file, no entries found." << endl;
+ return 1;
+ }
+
+ if (verbose) {
+ cout << "Finished parsing Pinconfig file" << endl;
+ for (auto&& pin_file : files_) {
+ cout << "file=" << pin_file.filename << endl;
+ for (auto&& range : pin_file.ranges) {
+ cout << "offset=" << range.offset << " bytes=" << range.length << endl;
+ }
+ }
+ }
+
+ return 0;
+}
+
+void PinTool::set_custom_zip_inspector(ZipMemInspector* inspector) {
+ delete zip_inspector_;
+ zip_inspector_ = inspector;
+}
+
+void PinTool::set_verbose_output(bool verbose) {
+ verbose_ = verbose;
+}
+
+void PinTool::read_probe_from_pinlist(std::string custom_probe_file) {
+ custom_probe_file_ = custom_probe_file;
+ VmaRangeGroup* custom_probe = new VmaRangeGroup();
+ read_pinlist_file(custom_probe_file_, custom_probe->ranges);
+ custom_probe->compute_total_size();
+ if (custom_probe->ranges.empty()) {
+ cerr << "Did not find any memory range in " << custom_probe_file_ << endl;
+ delete custom_probe;
+ return;
+ }
+ zip_inspector_->set_existing_probe(custom_probe);
+}
+
+int PinTool::probe_resident() {
+ return zip_inspector_->probe_resident();
+}
+
+void PinTool::compute_zip_entry_coverages() {
+ zip_inspector_->compute_per_file_coverage();
+ if (verbose_) {
+ std::vector<ZipEntryInfo> files = zip_inspector_->get_file_infos();
+ for (auto&& file : files) {
+ cout << "file found. name=" << file.name << " offset=" << file.offset_in_zip
+ << " uncompressed=" << file.uncompressed_size
+ << " compressed=" << file.file_size_bytes << endl
+ << endl;
+ }
+ }
+}
+
+void PinTool::dump_coverages(PinTool::DumpType dump_type) {
+ std::vector<ZipEntryCoverage>* file_coverages;
+ if (dump_type == PinTool::DumpType::FILTERED) {
+ file_coverages = &filtered_files_;
+ } else if (dump_type == PinTool::DumpType::FILE_COVERAGE) {
+ file_coverages = &(zip_inspector_->get_file_coverages());
+ } else { // PinTool::DumpType::PROBE
+ VmaRangeGroup* probe = zip_inspector_->get_probe();
+ file_coverages = new vector<ZipEntryCoverage>();
+ ZipEntryCoverage file;
+ file.coverage = *probe;
+ file.info.name = input_file_;
+ file.info.offset_in_zip = 0;
+ uint64_t file_size_bytes = get_file_size(input_file_);
+ if (file_size_bytes == -1) {
+ cerr << "Failed to dump, cannot fstat file: " << input_file_ << endl;
+ delete file_coverages;
+ return;
+ }
+ file.info.file_size_bytes = file_size_bytes;
+ file_coverages->push_back(file);
+ }
+
+ for (auto&& file : *file_coverages) {
+ uint64_t total_size = file.coverage.compute_total_size();
+ cout << file.info.name << " size(B)=" << file.info.file_size_bytes
+ << " resident(B)=" << total_size
+ << " resident(%)=" << (double)(total_size) / file.info.file_size_bytes * 100.0 << endl;
+ if (verbose_) {
+ cout << "file_base_zip_offset=" << file.info.offset_in_zip << endl;
+ }
+ cout << "file resident ranges" << endl;
+ if (dump_type != DumpType::PROBE) {
+ for (auto&& range : file.coverage.ranges) {
+ // The offset in the range represents the absolute absolute offset relative to the
+ // zip so substract the file base offset to get the relative offset within the file
+ // which may be what is worth for a user to specify in pinconfig.txt files.
+ uint64_t offset_in_file = range.offset - file.info.offset_in_zip;
+
+ cout << "zip_offset=" << range.offset << " file_offset=" << offset_in_file
+ << " total_bytes=" << range.length << endl;
+ }
+ } else {
+ for (auto&& range : file.coverage.ranges) {
+ cout << "file_offset=" << range.offset << " total_bytes=" << range.length << endl;
+ }
+ }
+ cout << endl;
+ }
+ cout << endl;
+ if (dump_type == DumpType::PROBE) {
+ // For other dump types we do not create memory, we reuse from class.
+ delete file_coverages;
+ }
+}
+
+void PinTool::filter_zip_entry_coverages(const std::string& pinconfig_filename) {
+ if (pinconfig_filename.length() == 0) {
+ // Nothing to do.
+ return;
+ }
+
+ PinConfig* pinconfig = new PinConfig();
+ if (pinconfig->parse(pinconfig_filename, verbose_) > 0) {
+ cerr << "Failed parsing pinconfig file " << pinconfig_filename << ". Skip filtering";
+ delete pinconfig;
+ return;
+ }
+
+ filter_zip_entry_coverages(pinconfig);
+}
+
+void PinTool::filter_zip_entry_coverages(PinConfig* pinconfig) {
+ pinconfig_ = pinconfig;
+
+ // Filter based on the per file configuration.
+ vector<ZipEntryCoverage> file_coverages = zip_inspector_->get_file_coverages();
+ vector<ZipEntryCoverage>& filtered_files = filtered_files_;
+
+ for (auto&& file_coverage : file_coverages) {
+ for (auto&& pinconfig_file : pinconfig_->files_) {
+ // Match each zip entry against every pattern in filter file.
+ std::string_view file_coverage_view(file_coverage.info.name.c_str());
+ std::string_view pinconfig_view(pinconfig_file.filename.c_str());
+ if (file_coverage_view.find(pinconfig_view) != std::string_view::npos) {
+ // Now that we found a match, create a file with offsets that are global to zip file
+ ZipEntryCoverage file_in_config = pinconfig_file.to_zipfilemem(file_coverage.info);
+ if (verbose_) {
+ cout << "Found a match: file=" << file_coverage.info.name
+ << " matching filter=" << pinconfig_file.filename << endl;
+ for (auto&& range : file_in_config.coverage.ranges) {
+ cout << "zip_offset=" << range.offset << " bytes=" << range.length << endl;
+ }
+ }
+ ZipEntryCoverage filtered_file =
+ file_coverage.compute_coverage(file_in_config.coverage);
+ filtered_files.push_back(filtered_file);
+ break;
+ }
+ }
+ }
+}
+
+std::vector<ZipEntryCoverage> PinTool::get_filtered_zip_entries() {
+ return filtered_files_;
+}
+
+void PinTool::write_coverages_as_pinlist(std::string output_pinlist, int64_t write_quota) {
+ std::vector<ZipEntryCoverage>* pinlist_coverages = nullptr;
+ if (!filtered_files_.empty()) {
+ // Highest preference is writing filtered files if they exist
+ if (verbose_) {
+ cout << "Writing pinconfig filtered file coverages" << endl;
+ }
+ pinlist_coverages = &filtered_files_;
+ } else if (!zip_inspector_->get_file_coverages().empty()) {
+ // Fallback to looking for file coverage computation
+ pinlist_coverages = &zip_inspector_->get_file_coverages();
+ if (verbose_) {
+ cout << "Writing regular file coverages." << endl;
+ }
+ }
+ if (pinlist_coverages == nullptr) {
+ cerr << "Failed to find coverage to write to: " << output_pinlist << endl;
+ return;
+ }
+ int res = write_pinlist_file(output_pinlist, *pinlist_coverages, write_quota);
+ if (res > 0) {
+ cerr << "Failed to write pin file at: " << output_pinlist << endl;
+ } else {
+ if (verbose_) {
+ cout << "Finished writing pin file at: " << output_pinlist << endl;
+ }
+ }
+} \ No newline at end of file
diff --git a/pinner/pintool.cpp b/pinner/pintool.cpp
new file mode 100644
index 00000000..9aa47b7b
--- /dev/null
+++ b/pinner/pintool.cpp
@@ -0,0 +1,343 @@
+#include <android-base/parseint.h>
+#include <fcntl.h>
+#include <sys/endian.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <meminspect.h>
+#include <pin_utils.h>
+
+using namespace std;
+using namespace android::base;
+
+enum ToolMode {
+ MAPPED_FILE, // Files that are mapped in memory
+ PINLIST, // pinlist.meta style file
+ UNKNOWN
+};
+
+void print_pinlist_ranges(const std::vector<VmaRange>& ranges) {
+ cout << "--pinlist memory ranges--" << endl;
+ for (auto&& range : ranges) {
+ cout << "start=" << range.offset << " bytes=" << range.length << endl;
+ }
+}
+
+void print_pinlist_summary(const std::vector<VmaRange>& ranges) {
+ cout << "--pinlist summary--" << endl;
+ uint64_t total_bytes = 0;
+ for (auto&& range : ranges) {
+ total_bytes += range.length;
+ }
+ cout << "total_bytes_to_pin=" << total_bytes << endl;
+}
+
+int perform_file_action(const vector<string>& options) {
+ std::string custom_probe_file;
+ std::string output_file;
+ std::string pinconfig_file;
+
+ bool verbose = false;
+ bool is_zip = false;
+ bool dump_results = false;
+ ProbeType probe_type = UNSET;
+ int64_t write_quota = -1; // unbounded by default
+
+ if (options.empty()) {
+ cerr << "Missing filename for file action, see usage for details." << endl;
+ return 1;
+ }
+
+ std::string input_file = options[0];
+
+ // Validate that the file exists.
+ if (get_file_size(input_file) == -1) {
+ cerr << "Error: Could not read file: " << input_file << endl;
+ return 1;
+ }
+
+ if (input_file.empty()) {
+ cerr << "Error: Should specify an input file." << endl;
+ return 1;
+ }
+
+ // Parse flags
+ for (int i = 1; i < options.size(); ++i) {
+ string option = options[i];
+ if (option == "--gen-probe") {
+ if (probe_type != ProbeType::UNSET) {
+ cerr << "Should only specify one probe treatment. See usage for details." << endl;
+ return 1;
+ }
+ probe_type = ProbeType::GENERATE;
+ continue;
+ }
+
+ if (option == "--use-probe") {
+ if (probe_type != ProbeType::UNSET) {
+ cerr << "Should only specify one probe treatment. See usage for details." << endl;
+ return 1;
+ }
+ probe_type = ProbeType::CUSTOM;
+ ++i;
+ custom_probe_file = options[i];
+ continue;
+ }
+ if (option == "--pinconfig") {
+ ++i;
+ pinconfig_file = options[i];
+ continue;
+ }
+ if (option == "-o") {
+ ++i;
+ output_file = options[i];
+ continue;
+ }
+ if (option == "--quota") {
+ ++i;
+ android::base::ParseInt(options[i], &write_quota);
+ continue;
+ }
+ if (option == "-v") {
+ verbose = true;
+ continue;
+ }
+ if (option == "--zip") {
+ is_zip = true;
+ continue;
+ }
+ if (option == "--dump") {
+ dump_results = true;
+ continue;
+ }
+ }
+
+ if (verbose) {
+ cout << "Setting output pinlist file: " << output_file.c_str() << endl;
+ cout << "Setting input file: " << input_file.c_str() << endl;
+ cout << "Setting pinconfig file: " << pinconfig_file.c_str() << endl;
+ cout << "Setting custom probe file: " << custom_probe_file.c_str() << endl;
+ cout << "Setting probe type: " << probe_type << endl;
+ cout << "Dump enabled: " << dump_results << endl;
+ cout << "Is Zip file: " << is_zip << endl;
+ if (write_quota != -1) {
+ cout << "Set Write quota: " << write_quota << endl;
+ }
+ }
+
+ PinTool pintool(input_file);
+
+ if (is_zip) {
+ pintool.set_verbose_output(verbose);
+ if (probe_type == ProbeType::CUSTOM) {
+ if (verbose) {
+ cout << "Using custom probe file: " << custom_probe_file << endl;
+ }
+ pintool.read_probe_from_pinlist(custom_probe_file);
+ } else if (probe_type == ProbeType::GENERATE) {
+ if (verbose) {
+ cout << "Generating probe" << endl;
+ }
+ int res = pintool.probe_resident();
+ if (res > 0) {
+ cerr << "Failed to generate probe. Error Code: " << res << endl;
+ return 1;
+ }
+ }
+ pintool.compute_zip_entry_coverages();
+
+ if (pinconfig_file.length() > 0) {
+ // We have provided a pinconfig file so perform filtering
+ // of computed coverages based on it.
+ pintool.filter_zip_entry_coverages(pinconfig_file);
+ }
+
+ if (dump_results) {
+ cout << endl << "----Unfiltered file coverages----" << endl << endl;
+ pintool.dump_coverages(PinTool::DumpType::FILE_COVERAGE);
+
+ if (pinconfig_file.length() > 0) {
+ cout << endl << "----Filtered file coverages----" << endl << endl;
+ pintool.dump_coverages(PinTool::DumpType::FILTERED);
+ }
+ }
+
+ if (output_file.length() > 0) {
+ pintool.write_coverages_as_pinlist(output_file, write_quota);
+ }
+
+ return 0;
+ } else {
+ if (probe_type != ProbeType::GENERATE) {
+ cerr << "Only generating probes is supported for non-zip files, please include "
+ "--gen-probe on your command"
+ << endl;
+ return 1;
+ }
+
+ // Generic file probing will just return resident memory and offsets
+ // without more contextual information.
+ VmaRangeGroup resident;
+
+ int res = pintool.probe_resident();
+ if (res > 0) {
+ cerr << "Failed to generate probe. Error Code: " << res << endl;
+ return 1;
+ }
+
+ pintool.dump_coverages(PinTool::DumpType::PROBE);
+
+ if (output_file.length() > 0) {
+ res = write_pinlist_file(output_file, resident.ranges, write_quota);
+ if (res > 0) {
+ cerr << "Failed to write pin file at: " << output_file << endl;
+ } else if (verbose) {
+ cout << "Finished writing pin file at: " << output_file << endl;
+ }
+ }
+ return res;
+ }
+ return 0;
+}
+
+int perform_pinlist_action(const vector<string>& options) {
+ string pinner_file;
+ bool verbose = false;
+ bool dump = false;
+ bool summary = false;
+
+ if (options.size() < 1) {
+ cerr << "Missing arguments for pinlist mode. See usage for details << endl";
+ return 1;
+ }
+ pinner_file = options[0];
+ for (int i = 1; i < options.size(); ++i) {
+ string option = options[i];
+
+ if (option == "-v") {
+ verbose = true;
+ }
+
+ if (option == "--dump") {
+ dump = true;
+ }
+
+ if (option == "--summary") {
+ summary = true;
+ }
+ }
+
+ if (pinner_file.empty()) {
+ cerr << "Error: Pinlist file to dump is missing. Specify it with '-p <file>'" << endl;
+ return 1;
+ }
+
+ if (verbose) {
+ cout << "Setting file to dump: " << pinner_file.c_str() << endl;
+ }
+
+ vector<VmaRange> vma_ranges;
+ if (read_pinlist_file(pinner_file, vma_ranges) == 1) {
+ cerr << "Failed reading pinlist file" << endl;
+ }
+
+ if (dump) {
+ print_pinlist_ranges(vma_ranges);
+ }
+
+ if (summary) {
+ print_pinlist_summary(vma_ranges);
+ }
+
+ return 0;
+}
+
+void print_usage() {
+ const string usage = R"(
+ Expected usage: pintool <mode> <required> [option]
+ where:
+ ./pintool <MODE>
+ <MODE>
+ file <filename> [option]
+ [option]
+ --gen-probe
+ Generate a probe from current resident memory based on provided "file"
+ --use-probe <path_to_input_pinlist.meta>
+ Use a previously generated pinlist.meta style file as the probe to match against.
+ --dump
+ Dump output contents to console.
+ --zip
+ Treat the file as a zip/apk file required for doing per-file coverage analysis and generation.
+ --pinconfig <path_to_pinconfig.txt>
+ Filter output coverage ranges using a provided pinconfig.txt style file. See README.md for samples
+ on the format of that file.
+ -v
+ Enable verbose output.
+
+ pinlist <pinlist_file> [option]
+ <pinlist_file>
+ this is the file that will be used for reading and it should follow the pinlist.meta format.
+ [option]
+ --dump
+ Dump <pinlist_file> contents to console output.
+ -v
+ Enable verbose output.
+ --summary
+ Summary results for the pinlist.meta file
+ )";
+ cout << usage.c_str();
+}
+
+int main(int argc, char** argv) {
+ if (argc == 1) {
+ print_usage();
+ return 0;
+ }
+
+ if (argc < 2) {
+ cerr << "<mode> is missing";
+ return 1;
+ }
+
+ if (strcmp(argv[1], "--help") == 0) {
+ print_usage();
+ return 0;
+ }
+
+ ToolMode mode = ToolMode::UNKNOWN;
+ if (strcmp(argv[1], "file") == 0) {
+ mode = ToolMode::MAPPED_FILE;
+ } else if (strcmp(argv[1], "pinlist") == 0) {
+ mode = ToolMode::PINLIST;
+ }
+
+ if (mode == ToolMode::UNKNOWN) {
+ cerr << "Failed to find mode: " << argv[1] << ". See usage for available modes." << endl;
+ return 1;
+ }
+
+ vector<string> options;
+ for (int i = 2; i < argc; ++i) {
+ options.push_back(argv[i]);
+ }
+
+ int res;
+ switch (mode) {
+ case ToolMode::MAPPED_FILE:
+ res = perform_file_action(options);
+ break;
+ case ToolMode::PINLIST:
+ res = perform_pinlist_action(options);
+ break;
+ case ToolMode::UNKNOWN:
+ cerr << "Unknown <MODE> see usage for details." << endl;
+ return 1;
+ }
+
+ return res;
+}
diff --git a/pinner/sample_pinconfig.txt b/pinner/sample_pinconfig.txt
new file mode 100644
index 00000000..3d9a13a6
--- /dev/null
+++ b/pinner/sample_pinconfig.txt
@@ -0,0 +1,5 @@
+file lib/arm64-v8a/libcrashpad_handler_trampoline.so
+file libmonochrome_64.so
+offset 108391596
+len 33348032
+file libarcore_sdk_c.so \ No newline at end of file
diff --git a/pinner/tests/Android.bp b/pinner/tests/Android.bp
new file mode 100644
index 00000000..f7b04d95
--- /dev/null
+++ b/pinner/tests/Android.bp
@@ -0,0 +1,95 @@
+// Copyright (C) 2018 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.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+ name: "meminspect_tests",
+
+ test_suites: ["device-tests"],
+
+ // Required for reading-writing files which are part of the tests.
+ require_root: true,
+
+ shared_libs: [
+ "libbase",
+ "libziparchive",
+ ],
+
+ cppflags: [
+ "-g",
+ "-Wall",
+ "-Werror",
+ ],
+
+ static_libs: [
+ "libmeminspect",
+ ],
+
+ target: {
+ android: {
+ srcs: ["meminspect_tests.cpp"],
+ },
+ },
+
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ "-O0", // as some tests rely on compiler keeping code as is
+ ],
+
+ compile_multilib: "first",
+}
+
+cc_test {
+ name: "pintool_tests",
+
+ test_suites: ["device-tests"],
+
+ // Required for reading-writing files which are part of the tests.
+ require_root: true,
+
+ shared_libs: [
+ "libbase",
+ "libziparchive",
+ ],
+
+ cppflags: [
+ "-g",
+ "-Wall",
+ "-Werror",
+ ],
+
+ static_libs: [
+ "libmeminspect",
+ ],
+
+ target: {
+ android: {
+ srcs: ["pintool_tests.cpp"],
+ },
+ },
+
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ "-O0", // as some tests rely on compiler keeping code as is
+ ],
+
+ compile_multilib: "first",
+}
diff --git a/pinner/tests/TEST_MAPPING b/pinner/tests/TEST_MAPPING
new file mode 100644
index 00000000..73af05f1
--- /dev/null
+++ b/pinner/tests/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+ "postsubmit": [
+ {
+ "name": "pintool_tests"
+ },
+ {
+ "name": "meminspect_tests"
+ }
+ ]
+} \ No newline at end of file
diff --git a/pinner/tests/meminspect_tests.cpp b/pinner/tests/meminspect_tests.cpp
new file mode 100644
index 00000000..98c51def
--- /dev/null
+++ b/pinner/tests/meminspect_tests.cpp
@@ -0,0 +1,284 @@
+#include <gtest/gtest.h>
+#include <stdio.h>
+
+#include <meminspect.h>
+
+using namespace std;
+
+/**
+ * This test is meant to be ran by directly pushing the test binary
+ * into the device as using atest will not provide sufficient privileges
+ * to execute drop_caches command.
+ */
+TEST(meminspect_test, inspect_matches_resident) {
+ // Create test file
+ string test_file = "/data/local/tmp/meminspect_test";
+ // If for any reason a test file already existed from a previous test, remove it.
+ remove(test_file.c_str());
+
+ int test_file_fd = open(test_file.c_str(), O_RDWR | O_CREAT, "w");
+ unsigned int page_size = sysconf(_SC_PAGESIZE);
+ if (test_file_fd == -1) {
+ ADD_FAILURE() << "Failed to open test file for writing. errno: " << std::strerror(errno);
+ close(test_file_fd);
+ remove(test_file.c_str());
+ return;
+ }
+
+ uint8_t* page_data = new uint8_t[page_size];
+ for (unsigned int i = 0; i < page_size; ++i) {
+ page_data[i] = i + 1;
+ }
+ int pages_to_write = 100;
+ for (int page = 0; page < pages_to_write; ++page) {
+ write(test_file_fd, page_data, page_size);
+ }
+ // fsync to ensure the data is flushed to disk.
+ if (fsync(test_file_fd) == -1) {
+ ADD_FAILURE() << "fsync failed errno: " << std::strerror(errno);
+ close(test_file_fd);
+ remove(test_file.c_str());
+ return;
+ }
+ close(test_file_fd);
+
+ // Drop the pagecache to ensure we do not have memory due to it staying there after write.
+ int drop_cache_fd = open("/proc/sys/vm/drop_caches", O_WRONLY);
+ if (drop_cache_fd == -1) {
+ ADD_FAILURE() << "failed opening drop caches fd errno: " << std::strerror(errno);
+ close(test_file_fd);
+ remove(test_file.c_str());
+ return;
+ }
+ write(drop_cache_fd, "3", 1);
+ fsync(drop_cache_fd);
+ close(drop_cache_fd);
+
+ // Open file and page in some memory
+ test_file_fd = open(test_file.c_str(), O_RDONLY, "r");
+ if (test_file_fd == -1) {
+ ADD_FAILURE() << "Failed to open test file for reading after creation. errno: "
+ << std::strerror(errno);
+ close(test_file_fd);
+ remove(test_file.c_str());
+ return;
+ }
+
+ char* base_address = (char*)mmap(0, page_size * pages_to_write, PROT_READ, MAP_SHARED,
+ test_file_fd, /*offset*/ 0);
+ if (base_address == (char*)-1) {
+ ADD_FAILURE() << "Failed to mmap file for reading after creation. errno: "
+ << std::strerror(errno);
+ close(test_file_fd);
+ remove(test_file.c_str());
+ return;
+ }
+
+ VmaRangeGroup vmas_resident;
+ int res = probe_resident_memory(test_file, vmas_resident, 1);
+ EXPECT_TRUE(res == 0);
+
+ // Probing the file without reading anything yields no resident memory
+ EXPECT_TRUE(vmas_resident.ranges.empty());
+
+ // Clear our to start fresh for next probe.
+ vmas_resident = VmaRangeGroup();
+
+ int pages_to_read = 1;
+ char* read_data = new char[pages_to_read];
+ for (int page = 0; page < pages_to_read; ++page) {
+ // Read 1 byte from each page to page it in.
+ read_data[page] = base_address[page * page_size];
+ }
+ res = probe_resident_memory(test_file, vmas_resident, 1);
+ EXPECT_TRUE(res == 0);
+
+ // The amount of memory paged in is outside our control, but we should have some.
+ uint64_t resident_total_size = vmas_resident.compute_total_size();
+ EXPECT_TRUE(resident_total_size > 0);
+ EXPECT_TRUE(vmas_resident.ranges.size() == 1);
+ EXPECT_TRUE(vmas_resident.ranges[0].offset == 0);
+ EXPECT_TRUE((uint64_t)vmas_resident.ranges[0].length == resident_total_size);
+
+ close(test_file_fd);
+ remove(test_file.c_str());
+}
+
+TEST(meminspect_test, custom_probe_coverage_matches_with_probe) {
+ ZipMemInspector inspector("");
+ VmaRangeGroup* probe = new VmaRangeGroup();
+ probe->ranges.push_back(VmaRange(0, 500));
+ probe->ranges.push_back(VmaRange(700, 100));
+ probe->ranges.push_back(VmaRange(1000, 500));
+ probe->ranges.push_back(VmaRange(2000, 100));
+ // Probed Resident Memory Offset ranges:
+ // [0,500],[700,800],[1000,1500],[2000,2100]
+ EXPECT_EQ(probe->compute_total_size(), (unsigned long long)1200);
+ inspector.set_existing_probe(probe);
+
+ // Emulate reading some files from the zip to compute their coverages
+ // fake1 memory offset ranges [100,300]
+ ZipEntryInfo info;
+ info.name = "fake1";
+ info.offset_in_zip = 100;
+ info.file_size_bytes = 200;
+ inspector.add_file_info(info);
+
+ // fake2 memory offset ranges [600,1200]
+ ZipEntryInfo info2;
+ info2.name = "fake2";
+ info2.offset_in_zip = 600;
+ info2.file_size_bytes = 600;
+ inspector.add_file_info(info2);
+
+ inspector.compute_per_file_coverage();
+ std::vector<ZipEntryCoverage> coverages = inspector.get_file_coverages();
+ EXPECT_EQ(coverages.size(), (size_t)2);
+
+ // Result coverage for fake1 should be: [100,300]
+ EXPECT_EQ(coverages[0].coverage.ranges[0].offset, (uint32_t)100);
+ EXPECT_EQ(coverages[0].coverage.ranges[0].length, (uint32_t)200);
+ EXPECT_EQ(coverages[0].coverage.compute_total_size(), (unsigned long long)200);
+ EXPECT_EQ(coverages[0].info.name, "fake1");
+ EXPECT_EQ(coverages[0].info.offset_in_zip, (uint32_t)100);
+ EXPECT_EQ(coverages[0].info.file_size_bytes, (uint32_t)200);
+
+ // coverage coverage for fake2 should be: [700,800] and [1000,1200]
+ EXPECT_EQ(coverages[1].coverage.ranges[0].offset, (uint32_t)700);
+ EXPECT_EQ(coverages[1].coverage.ranges[0].length, (uint32_t)100);
+ EXPECT_EQ(coverages[1].coverage.ranges[1].offset, (uint32_t)1000);
+ EXPECT_EQ(coverages[1].coverage.ranges[1].length, (uint32_t)200);
+ EXPECT_EQ(coverages[1].coverage.compute_total_size(), (unsigned long long)300); // 100 +
+ // 200
+ EXPECT_EQ(coverages[1].info.name, "fake2");
+ EXPECT_EQ(coverages[1].info.offset_in_zip, (uint32_t)600);
+ EXPECT_EQ(coverages[1].info.file_size_bytes, (uint32_t)600);
+}
+
+TEST(meminspect_test, whole_file_coverage_against_probe) {
+ ZipMemInspector inspector("");
+
+ // Emulate reading some files from the zip to compute their coverages
+ // fake1 memory offset ranges [100,300]
+ ZipEntryInfo info;
+ info.name = "fake1";
+ info.offset_in_zip = 100;
+ info.file_size_bytes = 200;
+ inspector.add_file_info(info);
+
+ // fake2 memory offset ranges [600,1200]
+ ZipEntryInfo info2;
+ info2.name = "fake2";
+ info2.offset_in_zip = 600;
+ info2.file_size_bytes = 600;
+ inspector.add_file_info(info2);
+
+ inspector.compute_per_file_coverage();
+ std::vector<ZipEntryCoverage> coverages = inspector.get_file_coverages();
+ EXPECT_EQ(coverages.size(), (size_t)2);
+
+ // Check that coverage matches entire file sizes
+ EXPECT_EQ(coverages[0].coverage.ranges[0].offset, (uint32_t)100);
+ EXPECT_EQ(coverages[0].coverage.ranges[0].length, (uint32_t)200);
+ EXPECT_EQ(coverages[0].coverage.compute_total_size(), (unsigned long long)200);
+ EXPECT_EQ(coverages[0].info.name, "fake1");
+ EXPECT_EQ(coverages[0].info.offset_in_zip, (uint32_t)100);
+ EXPECT_EQ(coverages[0].info.file_size_bytes, (uint32_t)200);
+
+ EXPECT_EQ(coverages[1].coverage.ranges[0].offset, (uint32_t)600);
+ EXPECT_EQ(coverages[1].coverage.ranges[0].length, (uint32_t)600);
+ EXPECT_EQ(coverages[1].coverage.compute_total_size(), (unsigned long long)600);
+ EXPECT_EQ(coverages[1].info.name, "fake2");
+ EXPECT_EQ(coverages[1].info.offset_in_zip, (uint32_t)600);
+ EXPECT_EQ(coverages[1].info.file_size_bytes, (uint32_t)600);
+}
+
+TEST(meminspect_test, file_multiple_ranges_matches_probe) {
+ VmaRangeGroup probe;
+ probe.ranges.push_back(VmaRange(0, 500));
+ probe.ranges.push_back(VmaRange(700, 100));
+ probe.ranges.push_back(VmaRange(1000, 500));
+ probe.ranges.push_back(VmaRange(2000, 100));
+ // Probed Resident Memory Offset ranges:
+ // [0,500],[700,800],[1000,1500],[2000,2100]
+ EXPECT_EQ(probe.compute_total_size(), (unsigned long long)1200);
+
+ std::vector<ZipEntryCoverage> desired_coverages;
+
+ // fake1 file resides between [100,1100]
+ // desired ranges are [100,200],[400,710],[820,850]
+ ZipEntryCoverage file1_mem;
+ file1_mem.info.name = "fake1";
+ file1_mem.info.offset_in_zip = 100;
+ file1_mem.info.file_size_bytes = 1000;
+ file1_mem.coverage.ranges.push_back(VmaRange(100, 100));
+ file1_mem.coverage.ranges.push_back(VmaRange(400, 310));
+ file1_mem.coverage.ranges.push_back(VmaRange(820, 30));
+ desired_coverages.push_back(file1_mem);
+
+ // fake2 memory offset ranges [1300,2100]
+ // desired ranges are [1400,1500],[1600,1650],[1800,2050]
+ ZipEntryCoverage file2_mem;
+ file2_mem.info.name = "fake2";
+ file2_mem.info.offset_in_zip = 1300;
+ file2_mem.info.file_size_bytes = 750;
+ file2_mem.coverage.ranges.push_back(VmaRange(1400, 100));
+ file2_mem.coverage.ranges.push_back(VmaRange(1600, 50));
+ file2_mem.coverage.ranges.push_back(VmaRange(1800, 250));
+ desired_coverages.push_back(file2_mem);
+
+ std::vector<ZipEntryCoverage> coverages =
+ ZipMemInspector::compute_coverage(desired_coverages, &probe);
+
+ EXPECT_EQ(coverages.size(), (size_t)2);
+
+ // Result coverage for fake1 should be: [100,200],[400,500],[700,710]
+ EXPECT_EQ(coverages[0].coverage.ranges[0].offset, (uint32_t)100);
+ EXPECT_EQ(coverages[0].coverage.ranges[0].length, (uint32_t)100);
+ EXPECT_EQ(coverages[0].coverage.ranges[1].offset, (uint32_t)400);
+ EXPECT_EQ(coverages[0].coverage.ranges[1].length, (uint32_t)100);
+ EXPECT_EQ(coverages[0].coverage.ranges[2].offset, (uint32_t)700);
+ EXPECT_EQ(coverages[0].coverage.ranges[2].length, (uint32_t)10);
+
+ EXPECT_EQ(coverages[0].coverage.compute_total_size(), (unsigned long long)210);
+ EXPECT_EQ(coverages[0].info.name, "fake1");
+ EXPECT_EQ(coverages[0].info.offset_in_zip, (uint32_t)100);
+ EXPECT_EQ(coverages[0].info.file_size_bytes, (uint32_t)1000);
+
+ // coverage coverage for fake2 should be: [1400,1500],[2000,2050]
+ EXPECT_EQ(coverages[1].coverage.ranges[0].offset, (uint32_t)1400);
+ EXPECT_EQ(coverages[1].coverage.ranges[0].length, (uint32_t)100);
+ EXPECT_EQ(coverages[1].coverage.ranges[1].offset, (uint32_t)2000);
+ EXPECT_EQ(coverages[1].coverage.ranges[1].length, (uint32_t)50);
+ EXPECT_EQ(coverages[1].coverage.compute_total_size(), (unsigned long long)150);
+ EXPECT_EQ(coverages[1].info.name, "fake2");
+ EXPECT_EQ(coverages[1].info.offset_in_zip, (uint32_t)1300);
+ EXPECT_EQ(coverages[1].info.file_size_bytes, (uint32_t)750);
+}
+
+TEST(meminspect_test, range_alignment_and_merge_matches) {
+ ZipMemInspector inspector("");
+ VmaRangeGroup* probe = new VmaRangeGroup();
+ probe->ranges.push_back(VmaRange(0, 500));
+ probe->ranges.push_back(VmaRange(700, 100));
+ int page_size = 4096;
+
+ // Probed Resident Memory Offset ranges:
+ // [0,500],[700,800]
+ inspector.set_existing_probe(probe);
+
+ // When we page align, we should end up with [0,500],[0,800]
+ align_ranges(probe->ranges, page_size);
+ EXPECT_EQ(probe->ranges[0].offset, (uint32_t)0);
+ EXPECT_EQ(probe->ranges[0].length, (uint32_t)500);
+ EXPECT_EQ(probe->ranges[1].offset, (uint32_t)0);
+ EXPECT_EQ(probe->ranges[1].length, (uint32_t)800);
+ EXPECT_EQ(probe->ranges.size(), (uint32_t)2);
+
+ // Because we have overlapping ranges, a union-merge should
+ // skip duplication of intersections and end up with [0,800]
+ std::vector<VmaRange> merged = merge_ranges(probe->ranges);
+ EXPECT_EQ(merged[0].offset, (uint32_t)0);
+ EXPECT_EQ(merged[0].length, (uint32_t)800);
+ EXPECT_EQ(merged.size(), (uint32_t)1);
+} \ No newline at end of file
diff --git a/pinner/tests/pintool_tests.cpp b/pinner/tests/pintool_tests.cpp
new file mode 100644
index 00000000..f351a90a
--- /dev/null
+++ b/pinner/tests/pintool_tests.cpp
@@ -0,0 +1,137 @@
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <list>
+
+#include <pin_utils.h>
+
+using namespace std;
+
+TEST(pintool_test, pinlist_matches_memranges) {
+ vector<VmaRange> vma_ranges;
+ unsigned int page_size = sysconf(_SC_PAGESIZE);
+ vma_ranges.push_back(VmaRange(0, 500));
+ vma_ranges.push_back(VmaRange(5000, 5500));
+ vma_ranges.push_back(VmaRange(21000, 13000));
+ vma_ranges.push_back(VmaRange(50000, 35000));
+
+ string test_file = "/data/local/tmp/pintool_test";
+ write_pinlist_file(test_file, vma_ranges);
+
+ vector<VmaRange> read_ranges;
+ read_pinlist_file(test_file, read_ranges);
+
+ EXPECT_EQ(vma_ranges.size(), read_ranges.size());
+ for (size_t i = 0; i < vma_ranges.size(); ++i) {
+ // We expect to write pinlists that are page-aligned, so
+ // we compare against page aligned offsets.
+ uint64_t unaligned_bytes = vma_ranges[i].offset % page_size;
+ EXPECT_EQ(vma_ranges[i].offset - unaligned_bytes, read_ranges[i].offset);
+ EXPECT_EQ(vma_ranges[i].length + unaligned_bytes, read_ranges[i].length);
+ }
+
+ remove(test_file.c_str());
+}
+
+TEST(pintool_test, pinlist_quota_applied) {
+ vector<VmaRange> vma_ranges;
+ unsigned int page_size = sysconf(_SC_PAGESIZE);
+ vma_ranges.push_back(VmaRange(0, 100));
+ vma_ranges.push_back(VmaRange(page_size, 500));
+ vma_ranges.push_back(VmaRange(page_size * 2, 300));
+ vma_ranges.push_back(VmaRange(page_size * 3, 200));
+
+ const int ranges_to_write = 700;
+ string test_file = "/data/local/tmp/pintool_test";
+ write_pinlist_file(test_file, vma_ranges, ranges_to_write);
+
+ vector<VmaRange> read_ranges;
+ read_pinlist_file(test_file, read_ranges);
+
+ int total_length = 0;
+ for (size_t i = 0; i < read_ranges.size(); ++i) {
+ total_length += read_ranges[i].length;
+ }
+ EXPECT_EQ(total_length, ranges_to_write);
+
+ remove(test_file.c_str());
+}
+
+TEST(pintool_test, pinconfig_filter_coverage_matches) {
+ VmaRangeGroup* probe = new VmaRangeGroup();
+ probe->ranges.push_back(VmaRange(0, 500));
+ probe->ranges.push_back(VmaRange(1000, 5000));
+
+ ZipMemInspector* inspector = new ZipMemInspector("");
+
+ // Probed Resident Memory Offset ranges:
+ // [0,500],[1000,6000]
+ probe->compute_total_size();
+ inspector->set_existing_probe(probe);
+
+ // Emulate reading some files from the zip to compute their coverages
+ // fake1 memory offset ranges [100,400]
+ ZipEntryInfo info;
+ info.name = "fake1";
+ info.offset_in_zip = 100;
+ info.file_size_bytes = 300;
+ inspector->add_file_info(info);
+
+ // fake2 memory offset ranges [600,3000]
+ ZipEntryInfo info2;
+ info2.name = "fake2";
+ info2.offset_in_zip = 600;
+ info2.file_size_bytes = 2400;
+ inspector->add_file_info(info2);
+
+ ZipEntryInfo info3;
+ info2.name = "fake3";
+ info2.offset_in_zip = 3100;
+ info2.file_size_bytes = 200;
+ inspector->add_file_info(info3);
+
+ // Create a fake pinconfig file
+ PinConfig* pinconfig = new PinConfig();
+
+ // First file we want it entirely so don't provide ranges
+ PinConfigFile pinconfig_file_1;
+ pinconfig_file_1.filename = "fake1";
+ pinconfig->files_.push_back(pinconfig_file_1);
+
+ // Add a partially matched file
+ PinConfigFile pinconfig_file_2;
+ pinconfig_file_2.filename = "fake2";
+ pinconfig_file_2.ranges.push_back(VmaRange(100, 500));
+ pinconfig_file_2.ranges.push_back(VmaRange(800, 200));
+ pinconfig->files_.push_back(pinconfig_file_2);
+
+ // Add a file that does not exist
+ PinConfigFile pinconfig_file_3;
+ pinconfig_file_3.filename = "fake4";
+ pinconfig_file_3.ranges.push_back(VmaRange(0, 1000));
+ pinconfig->files_.push_back(pinconfig_file_3);
+
+ PinTool pintool("");
+ pintool.set_custom_zip_inspector(inspector);
+ pintool.compute_zip_entry_coverages();
+ pintool.filter_zip_entry_coverages(pinconfig);
+
+ std::vector<ZipEntryCoverage> filtered = pintool.get_filtered_zip_entries();
+
+ // We only matched 2 files, one should not have matched to any filter.
+ EXPECT_EQ(filtered.size(), (unsigned long)2);
+ EXPECT_EQ(filtered[0].info.name, "fake1");
+ EXPECT_EQ(filtered[0].coverage.ranges[0].offset, (unsigned long)100);
+ EXPECT_EQ(filtered[0].coverage.ranges[0].length, (unsigned long)300);
+
+ // Probe Resident has [0,500],[1000,6000].
+ // fake2 file lives within [600,3000]
+ // fake2 relative offsets from pinconfig [100,600],[800,1000]
+ // fake2 absolute zip offsets are [700,1200],[1400,1600]
+ // then matching absolute offsets against resident yields [1000,1200],[1400,1600]
+ EXPECT_EQ(filtered[1].info.name, "fake2");
+ EXPECT_EQ(filtered[1].info.offset_in_zip, (unsigned long)600);
+ EXPECT_EQ(filtered[1].coverage.ranges[0].offset, (unsigned long)1000);
+ EXPECT_EQ(filtered[1].coverage.ranges[0].length, (unsigned long)200);
+ EXPECT_EQ(filtered[1].coverage.ranges[1].offset, (unsigned long)1400);
+ EXPECT_EQ(filtered[1].coverage.ranges[1].length, (unsigned long)200);
+} \ No newline at end of file
diff --git a/profcollectd/OWNERS b/profcollectd/OWNERS
index b380e395..be9e61fa 100644
--- a/profcollectd/OWNERS
+++ b/profcollectd/OWNERS
@@ -1,3 +1 @@
-srhines@google.com
-yabinc@google.com
-yikong@google.com
+include platform/prebuilts/clang/host/linux-x86:/OWNERS
diff --git a/profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl b/profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl
index bfc24446..07699309 100644
--- a/profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl
+++ b/profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl
@@ -24,7 +24,8 @@ interface IProfCollectd {
void terminate();
void trace_once(@utf8InCpp String tag);
void process();
- @utf8InCpp String report();
+ /** -1 if there is no usageSetting */
+ @utf8InCpp String report(int usageSetting);
@utf8InCpp String get_supported_provider();
void registerProviderStatusCallback(IProviderStatusCallback cb);
}
diff --git a/profcollectd/libprofcollectd/Android.bp b/profcollectd/libprofcollectd/Android.bp
index ed383569..62aa6734 100644
--- a/profcollectd/libprofcollectd/Android.bp
+++ b/profcollectd/libprofcollectd/Android.bp
@@ -45,9 +45,9 @@ rust_library {
"libanyhow",
"libbinder_rs", // Remove once b/179041241 is fixed.
"libchrono",
- "liblazy_static",
"liblog_rust",
"libmacaddr",
+ "libonce_cell",
"librand",
"librustutils",
"libserde", // Remove once b/179041241 is fixed.
@@ -56,7 +56,7 @@ rust_library {
"libzip",
],
rlibs: [
- "libprofcollect_libflags_rust",
+ "libflags_rust",
"libsimpleperf_profcollect_rust",
],
shared_libs: ["libsimpleperf_profcollect"],
diff --git a/profcollectd/libprofcollectd/bindings/libflags/Android.bp b/profcollectd/libprofcollectd/bindings/libflags/Android.bp
deleted file mode 100644
index fb846678..00000000
--- a/profcollectd/libprofcollectd/bindings/libflags/Android.bp
+++ /dev/null
@@ -1,51 +0,0 @@
-//
-// Copyright (C) 2021 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.
-//
-
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "system_extras_profcollectd_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["system_extras_profcollectd_license"],
-}
-
-cc_library_static {
- name: "libprofcollect_libflags",
- srcs: ["get_flags.cpp"],
- generated_headers: ["cxx-bridge-header"],
- generated_sources: ["libprofcollect_libflags_bridge_code"],
-}
-
-genrule {
- name: "libprofcollect_libflags_bridge_code",
- tools: ["cxxbridge"],
- cmd: "$(location cxxbridge) $(in) >> $(out)",
- srcs: ["lib.rs"],
- out: ["libprofcollect_libflags_cxx_generated.cc"],
-}
-
-rust_library {
- name: "libprofcollect_libflags_rust",
- crate_name: "profcollect_libflags_rust",
- srcs: ["lib.rs"],
- rustlibs: ["libcxx"],
- static_libs: ["libprofcollect_libflags"],
- shared_libs: [
- "libc++",
- "server_configurable_flags",
- ],
-}
diff --git a/profcollectd/libprofcollectd/bindings/libflags/get_flags.cpp b/profcollectd/libprofcollectd/bindings/libflags/get_flags.cpp
deleted file mode 100644
index d8bab841..00000000
--- a/profcollectd/libprofcollectd/bindings/libflags/get_flags.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2021 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.
- */
-
-#include "../../../../../server_configurable_flags/libflags/include/server_configurable_flags/get_flags.h"
-#include "get_flags.hpp"
-
-rust::String GetServerConfigurableFlag(rust::Str experiment_category_name,
- rust::Str experiment_flag_name, rust::Str default_value) {
- return server_configurable_flags::GetServerConfigurableFlag(std::string(experiment_category_name),
- std::string(experiment_flag_name),
- std::string(default_value));
-}
diff --git a/profcollectd/libprofcollectd/bindings/libflags/get_flags.hpp b/profcollectd/libprofcollectd/bindings/libflags/get_flags.hpp
deleted file mode 100644
index a42c52e8..00000000
--- a/profcollectd/libprofcollectd/bindings/libflags/get_flags.hpp
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 2021 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.
- */
-
-#pragma once
-
-#include "rust/cxx.h"
-
-rust::String GetServerConfigurableFlag(rust::Str, rust::Str, rust::Str);
diff --git a/profcollectd/libprofcollectd/bindings/libflags/lib.rs b/profcollectd/libprofcollectd/bindings/libflags/lib.rs
deleted file mode 100644
index c6435bbd..00000000
--- a/profcollectd/libprofcollectd/bindings/libflags/lib.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-//
-// Copyright (C) 2021 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.
-//
-
-//! This module implements safe wrappers for GetServerConfigurableFlag method
-//! from libflags.
-
-pub use ffi::GetServerConfigurableFlag;
-
-#[cxx::bridge]
-mod ffi {
- unsafe extern "C++" {
- include!("get_flags.hpp");
-
- /// Use the category name and flag name registered in SettingsToPropertiesMapper.java
- /// to query the experiment flag value. This method will return default_value if
- /// querying fails.
- /// Note that for flags from Settings.Global, experiment_category_name should
- /// always be global_settings.
- fn GetServerConfigurableFlag(
- experiment_category_name: &str,
- experiment_flag_name: &str,
- default_value: &str,
- ) -> String;
- }
-}
diff --git a/profcollectd/libprofcollectd/config.rs b/profcollectd/libprofcollectd/config.rs
index d68f02e7..8a6c9e4f 100644
--- a/profcollectd/libprofcollectd/config.rs
+++ b/profcollectd/libprofcollectd/config.rs
@@ -17,8 +17,8 @@
//! ProfCollect configurations.
use anyhow::Result;
-use lazy_static::lazy_static;
use macaddr::MacAddr6;
+use once_cell::sync::Lazy;
use rand::Rng;
use serde::{Deserialize, Serialize};
use std::error::Error;
@@ -27,20 +27,24 @@ use std::path::Path;
use std::str::FromStr;
use std::time::Duration;
-const PROFCOLLECT_CONFIG_NAMESPACE: &str = "profcollect_native_boot";
+const PROFCOLLECT_CONFIG_NAMESPACE: &str = "aconfig_flags.profcollect_native_boot";
const PROFCOLLECT_NODE_ID_PROPERTY: &str = "persist.profcollectd.node_id";
-const DEFAULT_BINARY_FILTER: &str = "^/(system|apex/.+)/(bin|lib|lib64)/.+";
+const DEFAULT_BINARY_FILTER: &str =
+ "(^/(system|apex/.+|vendor)/(bin|lib|lib64)/.+)|kernel.kallsyms";
pub const REPORT_RETENTION_SECS: u64 = 14 * 24 * 60 * 60; // 14 days.
// Static configs that cannot be changed.
-lazy_static! {
- pub static ref TRACE_OUTPUT_DIR: &'static Path = Path::new("/data/misc/profcollectd/trace/");
- pub static ref PROFILE_OUTPUT_DIR: &'static Path = Path::new("/data/misc/profcollectd/output/");
- pub static ref REPORT_OUTPUT_DIR: &'static Path = Path::new("/data/misc/profcollectd/report/");
- pub static ref CONFIG_FILE: &'static Path =
- Path::new("/data/misc/profcollectd/output/config.json");
-}
+pub static TRACE_OUTPUT_DIR: Lazy<&'static Path> =
+ Lazy::new(|| Path::new("/data/misc/profcollectd/trace/"));
+pub static PROFILE_OUTPUT_DIR: Lazy<&'static Path> =
+ Lazy::new(|| Path::new("/data/misc/profcollectd/output/"));
+pub static REPORT_OUTPUT_DIR: Lazy<&'static Path> =
+ Lazy::new(|| Path::new("/data/misc/profcollectd/report/"));
+pub static CONFIG_FILE: Lazy<&'static Path> =
+ Lazy::new(|| Path::new("/data/misc/profcollectd/output/config.json"));
+pub static LOG_FILE: Lazy<&'static Path> =
+ Lazy::new(|| Path::new("/data/misc/profcollectd/output/trace.log"));
/// Dynamic configs, stored in config.json.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
@@ -53,8 +57,6 @@ pub struct Config {
pub build_fingerprint: String,
/// Interval between collections.
pub collection_interval: Duration,
- /// Length of time each collection lasts for.
- pub sampling_period: Duration,
/// An optional filter to limit which binaries to or not to profile.
pub binary_filter: String,
/// Maximum size of the trace directory.
@@ -71,7 +73,6 @@ impl Config {
"collection_interval",
600,
)?),
- sampling_period: Duration::from_millis(get_device_config("sampling_period", 500)?),
binary_filter: get_device_config("binary_filter", DEFAULT_BINARY_FILTER.to_string())?,
max_trace_limit: get_device_config(
"max_trace_limit",
@@ -114,14 +115,18 @@ where
T::Err: Error + Send + Sync + 'static,
{
let default_value = default_value.to_string();
- let config = profcollect_libflags_rust::GetServerConfigurableFlag(
- PROFCOLLECT_CONFIG_NAMESPACE,
- key,
- &default_value,
- );
+ let config =
+ flags_rust::GetServerConfigurableFlag(PROFCOLLECT_CONFIG_NAMESPACE, key, &default_value);
Ok(T::from_str(&config)?)
}
+pub fn get_sampling_period() -> Duration {
+ let default_period = 500;
+ Duration::from_millis(
+ get_device_config("sampling_period", default_period).unwrap_or(default_period),
+ )
+}
+
fn get_property<T>(key: &str, default_value: T) -> Result<T>
where
T: FromStr + ToString,
@@ -151,7 +156,7 @@ pub fn clear_data() -> Result<()> {
read_dir(path)?
.filter_map(|e| e.ok())
.map(|e| e.path())
- .filter(|e| e.is_file())
+ .filter(|e| e.is_file() && e != *LOG_FILE)
.try_for_each(remove_file)?;
Ok(())
}
@@ -161,3 +166,11 @@ pub fn clear_data() -> Result<()> {
remove_files(&REPORT_OUTPUT_DIR)?;
Ok(())
}
+pub fn clear_processed_files() -> Result<()> {
+ read_dir(&PROFILE_OUTPUT_DIR as &Path)?
+ .filter_map(|e| e.ok())
+ .map(|e| e.path())
+ .filter(|e| e.is_file() && e != (&CONFIG_FILE as &Path))
+ .try_for_each(remove_file)?;
+ Ok(())
+}
diff --git a/profcollectd/libprofcollectd/lib.rs b/profcollectd/libprofcollectd/lib.rs
index da178f27..c8e39753 100644
--- a/profcollectd/libprofcollectd/lib.rs
+++ b/profcollectd/libprofcollectd/lib.rs
@@ -21,6 +21,7 @@ mod report;
mod scheduler;
mod service;
mod simpleperf_etm_trace_provider;
+mod simpleperf_lbr_trace_provider;
mod trace_provider;
#[cfg(feature = "test")]
@@ -114,7 +115,7 @@ pub fn process() -> Result<()> {
/// Process traces and report profile.
pub fn report() -> Result<String> {
- Ok(get_profcollectd_service()?.report()?)
+ Ok(get_profcollectd_service()?.report(report::NO_USAGE_SETTING)?)
}
/// Clear all local data.
@@ -125,11 +126,12 @@ pub fn reset() -> Result<()> {
/// Inits logging for Android
pub fn init_logging() {
- let min_log_level = if cfg!(feature = "test") { log::Level::Info } else { log::Level::Error };
+ let max_log_level =
+ if cfg!(feature = "test") { log::LevelFilter::Info } else { log::LevelFilter::Error };
android_logger::init_once(
android_logger::Config::default()
.with_tag("profcollectd")
- .with_min_level(min_log_level)
- .with_log_id(android_logger::LogId::System),
+ .with_max_level(max_log_level)
+ .with_log_buffer(android_logger::LogId::System),
);
}
diff --git a/profcollectd/libprofcollectd/logging_trace_provider.rs b/profcollectd/libprofcollectd/logging_trace_provider.rs
index fda4c66a..72e1c0d2 100644
--- a/profcollectd/libprofcollectd/logging_trace_provider.rs
+++ b/profcollectd/libprofcollectd/logging_trace_provider.rs
@@ -36,7 +36,7 @@ impl TraceProvider for LoggingTraceProvider {
true
}
- fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration) {
+ fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration, _binary_filter: &str) {
let trace_file = trace_provider::get_path(trace_dir, tag, LOGGING_TRACEFILE_EXTENSION);
log::info!(
@@ -47,10 +47,13 @@ impl TraceProvider for LoggingTraceProvider {
);
}
- fn process(&self, _trace_dir: &Path, _profile_dir: &Path) -> Result<()> {
+ fn process(&self, _trace_dir: &Path, _profile_dir: &Path, _binary_filter: &str) -> Result<()> {
log::info!("Process event triggered");
Ok(())
}
+
+ fn set_log_file(&self, _filename: &Path) {}
+ fn reset_log_file(&self) {}
}
impl LoggingTraceProvider {
diff --git a/profcollectd/libprofcollectd/report.rs b/profcollectd/libprofcollectd/report.rs
index 22789bd8..60410c1a 100644
--- a/profcollectd/libprofcollectd/report.rs
+++ b/profcollectd/libprofcollectd/report.rs
@@ -17,7 +17,6 @@
//! Pack profiles into reports.
use anyhow::{anyhow, Result};
-use lazy_static::lazy_static;
use macaddr::MacAddr6;
use std::fs::{self, File, Permissions};
use std::io::{Read, Write};
@@ -30,13 +29,18 @@ use zip::write::FileOptions;
use zip::CompressionMethod::Deflated;
use zip::ZipWriter;
-use crate::config::Config;
+use crate::config::{clear_processed_files, Config};
-lazy_static! {
- pub static ref UUID_CONTEXT: Context = Context::new(0);
-}
+pub const NO_USAGE_SETTING: i32 = -1;
+
+pub static UUID_CONTEXT: Context = Context::new(0);
-pub fn pack_report(profile: &Path, report: &Path, config: &Config) -> Result<String> {
+pub fn pack_report(
+ profile: &Path,
+ report: &Path,
+ config: &Config,
+ usage_setting: i32,
+) -> Result<String> {
let mut report = PathBuf::from(report);
let report_filename = get_report_filename(&config.node_id)?;
report.push(&report_filename);
@@ -70,23 +74,31 @@ pub fn pack_report(profile: &Path, report: &Path, config: &Config) -> Result<Str
zip.write_all(&buffer)?;
Ok(())
})?;
+
+ if usage_setting != NO_USAGE_SETTING {
+ zip.start_file("usage_setting", options)?;
+ zip.write_all(usage_setting.to_string().as_bytes())?;
+ }
zip.finish()?;
+ clear_processed_files()?;
Ok(report_filename)
}
fn get_report_filename(node_id: &MacAddr6) -> Result<String> {
let since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
- let ts =
- Timestamp::from_unix(&*UUID_CONTEXT, since_epoch.as_secs(), since_epoch.subsec_nanos());
- let uuid = Uuid::new_v1(ts, node_id.as_bytes())?;
+ let ts = Timestamp::from_unix(&UUID_CONTEXT, since_epoch.as_secs(), since_epoch.subsec_nanos());
+ let uuid = Uuid::new_v1(
+ ts,
+ node_id.as_bytes().try_into().expect("Invalid number of bytes in V1 UUID"),
+ );
Ok(uuid.to_string())
}
/// Get report creation timestamp through its filename (version 1 UUID).
pub fn get_report_ts(filename: &str) -> Result<SystemTime> {
let uuid_ts = Uuid::parse_str(filename)?
- .to_timestamp()
+ .get_timestamp()
.ok_or_else(|| anyhow!("filename is not a valid V1 UUID."))?
.to_unix();
Ok(SystemTime::UNIX_EPOCH + Duration::new(uuid_ts.0, uuid_ts.1))
diff --git a/profcollectd/libprofcollectd/scheduler.rs b/profcollectd/libprofcollectd/scheduler.rs
index 9af4d5d3..8695f57c 100644
--- a/profcollectd/libprofcollectd/scheduler.rs
+++ b/profcollectd/libprofcollectd/scheduler.rs
@@ -25,7 +25,7 @@ use std::sync::Mutex;
use std::thread;
use std::time::{Duration, Instant};
-use crate::config::{Config, PROFILE_OUTPUT_DIR, TRACE_OUTPUT_DIR};
+use crate::config::{get_sampling_period, Config, LOG_FILE, PROFILE_OUTPUT_DIR, TRACE_OUTPUT_DIR};
use crate::trace_provider::{self, TraceProvider};
use anyhow::{anyhow, ensure, Context, Result};
@@ -41,6 +41,7 @@ pub struct Scheduler {
impl Scheduler {
pub fn new() -> Result<Self> {
let p = trace_provider::get_trace_provider()?;
+ p.lock().map_err(|e| anyhow!(e.to_string()))?.set_log_file(&LOG_FILE);
Ok(Scheduler {
termination_ch: None,
trace_provider: p,
@@ -72,7 +73,8 @@ impl Scheduler {
trace_provider.lock().unwrap().trace(
&TRACE_OUTPUT_DIR,
"periodic",
- &config.sampling_period,
+ &get_sampling_period(),
+ &config.binary_filter,
);
}
}
@@ -95,7 +97,12 @@ impl Scheduler {
pub fn one_shot(&self, config: &Config, tag: &str) -> Result<()> {
let trace_provider = self.trace_provider.clone();
if check_space_limit(&TRACE_OUTPUT_DIR, config)? {
- trace_provider.lock().unwrap().trace(&TRACE_OUTPUT_DIR, tag, &config.sampling_period);
+ trace_provider.lock().unwrap().trace(
+ &TRACE_OUTPUT_DIR,
+ tag,
+ &get_sampling_period(),
+ &config.binary_filter,
+ );
}
Ok(())
}
@@ -158,6 +165,17 @@ impl Scheduler {
}
});
}
+
+ pub fn clear_trace_log(&self) -> Result<()> {
+ let provider = self.trace_provider.lock().map_err(|e| anyhow!(e.to_string()))?;
+ provider.reset_log_file();
+ let mut result = Ok(());
+ if LOG_FILE.exists() {
+ result = fs::remove_file(*LOG_FILE).map_err(|e| anyhow!(e));
+ }
+ provider.set_log_file(&LOG_FILE);
+ result
+ }
}
/// Run if space usage is under limit.
diff --git a/profcollectd/libprofcollectd/service.rs b/profcollectd/libprofcollectd/service.rs
index 8ae51527..3188888f 100644
--- a/profcollectd/libprofcollectd/service.rs
+++ b/profcollectd/libprofcollectd/service.rs
@@ -79,11 +79,11 @@ impl IProfCollectd for ProfcollectdBinderService {
.context("Failed to process profiles.")
.map_err(err_to_binder_status)
}
- fn report(&self) -> BinderResult<String> {
+ fn report(&self, usage_setting: i32) -> BinderResult<String> {
self.process()?;
let lock = &mut *self.lock();
- pack_report(&PROFILE_OUTPUT_DIR, &REPORT_OUTPUT_DIR, &lock.config)
+ pack_report(&PROFILE_OUTPUT_DIR, &REPORT_OUTPUT_DIR, &lock.config, usage_setting)
.context("Failed to create profile report.")
.map_err(err_to_binder_status)
}
@@ -132,6 +132,7 @@ impl ProfcollectdBinderService {
clear_data()?;
write(*CONFIG_FILE, new_config.to_string())?;
+ new_scheduler.clear_trace_log()?;
}
// Clear profile reports out of rentention period.
diff --git a/profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs b/profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs
index 14911ffb..8e6933e0 100644
--- a/profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs
+++ b/profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs
@@ -35,17 +35,32 @@ impl TraceProvider for SimpleperfEtmTraceProvider {
}
fn is_ready(&self) -> bool {
- simpleperf_profcollect::has_device_support()
+ simpleperf_profcollect::is_etm_device_available()
}
- fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration) {
+ fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration, binary_filter: &str) {
let trace_file = trace_provider::get_path(trace_dir, tag, ETM_TRACEFILE_EXTENSION);
+ // Record ETM data for kernel space only when it's not filtered out by binary_filter. So we
+ // can get more ETM data for user space when ETM data for kernel space isn't needed.
+ let event_name = if binary_filter.contains("kernel") { "cs-etm" } else { "cs-etm:u" };
+ let duration: String = sampling_period.as_secs_f64().to_string();
+ let args: Vec<&str> = vec![
+ "-a",
+ "-e",
+ event_name,
+ "--duration",
+ &duration,
+ "--decode-etm",
+ "--exclude-perf",
+ "--binary",
+ binary_filter,
+ "--no-dump-symbols",
+ "--no-dump-kernel-symbols",
+ "-o",
+ trace_file.to_str().unwrap(),
+ ];
- simpleperf_profcollect::record(
- &trace_file,
- sampling_period,
- simpleperf_profcollect::RecordScope::BOTH,
- );
+ simpleperf_profcollect::run_record_cmd(&args);
}
fn process(&self, trace_dir: &Path, profile_dir: &Path, binary_filter: &str) -> Result<()> {
@@ -64,7 +79,18 @@ impl TraceProvider for SimpleperfEtmTraceProvider {
.ok_or_else(|| anyhow!("Malformed trace path: {}", trace_file.display()))?,
);
profile_file.set_extension(ETM_PROFILE_EXTENSION);
- simpleperf_profcollect::process(&trace_file, &profile_file, binary_filter);
+
+ let args: Vec<&str> = vec![
+ "-i",
+ trace_file.to_str().unwrap(),
+ "-o",
+ profile_file.to_str().unwrap(),
+ "--output",
+ "branch-list",
+ "--binary",
+ binary_filter,
+ ];
+ simpleperf_profcollect::run_inject_cmd(&args);
remove_file(&trace_file)?;
Ok(())
};
@@ -76,10 +102,18 @@ impl TraceProvider for SimpleperfEtmTraceProvider {
.filter(is_etm_extension)
.try_for_each(process_trace_file)
}
+
+ fn set_log_file(&self, filename: &Path) {
+ simpleperf_profcollect::set_log_file(filename);
+ }
+
+ fn reset_log_file(&self) {
+ simpleperf_profcollect::reset_log_file();
+ }
}
impl SimpleperfEtmTraceProvider {
pub fn supported() -> bool {
- simpleperf_profcollect::has_driver_support()
+ simpleperf_profcollect::is_etm_driver_available()
}
}
diff --git a/profcollectd/libprofcollectd/simpleperf_lbr_trace_provider.rs b/profcollectd/libprofcollectd/simpleperf_lbr_trace_provider.rs
new file mode 100644
index 00000000..d59a7a4a
--- /dev/null
+++ b/profcollectd/libprofcollectd/simpleperf_lbr_trace_provider.rs
@@ -0,0 +1,119 @@
+//
+// 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 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.
+//
+
+//! Trace provider backed by Intel LBR, using simpleperf tool.
+use anyhow::{anyhow, Result};
+use std::fs::{read_dir, remove_file};
+use std::path::{Path, PathBuf};
+use std::time::Duration;
+use trace_provider::TraceProvider;
+
+use crate::trace_provider;
+
+static LBR_TRACEFILE_EXTENSION: &str = "lbrtrace";
+static LBR_PROFILE_EXTENSION: &str = "data";
+
+pub struct SimpleperfLbrTraceProvider {}
+
+impl TraceProvider for SimpleperfLbrTraceProvider {
+ fn get_name(&self) -> &'static str {
+ "simpleperf_lbr"
+ }
+
+ fn is_ready(&self) -> bool {
+ true
+ }
+
+ fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration, binary_filter: &str) {
+ let trace_file = trace_provider::get_path(trace_dir, tag, LBR_TRACEFILE_EXTENSION);
+ // Record ETM data for kernel space only when it's not filtered out by binary_filter. So we
+ // can get more ETM data for user space when ETM data for kernel space isn't needed.
+ let event_name =
+ if binary_filter.contains("kernel") { "cpu-cycles" } else { "cpu-cycles:u" };
+ let duration: String = sampling_period.as_secs_f64().to_string();
+ let args: Vec<&str> = vec![
+ "-a",
+ "-e",
+ event_name,
+ "--duration",
+ &duration,
+ "-b",
+ "--exclude-perf",
+ "--binary",
+ binary_filter,
+ "--no-dump-symbols",
+ "--no-dump-kernel-symbols",
+ "-o",
+ trace_file.to_str().unwrap(),
+ ];
+
+ simpleperf_profcollect::run_record_cmd(&args);
+ }
+
+ fn process(&self, trace_dir: &Path, profile_dir: &Path, binary_filter: &str) -> Result<()> {
+ let is_lbr_extension = |file: &PathBuf| {
+ file.extension()
+ .and_then(|f| f.to_str())
+ .filter(|ext| ext == &LBR_TRACEFILE_EXTENSION)
+ .is_some()
+ };
+
+ let process_trace_file = |trace_file: PathBuf| {
+ let mut profile_file = PathBuf::from(profile_dir);
+ profile_file.push(
+ trace_file
+ .file_name()
+ .ok_or_else(|| anyhow!("Malformed trace path: {}", trace_file.display()))?,
+ );
+ profile_file.set_extension(LBR_PROFILE_EXTENSION);
+
+ let args: Vec<&str> = vec![
+ "-i",
+ trace_file.to_str().unwrap(),
+ "-o",
+ profile_file.to_str().unwrap(),
+ "--output",
+ "branch-list",
+ "--binary",
+ binary_filter,
+ ];
+ simpleperf_profcollect::run_inject_cmd(&args);
+ remove_file(&trace_file)?;
+ Ok(())
+ };
+
+ read_dir(trace_dir)?
+ .filter_map(|e| e.ok())
+ .map(|e| e.path())
+ .filter(|e| e.is_file())
+ .filter(is_lbr_extension)
+ .try_for_each(process_trace_file)
+ }
+
+ fn set_log_file(&self, filename: &Path) {
+ simpleperf_profcollect::set_log_file(filename);
+ }
+
+ fn reset_log_file(&self) {
+ simpleperf_profcollect::reset_log_file();
+ }
+}
+
+impl SimpleperfLbrTraceProvider {
+ pub fn supported() -> bool {
+ simpleperf_profcollect::is_lbr_available()
+ }
+}
diff --git a/profcollectd/libprofcollectd/trace_provider.rs b/profcollectd/libprofcollectd/trace_provider.rs
index 13059199..20906219 100644
--- a/profcollectd/libprofcollectd/trace_provider.rs
+++ b/profcollectd/libprofcollectd/trace_provider.rs
@@ -23,6 +23,7 @@ use std::sync::{Arc, Mutex};
use std::time::Duration;
use crate::simpleperf_etm_trace_provider::SimpleperfEtmTraceProvider;
+use crate::simpleperf_lbr_trace_provider::SimpleperfLbrTraceProvider;
#[cfg(feature = "test")]
use crate::logging_trace_provider::LoggingTraceProvider;
@@ -30,8 +31,10 @@ use crate::logging_trace_provider::LoggingTraceProvider;
pub trait TraceProvider {
fn get_name(&self) -> &'static str;
fn is_ready(&self) -> bool;
- fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration);
+ fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration, binary_filter: &str);
fn process(&self, trace_dir: &Path, profile_dir: &Path, binary_filter: &str) -> Result<()>;
+ fn set_log_file(&self, filename: &Path);
+ fn reset_log_file(&self);
}
pub fn get_trace_provider() -> Result<Arc<Mutex<dyn TraceProvider + Send>>> {
@@ -39,6 +42,10 @@ pub fn get_trace_provider() -> Result<Arc<Mutex<dyn TraceProvider + Send>>> {
log::info!("simpleperf_etm trace provider registered.");
return Ok(Arc::new(Mutex::new(SimpleperfEtmTraceProvider {})));
}
+ if SimpleperfLbrTraceProvider::supported() {
+ log::info!("simpleperf_lbr trace provider registered.");
+ return Ok(Arc::new(Mutex::new(SimpleperfLbrTraceProvider {})));
+ }
#[cfg(feature = "test")]
if LoggingTraceProvider::supported() {
diff --git a/setuclamp/setuclamp.cpp b/setuclamp/setuclamp.cpp
deleted file mode 100644
index e40fb0fd..00000000
--- a/setuclamp/setuclamp.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2021 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.
- */
-
-#include <stdlib.h>
-#include <sys/syscall.h>
-#include <unistd.h>
-
-#include <iostream>
-
-[[noreturn]] static void usage(int exit_status) {
- std::cerr << "Usage: " << getprogname() << " <tid> <uclamp_min> <uclamp_max>" << std::endl
- << " tid Thread ID to apply the uclamp setting." << std::endl
- << " uclamp_min uclamp.min value range from [0, 1024]." << std::endl
- << " uclamp_max uclamp.max value range from [0, 1024]." << std::endl;
- exit(exit_status);
-}
-
-struct sched_attr {
- __u32 size;
- __u32 sched_policy;
- __u64 sched_flags;
- __s32 sched_nice;
- __u32 sched_priority;
- __u64 sched_runtime;
- __u64 sched_deadline;
- __u64 sched_period;
- __u32 sched_util_min;
- __u32 sched_util_max;
-};
-
-static int sched_setattr(int pid, struct sched_attr* attr, unsigned int flags) {
- return syscall(__NR_sched_setattr, pid, attr, flags);
-}
-
-static int set_uclamp(int32_t min, int32_t max, int tid) {
- sched_attr attr = {};
- attr.size = sizeof(attr);
-
- attr.sched_flags = (SCHED_FLAG_KEEP_ALL | SCHED_FLAG_UTIL_CLAMP);
- attr.sched_util_min = min;
- attr.sched_util_max = max;
-
- int ret = sched_setattr(tid, &attr, 0);
- if (ret) {
- int err = errno;
- std::cerr << "sched_setattr failed for thread " << tid << " err=" << err << std::endl;
- }
-
- return ret;
-}
-
-int main(int argc, char* argv[]) {
- if (argc != 4) {
- usage(EXIT_FAILURE);
- }
-
- int tid = atoi(argv[1]);
- int uclamp_min = atoi(argv[2]);
- int uclamp_max = atoi(argv[3]);
-
- return set_uclamp(uclamp_min, uclamp_max, tid);
-}
diff --git a/simpleperf/Android.bp b/simpleperf/Android.bp
index e44e12e1..4ebc0ed2 100644
--- a/simpleperf/Android.bp
+++ b/simpleperf/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_llvm_and_toolchains",
default_applicable_licenses: ["system_extras_simpleperf_license"],
}
@@ -32,18 +33,21 @@ license {
}
cc_defaults {
- name: "libsimpleperf_elf_read_static_reqs_defaults",
+ name: "libsimpleperf_readelf_defaults",
static_libs: [
- "libLLVMObject",
- "libLLVMBitReader",
- "libLLVMMC",
- "libLLVMMCParser",
- "libLLVMCore",
- "libLLVMSupport",
- "liblzma",
- "libz",
- "libziparchive",
+ "libsimpleperf_readelf",
],
+ target: {
+ linux: {
+ ldflags: ["-Wl,--exclude-libs=libsimpleperf_readelf.a"],
+ },
+ windows: {
+ host_ldlibs: [
+ "-lole32",
+ "-luuid",
+ ],
+ },
+ },
}
cc_defaults {
@@ -54,6 +58,7 @@ cc_defaults {
"-DUSE_BIONIC_UAPI_HEADERS",
"-fvisibility=hidden",
],
+ compile_multilib: "64",
include_dirs: ["bionic/libc/kernel"],
},
darwin: {
@@ -64,10 +69,8 @@ cc_defaults {
cflags: ["-DNO_LIBDEXFILE_SUPPORT"],
local_include_dirs: ["nonlinux_support/include"],
},
- },
- arch: {
- riscv64: {
- enabled: false,
+ android: {
+ compile_multilib: "both",
},
},
}
@@ -77,7 +80,7 @@ cc_library_static {
name: "libsimpleperf_etm_decoder",
defaults: [
"simpleperf_cflags",
- "libsimpleperf_elf_read_static_reqs_defaults",
+ "libsimpleperf_readelf_defaults",
],
host_supported: true,
srcs: ["ETMDecoder.cpp"],
@@ -120,7 +123,7 @@ cc_library_static {
cc_defaults {
name: "simpleperf_static_libs",
defaults: [
- "libsimpleperf_elf_read_static_reqs_defaults",
+ "libsimpleperf_readelf_defaults",
"simpleperf_cflags",
],
host_supported: true,
@@ -129,9 +132,12 @@ cc_defaults {
"libsimpleperf_regex",
"libbase",
"liblog",
+ "liblzma",
"libutils",
"libprotobuf-cpp-lite",
"libopencsd_decoder",
+ "libz",
+ "libziparchive",
],
target: {
linux: {
@@ -162,12 +168,15 @@ cc_defaults {
name: "simpleperf_shared_libs",
defaults: [
"simpleperf_cflags",
+ "libsimpleperf_readelf_defaults",
],
host_supported: true,
shared_libs: [
"libbase",
+ "liblog",
"liblzma",
"libprotobuf-cpp-lite",
+ "libz",
"libziparchive",
],
static_libs: [
@@ -180,7 +189,6 @@ cc_defaults {
shared_libs: [
"libcutils",
"libevent",
- "liblog",
"libprocinfo",
"libunwindstack",
],
@@ -193,16 +201,6 @@ cc_defaults {
"libdexfile", // libdexfile_support dependency
],
},
- host: {
- static_libs: [
- "libLLVMObject",
- "libLLVMBitReader",
- "libLLVMMC",
- "libLLVMMCParser",
- "libLLVMCore",
- "libLLVMSupport",
- ],
- },
windows: {
enabled: true,
},
@@ -210,22 +208,17 @@ cc_defaults {
use_version_lib: true,
}
-cc_defaults {
- name: "simpleperf_libs_for_tests",
- defaults: ["simpleperf_shared_libs"],
- target: {
- android: {
- // 32-bit libLLVM_android isn't shipped on device. So use static llvm libs in tests.
- static_libs: [
- "libLLVMObject",
- "libLLVMBitReader",
- "libLLVMMC",
- "libLLVMMCParser",
- "libLLVMCore",
- "libLLVMSupport",
- ],
- },
- },
+python_binary_host {
+ name: "event_table_generator",
+ srcs: ["event_table_generator.py"],
+}
+
+genrule {
+ name: "simpleperf_event_table",
+ out: ["event_table.cpp"],
+ srcs: ["event_table.json"],
+ tools: ["event_table_generator"],
+ cmd: "$(location event_table_generator) $(in) $(out)",
}
cc_defaults {
@@ -241,7 +234,8 @@ cc_defaults {
"cmd_report_sample.proto",
"command.cpp",
"dso.cpp",
- "etm_branch_list.proto",
+ "branch_list.proto",
+ "BranchListFile.cpp",
"event_attr.cpp",
"event_type.cpp",
"kallsyms.cpp",
@@ -258,6 +252,7 @@ cc_defaults {
"thread_tree.cpp",
"tracing.cpp",
"utils.cpp",
+ ":simpleperf_event_table",
],
target: {
android: {
@@ -304,7 +299,6 @@ cc_library_static {
"libsimpleperf_srcs",
"simpleperf_static_libs",
],
- compile_multilib: "both",
proto: {
type: "lite",
},
@@ -335,9 +329,7 @@ cc_binary {
static_libs: ["libsimpleperf"],
target: {
android: {
- shared_libs: [
- "libLLVM_android",
- ],
+ compile_multilib: "first",
},
},
init_rc: ["simpleperf.rc"],
@@ -348,9 +340,9 @@ cc_library {
defaults: ["simpleperf_shared_libs"],
srcs: ["profcollect.cpp"],
host_supported: false,
+ ldflags: ["-Wl,--exclude-libs=ALL"],
static_libs: ["libsimpleperf"],
shared_libs: [
- "libLLVM_android",
"libpower",
],
}
@@ -390,6 +382,11 @@ cc_binary {
dist: {
targets: ["simpleperf"],
},
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ },
srcs: [
"main.cpp",
],
@@ -397,13 +394,6 @@ cc_binary {
"libsimpleperf",
],
- compile_multilib: "both",
- multilib: {
- lib64: {
- suffix: "64",
- },
- },
-
target: {
android: {
static_executable: true,
@@ -431,6 +421,9 @@ cc_binary {
dir: "simpleperf/android/x86_64",
},
},
+ host: {
+ stem: "simpleperf",
+ },
darwin: {
dist: {
dir: "simpleperf/darwin/x86",
@@ -583,6 +576,7 @@ cc_defaults {
"cmd_report_test.cpp",
],
srcs: [
+ "BranchListFile_test.cpp",
"cmd_inject_test.cpp",
"cmd_kmem_test.cpp",
"cmd_merge_test.cpp",
@@ -629,6 +623,7 @@ cc_defaults {
"cmd_stat_test.cpp",
"cmd_trace_sched_test.cpp",
"environment_test.cpp",
+ "event_selection_set_test.cpp",
"IOEventLoop_test.cpp",
"JITDebugReader_test.cpp",
"MapRecordReader_test.cpp",
@@ -646,7 +641,7 @@ cc_test {
name: "simpleperf_unit_test",
defaults: [
"simpleperf_test_srcs",
- "simpleperf_libs_for_tests",
+ "simpleperf_shared_libs",
],
static_libs: [
"libgmock",
@@ -668,7 +663,7 @@ cc_test {
cc_test {
name: "simpleperf_cpu_hotplug_test",
defaults: [
- "simpleperf_libs_for_tests",
+ "simpleperf_shared_libs",
],
test_options: {
unit_test: true,
@@ -694,7 +689,7 @@ cc_library_static {
name: "libsimpleperf_cts_test",
defaults: [
"simpleperf_test_srcs",
- "simpleperf_libs_for_tests",
+ "simpleperf_shared_libs",
],
host_supported: false,
cflags: [
@@ -713,7 +708,7 @@ cc_library_static {
cc_test {
name: "simpleperf_record_test",
defaults: [
- "simpleperf_libs_for_tests",
+ "simpleperf_shared_libs",
],
srcs: [
"record_lib_test.cpp",
diff --git a/simpleperf/BranchListFile.cpp b/simpleperf/BranchListFile.cpp
new file mode 100644
index 00000000..253dcdbc
--- /dev/null
+++ b/simpleperf/BranchListFile.cpp
@@ -0,0 +1,440 @@
+/*
+ * 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 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.
+ */
+
+#include "BranchListFile.h"
+
+#include "ETMDecoder.h"
+#include "system/extras/simpleperf/branch_list.pb.h"
+
+namespace simpleperf {
+
+static constexpr const char* ETM_BRANCH_LIST_PROTO_MAGIC = "simpleperf:EtmBranchList";
+
+std::string ETMBranchToProtoString(const std::vector<bool>& branch) {
+ size_t bytes = (branch.size() + 7) / 8;
+ std::string res(bytes, '\0');
+ for (size_t i = 0; i < branch.size(); i++) {
+ if (branch[i]) {
+ res[i >> 3] |= 1 << (i & 7);
+ }
+ }
+ return res;
+}
+
+std::vector<bool> ProtoStringToETMBranch(const std::string& s, size_t bit_size) {
+ std::vector<bool> branch(bit_size, false);
+ for (size_t i = 0; i < bit_size; i++) {
+ if (s[i >> 3] & (1 << (i & 7))) {
+ branch[i] = true;
+ }
+ }
+ return branch;
+}
+
+static std::optional<proto::ETMBinary::BinaryType> ToProtoBinaryType(DsoType dso_type) {
+ switch (dso_type) {
+ case DSO_ELF_FILE:
+ return proto::ETMBinary::ELF_FILE;
+ case DSO_KERNEL:
+ return proto::ETMBinary::KERNEL;
+ case DSO_KERNEL_MODULE:
+ return proto::ETMBinary::KERNEL_MODULE;
+ default:
+ LOG(ERROR) << "unexpected dso type " << dso_type;
+ return std::nullopt;
+ }
+}
+
+bool ETMBinaryMapToString(const ETMBinaryMap& binary_map, std::string& s) {
+ proto::BranchList branch_list_proto;
+ branch_list_proto.set_magic(ETM_BRANCH_LIST_PROTO_MAGIC);
+ std::vector<char> branch_buf;
+ for (const auto& p : binary_map) {
+ const BinaryKey& key = p.first;
+ const ETMBinary& binary = p.second;
+ auto binary_proto = branch_list_proto.add_etm_data();
+
+ binary_proto->set_path(key.path);
+ if (!key.build_id.IsEmpty()) {
+ binary_proto->set_build_id(key.build_id.ToString().substr(2));
+ }
+ auto opt_binary_type = ToProtoBinaryType(binary.dso_type);
+ if (!opt_binary_type.has_value()) {
+ return false;
+ }
+ binary_proto->set_type(opt_binary_type.value());
+
+ for (const auto& addr_p : binary.branch_map) {
+ auto addr_proto = binary_proto->add_addrs();
+ addr_proto->set_addr(addr_p.first);
+
+ for (const auto& branch_p : addr_p.second) {
+ const std::vector<bool>& branch = branch_p.first;
+ auto branch_proto = addr_proto->add_branches();
+
+ branch_proto->set_branch(ETMBranchToProtoString(branch));
+ branch_proto->set_branch_size(branch.size());
+ branch_proto->set_count(branch_p.second);
+ }
+ }
+
+ if (binary.dso_type == DSO_KERNEL) {
+ binary_proto->mutable_kernel_info()->set_kernel_start_addr(key.kernel_start_addr);
+ }
+ }
+ if (!branch_list_proto.SerializeToString(&s)) {
+ LOG(ERROR) << "failed to serialize branch list binary map";
+ return false;
+ }
+ return true;
+}
+
+static std::optional<DsoType> ToDsoType(proto::ETMBinary::BinaryType binary_type) {
+ switch (binary_type) {
+ case proto::ETMBinary::ELF_FILE:
+ return DSO_ELF_FILE;
+ case proto::ETMBinary::KERNEL:
+ return DSO_KERNEL;
+ case proto::ETMBinary::KERNEL_MODULE:
+ return DSO_KERNEL_MODULE;
+ default:
+ LOG(ERROR) << "unexpected binary type " << binary_type;
+ return std::nullopt;
+ }
+}
+
+static UnorderedETMBranchMap BuildUnorderedETMBranchMap(const proto::ETMBinary& binary_proto) {
+ UnorderedETMBranchMap branch_map;
+ for (size_t i = 0; i < binary_proto.addrs_size(); i++) {
+ const auto& addr_proto = binary_proto.addrs(i);
+ auto& b_map = branch_map[addr_proto.addr()];
+ for (size_t j = 0; j < addr_proto.branches_size(); j++) {
+ const auto& branch_proto = addr_proto.branches(j);
+ std::vector<bool> branch =
+ ProtoStringToETMBranch(branch_proto.branch(), branch_proto.branch_size());
+ b_map[branch] = branch_proto.count();
+ }
+ }
+ return branch_map;
+}
+
+bool StringToETMBinaryMap(const std::string& s, ETMBinaryMap& binary_map) {
+ LBRData lbr_data;
+ return ParseBranchListData(s, binary_map, lbr_data);
+}
+
+class ETMThreadTreeWhenRecording : public ETMThreadTree {
+ public:
+ ETMThreadTreeWhenRecording(bool dump_maps_from_proc)
+ : dump_maps_from_proc_(dump_maps_from_proc) {}
+
+ ThreadTree& GetThreadTree() { return thread_tree_; }
+ void ExcludePid(pid_t pid) { exclude_pid_ = pid; }
+
+ const ThreadEntry* FindThread(int tid) override {
+ const ThreadEntry* thread = thread_tree_.FindThread(tid);
+ if (thread == nullptr) {
+ if (dump_maps_from_proc_) {
+ thread = FindThreadFromProc(tid);
+ }
+ if (thread == nullptr) {
+ return nullptr;
+ }
+ }
+ if (exclude_pid_ && exclude_pid_ == thread->pid) {
+ return nullptr;
+ }
+
+ if (dump_maps_from_proc_) {
+ DumpMapsFromProc(thread->pid);
+ }
+ return thread;
+ }
+
+ void DisableThreadExitRecords() override { thread_tree_.DisableThreadExitRecords(); }
+ const MapSet& GetKernelMaps() override { return thread_tree_.GetKernelMaps(); }
+
+ private:
+ const ThreadEntry* FindThreadFromProc(int tid) {
+ std::string comm;
+ pid_t pid;
+ if (ReadThreadNameAndPid(tid, &comm, &pid)) {
+ thread_tree_.SetThreadName(pid, tid, comm);
+ return thread_tree_.FindThread(tid);
+ }
+ return nullptr;
+ }
+
+ void DumpMapsFromProc(int pid) {
+ if (dumped_processes_.count(pid) == 0) {
+ dumped_processes_.insert(pid);
+ std::vector<ThreadMmap> maps;
+ if (GetThreadMmapsInProcess(pid, &maps)) {
+ for (const auto& map : maps) {
+ thread_tree_.AddThreadMap(pid, pid, map.start_addr, map.len, map.pgoff, map.name);
+ }
+ }
+ }
+ }
+
+ ThreadTree thread_tree_;
+ bool dump_maps_from_proc_;
+ std::unordered_set<int> dumped_processes_;
+ std::optional<pid_t> exclude_pid_;
+};
+
+class ETMBranchListGeneratorImpl : public ETMBranchListGenerator {
+ public:
+ ETMBranchListGeneratorImpl(bool dump_maps_from_proc)
+ : thread_tree_(dump_maps_from_proc), binary_filter_(nullptr) {}
+
+ void SetExcludePid(pid_t pid) override { thread_tree_.ExcludePid(pid); }
+ void SetBinaryFilter(const RegEx* binary_name_regex) override {
+ binary_filter_.SetRegex(binary_name_regex);
+ }
+
+ bool ProcessRecord(const Record& r, bool& consumed) override;
+ ETMBinaryMap GetETMBinaryMap() override;
+
+ private:
+ struct AuxRecordData {
+ uint64_t start;
+ uint64_t end;
+ bool formatted;
+ AuxRecordData(uint64_t start, uint64_t end, bool formatted)
+ : start(start), end(end), formatted(formatted) {}
+ };
+
+ struct PerCpuData {
+ std::vector<uint8_t> aux_data;
+ uint64_t data_offset = 0;
+ std::queue<AuxRecordData> aux_records;
+ };
+
+ bool ProcessAuxRecord(const AuxRecord& r);
+ bool ProcessAuxTraceRecord(const AuxTraceRecord& r);
+ void ProcessBranchList(const ETMBranchList& branch_list);
+
+ ETMThreadTreeWhenRecording thread_tree_;
+ uint64_t kernel_map_start_addr_ = 0;
+ BinaryFilter binary_filter_;
+ std::map<uint32_t, PerCpuData> cpu_map_;
+ std::unique_ptr<ETMDecoder> etm_decoder_;
+ std::unordered_map<Dso*, ETMBinary> branch_list_binary_map_;
+};
+
+bool ETMBranchListGeneratorImpl::ProcessRecord(const Record& r, bool& consumed) {
+ consumed = true; // No need to store any records.
+ uint32_t type = r.type();
+ if (type == PERF_RECORD_AUXTRACE_INFO) {
+ etm_decoder_ = ETMDecoder::Create(*static_cast<const AuxTraceInfoRecord*>(&r), thread_tree_);
+ if (!etm_decoder_) {
+ return false;
+ }
+ etm_decoder_->RegisterCallback(
+ [this](const ETMBranchList& branch) { ProcessBranchList(branch); });
+ return true;
+ }
+ if (type == PERF_RECORD_AUX) {
+ return ProcessAuxRecord(*static_cast<const AuxRecord*>(&r));
+ }
+ if (type == PERF_RECORD_AUXTRACE) {
+ return ProcessAuxTraceRecord(*static_cast<const AuxTraceRecord*>(&r));
+ }
+ if (type == PERF_RECORD_MMAP && r.InKernel()) {
+ auto& mmap_r = *static_cast<const MmapRecord*>(&r);
+ if (android::base::StartsWith(mmap_r.filename, DEFAULT_KERNEL_MMAP_NAME)) {
+ kernel_map_start_addr_ = mmap_r.data->addr;
+ }
+ }
+ thread_tree_.GetThreadTree().Update(r);
+ return true;
+}
+
+bool ETMBranchListGeneratorImpl::ProcessAuxRecord(const AuxRecord& r) {
+ OverflowResult result = SafeAdd(r.data->aux_offset, r.data->aux_size);
+ if (result.overflow || r.data->aux_size > SIZE_MAX) {
+ LOG(ERROR) << "invalid aux record";
+ return false;
+ }
+ size_t size = r.data->aux_size;
+ uint64_t start = r.data->aux_offset;
+ uint64_t end = result.value;
+ PerCpuData& data = cpu_map_[r.Cpu()];
+ if (start >= data.data_offset && end <= data.data_offset + data.aux_data.size()) {
+ // The ETM data is available. Process it now.
+ uint8_t* p = data.aux_data.data() + (start - data.data_offset);
+ if (!etm_decoder_) {
+ LOG(ERROR) << "ETMDecoder isn't created";
+ return false;
+ }
+ return etm_decoder_->ProcessData(p, size, !r.Unformatted(), r.Cpu());
+ }
+ // The ETM data isn't available. Put the aux record into queue.
+ data.aux_records.emplace(start, end, !r.Unformatted());
+ return true;
+}
+
+bool ETMBranchListGeneratorImpl::ProcessAuxTraceRecord(const AuxTraceRecord& r) {
+ OverflowResult result = SafeAdd(r.data->offset, r.data->aux_size);
+ if (result.overflow || r.data->aux_size > SIZE_MAX) {
+ LOG(ERROR) << "invalid auxtrace record";
+ return false;
+ }
+ size_t size = r.data->aux_size;
+ uint64_t start = r.data->offset;
+ uint64_t end = result.value;
+ PerCpuData& data = cpu_map_[r.Cpu()];
+ data.data_offset = start;
+ CHECK(r.location.addr != nullptr);
+ data.aux_data.resize(size);
+ memcpy(data.aux_data.data(), r.location.addr, size);
+
+ // Process cached aux records.
+ while (!data.aux_records.empty() && data.aux_records.front().start < end) {
+ const AuxRecordData& aux = data.aux_records.front();
+ if (aux.start >= start && aux.end <= end) {
+ uint8_t* p = data.aux_data.data() + (aux.start - start);
+ if (!etm_decoder_) {
+ LOG(ERROR) << "ETMDecoder isn't created";
+ return false;
+ }
+ if (!etm_decoder_->ProcessData(p, aux.end - aux.start, aux.formatted, r.Cpu())) {
+ return false;
+ }
+ }
+ data.aux_records.pop();
+ }
+ return true;
+}
+
+void ETMBranchListGeneratorImpl::ProcessBranchList(const ETMBranchList& branch_list) {
+ if (!binary_filter_.Filter(branch_list.dso)) {
+ return;
+ }
+ auto& branch_map = branch_list_binary_map_[branch_list.dso].branch_map;
+ ++branch_map[branch_list.addr][branch_list.branch];
+}
+
+ETMBinaryMap ETMBranchListGeneratorImpl::GetETMBinaryMap() {
+ ETMBinaryMap binary_map;
+ for (auto& p : branch_list_binary_map_) {
+ Dso* dso = p.first;
+ ETMBinary& binary = p.second;
+ binary.dso_type = dso->type();
+ BuildId build_id;
+ GetBuildId(*dso, build_id);
+ BinaryKey key(dso->Path(), build_id);
+ if (binary.dso_type == DSO_KERNEL) {
+ if (kernel_map_start_addr_ == 0) {
+ LOG(WARNING) << "Can't convert kernel ip addresses without kernel start addr. So remove "
+ "branches for the kernel.";
+ continue;
+ }
+ key.kernel_start_addr = kernel_map_start_addr_;
+ }
+ binary_map[key] = std::move(binary);
+ }
+ return binary_map;
+}
+
+std::unique_ptr<ETMBranchListGenerator> ETMBranchListGenerator::Create(bool dump_maps_from_proc) {
+ return std::unique_ptr<ETMBranchListGenerator>(
+ new ETMBranchListGeneratorImpl(dump_maps_from_proc));
+}
+
+ETMBranchListGenerator::~ETMBranchListGenerator() {}
+
+bool LBRDataToString(const LBRData& data, std::string& s) {
+ proto::BranchList branch_list_proto;
+ branch_list_proto.set_magic(ETM_BRANCH_LIST_PROTO_MAGIC);
+ auto lbr_proto = branch_list_proto.mutable_lbr_data();
+ for (const LBRSample& sample : data.samples) {
+ auto sample_proto = lbr_proto->add_samples();
+ sample_proto->set_binary_id(sample.binary_id);
+ sample_proto->set_vaddr_in_file(sample.vaddr_in_file);
+ for (const LBRBranch& branch : sample.branches) {
+ auto branch_proto = sample_proto->add_branches();
+ branch_proto->set_from_binary_id(branch.from_binary_id);
+ branch_proto->set_to_binary_id(branch.to_binary_id);
+ branch_proto->set_from_vaddr_in_file(branch.from_vaddr_in_file);
+ branch_proto->set_to_vaddr_in_file(branch.to_vaddr_in_file);
+ }
+ }
+ for (const BinaryKey& binary : data.binaries) {
+ auto binary_proto = lbr_proto->add_binaries();
+ binary_proto->set_path(binary.path);
+ binary_proto->set_build_id(binary.build_id.ToString().substr(2));
+ }
+ if (!branch_list_proto.SerializeToString(&s)) {
+ LOG(ERROR) << "failed to serialize lbr data";
+ return false;
+ }
+ return true;
+}
+
+bool ParseBranchListData(const std::string& s, ETMBinaryMap& etm_data, LBRData& lbr_data) {
+ proto::BranchList branch_list_proto;
+ if (!branch_list_proto.ParseFromString(s)) {
+ PLOG(ERROR) << "failed to read ETMBranchList msg";
+ return false;
+ }
+ if (branch_list_proto.magic() != ETM_BRANCH_LIST_PROTO_MAGIC) {
+ PLOG(ERROR) << "not in etm branch list format in branch_list.proto";
+ return false;
+ }
+ for (size_t i = 0; i < branch_list_proto.etm_data_size(); i++) {
+ const auto& binary_proto = branch_list_proto.etm_data(i);
+ BinaryKey key(binary_proto.path(), BuildId(binary_proto.build_id()));
+ if (binary_proto.has_kernel_info()) {
+ key.kernel_start_addr = binary_proto.kernel_info().kernel_start_addr();
+ }
+ ETMBinary& binary = etm_data[key];
+ auto dso_type = ToDsoType(binary_proto.type());
+ if (!dso_type) {
+ LOG(ERROR) << "invalid binary type " << binary_proto.type();
+ return false;
+ }
+ binary.dso_type = dso_type.value();
+ binary.branch_map = BuildUnorderedETMBranchMap(binary_proto);
+ }
+ if (branch_list_proto.has_lbr_data()) {
+ const auto& lbr_data_proto = branch_list_proto.lbr_data();
+ lbr_data.samples.resize(lbr_data_proto.samples_size());
+ for (size_t i = 0; i < lbr_data_proto.samples_size(); ++i) {
+ const auto& sample_proto = lbr_data_proto.samples(i);
+ LBRSample& sample = lbr_data.samples[i];
+ sample.binary_id = sample_proto.binary_id();
+ sample.vaddr_in_file = sample_proto.vaddr_in_file();
+ sample.branches.resize(sample_proto.branches_size());
+ for (size_t j = 0; j < sample_proto.branches_size(); ++j) {
+ const auto& branch_proto = sample_proto.branches(j);
+ LBRBranch& branch = sample.branches[j];
+ branch.from_binary_id = branch_proto.from_binary_id();
+ branch.to_binary_id = branch_proto.to_binary_id();
+ branch.from_vaddr_in_file = branch_proto.from_vaddr_in_file();
+ branch.to_vaddr_in_file = branch_proto.to_vaddr_in_file();
+ }
+ }
+ for (size_t i = 0; i < lbr_data_proto.binaries_size(); ++i) {
+ const auto& binary_proto = lbr_data_proto.binaries(i);
+ lbr_data.binaries.emplace_back(binary_proto.path(), BuildId(binary_proto.build_id()));
+ }
+ }
+ return true;
+}
+
+} // namespace simpleperf
diff --git a/simpleperf/BranchListFile.h b/simpleperf/BranchListFile.h
new file mode 100644
index 00000000..de97ef88
--- /dev/null
+++ b/simpleperf/BranchListFile.h
@@ -0,0 +1,176 @@
+/*
+ * 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 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.
+ */
+
+#pragma once
+
+#include "ETMDecoder.h"
+#include "RegEx.h"
+#include "thread_tree.h"
+#include "utils.h"
+
+namespace simpleperf {
+
+// When processing binary info in an input file, the binaries are identified by their path.
+// But this isn't sufficient when merging binary info from multiple input files. Because
+// binaries for the same path may be changed between generating input files. So after processing
+// each input file, we create BinaryKeys to identify binaries, which consider path, build_id and
+// kernel_start_addr (for vmlinux). kernel_start_addr affects how addresses in ETMBinary
+// are interpreted for vmlinux.
+struct BinaryKey {
+ std::string path;
+ BuildId build_id;
+ uint64_t kernel_start_addr = 0;
+
+ BinaryKey() {}
+
+ BinaryKey(const std::string& path, BuildId build_id) : path(path), build_id(build_id) {}
+
+ BinaryKey(const Dso* dso, uint64_t kernel_start_addr) : path(dso->Path()) {
+ build_id = Dso::FindExpectedBuildIdForPath(dso->Path());
+ if (dso->type() == DSO_KERNEL) {
+ this->kernel_start_addr = kernel_start_addr;
+ }
+ }
+
+ bool operator==(const BinaryKey& other) const {
+ return path == other.path && build_id == other.build_id &&
+ kernel_start_addr == other.kernel_start_addr;
+ }
+};
+
+struct BinaryKeyHash {
+ size_t operator()(const BinaryKey& key) const noexcept {
+ size_t seed = 0;
+ HashCombine(seed, key.path);
+ HashCombine(seed, key.build_id);
+ if (key.kernel_start_addr != 0) {
+ HashCombine(seed, key.kernel_start_addr);
+ }
+ return seed;
+ }
+};
+
+class BinaryFilter {
+ public:
+ BinaryFilter(const RegEx* binary_name_regex) : binary_name_regex_(binary_name_regex) {}
+
+ void SetRegex(const RegEx* binary_name_regex) {
+ binary_name_regex_ = binary_name_regex;
+ dso_filter_cache_.clear();
+ }
+
+ bool Filter(const Dso* dso) {
+ auto lookup = dso_filter_cache_.find(dso);
+ if (lookup != dso_filter_cache_.end()) {
+ return lookup->second;
+ }
+ bool match = Filter(dso->Path());
+ dso_filter_cache_.insert({dso, match});
+ return match;
+ }
+
+ bool Filter(const std::string& path) {
+ return binary_name_regex_ == nullptr || binary_name_regex_->Search(path);
+ }
+
+ private:
+ const RegEx* binary_name_regex_;
+ std::unordered_map<const Dso*, bool> dso_filter_cache_;
+};
+
+using UnorderedETMBranchMap =
+ std::unordered_map<uint64_t, std::unordered_map<std::vector<bool>, uint64_t>>;
+
+struct ETMBinary {
+ DsoType dso_type;
+ UnorderedETMBranchMap branch_map;
+
+ void Merge(const ETMBinary& other) {
+ for (auto& other_p : other.branch_map) {
+ auto it = branch_map.find(other_p.first);
+ if (it == branch_map.end()) {
+ branch_map[other_p.first] = std::move(other_p.second);
+ } else {
+ auto& map2 = it->second;
+ for (auto& other_p2 : other_p.second) {
+ auto it2 = map2.find(other_p2.first);
+ if (it2 == map2.end()) {
+ map2[other_p2.first] = other_p2.second;
+ } else {
+ OverflowSafeAdd(it2->second, other_p2.second);
+ }
+ }
+ }
+ }
+ }
+
+ ETMBranchMap GetOrderedBranchMap() const {
+ ETMBranchMap result;
+ for (const auto& p : branch_map) {
+ uint64_t addr = p.first;
+ const auto& b_map = p.second;
+ result[addr] = std::map<std::vector<bool>, uint64_t>(b_map.begin(), b_map.end());
+ }
+ return result;
+ }
+};
+
+using ETMBinaryMap = std::unordered_map<BinaryKey, ETMBinary, BinaryKeyHash>;
+bool ETMBinaryMapToString(const ETMBinaryMap& binary_map, std::string& s);
+bool StringToETMBinaryMap(const std::string& s, ETMBinaryMap& binary_map);
+
+// Convert ETM data into branch lists while recording.
+class ETMBranchListGenerator {
+ public:
+ static std::unique_ptr<ETMBranchListGenerator> Create(bool dump_maps_from_proc);
+
+ virtual ~ETMBranchListGenerator();
+ virtual void SetExcludePid(pid_t pid) = 0;
+ virtual void SetBinaryFilter(const RegEx* binary_name_regex) = 0;
+ virtual bool ProcessRecord(const Record& r, bool& consumed) = 0;
+ virtual ETMBinaryMap GetETMBinaryMap() = 0;
+};
+
+struct LBRBranch {
+ // If from_binary_id >= 1, it refers to LBRData.binaries[from_binary_id - 1]. Otherwise, it's
+ // invalid.
+ uint32_t from_binary_id = 0;
+ // If to_binary_id >= 1, it refers to LBRData.binaries[to_binary_id - 1]. Otherwise, it's invalid.
+ uint32_t to_binary_id = 0;
+ uint64_t from_vaddr_in_file = 0;
+ uint64_t to_vaddr_in_file = 0;
+};
+
+struct LBRSample {
+ // If binary_id >= 1, it refers to LBRData.binaries[binary_id - 1]. Otherwise, it's invalid.
+ uint32_t binary_id = 0;
+ uint64_t vaddr_in_file = 0;
+ std::vector<LBRBranch> branches;
+};
+
+struct LBRData {
+ std::vector<LBRSample> samples;
+ std::vector<BinaryKey> binaries;
+};
+
+bool LBRDataToString(const LBRData& data, std::string& s);
+bool ParseBranchListData(const std::string& s, ETMBinaryMap& etm_data, LBRData& lbr_data);
+
+// for testing
+std::string ETMBranchToProtoString(const std::vector<bool>& branch);
+std::vector<bool> ProtoStringToETMBranch(const std::string& s, size_t bit_size);
+
+} // namespace simpleperf
diff --git a/simpleperf/BranchListFile_test.cpp b/simpleperf/BranchListFile_test.cpp
new file mode 100644
index 00000000..d7f9a6a5
--- /dev/null
+++ b/simpleperf/BranchListFile_test.cpp
@@ -0,0 +1,36 @@
+/*
+ * 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 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "BranchListFile.h"
+
+using namespace simpleperf;
+
+// @CddTest = 6.1/C-0-2
+TEST(BranchListFile, etm_branch_to_proto_string) {
+ std::vector<bool> branch;
+ for (size_t i = 0; i < 100; i++) {
+ branch.push_back(i % 2 == 0);
+ std::string s = ETMBranchToProtoString(branch);
+ for (size_t j = 0; j <= i; j++) {
+ bool b = s[j >> 3] & (1 << (j & 7));
+ ASSERT_EQ(b, branch[j]);
+ }
+ std::vector<bool> branch2 = ProtoStringToETMBranch(s, branch.size());
+ ASSERT_EQ(branch, branch2);
+ }
+}
diff --git a/simpleperf/CallChainJoiner_test.cpp b/simpleperf/CallChainJoiner_test.cpp
index 90460e03..4b0fa29f 100644
--- a/simpleperf/CallChainJoiner_test.cpp
+++ b/simpleperf/CallChainJoiner_test.cpp
@@ -33,6 +33,7 @@ static bool JoinCallChain(LRUCache& cache, uint32_t tid, const std::vector<uint6
return tmp_ip == expected_output_ip && tmp_sp == expected_output_sp;
}
+// @CddTest = 6.1/C-0-2
TEST(LRUCache, different_nodes) {
LRUCache cache(sizeof(CacheNode) * 2, 1);
ASSERT_EQ(cache.Stat().max_node_count, 2u);
@@ -65,6 +66,7 @@ TEST(LRUCache, different_nodes) {
ASSERT_NE(cache.FindNode(1, ip[0], sp2[0]), nullptr);
}
+// @CddTest = 6.1/C-0-2
TEST(LRUCache, extend_chains) {
// matched_node_count_to_extend_callchain = 1
// c -> b
@@ -93,6 +95,7 @@ TEST(LRUCache, extend_chains) {
ASSERT_EQ(cache3.Stat().used_node_count, 4u);
}
+// @CddTest = 6.1/C-0-2
TEST(LRUCache, avoid_ip_sp_loop) {
LRUCache cache(sizeof(CacheNode) * 2, 1);
std::vector<uint64_t> ip = {0xa, 0xb};
@@ -104,6 +107,7 @@ TEST(LRUCache, avoid_ip_sp_loop) {
ASSERT_EQ(cache.Stat().recycled_node_count, 0u);
}
+// @CddTest = 6.1/C-0-2
TEST(LRUCache, one_chain) {
LRUCache cache(sizeof(CacheNode) * 4, 1);
ASSERT_EQ(cache.Stat().max_node_count, 4u);
@@ -125,6 +129,7 @@ TEST(LRUCache, one_chain) {
ASSERT_EQ(cache.Stat().recycled_node_count, 0u);
}
+// @CddTest = 6.1/C-0-2
TEST(LRUCache, many_chains) {
LRUCache cache(sizeof(CacheNode) * 12, 1);
// 4 -> 3 -> 2 -> 1
@@ -149,6 +154,7 @@ TEST(LRUCache, many_chains) {
ASSERT_EQ(cache.FindNode(0, 0xa, 0xa), nullptr);
}
+// @CddTest = 6.1/C-0-2
class CallChainJoinerTest : public ::testing::Test {
protected:
void SetUp() override {
@@ -164,6 +170,7 @@ class CallChainJoinerTest : public ::testing::Test {
std::unique_ptr<ScopedTempFiles> scoped_temp_files_;
};
+// @CddTest = 6.1/C-0-2
TEST_F(CallChainJoinerTest, smoke) {
CallChainJoiner joiner(sizeof(CacheNode) * 1024, 1, true);
for (pid_t pid = 0; pid < 10; ++pid) {
@@ -230,6 +237,7 @@ TEST_F(CallChainJoinerTest, smoke) {
ASSERT_EQ(joiner.GetStat().after_join_max_chain_length, 5u);
}
+// @CddTest = 6.1/C-0-2
TEST_F(CallChainJoinerTest, no_original_chains) {
CallChainJoiner joiner(sizeof(CacheNode) * 1024, 1, false);
ASSERT_TRUE(joiner.AddCallChain(0, 0, CallChainJoiner::ORIGINAL_OFFLINE, {1}, {1}));
@@ -249,6 +257,7 @@ TEST_F(CallChainJoinerTest, no_original_chains) {
joiner.DumpStat();
}
+// @CddTest = 6.1/C-0-2
TEST_F(CallChainJoinerTest, no_chains) {
CallChainJoiner joiner(sizeof(CacheNode) * 1024, 1, false);
ASSERT_TRUE(joiner.JoinCallChains());
diff --git a/simpleperf/ETMConstants.h b/simpleperf/ETMConstants.h
index c0897193..003d88ab 100644
--- a/simpleperf/ETMConstants.h
+++ b/simpleperf/ETMConstants.h
@@ -19,10 +19,12 @@
namespace simpleperf {
// Config bits from include/linux/coresight-pmu.h in the kernel
// For etm_event_config:
+static constexpr int ETM_OPT_CYCACC = 12;
static constexpr int ETM_OPT_CTXTID = 14;
static constexpr int ETM_OPT_CTXTID2 = 15;
static constexpr int ETM_OPT_TS = 28;
// For etm_config_reg:
+static constexpr int ETM4_CFG_BIT_CCI = 4;
static constexpr int ETM4_CFG_BIT_CTXTID = 6;
static constexpr int ETM4_CFG_BIT_VMID = 7;
static constexpr int ETM4_CFG_BIT_TS = 11;
diff --git a/simpleperf/ETMDecoder.cpp b/simpleperf/ETMDecoder.cpp
index 5e2f92e0..96b4f1a8 100644
--- a/simpleperf/ETMDecoder.cpp
+++ b/simpleperf/ETMDecoder.cpp
@@ -181,11 +181,9 @@ class PacketSink : public IPktDataIn<EtmV4ITrcPacket> {
// For each trace_id, when given an addr, find the thread and map it belongs to.
class MapLocator : public PacketCallback {
public:
- MapLocator(ThreadTree& thread_tree)
+ MapLocator(ETMThreadTree& thread_tree)
: PacketCallback(PacketCallback::MAP_LOCATOR), thread_tree_(thread_tree) {}
- ThreadTree& GetThreadTree() { return thread_tree_; }
-
// Return current thread id of a trace_id. If not available, return -1.
pid_t GetTid(uint8_t trace_id) const { return trace_data_[trace_id].tid; }
@@ -245,7 +243,7 @@ class MapLocator : public PacketCallback {
bool use_vmid = false; // use vmid for PID
};
- ThreadTree& thread_tree_;
+ ETMThreadTree& thread_tree_;
TraceData trace_data_[256];
};
@@ -294,8 +292,13 @@ class MemAccess : public ITargetMemAccess {
if (memory != nullptr && memory->getBufferSize() > map->pgoff &&
(memory->getBufferSize() - map->pgoff >= map->len)) {
data.buffer = memory->getBufferStart() + map->pgoff;
- } else {
+ } else if (memory == nullptr) {
data.buffer = nullptr;
+ } else {
+ // Memory was found, but the buffer is not good enough to be
+ // cached. "Invalidate" the cache by setting the map to
+ // null.
+ data.buffer_map = nullptr;
}
}
}
@@ -649,7 +652,11 @@ class BranchListParser : public PacketCallback {
// 2. Supports dumping data at different stages.
class ETMDecoderImpl : public ETMDecoder {
public:
- ETMDecoderImpl(ThreadTree& thread_tree) : thread_tree_(thread_tree) {}
+ ETMDecoderImpl(ETMThreadTree& thread_tree) : thread_tree_(thread_tree) {
+ // If the aux record for a thread is processed after it's thread exit record, we can't find
+ // the thread's maps when processing ETM data. To handle this, disable thread exit records.
+ thread_tree.DisableThreadExitRecords();
+ }
void CreateDecodeTree(const AuxTraceInfoRecord& auxtrace_info) {
uint8_t trace_id = 0;
@@ -803,7 +810,7 @@ class ETMDecoderImpl : public ETMDecoder {
}
// map ip address to binary path and binary offset
- ThreadTree& thread_tree_;
+ ETMThreadTree& thread_tree_;
// handle to build OpenCSD decoder
ETMV4IDecodeTree decode_tree_;
// map from cpu to trace id
@@ -840,7 +847,7 @@ bool ParseEtmDumpOption(const std::string& s, ETMDumpOption* option) {
}
std::unique_ptr<ETMDecoder> ETMDecoder::Create(const AuxTraceInfoRecord& auxtrace_info,
- ThreadTree& thread_tree) {
+ ETMThreadTree& thread_tree) {
auto decoder = std::make_unique<ETMDecoderImpl>(thread_tree);
decoder->CreateDecodeTree(auxtrace_info);
return std::unique_ptr<ETMDecoder>(decoder.release());
@@ -928,8 +935,8 @@ class BranchDecoder {
InstructionDecoder instruction_decoder_;
};
-android::base::expected<void, std::string> ConvertBranchMapToInstrRanges(
- Dso* dso, const BranchMap& branch_map, const ETMDecoder::InstrRangeCallbackFn& callback) {
+android::base::expected<void, std::string> ConvertETMBranchMapToInstrRanges(
+ Dso* dso, const ETMBranchMap& branch_map, const ETMDecoder::InstrRangeCallbackFn& callback) {
ETMInstrRange instr_range;
instr_range.dso = dso;
diff --git a/simpleperf/ETMDecoder.h b/simpleperf/ETMDecoder.h
index 5f9daebd..cb5c8dc8 100644
--- a/simpleperf/ETMDecoder.h
+++ b/simpleperf/ETMDecoder.h
@@ -61,10 +61,19 @@ struct ETMBranchList {
std::vector<bool> branch;
};
+// ThreadTree interface used by ETMDecoder
+class ETMThreadTree {
+ public:
+ virtual ~ETMThreadTree() {}
+ virtual void DisableThreadExitRecords() = 0;
+ virtual const ThreadEntry* FindThread(int tid) = 0;
+ virtual const MapSet& GetKernelMaps() = 0;
+};
+
class ETMDecoder {
public:
static std::unique_ptr<ETMDecoder> Create(const AuxTraceInfoRecord& auxtrace_info,
- ThreadTree& thread_tree);
+ ETMThreadTree& thread_tree);
virtual ~ETMDecoder() {}
virtual void EnableDump(const ETMDumpOption& option) = 0;
@@ -81,9 +90,9 @@ class ETMDecoder {
// Map from addrs to a map of (branch_list, count).
// Use maps instead of unordered_maps. Because it helps locality by decoding instructions for sorted
// addresses.
-using BranchMap = std::map<uint64_t, std::map<std::vector<bool>, uint64_t>>;
+using ETMBranchMap = std::map<uint64_t, std::map<std::vector<bool>, uint64_t>>;
-android::base::expected<void, std::string> ConvertBranchMapToInstrRanges(
- Dso* dso, const BranchMap& branch_map, const ETMDecoder::InstrRangeCallbackFn& callback);
+android::base::expected<void, std::string> ConvertETMBranchMapToInstrRanges(
+ Dso* dso, const ETMBranchMap& branch_map, const ETMDecoder::InstrRangeCallbackFn& callback);
} // namespace simpleperf \ No newline at end of file
diff --git a/simpleperf/ETMRecorder.cpp b/simpleperf/ETMRecorder.cpp
index 554e8f52..a722df5c 100644
--- a/simpleperf/ETMRecorder.cpp
+++ b/simpleperf/ETMRecorder.cpp
@@ -38,8 +38,6 @@ namespace simpleperf {
using android::base::expected;
using android::base::unexpected;
-static constexpr bool ETM_RECORD_TIMESTAMP = false;
-
static const std::string ETM_DIR = "/sys/bus/event_source/devices/cs_etm/";
// from coresight_get_trace_id(int cpu) in include/linux/coresight-pmu.h
@@ -80,6 +78,10 @@ bool ETMPerCpu::IsTimestampSupported() const {
return GetBits(trcidr0, 24, 28) > 0;
}
+bool ETMPerCpu::IsCycAccSupported() const {
+ return GetBits(trcidr0, 7, 7);
+}
+
bool ETMPerCpu::IsEnabled() const {
return GetBits(trcauthstatus, 0, 3) == 0xc;
}
@@ -195,6 +197,7 @@ void ETMRecorder::SetEtmPerfEventAttr(perf_event_attr* attr) {
BuildEtmConfig();
attr->config = etm_event_config_;
attr->config2 = sink_config_;
+ attr->config3 = cc_threshold_config_;
}
void ETMRecorder::BuildEtmConfig() {
@@ -208,7 +211,7 @@ void ETMRecorder::BuildEtmConfig() {
etm_config_reg_ |= 1U << ETM4_CFG_BIT_CTXTID;
}
- if (ETM_RECORD_TIMESTAMP) {
+ if (record_timestamp_) {
bool ts_supported = true;
for (auto& p : etm_info_) {
ts_supported &= p.second.IsTimestampSupported();
@@ -218,6 +221,21 @@ void ETMRecorder::BuildEtmConfig() {
etm_config_reg_ |= 1U << ETM4_CFG_BIT_TS;
}
}
+
+ if (record_cycles_) {
+ bool cycles_supported = true;
+ for (auto& p : etm_info_) {
+ cycles_supported &= p.second.IsCycAccSupported();
+ }
+ if (cycles_supported) {
+ etm_event_config_ |= 1ULL << ETM_OPT_CYCACC;
+ etm_config_reg_ |= 1U << ETM4_CFG_BIT_CCI;
+
+ if (cycle_threshold_) {
+ cc_threshold_config_ |= cycle_threshold_;
+ }
+ }
+ }
}
}
@@ -264,4 +282,16 @@ size_t ETMRecorder::GetAddrFilterPairs() {
return min_pairs;
}
-} // namespace simpleperf \ No newline at end of file
+void ETMRecorder::SetRecordTimestamp(bool record) {
+ record_timestamp_ = record;
+}
+
+void ETMRecorder::SetRecordCycles(bool record) {
+ record_cycles_ = record;
+}
+
+void ETMRecorder::SetCycleThreshold(size_t threshold) {
+ cycle_threshold_ = threshold;
+}
+
+} // namespace simpleperf
diff --git a/simpleperf/ETMRecorder.h b/simpleperf/ETMRecorder.h
index f304f601..925c14e0 100644
--- a/simpleperf/ETMRecorder.h
+++ b/simpleperf/ETMRecorder.h
@@ -41,6 +41,7 @@ struct ETMPerCpu {
int GetMajorVersion() const;
bool IsContextIDSupported() const;
bool IsTimestampSupported() const;
+ bool IsCycAccSupported() const;
bool IsEnabled() const;
};
@@ -62,6 +63,9 @@ class ETMRecorder {
void SetEtmPerfEventAttr(perf_event_attr* attr);
AuxTraceInfoRecord CreateAuxTraceInfoRecord();
size_t GetAddrFilterPairs();
+ void SetRecordTimestamp(bool record);
+ void SetRecordCycles(bool record);
+ void SetCycleThreshold(size_t threshold);
private:
bool ReadEtmInfo();
@@ -76,9 +80,15 @@ class ETMRecorder {
bool use_contextid2_ = false;
// select etm options (timestamp, context_id, ...), setting in perf_event_attr->config
uint64_t etm_event_config_ = 0;
+ // set cycle count threshold using perf_event_attr->config3
+ uint64_t cc_threshold_config_ = 0;
// record etm options in AuxTraceInfoRecord
uint32_t etm_config_reg_ = 0;
std::map<int, ETMPerCpu> etm_info_;
+
+ bool record_timestamp_ = false;
+ bool record_cycles_ = false;
+ uint64_t cycle_threshold_ = 0;
};
-} // namespace simpleperf \ No newline at end of file
+} // namespace simpleperf
diff --git a/simpleperf/IOEventLoop.cpp b/simpleperf/IOEventLoop.cpp
index 239fff97..d961332e 100644
--- a/simpleperf/IOEventLoop.cpp
+++ b/simpleperf/IOEventLoop.cpp
@@ -40,8 +40,7 @@ struct IOEvent {
}
};
-IOEventLoop::IOEventLoop()
- : ebase_(nullptr), has_error_(false), use_precise_timer_(false), in_loop_(false) {}
+IOEventLoop::IOEventLoop() : ebase_(nullptr), has_error_(false), in_loop_(false) {}
IOEventLoop::~IOEventLoop() {
events_.clear();
@@ -50,21 +49,11 @@ IOEventLoop::~IOEventLoop() {
}
}
-bool IOEventLoop::UsePreciseTimer() {
- if (ebase_ != nullptr) {
- return false; // Too late to set the flag.
- }
- use_precise_timer_ = true;
- return true;
-}
-
bool IOEventLoop::EnsureInit() {
if (ebase_ == nullptr) {
event_config* cfg = event_config_new();
if (cfg != nullptr) {
- if (use_precise_timer_) {
- event_config_set_flag(cfg, EVENT_BASE_FLAG_PRECISE_TIMER);
- }
+ event_config_set_flag(cfg, EVENT_BASE_FLAG_PRECISE_TIMER);
if (event_config_avoid_method(cfg, "epoll") != 0) {
LOG(ERROR) << "event_config_avoid_method";
return false;
@@ -150,6 +139,11 @@ IOEventRef IOEventLoop::AddPeriodicEvent(timeval duration, const std::function<b
return AddEvent(-1, EV_PERSIST, &duration, callback, priority);
}
+IOEventRef IOEventLoop::AddOneTimeEvent(timeval duration, const std::function<bool()>& callback,
+ IOEventPriority priority) {
+ return AddEvent(-1, 0, &duration, callback, priority);
+}
+
IOEventRef IOEventLoop::AddEvent(int fd_or_sig, int16_t events, timeval* timeout,
const std::function<bool()>& callback, IOEventPriority priority) {
if (!EnsureInit()) {
diff --git a/simpleperf/IOEventLoop.h b/simpleperf/IOEventLoop.h
index 1578a4d0..cc5c9bfd 100644
--- a/simpleperf/IOEventLoop.h
+++ b/simpleperf/IOEventLoop.h
@@ -46,9 +46,6 @@ class IOEventLoop {
IOEventLoop();
~IOEventLoop();
- // Use precise timer for periodic events which want precision in ms.
- bool UsePreciseTimer();
-
// Register a read Event, so [callback] is called when [fd] can be read
// without blocking. If registered successfully, return the reference
// to control the Event, otherwise return nullptr.
@@ -74,6 +71,10 @@ class IOEventLoop {
IOEventRef AddPeriodicEvent(timeval duration, const std::function<bool()>& callback,
IOEventPriority priority = IOEventLowPriority);
+ // Register a one time Event, so [callback] is called once after [duration].
+ IOEventRef AddOneTimeEvent(timeval duration, const std::function<bool()>& callback,
+ IOEventPriority priority = IOEventLowPriority);
+
// Run a loop polling for Events. It only exits when ExitLoop() is called
// in a callback function of registered Events.
bool RunLoop();
@@ -99,7 +100,6 @@ class IOEventLoop {
event_base* ebase_;
std::vector<std::unique_ptr<IOEvent>> events_;
bool has_error_;
- bool use_precise_timer_;
bool in_loop_;
};
diff --git a/simpleperf/IOEventLoop_test.cpp b/simpleperf/IOEventLoop_test.cpp
index 658fe820..fbbc0fc8 100644
--- a/simpleperf/IOEventLoop_test.cpp
+++ b/simpleperf/IOEventLoop_test.cpp
@@ -26,6 +26,7 @@
using namespace simpleperf;
+// @CddTest = 6.1/C-0-2
TEST(IOEventLoop, read) {
int fd[2];
ASSERT_EQ(0, pipe(fd));
@@ -65,6 +66,7 @@ TEST(IOEventLoop, read) {
close(fd[1]);
}
+// @CddTest = 6.1/C-0-2
TEST(IOEventLoop, write) {
int fd[2];
ASSERT_EQ(0, pipe(fd));
@@ -101,6 +103,7 @@ TEST(IOEventLoop, write) {
ASSERT_EQ(100, count);
}
+// @CddTest = 6.1/C-0-2
TEST(IOEventLoop, signal) {
IOEventLoop loop;
int count = 0;
@@ -123,15 +126,12 @@ TEST(IOEventLoop, signal) {
ASSERT_EQ(100, count);
}
-void TestPeriodicEvents(int period_in_us, int iterations, bool precise) {
+void TestPeriodicEvents(int period_in_us, int iterations) {
timeval tv;
tv.tv_sec = period_in_us / 1000000;
tv.tv_usec = period_in_us % 1000000;
int count = 0;
IOEventLoop loop;
- if (precise) {
- ASSERT_TRUE(loop.UsePreciseTimer());
- }
ASSERT_TRUE(loop.AddPeriodicEvent(tv, [&]() {
if (++count == iterations) {
loop.ExitLoop();
@@ -145,19 +145,46 @@ void TestPeriodicEvents(int period_in_us, int iterations, bool precise) {
double time_used =
std::chrono::duration_cast<std::chrono::duration<double>>(end_time - start_time).count();
double min_time_in_sec = period_in_us / 1e6 * iterations;
- double max_time_in_sec = min_time_in_sec + (precise ? 0.3 : 1);
+ double max_time_in_sec = min_time_in_sec + 0.3;
ASSERT_GE(time_used, min_time_in_sec);
ASSERT_LT(time_used, max_time_in_sec);
}
+// @CddTest = 6.1/C-0-2
TEST(IOEventLoop, periodic) {
- TestPeriodicEvents(1000000, 1, false);
+ TestPeriodicEvents(1000, 100);
}
-TEST(IOEventLoop, periodic_precise) {
- TestPeriodicEvents(1000, 100, true);
+// @CddTest = 6.1/C-0-2
+TEST(IOEventLoop, one_time_event) {
+ int duration_in_us = 1000;
+ timeval tv = {};
+ tv.tv_usec = duration_in_us;
+ int count = 0;
+ auto callback_time = std::chrono::steady_clock::now();
+ IOEventLoop loop;
+ // Add a one time event to test callback count and time.
+ ASSERT_TRUE(loop.AddOneTimeEvent(tv, [&]() {
+ ++count;
+ callback_time = std::chrono::steady_clock::now();
+ return true;
+ }));
+ // Add another one time event to exit loop.
+ tv.tv_usec = duration_in_us * 3;
+ ASSERT_TRUE(loop.AddOneTimeEvent(tv, [&]() { return loop.ExitLoop(); }));
+
+ auto start_time = std::chrono::steady_clock::now();
+ ASSERT_TRUE(loop.RunLoop());
+ ASSERT_EQ(1, count);
+ double time_used =
+ std::chrono::duration_cast<std::chrono::duration<double>>(callback_time - start_time).count();
+ double min_time_in_sec = duration_in_us / 1e6;
+ double max_time_in_sec = min_time_in_sec + 0.3;
+ ASSERT_GE(time_used, min_time_in_sec);
+ ASSERT_LT(time_used, max_time_in_sec);
}
+// @CddTest = 6.1/C-0-2
TEST(IOEventLoop, read_and_del_event) {
int fd[2];
ASSERT_EQ(0, pipe(fd));
@@ -183,6 +210,7 @@ TEST(IOEventLoop, read_and_del_event) {
close(fd[1]);
}
+// @CddTest = 6.1/C-0-2
TEST(IOEventLoop, disable_enable_event) {
int fd[2];
ASSERT_EQ(0, pipe(fd));
@@ -220,6 +248,7 @@ TEST(IOEventLoop, disable_enable_event) {
close(fd[1]);
}
+// @CddTest = 6.1/C-0-2
TEST(IOEventLoop, disable_enable_periodic_event) {
timeval tv;
tv.tv_sec = 0;
@@ -246,11 +275,13 @@ TEST(IOEventLoop, disable_enable_periodic_event) {
ASSERT_EQ(2u, periodic_count);
}
+// @CddTest = 6.1/C-0-2
TEST(IOEventLoop, exit_before_loop) {
IOEventLoop loop;
ASSERT_TRUE(loop.ExitLoop());
}
+// @CddTest = 6.1/C-0-2
TEST(IOEventLoop, priority) {
int low_priority_fd[2];
ASSERT_EQ(0, pipe(low_priority_fd));
diff --git a/simpleperf/JITDebugReader.cpp b/simpleperf/JITDebugReader.cpp
index 28058940..b5430c70 100644
--- a/simpleperf/JITDebugReader.cpp
+++ b/simpleperf/JITDebugReader.cpp
@@ -37,17 +37,19 @@
#include "dso.h"
#include "environment.h"
#include "read_apk.h"
+#include "read_dex_file.h"
#include "read_elf.h"
#include "utils.h"
namespace simpleperf {
+using namespace JITDebugReader_impl;
using android::base::StartsWith;
using android::base::StringPrintf;
// If the size of a symfile is larger than EXPECTED_MAX_SYMFILE_SIZE, we don't want to read it
// remotely.
-static constexpr size_t MAX_JIT_SYMFILE_SIZE = 1024 * 1024u;
+static constexpr size_t MAX_JIT_SYMFILE_SIZE = 1 * kMegabyte;
// It takes about 30us-130us on Pixel (depending on the cpu frequency) to check if the descriptors
// have been updated (most time spent in process_vm_preadv). We want to know if the JIT debug info
@@ -298,7 +300,7 @@ bool JITDebugReader::ReadAllProcesses() {
++it;
}
}
- if (!AddDebugInfo(debug_info, true)) {
+ if (!AddDebugInfo(std::move(debug_info), true)) {
return false;
}
if (!processes_.empty()) {
@@ -311,7 +313,7 @@ bool JITDebugReader::ReadProcess(pid_t pid) {
auto it = processes_.find(pid);
if (it != processes_.end()) {
std::vector<JITDebugInfo> debug_info;
- return ReadProcess(it->second, &debug_info) && AddDebugInfo(debug_info, false);
+ return ReadProcess(it->second, &debug_info) && AddDebugInfo(std::move(debug_info), false);
}
return true;
}
@@ -446,11 +448,14 @@ const JITDebugReader::DescriptorsLocation* JITDebugReader::GetDescriptorsLocatio
LOG(ERROR) << "failed to read min_exec_vaddr from " << art_lib_path << ": " << status;
return nullptr;
}
+
+ const size_t kPageSize = getpagesize();
+ const size_t kPageMask = ~(kPageSize - 1);
uint64_t file_offset;
uint64_t min_vaddr_in_file = elf->ReadMinExecutableVaddr(&file_offset);
// min_vaddr_in_file is the min vaddr of executable segments. It may not be page aligned.
- // And dynamic linker will create map mapping to (segment.p_vaddr & PAGE_MASK).
- uint64_t aligned_segment_vaddr = min_vaddr_in_file & PAGE_MASK;
+ // And dynamic linker will create map mapping to (segment.p_vaddr & kPageMask).
+ uint64_t aligned_segment_vaddr = min_vaddr_in_file & kPageMask;
const char* jit_str = "__jit_debug_descriptor";
const char* dex_str = "__dex_debug_descriptor";
uint64_t jit_addr = 0u;
@@ -716,36 +721,65 @@ void JITDebugReader::ReadDexFileDebugInfo(Process& process,
std::string file_path;
std::string zip_path;
std::string entry_path;
- std::shared_ptr<ThreadMmap> extracted_dex_file_map;
+ std::shared_ptr<ThreadMmap> dex_file_map;
+ std::vector<Symbol> symbols;
+ // Offset of dex file in .vdex file or .apk file.
+ uint64_t dex_file_offset = dex_entry.symfile_addr - it->start_addr + it->pgoff;
if (ParseExtractedInMemoryPath(it->name, &zip_path, &entry_path)) {
file_path = GetUrlInApk(zip_path, entry_path);
- extracted_dex_file_map = std::make_shared<ThreadMmap>(*it);
- } else {
- if (!IsRegularFile(it->name)) {
- // TODO: read dex file only exist in memory?
- continue;
- }
+ dex_file_map = std::make_shared<ThreadMmap>(*it);
+ } else if (IsRegularFile(it->name)) {
file_path = it->name;
+ } else {
+ // Read a dex file only existing in memory.
+ file_path = StringPrintf("%s_pid_%d_addr_0x%" PRIx64 "-0x%" PRIx64 "", kDexFileInMemoryPrefix,
+ process.pid, dex_entry.symfile_addr,
+ dex_entry.symfile_addr + dex_entry.symfile_size);
+ dex_file_map.reset(new ThreadMmap(dex_entry.symfile_addr, dex_entry.symfile_size, 0,
+ file_path.c_str(), PROT_READ));
+ symbols = ReadDexFileSymbolsInMemory(process, dex_entry.symfile_addr, dex_entry.symfile_size);
+ dex_file_offset = 0;
}
- // Offset of dex file in .vdex file or .apk file.
- uint64_t dex_file_offset = dex_entry.symfile_addr - it->start_addr + it->pgoff;
+
debug_info->emplace_back(process.pid, dex_entry.timestamp, dex_file_offset, file_path,
- extracted_dex_file_map);
+ dex_file_map, std::move(symbols));
LOG(VERBOSE) << "DexFile " << file_path << "+" << std::hex << dex_file_offset << " in map ["
<< it->start_addr << " - " << (it->start_addr + it->len) << "] with size "
<< dex_entry.symfile_size;
}
}
-bool JITDebugReader::AddDebugInfo(const std::vector<JITDebugInfo>& debug_info,
- bool sync_kernel_records) {
+std::vector<Symbol> JITDebugReader::ReadDexFileSymbolsInMemory(Process& process, uint64_t addr,
+ uint64_t size) {
+ std::vector<Symbol> symbols;
+ std::vector<uint8_t> data(size, 0);
+ if (!ReadRemoteMem(process, addr, size, data.data())) {
+ LOG(DEBUG) << "failed to read dex file in memory for process " << process.pid << ", addr "
+ << std::hex << addr << "-" << (addr + size);
+ return symbols;
+ }
+
+ auto process_symbol = [&](DexFileSymbol* symbol) {
+ symbols.emplace_back(symbol->name, symbol->addr, symbol->size);
+ };
+ if (!ReadSymbolsFromDexFileInMemory(data.data(), data.size(), "dex_file_in_memory", {0},
+ process_symbol)) {
+ LOG(DEBUG) << "failed to parse dex file in memory for process " << process.pid << ", addr "
+ << std::hex << addr << "-" << (addr + size);
+ return symbols;
+ }
+ std::sort(symbols.begin(), symbols.end(), Symbol::CompareValueByAddr);
+ return symbols;
+}
+
+bool JITDebugReader::AddDebugInfo(std::vector<JITDebugInfo> debug_info, bool sync_kernel_records) {
if (!debug_info.empty()) {
if (sync_option_ == SyncOption::kSyncWithRecords) {
for (auto& info : debug_info) {
debug_info_q_.push(std::move(info));
}
} else {
- return debug_info_callback_(debug_info, sync_kernel_records);
+ return debug_info_callback_(std::move(debug_info), sync_kernel_records);
}
}
return true;
diff --git a/simpleperf/JITDebugReader.h b/simpleperf/JITDebugReader.h
index b9e984d5..4483ddf0 100644
--- a/simpleperf/JITDebugReader.h
+++ b/simpleperf/JITDebugReader.h
@@ -38,6 +38,52 @@ namespace simpleperf {
inline constexpr const char* kJITAppCacheFile = "jit_app_cache";
inline constexpr const char* kJITZygoteCacheFile = "jit_zygote_cache";
+inline constexpr const char* kDexFileInMemoryPrefix = "dexfile_in_memory";
+
+namespace JITDebugReader_impl {
+
+enum class DescriptorType {
+ kDEX,
+ kJIT,
+};
+
+// An arch-independent representation of JIT/dex debug descriptor.
+struct Descriptor {
+ DescriptorType type;
+ int version = 0;
+ uint32_t action_seqlock = 0; // incremented before and after any modification
+ uint64_t action_timestamp = 0; // CLOCK_MONOTONIC time of last action
+ uint64_t first_entry_addr = 0;
+};
+
+// An arch-independent representation of JIT/dex code entry.
+struct CodeEntry {
+ uint64_t addr;
+ uint64_t symfile_addr;
+ uint64_t symfile_size;
+ uint64_t timestamp; // CLOCK_MONOTONIC time of last action
+};
+
+struct Process {
+ pid_t pid = -1;
+ bool initialized = false;
+ bool died = false;
+ bool is_64bit = false;
+ // remote addr of jit descriptor
+ uint64_t jit_descriptor_addr = 0;
+ // remote addr of dex descriptor
+ uint64_t dex_descriptor_addr = 0;
+
+ // The state we know about the remote jit debug descriptor.
+ Descriptor last_jit_descriptor;
+ // The state we know about the remote dex debug descriptor.
+ Descriptor last_dex_descriptor;
+
+ // memory space for /memfd:jit-zygote-cache
+ std::vector<std::pair<uint64_t, uint64_t>> jit_zygote_cache_ranges_;
+};
+
+} // namespace JITDebugReader_impl
// JITDebugInfo represents the debug info of a JITed Java method or a dex file.
struct JITDebugInfo {
@@ -47,6 +93,7 @@ struct JITDebugInfo {
} type;
pid_t pid; // Process of the debug info
uint64_t timestamp; // Monotonic timestamp for the creation of the debug info
+
union {
struct {
uint64_t jit_code_addr; // The start addr of the JITed code
@@ -59,10 +106,13 @@ struct JITDebugInfo {
std::string file_path;
uint64_t file_offset;
- // The map for dex file extracted in memory. On Android Q, ART extracts dex files in apk files
- // directly into memory, and names it using prctl(). The kernel doesn't generate a new mmap
- // record for it. So we need to dump it manually.
- std::shared_ptr<ThreadMmap> extracted_dex_file_map;
+ // dex_file_map may be a dex file extracted in memory. On Android >= Q, ART may extract dex files
+ // in apk files directly into memory, and name it using prctl(). The kernel doesn't generate a
+ // new mmap record for it. So we need to dump it manually.
+ // Or dex_file_map may be a dex file created in memory. In that case, symbols are read from the
+ // dex file.
+ std::shared_ptr<ThreadMmap> dex_file_map;
+ std::vector<Symbol> symbols;
JITDebugInfo(pid_t pid, uint64_t timestamp, uint64_t jit_code_addr, uint64_t jit_code_len,
const std::string& file_path, uint64_t file_offset)
@@ -75,15 +125,16 @@ struct JITDebugInfo {
file_offset(file_offset) {}
JITDebugInfo(pid_t pid, uint64_t timestamp, uint64_t dex_file_offset,
- const std::string& file_path,
- const std::shared_ptr<ThreadMmap>& extracted_dex_file_map)
+ const std::string& file_path, const std::shared_ptr<ThreadMmap>& dex_file_map,
+ std::vector<Symbol> symbols)
: type(JIT_DEBUG_DEX_FILE),
pid(pid),
timestamp(timestamp),
dex_file_offset(dex_file_offset),
file_path(file_path),
file_offset(0),
- extracted_dex_file_map(extracted_dex_file_map) {}
+ dex_file_map(dex_file_map),
+ symbols(std::move(symbols)) {}
bool operator>(const JITDebugInfo& other) const { return timestamp > other.timestamp; }
};
@@ -94,6 +145,10 @@ class TempSymFile;
// corresponding debug interface in ART is at art/runtime/jit/debugger_interface.cc.
class JITDebugReader {
public:
+ using Descriptor = JITDebugReader_impl::Descriptor;
+ using CodeEntry = JITDebugReader_impl::CodeEntry;
+ using Process = JITDebugReader_impl::Process;
+
enum class SymFileOption {
kDropSymFiles, // JIT symfiles are dropped after recording.
kKeepSymFiles, // JIT symfiles are kept after recording, usually for debug unwinding.
@@ -113,7 +168,7 @@ class JITDebugReader {
bool SyncWithRecords() const { return sync_option_ == SyncOption::kSyncWithRecords; }
- typedef std::function<bool(const std::vector<JITDebugInfo>&, bool)> debug_info_callback_t;
+ typedef std::function<bool(std::vector<JITDebugInfo>, bool)> debug_info_callback_t;
bool RegisterDebugInfoCallback(IOEventLoop* loop, const debug_info_callback_t& callback);
// There are two ways to select which processes to monitor. One is using MonitorProcess(), the
@@ -134,48 +189,11 @@ class JITDebugReader {
path.find(std::string("_") + kJITZygoteCacheFile + ":") != path.npos;
}
- private:
- enum class DescriptorType {
- kDEX,
- kJIT,
- };
-
- // An arch-independent representation of JIT/dex debug descriptor.
- struct Descriptor {
- DescriptorType type;
- int version = 0;
- uint32_t action_seqlock = 0; // incremented before and after any modification
- uint64_t action_timestamp = 0; // CLOCK_MONOTONIC time of last action
- uint64_t first_entry_addr = 0;
- };
-
- // An arch-independent representation of JIT/dex code entry.
- struct CodeEntry {
- uint64_t addr;
- uint64_t symfile_addr;
- uint64_t symfile_size;
- uint64_t timestamp; // CLOCK_MONOTONIC time of last action
- };
-
- struct Process {
- pid_t pid = -1;
- bool initialized = false;
- bool died = false;
- bool is_64bit = false;
- // remote addr of jit descriptor
- uint64_t jit_descriptor_addr = 0;
- // remote addr of dex descriptor
- uint64_t dex_descriptor_addr = 0;
-
- // The state we know about the remote jit debug descriptor.
- Descriptor last_jit_descriptor;
- // The state we know about the remote dex debug descriptor.
- Descriptor last_dex_descriptor;
-
- // memory space for /memfd:jit-zygote-cache
- std::vector<std::pair<uint64_t, uint64_t>> jit_zygote_cache_ranges_;
- };
+ // exported for testing
+ void ReadDexFileDebugInfo(Process& process, const std::vector<CodeEntry>& dex_entries,
+ std::vector<JITDebugInfo>* debug_info);
+ private:
// The location of descriptors in libart.so.
struct DescriptorsLocation {
bool is_64bit = false;
@@ -208,9 +226,8 @@ class JITDebugReader {
bool ReadJITCodeDebugInfo(Process& process, const std::vector<CodeEntry>& jit_entries,
std::vector<JITDebugInfo>* debug_info);
TempSymFile* GetTempSymFile(Process& process, const CodeEntry& jit_entry);
- void ReadDexFileDebugInfo(Process& process, const std::vector<CodeEntry>& dex_entries,
- std::vector<JITDebugInfo>* debug_info);
- bool AddDebugInfo(const std::vector<JITDebugInfo>& debug_info, bool sync_kernel_records);
+ std::vector<Symbol> ReadDexFileSymbolsInMemory(Process& process, uint64_t addr, uint64_t size);
+ bool AddDebugInfo(std::vector<JITDebugInfo> debug_info, bool sync_kernel_records);
const std::string symfile_prefix_;
SymFileOption symfile_option_;
diff --git a/simpleperf/JITDebugReader_test.cpp b/simpleperf/JITDebugReader_test.cpp
index a0f53529..4aaad813 100644
--- a/simpleperf/JITDebugReader_test.cpp
+++ b/simpleperf/JITDebugReader_test.cpp
@@ -14,15 +14,25 @@
* limitations under the License.
*/
+#include "JITDebugReader.h"
#include "JITDebugReader_impl.h"
+#include <sys/mman.h>
+
#include <android-base/file.h>
+#include <android-base/scopeguard.h>
+#include <android-base/strings.h>
#include <android-base/test_utils.h>
+#include <android-base/unique_fd.h>
+#include "get_test_data.h"
+#include "utils.h"
#include <gtest/gtest.h>
using namespace simpleperf;
+using namespace simpleperf::JITDebugReader_impl;
+// @CddTest = 6.1/C-0-2
TEST(TempSymFile, smoke) {
TemporaryFile tmpfile;
std::unique_ptr<TempSymFile> symfile = TempSymFile::Create(tmpfile.path, false);
@@ -41,3 +51,50 @@ TEST(TempSymFile, smoke) {
ASSERT_TRUE(android::base::ReadFullyAtOffset(tmpfile.fd, buf, test_data.size(), offset));
ASSERT_EQ(strncmp(test_data.c_str(), buf, test_data.size()), 0);
}
+
+// @CddTest = 6.1/C-0-2
+TEST(JITDebugReader, read_dex_file_in_memory) {
+ // 1. Create dex file in memory. Use mmap instead of malloc, to avoid the pointer from
+ // being modified by memory tag (or pointer authentication?) on ARM64.
+ std::string dex_file = GetTestData("base.vdex");
+ uint64_t file_size = GetFileSize(dex_file);
+ const uint64_t dex_file_offset = 0x28;
+ ASSERT_GT(file_size, dex_file_offset);
+ uint64_t symfile_size = file_size - dex_file_offset;
+ void* symfile_addr =
+ mmap(nullptr, symfile_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ ASSERT_NE(symfile_addr, nullptr);
+ android::base::ScopeGuard g([&]() { munmap(symfile_addr, symfile_size); });
+ android::base::unique_fd fd(open(dex_file.c_str(), O_RDONLY | O_CLOEXEC));
+ ASSERT_TRUE(fd.ok());
+ ASSERT_TRUE(android::base::ReadFullyAtOffset(fd, symfile_addr, symfile_size, dex_file_offset));
+
+ // 2. Create CodeEntry pointing to the dex file in memory.
+ Process process;
+ process.pid = getpid();
+ process.initialized = true;
+ std::vector<CodeEntry> code_entries(1);
+ code_entries[0].addr = reinterpret_cast<uintptr_t>(&code_entries[0]);
+ code_entries[0].symfile_addr = reinterpret_cast<uintptr_t>(symfile_addr);
+ code_entries[0].symfile_size = symfile_size;
+ code_entries[0].timestamp = 0;
+
+ // 3. Test reading symbols from dex file in memory.
+ JITDebugReader reader("", JITDebugReader::SymFileOption::kDropSymFiles,
+ JITDebugReader::SyncOption::kNoSync);
+ std::vector<JITDebugInfo> debug_info;
+ reader.ReadDexFileDebugInfo(process, code_entries, &debug_info);
+ ASSERT_EQ(debug_info.size(), 1);
+ const JITDebugInfo& info = debug_info[0];
+ ASSERT_TRUE(info.dex_file_map);
+ ASSERT_EQ(info.dex_file_map->start_addr, reinterpret_cast<uintptr_t>(symfile_addr));
+ ASSERT_EQ(info.dex_file_map->len, symfile_size);
+ ASSERT_TRUE(android::base::StartsWith(info.dex_file_map->name, kDexFileInMemoryPrefix));
+ ASSERT_EQ(info.symbols.size(), 12435);
+ // 4. Test if the symbols are sorted.
+ uint64_t prev_addr = 0;
+ for (const auto& symbol : info.symbols) {
+ ASSERT_LE(prev_addr, symbol.addr);
+ prev_addr = symbol.addr;
+ }
+}
diff --git a/simpleperf/MapRecordReader_test.cpp b/simpleperf/MapRecordReader_test.cpp
index 669b31fb..d194cb56 100644
--- a/simpleperf/MapRecordReader_test.cpp
+++ b/simpleperf/MapRecordReader_test.cpp
@@ -26,6 +26,7 @@
using namespace simpleperf;
+// @CddTest = 6.1/C-0-2
class MapRecordReaderTest : public ::testing::Test {
protected:
bool CreateMapRecordReader() {
@@ -54,12 +55,14 @@ class MapRecordReaderTest : public ::testing::Test {
size_t comm_record_count_ = 0;
};
+// @CddTest = 6.1/C-0-2
TEST_F(MapRecordReaderTest, ReadKernelMaps) {
ASSERT_TRUE(CreateMapRecordReader());
ASSERT_TRUE(reader_->ReadKernelMaps());
ASSERT_GT(map_record_count_, 0);
}
+// @CddTest = 6.1/C-0-2
TEST_F(MapRecordReaderTest, ReadProcessMaps) {
ASSERT_TRUE(CreateMapRecordReader());
ASSERT_TRUE(reader_->ReadProcessMaps(getpid(), 0));
@@ -67,6 +70,7 @@ TEST_F(MapRecordReaderTest, ReadProcessMaps) {
ASSERT_GT(comm_record_count_, 0);
}
+// @CddTest = 6.1/C-0-2
TEST_F(MapRecordReaderTest, MapRecordThread) {
#ifdef __ANDROID__
std::string tmpdir = "/data/local/tmp";
diff --git a/simpleperf/OfflineUnwinder_test.cpp b/simpleperf/OfflineUnwinder_test.cpp
index 7f9d9aab..c13e229c 100644
--- a/simpleperf/OfflineUnwinder_test.cpp
+++ b/simpleperf/OfflineUnwinder_test.cpp
@@ -44,6 +44,7 @@ bool CheckUnwindMaps(UnwindMaps& maps, const MapSet& map_set) {
return true;
}
+// @CddTest = 6.1/C-0-2
TEST(OfflineUnwinder, UnwindMaps) {
// 1. Create fake map entries.
std::unique_ptr<Dso> fake_dso = Dso::CreateDso(DSO_UNKNOWN_FILE, "unknown");
@@ -90,6 +91,7 @@ TEST(OfflineUnwinder, UnwindMaps) {
ASSERT_TRUE(CheckUnwindMaps(maps, map_set));
}
+// @CddTest = 6.1/C-0-2
TEST(OfflineUnwinder, CollectMetaInfo) {
std::unordered_map<std::string, std::string> info_map;
OfflineUnwinder::CollectMetaInfo(&info_map);
@@ -100,6 +102,7 @@ TEST(OfflineUnwinder, CollectMetaInfo) {
}
}
+// @CddTest = 6.1/C-0-2
TEST(OfflineUnwinder, ARM64PackMask) {
std::unordered_map<std::string, std::string> info_map;
info_map[OfflineUnwinder::META_KEY_ARM64_PAC_MASK] = "0xff00000000";
diff --git a/simpleperf/ProbeEvents.cpp b/simpleperf/ProbeEvents.cpp
index 616bafe4..53b2d47f 100644
--- a/simpleperf/ProbeEvents.cpp
+++ b/simpleperf/ProbeEvents.cpp
@@ -30,6 +30,7 @@
#include "RegEx.h"
#include "environment.h"
+#include "event_selection_set.h"
#include "event_type.h"
#include "utils.h"
@@ -92,6 +93,14 @@ bool ProbeEvents::ParseKprobeEventName(const std::string& kprobe_cmd, ProbeEvent
return true;
}
+ProbeEvents::~ProbeEvents() {
+ if (!IsEmpty()) {
+ // Probe events can be deleted only when no perf event file is using them.
+ event_selection_set_.CloseEventFiles();
+ Clear();
+ }
+}
+
bool ProbeEvents::IsKprobeSupported() {
if (!kprobe_control_path_.has_value()) {
kprobe_control_path_ = "";
@@ -123,7 +132,8 @@ bool ProbeEvents::IsProbeEvent(const std::string& event_name) {
}
bool ProbeEvents::CreateProbeEventIfNotExist(const std::string& event_name) {
- if (EventTypeManager::Instance().FindType(event_name) != nullptr) {
+ if (!IsProbeEvent(event_name) || (EventTypeManager::Instance().FindType(event_name) != nullptr)) {
+ // No need to create a probe event.
return true;
}
std::string function_name = event_name.substr(kKprobeEventPrefix.size());
diff --git a/simpleperf/ProbeEvents.h b/simpleperf/ProbeEvents.h
index 39fa65f2..645dbc91 100644
--- a/simpleperf/ProbeEvents.h
+++ b/simpleperf/ProbeEvents.h
@@ -22,6 +22,8 @@
namespace simpleperf {
+class EventSelectionSet;
+
struct ProbeEvent {
std::string group_name;
std::string event_name;
@@ -31,22 +33,24 @@ struct ProbeEvent {
// delete them in ProbeEvents::clear().
class ProbeEvents {
public:
- ~ProbeEvents() { Clear(); }
+ ProbeEvents(EventSelectionSet& event_selection_set) : event_selection_set_(event_selection_set) {}
+ ~ProbeEvents();
static bool ParseKprobeEventName(const std::string& kprobe_cmd, ProbeEvent* event);
bool IsKprobeSupported();
// Accept kprobe cmd as in <linux_kernel>/Documentation/trace/kprobetrace.rst.
bool AddKprobe(const std::string& kprobe_cmd);
- bool IsProbeEvent(const std::string& event_name);
// If not exist, add a kprobe tracepoint at the function entry.
bool CreateProbeEventIfNotExist(const std::string& event_name);
- bool IsEmpty() const { return kprobe_events_.empty(); }
- void Clear();
private:
+ bool IsProbeEvent(const std::string& event_name);
+ bool IsEmpty() const { return kprobe_events_.empty(); }
+ void Clear();
bool WriteKprobeCmd(const std::string& kprobe_cmd);
+ EventSelectionSet& event_selection_set_;
std::vector<ProbeEvent> kprobe_events_;
std::optional<std::string> kprobe_control_path_;
};
diff --git a/simpleperf/ProbeEvents_test.cpp b/simpleperf/ProbeEvents_test.cpp
index be135386..630c5761 100644
--- a/simpleperf/ProbeEvents_test.cpp
+++ b/simpleperf/ProbeEvents_test.cpp
@@ -23,6 +23,7 @@
using namespace simpleperf;
+// @CddTest = 6.1/C-0-2
TEST(probe_events, ParseKprobeEventName) {
ProbeEvent event;
ASSERT_TRUE(ProbeEvents::ParseKprobeEventName("p:myprobe do_sys_open", &event));
diff --git a/simpleperf/RecordFilter.cpp b/simpleperf/RecordFilter.cpp
index 34054286..d6d25154 100644
--- a/simpleperf/RecordFilter.cpp
+++ b/simpleperf/RecordFilter.cpp
@@ -26,6 +26,152 @@ namespace simpleperf {
namespace {
+class CpuFilter : public RecordFilterCondition {
+ public:
+ void AddCpus(const std::set<int>& cpus) { cpus_.insert(cpus.begin(), cpus.end()); }
+
+ bool Check(const SampleRecord& sample) override {
+ int cpu = static_cast<int>(sample.cpu_data.cpu);
+ return cpus_.empty() || cpus_.count(cpu) == 1;
+ }
+
+ private:
+ std::set<int> cpus_;
+};
+
+class PidFilter : public RecordFilterCondition {
+ public:
+ void AddPids(const std::set<pid_t>& pids, bool exclude) {
+ auto& dest = exclude ? exclude_pids_ : include_pids_;
+ dest.insert(pids.begin(), pids.end());
+ }
+
+ bool Check(const SampleRecord& sample) override {
+ uint32_t pid = sample.tid_data.pid;
+ if (!include_pids_.empty() && include_pids_.count(pid) == 0) {
+ return false;
+ }
+ return exclude_pids_.count(pid) == 0;
+ }
+
+ private:
+ std::set<pid_t> include_pids_;
+ std::set<pid_t> exclude_pids_;
+};
+
+class TidFilter : public RecordFilterCondition {
+ public:
+ void AddTids(const std::set<pid_t>& tids, bool exclude) {
+ auto& dest = exclude ? exclude_tids_ : include_tids_;
+ dest.insert(tids.begin(), tids.end());
+ }
+
+ bool Check(const SampleRecord& sample) override {
+ uint32_t tid = sample.tid_data.tid;
+ if (!include_tids_.empty() && include_tids_.count(tid) == 0) {
+ return false;
+ }
+ return exclude_tids_.count(tid) == 0;
+ }
+
+ private:
+ std::set<pid_t> include_tids_;
+ std::set<pid_t> exclude_tids_;
+};
+
+class ProcessNameFilter : public RecordFilterCondition {
+ public:
+ ProcessNameFilter(const ThreadTree& thread_tree) : thread_tree_(thread_tree) {}
+
+ bool AddProcessNameRegex(const std::string& process_name, bool exclude) {
+ if (auto regex = RegEx::Create(process_name); regex != nullptr) {
+ auto& dest = exclude ? exclude_names_ : include_names_;
+ dest.emplace_back(std::move(regex));
+ return true;
+ }
+ return false;
+ }
+
+ bool Check(const SampleRecord& sample) override {
+ ThreadEntry* process = thread_tree_.FindThread(sample.tid_data.pid);
+ if (process == nullptr) {
+ return false;
+ }
+ std::string_view process_name = process->comm;
+ if (!include_names_.empty() && !SearchInRegs(process_name, include_names_)) {
+ return false;
+ }
+ return !SearchInRegs(process_name, exclude_names_);
+ }
+
+ private:
+ const ThreadTree& thread_tree_;
+ std::vector<std::unique_ptr<RegEx>> include_names_;
+ std::vector<std::unique_ptr<RegEx>> exclude_names_;
+};
+
+class ThreadNameFilter : public RecordFilterCondition {
+ public:
+ ThreadNameFilter(const ThreadTree& thread_tree) : thread_tree_(thread_tree) {}
+
+ bool AddThreadNameRegex(const std::string& thread_name, bool exclude) {
+ if (auto regex = RegEx::Create(thread_name); regex != nullptr) {
+ auto& dest = exclude ? exclude_names_ : include_names_;
+ dest.emplace_back(std::move(regex));
+ return true;
+ }
+ return false;
+ }
+
+ bool Check(const SampleRecord& sample) override {
+ ThreadEntry* thread = thread_tree_.FindThread(sample.tid_data.tid);
+ if (thread == nullptr) {
+ return false;
+ }
+ std::string_view thread_name = thread->comm;
+ if (!include_names_.empty() && !SearchInRegs(thread_name, include_names_)) {
+ return false;
+ }
+ return !SearchInRegs(thread_name, exclude_names_);
+ }
+
+ private:
+ const ThreadTree& thread_tree_;
+ std::vector<std::unique_ptr<RegEx>> include_names_;
+ std::vector<std::unique_ptr<RegEx>> exclude_names_;
+};
+
+class UidFilter : public RecordFilterCondition {
+ public:
+ void AddUids(const std::set<uint32_t>& uids, bool exclude) {
+ auto& dest = exclude ? exclude_uids_ : include_uids_;
+ dest.insert(uids.begin(), uids.end());
+ }
+
+ bool Check(const SampleRecord& sample) override {
+ uint32_t pid = sample.tid_data.pid;
+ std::optional<uint32_t> uid;
+ if (auto it = pid_to_uid_map_.find(pid); it != pid_to_uid_map_.end()) {
+ uid = it->second;
+ } else {
+ uid = GetProcessUid(pid);
+ pid_to_uid_map_[pid] = uid;
+ }
+ if (!uid) {
+ return false;
+ }
+ if (!include_uids_.empty() && include_uids_.count(uid.value()) == 0) {
+ return false;
+ }
+ return exclude_uids_.count(uid.value()) == 0;
+ }
+
+ private:
+ std::set<uint32_t> include_uids_;
+ std::set<uint32_t> exclude_uids_;
+ std::unordered_map<uint32_t, std::optional<uint32_t>> pid_to_uid_map_;
+};
+
using TimeRange = std::pair<uint64_t, uint64_t>;
class TimeRanges {
@@ -80,7 +226,7 @@ class TimeRanges {
} // namespace
-class TimeFilter {
+class TimeFilter : public RecordFilterCondition {
public:
const std::string& GetClock() const { return clock_; }
void SetClock(const std::string& clock) { clock_ = clock; }
@@ -111,7 +257,7 @@ class TimeFilter {
return global_ranges_.Empty() && process_ranges_.empty() && thread_ranges_.empty();
}
- bool Check(const SampleRecord& sample) const {
+ bool Check(const SampleRecord& sample) override {
uint64_t timestamp = sample.Timestamp();
if (!global_ranges_.Empty() && !global_ranges_.InRange(timestamp)) {
return false;
@@ -280,6 +426,13 @@ bool RecordFilter::ParseOptions(OptionValueMap& options) {
}
}
}
+ for (const OptionValue& value : options.PullValues("--cpu")) {
+ if (auto cpus = GetCpusFromString(*value.str_value); cpus) {
+ AddCpus(cpus.value());
+ } else {
+ return false;
+ }
+ }
if (auto value = options.PullValue("--filter-file"); value) {
if (!SetFilterFile(*value->str_value)) {
return false;
@@ -288,42 +441,54 @@ bool RecordFilter::ParseOptions(OptionValueMap& options) {
return true;
}
+void RecordFilter::AddCpus(const std::set<int>& cpus) {
+ std::unique_ptr<RecordFilterCondition>& cpu_filter = conditions_["cpu"];
+ if (!cpu_filter) {
+ cpu_filter.reset(new CpuFilter);
+ }
+ static_cast<CpuFilter&>(*cpu_filter).AddCpus(cpus);
+}
+
void RecordFilter::AddPids(const std::set<pid_t>& pids, bool exclude) {
- RecordFilterCondition& cond = GetCondition(exclude);
- cond.used = true;
- cond.pids.insert(pids.begin(), pids.end());
+ std::unique_ptr<RecordFilterCondition>& pid_filter = conditions_["pid"];
+ if (!pid_filter) {
+ pid_filter.reset(new PidFilter);
+ }
+ static_cast<PidFilter&>(*pid_filter).AddPids(pids, exclude);
}
void RecordFilter::AddTids(const std::set<pid_t>& tids, bool exclude) {
- RecordFilterCondition& cond = GetCondition(exclude);
- cond.used = true;
- cond.tids.insert(tids.begin(), tids.end());
+ std::unique_ptr<RecordFilterCondition>& tid_filter = conditions_["tid"];
+ if (!tid_filter) {
+ tid_filter.reset(new TidFilter);
+ }
+ static_cast<TidFilter&>(*tid_filter).AddTids(tids, exclude);
}
bool RecordFilter::AddProcessNameRegex(const std::string& process_name, bool exclude) {
- RecordFilterCondition& cond = GetCondition(exclude);
- cond.used = true;
- if (auto regex = RegEx::Create(process_name); regex != nullptr) {
- cond.process_name_regs.emplace_back(std::move(regex));
- return true;
+ std::unique_ptr<RecordFilterCondition>& process_name_filter = conditions_["process_name"];
+ if (!process_name_filter) {
+ process_name_filter.reset(new ProcessNameFilter(thread_tree_));
}
- return false;
+ return static_cast<ProcessNameFilter&>(*process_name_filter)
+ .AddProcessNameRegex(process_name, exclude);
}
bool RecordFilter::AddThreadNameRegex(const std::string& thread_name, bool exclude) {
- RecordFilterCondition& cond = GetCondition(exclude);
- cond.used = true;
- if (auto regex = RegEx::Create(thread_name); regex != nullptr) {
- cond.thread_name_regs.emplace_back(std::move(regex));
- return true;
+ std::unique_ptr<RecordFilterCondition>& thread_name_filter = conditions_["thread_name"];
+ if (!thread_name_filter) {
+ thread_name_filter.reset(new ThreadNameFilter(thread_tree_));
}
- return false;
+ return static_cast<ThreadNameFilter&>(*thread_name_filter)
+ .AddThreadNameRegex(thread_name, exclude);
}
void RecordFilter::AddUids(const std::set<uint32_t>& uids, bool exclude) {
- RecordFilterCondition& cond = GetCondition(exclude);
- cond.used = true;
- cond.uids.insert(uids.begin(), uids.end());
+ std::unique_ptr<RecordFilterCondition>& uid_filter = conditions_["uid"];
+ if (!uid_filter) {
+ uid_filter.reset(new UidFilter);
+ }
+ return static_cast<UidFilter&>(*uid_filter).AddUids(uids, exclude);
}
bool RecordFilter::SetFilterFile(const std::string& filename) {
@@ -331,86 +496,33 @@ bool RecordFilter::SetFilterFile(const std::string& filename) {
if (!reader.Read()) {
return false;
}
- time_filter_ = std::move(reader.GetTimeFilter());
+ conditions_["time"] = std::move(reader.GetTimeFilter());
return true;
}
-bool RecordFilter::Check(const SampleRecord* r) {
- if (exclude_condition_.used && CheckCondition(r, exclude_condition_)) {
- return false;
- }
- if (include_condition_.used && !CheckCondition(r, include_condition_)) {
- return false;
- }
- if (time_filter_ && !time_filter_->Check(*r)) {
- return false;
+bool RecordFilter::Check(const SampleRecord& r) {
+ for (auto& p : conditions_) {
+ if (!p.second->Check(r)) {
+ return false;
+ }
}
return true;
}
bool RecordFilter::CheckClock(const std::string& clock) {
- if (time_filter_ && time_filter_->GetClock() != clock) {
- LOG(ERROR) << "clock generating sample timestamps is " << clock
- << ", which doesn't match clock used in time filter " << time_filter_->GetClock();
- return false;
+ if (auto it = conditions_.find("time"); it != conditions_.end()) {
+ TimeFilter& time_filter = static_cast<TimeFilter&>(*it->second);
+ if (time_filter.GetClock() != clock) {
+ LOG(ERROR) << "clock generating sample timestamps is " << clock
+ << ", which doesn't match clock used in time filter " << time_filter.GetClock();
+ return false;
+ }
}
return true;
}
void RecordFilter::Clear() {
- exclude_condition_ = RecordFilterCondition();
- include_condition_ = RecordFilterCondition();
- pid_to_uid_map_.clear();
-}
-
-bool RecordFilter::CheckCondition(const SampleRecord* r, const RecordFilterCondition& condition) {
- if (condition.pids.count(r->tid_data.pid) == 1) {
- return true;
- }
- if (condition.tids.count(r->tid_data.tid) == 1) {
- return true;
- }
- if (!condition.process_name_regs.empty()) {
- if (ThreadEntry* process = thread_tree_.FindThread(r->tid_data.pid); process != nullptr) {
- if (SearchInRegs(process->comm, condition.process_name_regs)) {
- return true;
- }
- }
- }
- if (!condition.thread_name_regs.empty()) {
- if (ThreadEntry* thread = thread_tree_.FindThread(r->tid_data.tid); thread != nullptr) {
- if (SearchInRegs(thread->comm, condition.thread_name_regs)) {
- return true;
- }
- }
- }
- if (!condition.uids.empty()) {
- if (auto uid_value = GetUidForProcess(r->tid_data.pid); uid_value) {
- if (condition.uids.count(uid_value.value()) == 1) {
- return true;
- }
- }
- }
- return false;
-}
-
-bool RecordFilter::SearchInRegs(std::string_view s,
- const std::vector<std::unique_ptr<RegEx>>& regs) {
- for (auto& reg : regs) {
- if (reg->Search(s)) {
- return true;
- }
- }
- return false;
-}
-
-std::optional<uint32_t> RecordFilter::GetUidForProcess(pid_t pid) {
- if (auto it = pid_to_uid_map_.find(pid); it != pid_to_uid_map_.end()) {
- return it->second;
- }
- auto uid = GetProcessUid(pid);
- pid_to_uid_map_[pid] = uid;
- return uid;
+ conditions_.clear();
}
} // namespace simpleperf
diff --git a/simpleperf/RecordFilter.h b/simpleperf/RecordFilter.h
index cf413c12..40206914 100644
--- a/simpleperf/RecordFilter.h
+++ b/simpleperf/RecordFilter.h
@@ -18,6 +18,7 @@
#include <sys/types.h>
+#include <map>
#include <optional>
#include <set>
#include <string>
@@ -39,27 +40,29 @@ namespace simpleperf {
"--exclude-thread-name thread_name_regex Exclude samples for threads with name containing\n" \
" the regular expression.\n" \
"--exclude-uid uid1,uid2,... Exclude samples for processes belonging to selected uids.\n" \
- "--include-pid pid1,pid2,... Include samples for selected processes.\n" \
- "--include-tid tid1,tid2,... Include samples for selected threads.\n" \
- "--include-process-name process_name_regex Include samples for processes with name\n" \
+ "--include-pid pid1,pid2,... Only include samples for selected processes.\n" \
+ "--include-tid tid1,tid2,... Only include samples for selected threads.\n" \
+ "--include-process-name process_name_regex Only include samples for processes with name\n" \
" containing the regular expression.\n" \
- "--include-thread-name thread_name_regex Include samples for threads with name containing\n" \
- " the regular expression.\n" \
- "--include-uid uid1,uid2,... Include samples for processes belonging to selected uids.\n"
+ "--include-thread-name thread_name_regex Only include samples for threads with name\n" \
+ " containing the regular expression.\n" \
+ "--include-uid uid1,uid2,... Only include samples for processes belonging to selected uids.\n"
#define RECORD_FILTER_OPTION_HELP_MSG_FOR_REPORTING \
+ "--cpu cpu_item1,cpu_item2,... Only include samples for the selected cpus. cpu_item can be a\n" \
+ " number like 1, or a range like 0-3.\n" \
"--exclude-pid pid1,pid2,... Exclude samples for selected processes.\n" \
"--exclude-tid tid1,tid2,... Exclude samples for selected threads.\n" \
"--exclude-process-name process_name_regex Exclude samples for processes with name\n" \
" containing the regular expression.\n" \
"--exclude-thread-name thread_name_regex Exclude samples for threads with name containing\n" \
" the regular expression.\n" \
- "--include-pid pid1,pid2,... Include samples for selected processes.\n" \
- "--include-tid tid1,tid2,... Include samples for selected threads.\n" \
- "--include-process-name process_name_regex Include samples for processes with name\n" \
+ "--include-pid pid1,pid2,... Only include samples for selected processes.\n" \
+ "--include-tid tid1,tid2,... Only include samples for selected threads.\n" \
+ "--include-process-name process_name_regex Only include samples for processes with name\n" \
+ " containing the regular expression.\n" \
+ "--include-thread-name thread_name_regex Only include samples for threads with name\n" \
" containing the regular expression.\n" \
- "--include-thread-name thread_name_regex Include samples for threads with name containing\n" \
- " the regular expression.\n" \
"--filter-file <file> Use filter file to filter samples based on timestamps. The\n" \
" file format is in doc/sampler_filter.md.\n"
@@ -86,6 +89,8 @@ inline OptionFormatMap GetRecordFilterOptionFormats(bool for_recording) {
"--include-uid",
OptionFormat({OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}));
} else {
+ option_formats.emplace("--cpu", OptionFormat({OptionValueType::STRING, OptionType::MULTIPLE,
+ AppRunnerType::ALLOWED}));
option_formats.emplace(
"--filter-file",
OptionFormat({OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::ALLOWED}));
@@ -93,25 +98,25 @@ inline OptionFormatMap GetRecordFilterOptionFormats(bool for_recording) {
return option_formats;
}
-struct RecordFilterCondition {
- bool used = false;
- std::set<pid_t> pids;
- std::set<pid_t> tids;
- std::vector<std::unique_ptr<RegEx>> process_name_regs;
- std::vector<std::unique_ptr<RegEx>> thread_name_regs;
- std::set<uint32_t> uids;
-};
+class RecordFilterCondition {
+ public:
+ virtual ~RecordFilterCondition() {}
-class TimeFilter;
+ // Return true if the record passes this condition.
+ virtual bool Check(const SampleRecord& sample) = 0;
+};
-// Filter SampleRecords based on the rule below:
-// out_sample_records = (in_sample_records & ~exclude_conditions) & include_conditions
-// By default, exclude_conditions = 0, include_conditions = 1.
+// Filter a SampleRecord based on its fields:
+// pid, process_name, tid, thread_name, user_id, time (via FilterFile).
+// Each field is checked separately. To pass the filter, a sample should pass the check of each
+// field. For example, if we set to include pid 1 and exclude tid 2, a sample should have
+// `pid == 1 && tid != 2` to pass.
class RecordFilter {
public:
RecordFilter(const ThreadTree& thread_tree);
~RecordFilter();
bool ParseOptions(OptionValueMap& options);
+ void AddCpus(const std::set<int>& cpus);
void AddPids(const std::set<pid_t>& pids, bool exclude);
void AddTids(const std::set<pid_t>& tids, bool exclude);
bool AddProcessNameRegex(const std::string& process_name, bool exclude);
@@ -120,26 +125,17 @@ class RecordFilter {
bool SetFilterFile(const std::string& filename);
// Return true if the record passes filter.
- bool Check(const SampleRecord* r);
+ bool Check(const SampleRecord& r);
// Check if the clock matches the clock for timestamps in the filter file.
bool CheckClock(const std::string& clock);
- RecordFilterCondition& GetCondition(bool exclude) {
- return exclude ? exclude_condition_ : include_condition_;
- }
+ // Clear filter conditions.
void Clear();
private:
- bool CheckCondition(const SampleRecord* r, const RecordFilterCondition& condition);
- bool SearchInRegs(std::string_view s, const std::vector<std::unique_ptr<RegEx>>& regs);
- std::optional<uint32_t> GetUidForProcess(pid_t pid);
-
const ThreadTree& thread_tree_;
- RecordFilterCondition exclude_condition_;
- RecordFilterCondition include_condition_;
- std::unordered_map<pid_t, std::optional<uint32_t>> pid_to_uid_map_;
- std::unique_ptr<TimeFilter> time_filter_;
+ std::map<std::string, std::unique_ptr<RecordFilterCondition>> conditions_;
};
} // namespace simpleperf
diff --git a/simpleperf/RecordFilter_test.cpp b/simpleperf/RecordFilter_test.cpp
index 88345edd..1d5d1a06 100644
--- a/simpleperf/RecordFilter_test.cpp
+++ b/simpleperf/RecordFilter_test.cpp
@@ -18,6 +18,10 @@
#include <gtest/gtest.h>
+#if defined(__linux__)
+#include <unistd.h>
+#endif // defined(__linux__)
+
#include <memory>
#include "event_attr.h"
@@ -26,6 +30,7 @@
using namespace simpleperf;
+// @CddTest = 6.1/C-0-2
class RecordFilterTest : public ::testing::Test {
public:
RecordFilterTest() : filter(thread_tree) {}
@@ -37,10 +42,10 @@ class RecordFilterTest : public ::testing::Test {
record.reset(new SampleRecord(attr, 0, 0, 0, 0, 0, 0, 0, {}, {}, {}, 0));
}
- SampleRecord* GetRecord(uint32_t pid, uint32_t tid) {
+ SampleRecord& GetRecord(uint32_t pid, uint32_t tid) {
record->tid_data.pid = pid;
record->tid_data.tid = tid;
- return record.get();
+ return *record;
}
bool SetFilterData(const std::string& data) {
@@ -54,22 +59,36 @@ class RecordFilterTest : public ::testing::Test {
std::unique_ptr<SampleRecord> record;
};
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFilterTest, no_filter) {
ASSERT_TRUE(filter.Check(GetRecord(0, 0)));
}
+// @CddTest = 6.1/C-0-2
+TEST_F(RecordFilterTest, cpu) {
+ filter.AddCpus({1});
+ SampleRecord& r = GetRecord(0, 0);
+ r.cpu_data.cpu = 1;
+ ASSERT_TRUE(filter.Check(r));
+ r.cpu_data.cpu = 2;
+ ASSERT_FALSE(filter.Check(r));
+}
+
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFilterTest, exclude_pid) {
filter.AddPids({1}, true);
ASSERT_FALSE(filter.Check(GetRecord(1, 1)));
ASSERT_TRUE(filter.Check(GetRecord(2, 2)));
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFilterTest, exclude_tid) {
filter.AddTids({1}, true);
ASSERT_FALSE(filter.Check(GetRecord(1, 1)));
ASSERT_TRUE(filter.Check(GetRecord(1, 2)));
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFilterTest, exclude_process_name_regex) {
ASSERT_TRUE(filter.AddProcessNameRegex("processA", true));
thread_tree.SetThreadName(1, 1, "processA1");
@@ -78,6 +97,7 @@ TEST_F(RecordFilterTest, exclude_process_name_regex) {
ASSERT_TRUE(filter.Check(GetRecord(2, 2)));
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFilterTest, exclude_thread_name_regex) {
ASSERT_TRUE(filter.AddThreadNameRegex("threadA", true));
thread_tree.SetThreadName(1, 1, "processA_threadA");
@@ -86,28 +106,35 @@ TEST_F(RecordFilterTest, exclude_thread_name_regex) {
ASSERT_TRUE(filter.Check(GetRecord(1, 2)));
}
+#if defined(__linux__)
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFilterTest, exclude_uid) {
pid_t pid = getpid();
std::optional<uint32_t> uid = GetProcessUid(pid);
ASSERT_TRUE(uid.has_value());
filter.AddUids({uid.value()}, true);
ASSERT_FALSE(filter.Check(GetRecord(pid, pid)));
+ // The check fails if a process can't find its corresponding uid.
uint32_t pid_not_exist = UINT32_MAX;
- ASSERT_TRUE(filter.Check(GetRecord(pid_not_exist, pid_not_exist)));
+ ASSERT_FALSE(filter.Check(GetRecord(pid_not_exist, pid_not_exist)));
}
+#endif // defined(__linux__)
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFilterTest, include_pid) {
filter.AddPids({1}, false);
ASSERT_TRUE(filter.Check(GetRecord(1, 1)));
ASSERT_FALSE(filter.Check(GetRecord(2, 2)));
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFilterTest, include_tid) {
filter.AddTids({1}, false);
ASSERT_TRUE(filter.Check(GetRecord(1, 1)));
ASSERT_FALSE(filter.Check(GetRecord(1, 2)));
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFilterTest, include_process_name_regex) {
ASSERT_TRUE(filter.AddProcessNameRegex("processA", false));
thread_tree.SetThreadName(1, 1, "processA1");
@@ -116,6 +143,7 @@ TEST_F(RecordFilterTest, include_process_name_regex) {
ASSERT_FALSE(filter.Check(GetRecord(2, 2)));
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFilterTest, include_thread_name_regex) {
ASSERT_TRUE(filter.AddThreadNameRegex("threadA", false));
thread_tree.SetThreadName(1, 1, "processA_threadA");
@@ -124,6 +152,8 @@ TEST_F(RecordFilterTest, include_thread_name_regex) {
ASSERT_FALSE(filter.Check(GetRecord(1, 2)));
}
+#if defined(__linux__)
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFilterTest, include_uid) {
pid_t pid = getpid();
std::optional<uint32_t> uid = GetProcessUid(pid);
@@ -133,80 +163,85 @@ TEST_F(RecordFilterTest, include_uid) {
uint32_t pid_not_exist = UINT32_MAX;
ASSERT_FALSE(filter.Check(GetRecord(pid_not_exist, pid_not_exist)));
}
+#endif // defined(__linux__)
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFilterTest, global_time_filter) {
ASSERT_TRUE(
SetFilterData("GLOBAL_BEGIN 1000\n"
"GLOBAL_END 2000\n"
"GLOBAL_BEGIN 3000\n"
"GLOBAL_END 4000"));
- SampleRecord* r = GetRecord(1, 1);
- r->time_data.time = 0;
+ SampleRecord& r = GetRecord(1, 1);
+ r.time_data.time = 0;
ASSERT_FALSE(filter.Check(r));
- r->time_data.time = 999;
+ r.time_data.time = 999;
ASSERT_FALSE(filter.Check(r));
- r->time_data.time = 1000;
+ r.time_data.time = 1000;
ASSERT_TRUE(filter.Check(r));
- r->time_data.time = 1001;
+ r.time_data.time = 1001;
ASSERT_TRUE(filter.Check(r));
- r->time_data.time = 1999;
+ r.time_data.time = 1999;
ASSERT_TRUE(filter.Check(r));
- r->time_data.time = 2000;
+ r.time_data.time = 2000;
ASSERT_FALSE(filter.Check(r));
- r->time_data.time = 2001;
+ r.time_data.time = 2001;
ASSERT_FALSE(filter.Check(r));
- r->time_data.time = 3000;
+ r.time_data.time = 3000;
ASSERT_TRUE(filter.Check(r));
- r->time_data.time = 4000;
+ r.time_data.time = 4000;
ASSERT_FALSE(filter.Check(r));
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFilterTest, process_time_filter) {
ASSERT_TRUE(
SetFilterData("PROCESS_BEGIN 1 1000\n"
"PROCESS_END 1 2000"));
- SampleRecord* r = GetRecord(1, 1);
- r->time_data.time = 0;
+ SampleRecord& r = GetRecord(1, 1);
+ r.time_data.time = 0;
ASSERT_FALSE(filter.Check(r));
- r->time_data.time = 999;
+ r.time_data.time = 999;
ASSERT_FALSE(filter.Check(r));
- r->time_data.time = 1000;
+ r.time_data.time = 1000;
ASSERT_TRUE(filter.Check(r));
- r->time_data.time = 1001;
+ r.time_data.time = 1001;
ASSERT_TRUE(filter.Check(r));
- r->time_data.time = 1999;
+ r.time_data.time = 1999;
ASSERT_TRUE(filter.Check(r));
- r->time_data.time = 2000;
+ r.time_data.time = 2000;
ASSERT_FALSE(filter.Check(r));
// When process time filters are used, not mentioned processes should be filtered.
- r->tid_data.pid = 2;
- r->time_data.time = 1000;
+ r.tid_data.pid = 2;
+ r.time_data.time = 1000;
ASSERT_FALSE(filter.Check(r));
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFilterTest, thread_time_filter) {
ASSERT_TRUE(
SetFilterData("THREAD_BEGIN 1 1000\n"
"THREAD_END 1 2000"));
- SampleRecord* r = GetRecord(1, 1);
- r->time_data.time = 0;
+ SampleRecord& r = GetRecord(1, 1);
+ r.time_data.time = 0;
ASSERT_FALSE(filter.Check(r));
- r->time_data.time = 999;
+ r.time_data.time = 999;
ASSERT_FALSE(filter.Check(r));
- r->time_data.time = 1000;
+ r.time_data.time = 1000;
ASSERT_TRUE(filter.Check(r));
- r->time_data.time = 1001;
+ r.time_data.time = 1001;
ASSERT_TRUE(filter.Check(r));
- r->time_data.time = 1999;
+ r.time_data.time = 1999;
ASSERT_TRUE(filter.Check(r));
- r->time_data.time = 2000;
+ r.time_data.time = 2000;
ASSERT_FALSE(filter.Check(r));
// When thread time filters are used, not mentioned threads should be filtered.
- r->tid_data.tid = 2;
- r->time_data.time = 1000;
+ r.tid_data.tid = 2;
+ r.time_data.time = 1000;
ASSERT_FALSE(filter.Check(r));
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFilterTest, clock_in_time_filter) {
// If there is no filter data, any clock is fine.
ASSERT_TRUE(filter.CheckClock("monotonic"));
@@ -221,6 +256,7 @@ TEST_F(RecordFilterTest, clock_in_time_filter) {
ASSERT_FALSE(filter.CheckClock("monotonic"));
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFilterTest, error_in_time_filter) {
// no timestamp error
ASSERT_FALSE(SetFilterData("GLOBAL_BEGIN"));
@@ -253,7 +289,7 @@ class ParseRecordFilterCommand : public Command {
ParseRecordFilterCommand(RecordFilter& filter) : Command("", "", ""), filter_(filter) {}
bool Run(const std::vector<std::string>& args) override {
- const auto option_formats = GetRecordFilterOptionFormats(true);
+ const auto option_formats = GetRecordFilterOptionFormats(for_recording);
OptionValueMap options;
std::vector<std::pair<OptionName, OptionValue>> ordered_options;
@@ -264,12 +300,15 @@ class ParseRecordFilterCommand : public Command {
return filter_.ParseOptions(options);
}
+ bool for_recording = true;
+
private:
RecordFilter& filter_;
};
} // namespace
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFilterTest, parse_options) {
ParseRecordFilterCommand filter_cmd(filter);
@@ -277,25 +316,45 @@ TEST_F(RecordFilterTest, parse_options) {
std::string prefix = exclude ? "--exclude-" : "--include-";
ASSERT_TRUE(filter_cmd.Run({prefix + "pid", "1,2", prefix + "pid", "3"}));
- ASSERT_EQ(filter.GetCondition(exclude).pids, std::set<pid_t>({1, 2, 3}));
+ ASSERT_EQ(filter.Check(GetRecord(1, 1)), !exclude);
+ ASSERT_EQ(filter.Check(GetRecord(2, 2)), !exclude);
+ ASSERT_EQ(filter.Check(GetRecord(3, 3)), !exclude);
+
ASSERT_TRUE(filter_cmd.Run({prefix + "tid", "1,2", prefix + "tid", "3"}));
- ASSERT_EQ(filter.GetCondition(exclude).tids, std::set<pid_t>({1, 2, 3}));
+ ASSERT_EQ(filter.Check(GetRecord(1, 1)), !exclude);
+ ASSERT_EQ(filter.Check(GetRecord(1, 2)), !exclude);
+ ASSERT_EQ(filter.Check(GetRecord(1, 3)), !exclude);
ASSERT_TRUE(
filter_cmd.Run({prefix + "process-name", "processA", prefix + "process-name", "processB"}));
- auto& process_regs = filter.GetCondition(exclude).process_name_regs;
- ASSERT_EQ(process_regs.size(), 2);
- ASSERT_TRUE(process_regs[0]->Match("processA"));
- ASSERT_TRUE(process_regs[1]->Match("processB"));
+ thread_tree.SetThreadName(1, 1, "processA");
+ thread_tree.SetThreadName(2, 2, "processB");
+ ASSERT_EQ(filter.Check(GetRecord(1, 1)), !exclude);
+ ASSERT_EQ(filter.Check(GetRecord(2, 2)), !exclude);
ASSERT_TRUE(
filter_cmd.Run({prefix + "thread-name", "threadA", prefix + "thread-name", "threadB"}));
- auto& thread_regs = filter.GetCondition(exclude).thread_name_regs;
- ASSERT_EQ(thread_regs.size(), 2);
- ASSERT_TRUE(thread_regs[0]->Match("threadA"));
- ASSERT_TRUE(thread_regs[1]->Match("threadB"));
+ thread_tree.SetThreadName(1, 11, "threadA");
+ thread_tree.SetThreadName(1, 12, "threadB");
+ ASSERT_EQ(filter.Check(GetRecord(1, 11)), !exclude);
+ ASSERT_EQ(filter.Check(GetRecord(2, 12)), !exclude);
ASSERT_TRUE(filter_cmd.Run({prefix + "uid", "1,2", prefix + "uid", "3"}));
- ASSERT_EQ(filter.GetCondition(exclude).uids, std::set<uint32_t>({1, 2, 3}));
+#if defined(__linux__)
+ pid_t pid = getpid();
+ uid_t uid = getuid();
+ ASSERT_TRUE(filter_cmd.Run({prefix + "uid", std::to_string(uid)}));
+ ASSERT_EQ(filter.Check(GetRecord(pid, pid)), !exclude);
+#endif // defined(__linux__)
}
+
+ filter_cmd.for_recording = false;
+ ASSERT_TRUE(filter_cmd.Run({"--cpu", "0", "--cpu", "1-3"}));
+ SampleRecord& r = GetRecord(0, 0);
+ r.cpu_data.cpu = 0;
+ ASSERT_TRUE(filter.Check(r));
+ r.cpu_data.cpu = 2;
+ ASSERT_TRUE(filter.Check(r));
+ r.cpu_data.cpu = 4;
+ ASSERT_FALSE(filter.Check(r));
}
diff --git a/simpleperf/RecordReadThread.cpp b/simpleperf/RecordReadThread.cpp
index ae632d38..2d034bc1 100644
--- a/simpleperf/RecordReadThread.cpp
+++ b/simpleperf/RecordReadThread.cpp
@@ -29,8 +29,8 @@
namespace simpleperf {
-static constexpr size_t kDefaultLowBufferLevel = 10 * 1024 * 1024u;
-static constexpr size_t kDefaultCriticalBufferLevel = 5 * 1024 * 1024u;
+static constexpr size_t kDefaultLowBufferLevel = 10 * kMegabyte;
+static constexpr size_t kDefaultCriticalBufferLevel = 5 * kMegabyte;
RecordBuffer::RecordBuffer(size_t buffer_size)
: read_head_(0), write_head_(0), buffer_size_(buffer_size), buffer_(new char[buffer_size]) {}
@@ -223,7 +223,7 @@ bool KernelRecordReader::MoveToNextRecord(const RecordParser& parser) {
RecordReadThread::RecordReadThread(size_t record_buffer_size, const perf_event_attr& attr,
size_t min_mmap_pages, size_t max_mmap_pages,
- size_t aux_buffer_size, bool allow_cutting_samples,
+ size_t aux_buffer_size, bool allow_truncating_samples,
bool exclude_perf)
: record_buffer_(record_buffer_size),
record_parser_(attr),
@@ -239,7 +239,7 @@ RecordReadThread::RecordReadThread(size_t record_buffer_size, const perf_event_a
LOG(VERBOSE) << "user buffer size = " << record_buffer_size
<< ", low_level size = " << record_buffer_low_level_
<< ", critical_level size = " << record_buffer_critical_level_;
- if (!allow_cutting_samples) {
+ if (!allow_truncating_samples) {
record_buffer_low_level_ = record_buffer_critical_level_;
}
if (exclude_perf) {
@@ -408,6 +408,7 @@ bool RecordReadThread::HandleAddEventFds(IOEventLoop& loop,
success = false;
break;
}
+ has_etm_events_ = true;
}
cpu_map[fd->Cpu()] = fd;
} else {
@@ -533,12 +534,12 @@ void RecordReadThread::PushRecordToRecordBuffer(KernelRecordReader* kernel_recor
if (free_size < record_buffer_critical_level_) {
// When the free size in record buffer is below critical level, drop sample records to save
// space for more important records (like mmap or fork records).
- stat_.lost_samples++;
+ stat_.userspace_lost_samples++;
return;
}
size_t stack_size_limit = stack_size_in_sample_record_;
if (free_size < record_buffer_low_level_) {
- // When the free size in record buffer is below low level, cut the stack data in sample
+ // When the free size in record buffer is below low level, truncate the stack data in sample
// records to 1K. This makes the unwinder unwind only part of the callchains, but hopefully
// the call chain joiner can complete the callchains.
stack_size_limit = 1024;
@@ -580,10 +581,10 @@ void RecordReadThread::PushRecordToRecordBuffer(KernelRecordReader* kernel_recor
memcpy(p + pos + new_stack_size, &new_stack_size, sizeof(uint64_t));
record_buffer_.FinishWrite();
if (new_stack_size < dyn_stack_size) {
- stat_.cut_stack_samples++;
+ stat_.userspace_truncated_stack_samples++;
}
} else {
- stat_.lost_samples++;
+ stat_.userspace_lost_samples++;
}
return;
}
@@ -603,18 +604,26 @@ void RecordReadThread::PushRecordToRecordBuffer(KernelRecordReader* kernel_recor
// only after we have collected the aux data.
event_fds_disabled_by_kernel_.insert(kernel_record_reader->GetEventFd());
}
+ } else if (header.type == PERF_RECORD_LOST) {
+ LostRecord r;
+ if (r.Parse(attr_, p, p + header.size)) {
+ stat_.kernelspace_lost_records += static_cast<size_t>(r.lost);
+ }
}
record_buffer_.FinishWrite();
} else {
if (header.type == PERF_RECORD_SAMPLE) {
- stat_.lost_samples++;
+ stat_.userspace_lost_samples++;
} else {
- stat_.lost_non_samples++;
+ stat_.userspace_lost_non_samples++;
}
}
}
void RecordReadThread::ReadAuxDataFromKernelBuffer(bool* has_data) {
+ if (!has_etm_events_) {
+ return;
+ }
for (auto& reader : kernel_record_readers_) {
EventFd* event_fd = reader.GetEventFd();
if (event_fd->HasAuxBuffer()) {
@@ -654,6 +663,14 @@ void RecordReadThread::ReadAuxDataFromKernelBuffer(bool* has_data) {
}
bool RecordReadThread::SendDataNotificationToMainThread() {
+ if (has_etm_events_) {
+ // For ETM recording, the default buffer size is large enough to hold ETM data for several
+ // seconds. To reduce impact of processing ETM data (especially when --decode-etm is used),
+ // delay processing ETM data until the buffer is half full.
+ if (record_buffer_.GetFreeSize() >= record_buffer_.size() / 2) {
+ return true;
+ }
+ }
if (!has_data_notification_.load(std::memory_order_relaxed)) {
has_data_notification_ = true;
char unused = 0;
diff --git a/simpleperf/RecordReadThread.h b/simpleperf/RecordReadThread.h
index 47dfbb20..893f8234 100644
--- a/simpleperf/RecordReadThread.h
+++ b/simpleperf/RecordReadThread.h
@@ -90,9 +90,10 @@ class RecordParser {
};
struct RecordStat {
- size_t lost_samples = 0;
- size_t lost_non_samples = 0;
- size_t cut_stack_samples = 0;
+ size_t kernelspace_lost_records = 0;
+ size_t userspace_lost_samples = 0;
+ size_t userspace_lost_non_samples = 0;
+ size_t userspace_truncated_stack_samples = 0;
uint64_t aux_data_size = 0;
uint64_t lost_aux_data_size = 0;
};
@@ -130,8 +131,8 @@ class KernelRecordReader {
class RecordReadThread {
public:
RecordReadThread(size_t record_buffer_size, const perf_event_attr& attr, size_t min_mmap_pages,
- size_t max_mmap_pages, size_t aux_buffer_size, bool allow_cutting_samples = true,
- bool exclude_perf = false);
+ size_t max_mmap_pages, size_t aux_buffer_size,
+ bool allow_truncating_samples = true, bool exclude_perf = false);
~RecordReadThread();
void SetBufferLevels(size_t record_buffer_low_level, size_t record_buffer_critical_level) {
record_buffer_low_level_ = record_buffer_low_level;
@@ -210,6 +211,7 @@ class RecordReadThread {
std::unique_ptr<std::thread> read_thread_;
std::vector<KernelRecordReader> kernel_record_readers_;
pid_t exclude_pid_ = -1;
+ bool has_etm_events_ = false;
std::unordered_set<EventFd*> event_fds_disabled_by_kernel_;
diff --git a/simpleperf/RecordReadThread_test.cpp b/simpleperf/RecordReadThread_test.cpp
index 3c90e4be..2ff9a460 100644
--- a/simpleperf/RecordReadThread_test.cpp
+++ b/simpleperf/RecordReadThread_test.cpp
@@ -32,6 +32,7 @@ using ::testing::Truly;
using namespace simpleperf;
+// @CddTest = 6.1/C-0-2
class RecordBufferTest : public ::testing::Test {
protected:
void PushRecord(uint32_t type, size_t size) {
@@ -57,6 +58,7 @@ class RecordBufferTest : public ::testing::Test {
std::unique_ptr<RecordBuffer> buffer_;
};
+// @CddTest = 6.1/C-0-2
TEST_F(RecordBufferTest, fifo) {
for (size_t loop = 0; loop < 10; ++loop) {
buffer_.reset(new RecordBuffer(sizeof(perf_event_header) * 10));
@@ -73,11 +75,12 @@ TEST_F(RecordBufferTest, fifo) {
}
}
+// @CddTest = 6.1/C-0-2
TEST(RecordParser, smoke) {
std::unique_ptr<RecordFileReader> reader =
RecordFileReader::CreateInstance(GetTestData(PERF_DATA_NO_UNWIND));
ASSERT_TRUE(reader);
- RecordParser parser(*reader->AttrSection()[0].attr);
+ RecordParser parser(reader->AttrSection()[0].attr);
auto process_record = [&](std::unique_ptr<Record> record) {
if (record->type() == PERF_RECORD_MMAP || record->type() == PERF_RECORD_COMM ||
record->type() == PERF_RECORD_FORK || record->type() == PERF_RECORD_SAMPLE) {
@@ -113,6 +116,7 @@ TEST(RecordParser, smoke) {
}));
}
+// @CddTest = 6.1/C-0-2
TEST(RecordParser, GetStackSizePos_with_PerfSampleReadType) {
const EventType* type = FindEventTypeByName("cpu-clock");
ASSERT_TRUE(type != nullptr);
@@ -190,6 +194,7 @@ static inline std::function<bool(size_t&)> SetArg(size_t value) {
};
}
+// @CddTest = 6.1/C-0-2
TEST(KernelRecordReader, smoke) {
// 1. Create fake records.
perf_event_attr attr = CreateFakeEventAttr();
@@ -229,6 +234,7 @@ TEST(KernelRecordReader, smoke) {
ASSERT_FALSE(reader.MoveToNextRecord(parser));
}
+// @CddTest = 6.1/C-0-2
class RecordReadThreadTest : public ::testing::Test {
protected:
std::vector<EventFd*> CreateFakeEventFds(const perf_event_attr& attr, size_t event_fd_count) {
@@ -270,6 +276,7 @@ class RecordReadThreadTest : public ::testing::Test {
std::vector<std::unique_ptr<MockEventFd>> event_fds_;
};
+// @CddTest = 6.1/C-0-2
TEST_F(RecordReadThreadTest, handle_cmds) {
perf_event_attr attr = CreateFakeEventAttr();
records_ = CreateFakeRecords(attr, 2, 0, 0);
@@ -291,6 +298,7 @@ TEST_F(RecordReadThreadTest, handle_cmds) {
ASSERT_TRUE(thread.StopReadThread());
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordReadThreadTest, read_records) {
perf_event_attr attr = CreateFakeEventAttr();
RecordReadThread thread(128 * 1024, attr, 1, 1, 0);
@@ -323,6 +331,7 @@ TEST_F(RecordReadThreadTest, read_records) {
}
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordReadThreadTest, process_sample_record) {
perf_event_attr attr = CreateFakeEventAttr();
attr.sample_type |= PERF_SAMPLE_STACK_USER;
@@ -370,13 +379,14 @@ TEST_F(RecordReadThreadTest, process_sample_record) {
thread.SetBufferLevels(record_buffer_size, record_buffer_size);
read_record(r);
ASSERT_FALSE(r);
- ASSERT_EQ(thread.GetStat().lost_samples, 1u);
- ASSERT_EQ(thread.GetStat().lost_non_samples, 0u);
- ASSERT_EQ(thread.GetStat().cut_stack_samples, 1u);
+ ASSERT_EQ(thread.GetStat().userspace_lost_samples, 1u);
+ ASSERT_EQ(thread.GetStat().userspace_lost_non_samples, 0u);
+ ASSERT_EQ(thread.GetStat().userspace_truncated_stack_samples, 1u);
}
// Test that the data notification exists until the RecordBuffer is empty. So we can read all
// records even if reading one record at a time.
+// @CddTest = 6.1/C-0-2
TEST_F(RecordReadThreadTest, has_data_notification_until_buffer_empty) {
perf_event_attr attr = CreateFakeEventAttr();
RecordReadThread thread(128 * 1024, attr, 1, 1, 0);
@@ -403,7 +413,8 @@ TEST_F(RecordReadThreadTest, has_data_notification_until_buffer_empty) {
ASSERT_TRUE(thread.RemoveEventFds(event_fds));
}
-TEST_F(RecordReadThreadTest, no_cut_samples) {
+// @CddTest = 6.1/C-0-2
+TEST_F(RecordReadThreadTest, no_truncated_samples) {
perf_event_attr attr = CreateFakeEventAttr();
attr.sample_type |= PERF_SAMPLE_STACK_USER;
attr.sample_stack_user = 64 * 1024;
@@ -421,11 +432,12 @@ TEST_F(RecordReadThreadTest, no_cut_samples) {
received_samples++;
}
ASSERT_GT(received_samples, 0u);
- ASSERT_GT(thread.GetStat().lost_samples, 0u);
- ASSERT_EQ(thread.GetStat().lost_samples, total_samples - received_samples);
- ASSERT_EQ(thread.GetStat().cut_stack_samples, 0u);
+ ASSERT_GT(thread.GetStat().userspace_lost_samples, 0u);
+ ASSERT_EQ(thread.GetStat().userspace_lost_samples, total_samples - received_samples);
+ ASSERT_EQ(thread.GetStat().userspace_truncated_stack_samples, 0u);
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordReadThreadTest, exclude_perf) {
perf_event_attr attr = CreateFakeEventAttr();
attr.sample_type |= PERF_SAMPLE_STACK_USER;
@@ -475,6 +487,7 @@ struct FakeAuxData {
: buf1(buf1_size, c), buf2(buf2_size, c), pad(pad_size, 0), lost(lost) {}
};
+// @CddTest = 6.1/C-0-2
TEST_F(RecordReadThreadTest, read_aux_data) {
ScopedEventTypes scoped_types("cs-etm,0,0");
const EventType* type = FindEventTypeByName("cs-etm");
@@ -569,4 +582,4 @@ TEST_F(RecordReadThreadTest, read_aux_data) {
}
ASSERT_EQ(aux_data_size, thread.GetStat().aux_data_size);
ASSERT_EQ(lost_aux_data_size, thread.GetStat().lost_aux_data_size);
-} \ No newline at end of file
+}
diff --git a/simpleperf/RegEx.cpp b/simpleperf/RegEx.cpp
index 97bb45bb..6de8440a 100644
--- a/simpleperf/RegEx.cpp
+++ b/simpleperf/RegEx.cpp
@@ -77,4 +77,13 @@ std::unique_ptr<RegEx> RegEx::Create(std::string_view pattern) {
}
}
+bool SearchInRegs(std::string_view s, const std::vector<std::unique_ptr<RegEx>>& regs) {
+ for (auto& reg : regs) {
+ if (reg->Search(s)) {
+ return true;
+ }
+ }
+ return false;
+}
+
} // namespace simpleperf
diff --git a/simpleperf/RegEx.h b/simpleperf/RegEx.h
index c1e4f511..6e75afff 100644
--- a/simpleperf/RegEx.h
+++ b/simpleperf/RegEx.h
@@ -20,6 +20,7 @@
#include <optional>
#include <string>
#include <string_view>
+#include <vector>
namespace simpleperf {
@@ -51,4 +52,6 @@ class RegEx {
std::string pattern_;
};
+bool SearchInRegs(std::string_view s, const std::vector<std::unique_ptr<RegEx>>& regs);
+
} // namespace simpleperf
diff --git a/simpleperf/RegEx_test.cpp b/simpleperf/RegEx_test.cpp
index cbb25ae0..978772c4 100644
--- a/simpleperf/RegEx_test.cpp
+++ b/simpleperf/RegEx_test.cpp
@@ -20,6 +20,7 @@
using namespace simpleperf;
+// @CddTest = 6.1/C-0-2
TEST(RegEx, smoke) {
auto re = RegEx::Create("b+");
ASSERT_EQ(re->GetPattern(), "b+");
@@ -42,6 +43,7 @@ TEST(RegEx, smoke) {
ASSERT_EQ(re->Replace("ababb", "c").value(), "acac");
}
+// @CddTest = 6.1/C-0-2
TEST(RegEx, invalid_pattern) {
ASSERT_TRUE(RegEx::Create("?hello") == nullptr);
}
diff --git a/simpleperf/branch_list.proto b/simpleperf/branch_list.proto
new file mode 100644
index 00000000..6d6bfb17
--- /dev/null
+++ b/simpleperf/branch_list.proto
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2020 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 branch list file format is generated by the inject command. It contains
+// a single BranchList message.
+
+syntax = "proto3";
+
+package simpleperf.proto;
+
+message BranchList {
+ // Used to identify format in generated proto files.
+ // Should always be "simpleperf:EtmBranchList".
+ string magic = 1;
+ repeated ETMBinary etm_data = 2;
+ LBRData lbr_data = 3;
+}
+
+message ETMBinary {
+ string path = 1;
+ string build_id = 2;
+
+ message Address {
+ // vaddr in binary, instr addr before the first branch
+ uint64 addr = 1;
+
+ message Branch {
+ // Each bit represents a branch: 0 for not branch, 1 for branch.
+ // Bit 0 comes first, bit 7 comes last.
+ bytes branch = 1;
+ uint32 branch_size = 2;
+ uint64 count = 3;
+ }
+
+ repeated Branch branches = 2;
+ }
+
+ repeated Address addrs = 3;
+
+ enum BinaryType {
+ ELF_FILE = 0;
+ KERNEL = 1;
+ KERNEL_MODULE = 2;
+ }
+ BinaryType type = 4;
+
+ message KernelBinaryInfo {
+ // kernel_start_addr is used to convert kernel ip address to vaddr in vmlinux.
+ // If it is zero, the Address in KERNEL binary has been converted to vaddr. Otherwise,
+ // the Address in KERNEL binary is still ip address, and need to be converted later.
+ uint64 kernel_start_addr = 1;
+ }
+
+ KernelBinaryInfo kernel_info = 5;
+}
+
+message LBRData {
+ repeated Sample samples = 1;
+ repeated Binary binaries = 2;
+
+ message Sample {
+ // If binary_id >= 1, it refers to LBRData.binaries[binary_id - 1]. Otherwise, it's invalid.
+ uint32 binary_id = 1;
+ uint64 vaddr_in_file = 2;
+ repeated Branch branches = 3;
+
+ message Branch {
+ // If from_binary_id >= 1, it refers to LBRData.binaries[from_binary_id - 1]. Otherwise, it's
+ // invalid.
+ uint32 from_binary_id = 1;
+ // If to_binary_id >= 1, it refers to LBRData.binaries[to_binary_id - 1]. Otherwise, it's
+ // invalid.
+ uint32 to_binary_id = 2;
+ uint64 from_vaddr_in_file = 3;
+ uint64 to_vaddr_in_file = 4;
+ }
+ }
+
+ message Binary {
+ string path = 1;
+ string build_id = 2;
+ }
+}
diff --git a/simpleperf/cmd_api_test.cpp b/simpleperf/cmd_api_test.cpp
index 41a2a165..d1fb044a 100644
--- a/simpleperf/cmd_api_test.cpp
+++ b/simpleperf/cmd_api_test.cpp
@@ -90,6 +90,7 @@ static void RecordApp(const std::string& package_name, const std::string& apk_pa
#endif // defined(__ANDROID__)
+// @CddTest = 6.1/C-0-2
TEST(cmd_api, java_app) {
#if defined(__ANDROID__)
RecordApp("simpleperf.demo.java_api", GetTestData("java_api.apk"));
@@ -98,10 +99,11 @@ TEST(cmd_api, java_app) {
#endif
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_api, native_app) {
#if defined(__ANDROID__)
RecordApp("simpleperf.demo.cpp_api", GetTestData("cpp_api.apk"));
#else
GTEST_LOG_(INFO) << "This test tests recording apps on Android.";
#endif
-} \ No newline at end of file
+}
diff --git a/simpleperf/cmd_boot_record_test.cpp b/simpleperf/cmd_boot_record_test.cpp
index 25b2aff0..73266ce2 100644
--- a/simpleperf/cmd_boot_record_test.cpp
+++ b/simpleperf/cmd_boot_record_test.cpp
@@ -28,6 +28,7 @@ static std::unique_ptr<Command> BootRecordCmd() {
return CreateCommandInstance("boot-record");
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_boot_record, smoke) {
TEST_REQUIRE_ROOT();
ASSERT_TRUE(BootRecordCmd()->Run({"--enable", "-a -g --duration 1"}));
diff --git a/simpleperf/cmd_debug_unwind.cpp b/simpleperf/cmd_debug_unwind.cpp
index 7af7aa38..52d3c96c 100644
--- a/simpleperf/cmd_debug_unwind.cpp
+++ b/simpleperf/cmd_debug_unwind.cpp
@@ -403,12 +403,13 @@ class TestFileGenerator : public RecordFileProcessor {
bool WriteMapsForSample(const SampleRecord& r) {
ThreadEntry* thread = thread_tree_.FindThread(r.tid_data.tid);
if (thread != nullptr && thread->maps) {
- auto attr = reader_->AttrSection()[0].attr;
- auto event_id = reader_->AttrSection()[0].ids[0];
+ const EventAttrIds& attrs = reader_->AttrSection();
+ const perf_event_attr& attr = attrs[0].attr;
+ uint64_t event_id = attrs[0].ids[0];
for (const auto& p : thread->maps->maps) {
const MapEntry* map = p.second;
- Mmap2Record map_record(*attr, false, r.tid_data.pid, r.tid_data.tid, map->start_addr,
+ Mmap2Record map_record(attr, false, r.tid_data.pid, r.tid_data.tid, map->start_addr,
map->len, map->pgoff, map->flags, map->dso->Path(), event_id,
r.Timestamp());
if (!writer_->WriteRecord(map_record)) {
@@ -425,7 +426,7 @@ class TestFileGenerator : public RecordFileProcessor {
}
std::unordered_set<int> feature_types_to_copy = {
PerfFileFormat::FEAT_ARCH, PerfFileFormat::FEAT_CMDLINE, PerfFileFormat::FEAT_META_INFO};
- const size_t BUFFER_SIZE = 64 * 1024;
+ const size_t BUFFER_SIZE = 64 * kKilobyte;
std::string buffer(BUFFER_SIZE, '\0');
for (const auto& p : reader_->FeatureSectionDescriptors()) {
auto feat_type = p.first;
diff --git a/simpleperf/cmd_debug_unwind_test.cpp b/simpleperf/cmd_debug_unwind_test.cpp
index d0bbfe8f..2bf14a59 100644
--- a/simpleperf/cmd_debug_unwind_test.cpp
+++ b/simpleperf/cmd_debug_unwind_test.cpp
@@ -35,6 +35,7 @@ static std::unique_ptr<Command> DebugUnwindCmd() {
return CreateCommandInstance("debug-unwind");
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_debug_unwind, unwind_sample_option) {
std::string input_data = GetTestData(PERF_DATA_NO_UNWIND);
CaptureStdout capture;
@@ -44,6 +45,7 @@ TEST(cmd_debug_unwind, unwind_sample_option) {
ASSERT_NE(capture.Finish().find("sample_time: 1516379654300997"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_debug_unwind, sample_time_option) {
std::string input_data = GetTestData(PERF_DATA_NO_UNWIND);
CaptureStdout capture;
@@ -58,6 +60,7 @@ TEST(cmd_debug_unwind, sample_time_option) {
ASSERT_NE(output.find("sample_time: 1516379655959122"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_debug_unwind, output_option) {
std::string input_data = GetTestData(PERF_DATA_NO_UNWIND);
TemporaryFile tmpfile;
@@ -69,6 +72,7 @@ TEST(cmd_debug_unwind, output_option) {
ASSERT_NE(output.find("sample_time: 1516379654300997"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_debug_unwind, symfs_option) {
std::string input_data = GetTestData(NATIVELIB_IN_APK_PERF_DATA);
CaptureStdout capture;
@@ -80,6 +84,7 @@ TEST(cmd_debug_unwind, symfs_option) {
std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_debug_unwind, unwind_with_ip_zero_in_callchain) {
CaptureStdout capture;
ASSERT_TRUE(capture.Start());
@@ -88,6 +93,7 @@ TEST(cmd_debug_unwind, unwind_with_ip_zero_in_callchain) {
ASSERT_NE(capture.Finish().find("sample_time: 152526249937103"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_debug_unwind, unwind_embedded_lib_in_apk) {
// Check if we can unwind through a native library embedded in an apk. In the profiling data
// file, there is a sample with ip address pointing to
@@ -107,6 +113,7 @@ TEST(cmd_debug_unwind, unwind_embedded_lib_in_apk) {
ASSERT_NE(output.find("dso_2: /bionic/lib64/libc.so"), std::string::npos) << output;
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_debug_unwind, unwind_sample_in_unwinding_debug_info_file) {
CaptureStdout capture;
ASSERT_TRUE(capture.Start());
@@ -116,6 +123,7 @@ TEST(cmd_debug_unwind, unwind_sample_in_unwinding_debug_info_file) {
ASSERT_NE(output.find("symbol_5: android.os.Handler.post"), std::string::npos) << output;
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_debug_unwind, skip_sample_print_option) {
std::string input_data = GetTestData(PERF_DATA_NO_UNWIND);
CaptureStdout capture;
@@ -128,6 +136,7 @@ TEST(cmd_debug_unwind, skip_sample_print_option) {
ASSERT_NE(output.find("unwinding_sample_count: 8"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_debug_unwind, generate_test_file) {
TemporaryFile tmpfile;
close(tmpfile.release());
@@ -143,6 +152,7 @@ TEST(cmd_debug_unwind, generate_test_file) {
ASSERT_NE(output.find("symbol_2: android.os.Handler.enqueueMessage"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_debug_unwind, generate_test_file_with_build_id) {
TemporaryFile tmpfile;
close(tmpfile.release());
@@ -157,6 +167,7 @@ TEST(cmd_debug_unwind, generate_test_file_with_build_id) {
ASSERT_STREQ(build_ids[0].filename, "/apex/com.android.runtime/lib64/bionic/libc.so");
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_debug_unwind, generate_report) {
TemporaryFile tmpfile;
close(tmpfile.release());
@@ -169,6 +180,7 @@ TEST(cmd_debug_unwind, generate_report) {
ASSERT_NE(output.find("symbol_2: android.os.Handler.enqueueMessage"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_debug_unwind, unwind_sample_for_small_map_range) {
CaptureStdout capture;
ASSERT_TRUE(capture.Start());
diff --git a/simpleperf/cmd_dumprecord.cpp b/simpleperf/cmd_dumprecord.cpp
index 89e56b67..a9c4635b 100644
--- a/simpleperf/cmd_dumprecord.cpp
+++ b/simpleperf/cmd_dumprecord.cpp
@@ -26,6 +26,7 @@
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#include "BranchListFile.h"
#include "ETMDecoder.h"
#include "command.h"
#include "dso.h"
@@ -178,6 +179,18 @@ ExtractFieldFn GetExtractFieldFunction(const TracingField& field) {
return ExtractUnknownField;
}
+class ETMThreadTreeForDumpCmd : public ETMThreadTree {
+ public:
+ ETMThreadTreeForDumpCmd(ThreadTree& thread_tree) : thread_tree_(thread_tree) {}
+
+ void DisableThreadExitRecords() override { thread_tree_.DisableThreadExitRecords(); }
+ const ThreadEntry* FindThread(int tid) override { return thread_tree_.FindThread(tid); }
+ const MapSet& GetKernelMaps() override { return thread_tree_.GetKernelMaps(); }
+
+ private:
+ ThreadTree& thread_tree_;
+};
+
class DumpRecordCommand : public Command {
public:
DumpRecordCommand()
@@ -201,7 +214,8 @@ class DumpRecordCommand : public Command {
bool ProcessRecord(Record* r);
void ProcessSampleRecord(const SampleRecord& r);
void ProcessCallChainRecord(const CallChainRecord& r);
- SymbolInfo GetSymbolInfo(uint32_t pid, uint32_t tid, uint64_t ip, bool in_kernel);
+ SymbolInfo GetSymbolInfo(uint32_t pid, uint32_t tid, uint64_t ip,
+ std::optional<bool> in_kernel = std::nullopt);
bool ProcessTracingData(const TracingDataRecord& r);
bool DumpAuxData(const AuxRecord& aux);
bool DumpFeatureSection();
@@ -212,6 +226,7 @@ class DumpRecordCommand : public Command {
std::unique_ptr<RecordFileReader> record_file_reader_;
std::unique_ptr<ETMDecoder> etm_decoder_;
+ std::unique_ptr<ETMThreadTree> etm_thread_tree_;
ThreadTree thread_tree_;
std::vector<EventInfo> events_;
@@ -306,11 +321,11 @@ void DumpRecordCommand::DumpFileHeader() {
}
void DumpRecordCommand::DumpAttrSection() {
- std::vector<EventAttrWithId> attrs = record_file_reader_->AttrSection();
+ const EventAttrIds& attrs = record_file_reader_->AttrSection();
for (size_t i = 0; i < attrs.size(); ++i) {
const auto& attr = attrs[i];
printf("attr %zu:\n", i + 1);
- DumpPerfEventAttr(*attr.attr, 1);
+ DumpPerfEventAttr(attr.attr, 1);
if (!attr.ids.empty()) {
printf(" ids:");
for (const auto& id : attr.ids) {
@@ -344,7 +359,8 @@ bool DumpRecordCommand::ProcessRecord(Record* r) {
ProcessCallChainRecord(*static_cast<CallChainRecord*>(r));
break;
case PERF_RECORD_AUXTRACE_INFO: {
- etm_decoder_ = ETMDecoder::Create(*static_cast<AuxTraceInfoRecord*>(r), thread_tree_);
+ etm_thread_tree_.reset(new ETMThreadTreeForDumpCmd(thread_tree_));
+ etm_decoder_ = ETMDecoder::Create(*static_cast<AuxTraceInfoRecord*>(r), *etm_thread_tree_);
if (etm_decoder_) {
etm_decoder_->EnableDump(etm_dump_option_);
} else {
@@ -382,6 +398,19 @@ void DumpRecordCommand::ProcessSampleRecord(const SampleRecord& sr) {
s.vaddr_in_file);
}
}
+ if (sr.sample_type & PERF_SAMPLE_BRANCH_STACK) {
+ PrintIndented(1, "branch_stack:\n");
+ for (size_t i = 0; i < sr.branch_stack_data.stack_nr; ++i) {
+ uint64_t from_ip = sr.branch_stack_data.stack[i].from;
+ uint64_t to_ip = sr.branch_stack_data.stack[i].to;
+ SymbolInfo from_symbol = GetSymbolInfo(sr.tid_data.pid, sr.tid_data.tid, from_ip);
+ SymbolInfo to_symbol = GetSymbolInfo(sr.tid_data.pid, sr.tid_data.tid, to_ip);
+ PrintIndented(2, "%s (%s[+%" PRIx64 "]) -> %s (%s[+%" PRIx64 "])\n",
+ from_symbol.symbol->DemangledName(), from_symbol.dso->Path().c_str(),
+ from_symbol.vaddr_in_file, to_symbol.symbol->DemangledName(),
+ to_symbol.dso->Path().c_str(), to_symbol.vaddr_in_file);
+ }
+ }
// Dump tracepoint fields.
if (!events_.empty()) {
size_t attr_index = record_file_reader_->GetAttrIndexOfRecord(&sr);
@@ -407,9 +436,14 @@ void DumpRecordCommand::ProcessCallChainRecord(const CallChainRecord& cr) {
}
SymbolInfo DumpRecordCommand::GetSymbolInfo(uint32_t pid, uint32_t tid, uint64_t ip,
- bool in_kernel) {
+ std::optional<bool> in_kernel) {
ThreadEntry* thread = thread_tree_.FindThreadOrNew(pid, tid);
- const MapEntry* map = thread_tree_.FindMap(thread, ip, in_kernel);
+ const MapEntry* map;
+ if (in_kernel.has_value()) {
+ map = thread_tree_.FindMap(thread, ip, in_kernel.value());
+ } else {
+ map = thread_tree_.FindMap(thread, ip);
+ }
SymbolInfo info;
info.symbol = thread_tree_.FindSymbol(map, ip, &info.vaddr_in_file, &info.dso);
return info;
@@ -423,8 +457,9 @@ bool DumpRecordCommand::DumpAuxData(const AuxRecord& aux) {
size_t size = aux.data->aux_size;
if (size > 0) {
std::vector<uint8_t> data;
- if (!record_file_reader_->ReadAuxData(aux.Cpu(), aux.data->aux_offset, size, &data)) {
- return false;
+ bool error = false;
+ if (!record_file_reader_->ReadAuxData(aux.Cpu(), aux.data->aux_offset, size, data, error)) {
+ return !error;
}
if (!etm_decoder_) {
LOG(ERROR) << "ETMDecoder isn't created";
@@ -440,17 +475,21 @@ bool DumpRecordCommand::ProcessTracingData(const TracingDataRecord& r) {
if (!tracing) {
return false;
}
- std::vector<EventAttrWithId> attrs = record_file_reader_->AttrSection();
+ const EventAttrIds& attrs = record_file_reader_->AttrSection();
events_.resize(attrs.size());
for (size_t i = 0; i < attrs.size(); i++) {
auto& attr = attrs[i].attr;
auto& event = events_[i];
- if (attr->type != PERF_TYPE_TRACEPOINT) {
+ if (attr.type != PERF_TYPE_TRACEPOINT) {
continue;
}
- TracingFormat format = tracing->GetTracingFormatHavingId(attr->config);
- event.tp_fields = format.fields;
+ std::optional<TracingFormat> format = tracing->GetTracingFormatHavingId(attr.config);
+ if (!format.has_value()) {
+ LOG(ERROR) << "failed to get tracing format";
+ return false;
+ }
+ event.tp_fields = format.value().fields;
// Decide dump function for each field.
for (size_t j = 0; j < event.tp_fields.size(); j++) {
auto& field = event.tp_fields[j];
@@ -525,6 +564,35 @@ bool DumpRecordCommand::DumpFeatureSection() {
PrintIndented(2, "size: %" PRIu64 "\n", file.size);
}
}
+ } else if (feature == FEAT_ETM_BRANCH_LIST) {
+ std::string data;
+ if (!record_file_reader_->ReadFeatureSection(FEAT_ETM_BRANCH_LIST, &data)) {
+ return false;
+ }
+ ETMBinaryMap binary_map;
+ if (!StringToETMBinaryMap(data, binary_map)) {
+ return false;
+ }
+ PrintIndented(1, "etm_branch_list:\n");
+ for (const auto& [key, binary] : binary_map) {
+ PrintIndented(2, "path: %s\n", key.path.c_str());
+ PrintIndented(2, "build_id: %s\n", key.build_id.ToString().c_str());
+ PrintIndented(2, "binary_type: %s\n", DsoTypeToString(binary.dso_type));
+ if (binary.dso_type == DSO_KERNEL) {
+ PrintIndented(2, "kernel_start_addr: 0x%" PRIx64 "\n", key.kernel_start_addr);
+ }
+ for (const auto& [addr, branches] : binary.GetOrderedBranchMap()) {
+ PrintIndented(3, "addr: 0x%" PRIx64 "\n", addr);
+ for (const auto& [branch, count] : branches) {
+ std::string s = "0b";
+ for (auto it = branch.rbegin(); it != branch.rend(); ++it) {
+ s.push_back(*it ? '1' : '0');
+ }
+ PrintIndented(3, "branch: %s\n", s.c_str());
+ PrintIndented(3, "count: %" PRIu64 "\n", count);
+ }
+ }
+ }
}
}
return true;
diff --git a/simpleperf/cmd_dumprecord_test.cpp b/simpleperf/cmd_dumprecord_test.cpp
index e6cc5e23..ffdcbc2d 100644
--- a/simpleperf/cmd_dumprecord_test.cpp
+++ b/simpleperf/cmd_dumprecord_test.cpp
@@ -26,22 +26,27 @@ static std::unique_ptr<Command> DumpCmd() {
return CreateCommandInstance("dump");
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_dump, record_file_option) {
ASSERT_TRUE(DumpCmd()->Run({GetTestData("perf.data")}));
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_dump, input_option) {
ASSERT_TRUE(DumpCmd()->Run({"-i", GetTestData("perf.data")}));
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_dump, dump_data_generated_by_linux_perf) {
ASSERT_TRUE(DumpCmd()->Run({GetTestData(PERF_DATA_GENERATED_BY_LINUX_PERF)}));
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_dump, dump_callchain_records) {
ASSERT_TRUE(DumpCmd()->Run({GetTestData(PERF_DATA_WITH_CALLCHAIN_RECORD)}));
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_dump, dump_callchain_of_sample_records) {
CaptureStdout capture;
ASSERT_TRUE(capture.Start());
@@ -51,6 +56,7 @@ TEST(cmd_dump, dump_callchain_of_sample_records) {
ASSERT_NE(data.find("__ioctl (/system/lib64/libc.so[+70b6c])"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_dump, dump_tracepoint_fields_of_sample_records) {
CaptureStdout capture;
ASSERT_TRUE(capture.Start());
@@ -66,6 +72,7 @@ TEST(cmd_dump, dump_tracepoint_fields_of_sample_records) {
std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_dump, etm_data) {
CaptureStdout capture;
ASSERT_TRUE(capture.Start());
@@ -78,6 +85,7 @@ TEST(cmd_dump, etm_data) {
ASSERT_NE(data.find("OCSD_GEN_TRC_ELEM_INSTR_RANGE"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_dump, dump_arm_regs_recorded_in_arm64) {
ASSERT_TRUE(DumpCmd()->Run({GetTestData("perf_with_arm_regs.data")}));
}
diff --git a/simpleperf/cmd_inject.cpp b/simpleperf/cmd_inject.cpp
index 43cfc728..182f4fd2 100644
--- a/simpleperf/cmd_inject.cpp
+++ b/simpleperf/cmd_inject.cpp
@@ -25,42 +25,19 @@
#include <android-base/parseint.h>
#include <android-base/strings.h>
+#include "BranchListFile.h"
#include "ETMDecoder.h"
#include "RegEx.h"
-#include "cmd_inject_impl.h"
#include "command.h"
#include "record_file.h"
-#include "system/extras/simpleperf/etm_branch_list.pb.h"
+#include "system/extras/simpleperf/branch_list.pb.h"
#include "thread_tree.h"
#include "utils.h"
namespace simpleperf {
-std::string BranchToProtoString(const std::vector<bool>& branch) {
- size_t bytes = (branch.size() + 7) / 8;
- std::string res(bytes, '\0');
- for (size_t i = 0; i < branch.size(); i++) {
- if (branch[i]) {
- res[i >> 3] |= 1 << (i & 7);
- }
- }
- return res;
-}
-
-std::vector<bool> ProtoStringToBranch(const std::string& s, size_t bit_size) {
- std::vector<bool> branch(bit_size, false);
- for (size_t i = 0; i < bit_size; i++) {
- if (s[i >> 3] & (1 << (i & 7))) {
- branch[i] = true;
- }
- }
- return branch;
-}
-
namespace {
-constexpr const char* ETM_BRANCH_LIST_PROTO_MAGIC = "simpleperf:EtmBranchList";
-
using AddrPair = std::pair<uint64_t, uint64_t>;
struct AddrPairHash {
@@ -77,57 +54,21 @@ enum class OutputFormat {
BranchList,
};
-// When processing binary info in an input file, the binaries are identified by their path.
-// But this isn't sufficient when merging binary info from multiple input files. Because
-// binaries for the same path may be changed between generating input files. So after processing
-// each input file, we create BinaryKeys to identify binaries, which consider path, build_id and
-// kernel_start_addr (for vmlinux). kernel_start_addr affects how addresses in BranchListBinaryInfo
-// are interpreted for vmlinux.
-struct BinaryKey {
- std::string path;
- BuildId build_id;
- uint64_t kernel_start_addr = 0;
-
- BinaryKey() {}
+struct AutoFDOBinaryInfo {
+ uint64_t first_load_segment_addr = 0;
+ std::unordered_map<uint64_t, uint64_t> address_count_map;
+ std::unordered_map<AddrPair, uint64_t, AddrPairHash> range_count_map;
+ std::unordered_map<AddrPair, uint64_t, AddrPairHash> branch_count_map;
- BinaryKey(const std::string& path, BuildId build_id) : path(path), build_id(build_id) {}
+ void AddAddress(uint64_t addr) { OverflowSafeAdd(address_count_map[addr], 1); }
- BinaryKey(Dso* dso, uint64_t kernel_start_addr) : path(dso->Path()) {
- build_id = Dso::FindExpectedBuildIdForPath(dso->Path());
- if (dso->type() == DSO_KERNEL) {
- this->kernel_start_addr = kernel_start_addr;
- }
+ void AddRange(uint64_t begin, uint64_t end) {
+ OverflowSafeAdd(range_count_map[std::make_pair(begin, end)], 1);
}
- bool operator==(const BinaryKey& other) const {
- return path == other.path && build_id == other.build_id &&
- kernel_start_addr == other.kernel_start_addr;
+ void AddBranch(uint64_t from, uint64_t to) {
+ OverflowSafeAdd(branch_count_map[std::make_pair(from, to)], 1);
}
-};
-
-struct BinaryKeyHash {
- size_t operator()(const BinaryKey& key) const noexcept {
- size_t seed = 0;
- HashCombine(seed, key.path);
- HashCombine(seed, key.build_id);
- if (key.kernel_start_addr != 0) {
- HashCombine(seed, key.kernel_start_addr);
- }
- return seed;
- }
-};
-
-static void OverflowSafeAdd(uint64_t& dest, uint64_t add) {
- if (__builtin_add_overflow(dest, add, &dest)) {
- LOG(WARNING) << "Branch count overflow happened.";
- dest = UINT64_MAX;
- }
-}
-
-struct AutoFDOBinaryInfo {
- uint64_t first_load_segment_addr = 0;
- std::unordered_map<AddrPair, uint64_t, AddrPairHash> range_count_map;
- std::unordered_map<AddrPair, uint64_t, AddrPairHash> branch_count_map;
void AddInstrRange(const ETMInstrRange& instr_range) {
uint64_t total_count = instr_range.branch_taken_count;
@@ -141,6 +82,12 @@ struct AutoFDOBinaryInfo {
}
void Merge(const AutoFDOBinaryInfo& other) {
+ for (const auto& p : other.address_count_map) {
+ auto res = address_count_map.emplace(p.first, p.second);
+ if (!res.second) {
+ OverflowSafeAdd(res.first->second, p.second);
+ }
+ }
for (const auto& p : other.range_count_map) {
auto res = range_count_map.emplace(p.first, p.second);
if (!res.second) {
@@ -156,82 +103,11 @@ struct AutoFDOBinaryInfo {
}
};
-using UnorderedBranchMap =
- std::unordered_map<uint64_t, std::unordered_map<std::vector<bool>, uint64_t>>;
-
-struct BranchListBinaryInfo {
- DsoType dso_type;
- UnorderedBranchMap branch_map;
-
- void Merge(const BranchListBinaryInfo& other) {
- for (auto& other_p : other.branch_map) {
- auto it = branch_map.find(other_p.first);
- if (it == branch_map.end()) {
- branch_map[other_p.first] = std::move(other_p.second);
- } else {
- auto& map2 = it->second;
- for (auto& other_p2 : other_p.second) {
- auto it2 = map2.find(other_p2.first);
- if (it2 == map2.end()) {
- map2[other_p2.first] = other_p2.second;
- } else {
- OverflowSafeAdd(it2->second, other_p2.second);
- }
- }
- }
- }
- }
-
- BranchMap GetOrderedBranchMap() const {
- BranchMap result;
- for (const auto& p : branch_map) {
- uint64_t addr = p.first;
- const auto& b_map = p.second;
- result[addr] = std::map<std::vector<bool>, uint64_t>(b_map.begin(), b_map.end());
- }
- return result;
- }
-};
-
using AutoFDOBinaryCallback = std::function<void(const BinaryKey&, AutoFDOBinaryInfo&)>;
-using BranchListBinaryCallback = std::function<void(const BinaryKey&, BranchListBinaryInfo&)>;
-
-class ThreadTreeWithFilter : public ThreadTree {
- public:
- void ExcludePid(pid_t pid) { exclude_pid_ = pid; }
-
- ThreadEntry* FindThread(int tid) const override {
- ThreadEntry* thread = ThreadTree::FindThread(tid);
- if (thread != nullptr && exclude_pid_ && thread->pid == exclude_pid_) {
- return nullptr;
- }
- return thread;
- }
-
- private:
- std::optional<pid_t> exclude_pid_;
-};
-
-class DsoFilter {
- public:
- DsoFilter(const RegEx* binary_name_regex) : binary_name_regex_(binary_name_regex) {}
-
- bool FilterDso(Dso* dso) {
- auto lookup = dso_filter_cache_.find(dso);
- if (lookup != dso_filter_cache_.end()) {
- return lookup->second;
- }
- bool match = (binary_name_regex_ == nullptr) || binary_name_regex_->Search(dso->Path());
- dso_filter_cache_.insert({dso, match});
- return match;
- }
+using ETMBinaryCallback = std::function<void(const BinaryKey&, ETMBinary&)>;
+using LBRDataCallback = std::function<void(LBRData&)>;
- private:
- const RegEx* binary_name_regex_;
- std::unordered_map<Dso*, bool> dso_filter_cache_;
-};
-
-static uint64_t GetFirstLoadSegmentVaddr(Dso* dso) {
+static uint64_t GetFirstLoadSegmentVaddr(const Dso* dso) {
ElfStatus status;
if (auto elf = ElfFile::Open(dso->GetDebugFilePath(), &status); elf) {
for (const auto& segment : elf->GetProgramHeader()) {
@@ -243,61 +119,132 @@ static uint64_t GetFirstLoadSegmentVaddr(Dso* dso) {
return 0;
}
-// Read perf.data, and generate AutoFDOBinaryInfo or BranchListBinaryInfo.
-// To avoid resetting data, it only processes one input file per instance.
+// Base class for reading perf.data and generating AutoFDO or branch list data.
class PerfDataReader {
public:
- PerfDataReader(const std::string& filename, bool exclude_perf, ETMDumpOption etm_dump_option,
+ static std::string GetDataType(RecordFileReader& reader) {
+ const EventAttrIds& attrs = reader.AttrSection();
+ if (attrs.size() != 1) {
+ return "unknown";
+ }
+ const perf_event_attr& attr = attrs[0].attr;
+ if (IsEtmEventType(attr.type)) {
+ return "etm";
+ }
+ if (attr.sample_type & PERF_SAMPLE_BRANCH_STACK) {
+ return "lbr";
+ }
+ return "unknown";
+ }
+
+ PerfDataReader(std::unique_ptr<RecordFileReader> reader, bool exclude_perf,
const RegEx* binary_name_regex)
- : filename_(filename),
+ : reader_(std::move(reader)),
exclude_perf_(exclude_perf),
- etm_dump_option_(etm_dump_option),
- dso_filter_(binary_name_regex) {}
+ binary_filter_(binary_name_regex) {}
+ virtual ~PerfDataReader() {}
- void SetCallback(const AutoFDOBinaryCallback& callback) { autofdo_callback_ = callback; }
- void SetCallback(const BranchListBinaryCallback& callback) { branch_list_callback_ = callback; }
+ std::string GetDataType() const { return GetDataType(*reader_); }
- bool Read() {
- record_file_reader_ = RecordFileReader::CreateInstance(filename_);
- if (!record_file_reader_) {
- return false;
- }
+ void AddCallback(const AutoFDOBinaryCallback& callback) { autofdo_callback_ = callback; }
+ void AddCallback(const ETMBinaryCallback& callback) { etm_binary_callback_ = callback; }
+ void AddCallback(const LBRDataCallback& callback) { lbr_data_callback_ = callback; }
+
+ virtual bool Read() {
if (exclude_perf_) {
- const auto& info_map = record_file_reader_->GetMetaInfoFeature();
+ const auto& info_map = reader_->GetMetaInfoFeature();
if (auto it = info_map.find("recording_process"); it == info_map.end()) {
- LOG(ERROR) << filename_ << " doesn't support --exclude-perf";
+ LOG(ERROR) << reader_->FileName() << " doesn't support --exclude-perf";
return false;
} else {
int pid;
if (!android::base::ParseInt(it->second, &pid, 0)) {
- LOG(ERROR) << "invalid recording_process " << it->second << " in " << filename_;
+ LOG(ERROR) << "invalid recording_process " << it->second << " in " << reader_->FileName();
return false;
}
- thread_tree_.ExcludePid(pid);
+ exclude_pid_ = pid;
}
}
- if (!record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_)) {
+
+ if (!reader_->LoadBuildIdAndFileFeatures(thread_tree_)) {
return false;
}
- if (!record_file_reader_->ReadDataSection([this](auto r) { return ProcessRecord(r.get()); })) {
+ if (!reader_->ReadDataSection([this](auto r) { return ProcessRecord(*r); })) {
return false;
}
- if (etm_decoder_ && !etm_decoder_->FinishData()) {
- return false;
+ return PostProcess();
+ }
+
+ protected:
+ virtual bool ProcessRecord(Record& r) = 0;
+ virtual bool PostProcess() = 0;
+
+ void ProcessAutoFDOBinaryInfo() {
+ for (auto& p : autofdo_binary_map_) {
+ const Dso* dso = p.first;
+ AutoFDOBinaryInfo& binary = p.second;
+ binary.first_load_segment_addr = GetFirstLoadSegmentVaddr(dso);
+ autofdo_callback_(BinaryKey(dso, 0), binary);
}
- if (autofdo_callback_) {
- ProcessAutoFDOBinaryInfo();
- } else if (branch_list_callback_) {
- ProcessBranchListBinaryInfo();
+ }
+
+ const std::string data_type_;
+ std::unique_ptr<RecordFileReader> reader_;
+ bool exclude_perf_;
+ BinaryFilter binary_filter_;
+
+ std::optional<int> exclude_pid_;
+ ThreadTree thread_tree_;
+ AutoFDOBinaryCallback autofdo_callback_;
+ ETMBinaryCallback etm_binary_callback_;
+ LBRDataCallback lbr_data_callback_;
+ // Store results for AutoFDO.
+ std::unordered_map<const Dso*, AutoFDOBinaryInfo> autofdo_binary_map_;
+};
+
+class ETMThreadTreeWithFilter : public ETMThreadTree {
+ public:
+ ETMThreadTreeWithFilter(ThreadTree& thread_tree, std::optional<int>& exclude_pid)
+ : thread_tree_(thread_tree), exclude_pid_(exclude_pid) {}
+
+ void DisableThreadExitRecords() override { thread_tree_.DisableThreadExitRecords(); }
+
+ const ThreadEntry* FindThread(int tid) override {
+ const ThreadEntry* thread = thread_tree_.FindThread(tid);
+ if (thread != nullptr && exclude_pid_ && thread->pid == exclude_pid_) {
+ return nullptr;
}
- return true;
+ return thread;
+ }
+
+ const MapSet& GetKernelMaps() override { return thread_tree_.GetKernelMaps(); }
+
+ private:
+ ThreadTree& thread_tree_;
+ std::optional<int>& exclude_pid_;
+};
+
+// Read perf.data with ETM data and generate AutoFDO or branch list data.
+class ETMPerfDataReader : public PerfDataReader {
+ public:
+ ETMPerfDataReader(std::unique_ptr<RecordFileReader> reader, bool exclude_perf,
+ const RegEx* binary_name_regex, ETMDumpOption etm_dump_option)
+ : PerfDataReader(std::move(reader), exclude_perf, binary_name_regex),
+ etm_dump_option_(etm_dump_option),
+ etm_thread_tree_(thread_tree_, exclude_pid_) {}
+
+ bool Read() override {
+ if (reader_->HasFeature(PerfFileFormat::FEAT_ETM_BRANCH_LIST)) {
+ return ProcessETMBranchListFeature();
+ }
+ return PerfDataReader::Read();
}
private:
- bool ProcessRecord(Record* r) {
- thread_tree_.Update(*r);
- if (r->type() == PERF_RECORD_AUXTRACE_INFO) {
- etm_decoder_ = ETMDecoder::Create(*static_cast<AuxTraceInfoRecord*>(r), thread_tree_);
+ bool ProcessRecord(Record& r) override {
+ thread_tree_.Update(r);
+ if (r.type() == PERF_RECORD_AUXTRACE_INFO) {
+ etm_decoder_ = ETMDecoder::Create(static_cast<AuxTraceInfoRecord&>(r), etm_thread_tree_);
if (!etm_decoder_) {
return false;
}
@@ -305,32 +252,32 @@ class PerfDataReader {
if (autofdo_callback_) {
etm_decoder_->RegisterCallback(
[this](const ETMInstrRange& range) { ProcessInstrRange(range); });
- } else if (branch_list_callback_) {
+ } else if (etm_binary_callback_) {
etm_decoder_->RegisterCallback(
- [this](const ETMBranchList& branch) { ProcessBranchList(branch); });
+ [this](const ETMBranchList& branch) { ProcessETMBranchList(branch); });
}
- } else if (r->type() == PERF_RECORD_AUX) {
- AuxRecord* aux = static_cast<AuxRecord*>(r);
- if (aux->data->aux_size > SIZE_MAX) {
+ } else if (r.type() == PERF_RECORD_AUX) {
+ AuxRecord& aux = static_cast<AuxRecord&>(r);
+ if (aux.data->aux_size > SIZE_MAX) {
LOG(ERROR) << "invalid aux size";
return false;
}
- size_t aux_size = aux->data->aux_size;
+ size_t aux_size = aux.data->aux_size;
if (aux_size > 0) {
- if (!record_file_reader_->ReadAuxData(aux->Cpu(), aux->data->aux_offset, aux_size,
- &aux_data_buffer_)) {
- LOG(ERROR) << "failed to read aux data in " << filename_;
- return false;
+ bool error = false;
+ if (!reader_->ReadAuxData(aux.Cpu(), aux.data->aux_offset, aux_size, aux_data_buffer_,
+ error)) {
+ return !error;
}
if (!etm_decoder_) {
LOG(ERROR) << "ETMDecoder isn't created";
return false;
}
- return etm_decoder_->ProcessData(aux_data_buffer_.data(), aux_size, !aux->Unformatted(),
- aux->Cpu());
+ return etm_decoder_->ProcessData(aux_data_buffer_.data(), aux_size, !aux.Unformatted(),
+ aux.Cpu());
}
- } else if (r->type() == PERF_RECORD_MMAP && r->InKernel()) {
- auto& mmap_r = *static_cast<MmapRecord*>(r);
+ } else if (r.type() == PERF_RECORD_MMAP && r.InKernel()) {
+ auto& mmap_r = static_cast<MmapRecord&>(r);
if (android::base::StartsWith(mmap_r.filename, DEFAULT_KERNEL_MMAP_NAME)) {
kernel_map_start_addr_ = mmap_r.data->addr;
}
@@ -338,36 +285,65 @@ class PerfDataReader {
return true;
}
+ bool PostProcess() override {
+ if (etm_decoder_ && !etm_decoder_->FinishData()) {
+ return false;
+ }
+ if (autofdo_callback_) {
+ ProcessAutoFDOBinaryInfo();
+ } else if (etm_binary_callback_) {
+ ProcessETMBinary();
+ }
+ return true;
+ }
+
+ bool ProcessETMBranchListFeature() {
+ if (exclude_perf_) {
+ LOG(WARNING) << "--exclude-perf has no effect on perf.data with etm branch list";
+ }
+ if (autofdo_callback_) {
+ LOG(ERROR) << "convert to autofdo format isn't support on perf.data with etm branch list";
+ return false;
+ }
+ CHECK(etm_binary_callback_);
+ std::string s;
+ if (!reader_->ReadFeatureSection(PerfFileFormat::FEAT_ETM_BRANCH_LIST, &s)) {
+ return false;
+ }
+ ETMBinaryMap binary_map;
+ if (!StringToETMBinaryMap(s, binary_map)) {
+ return false;
+ }
+ for (auto& [key, binary] : binary_map) {
+ if (!binary_filter_.Filter(key.path)) {
+ continue;
+ }
+ etm_binary_callback_(key, binary);
+ }
+ return true;
+ }
+
void ProcessInstrRange(const ETMInstrRange& instr_range) {
- if (!dso_filter_.FilterDso(instr_range.dso)) {
+ if (!binary_filter_.Filter(instr_range.dso)) {
return;
}
autofdo_binary_map_[instr_range.dso].AddInstrRange(instr_range);
}
- void ProcessBranchList(const ETMBranchList& branch_list) {
- if (!dso_filter_.FilterDso(branch_list.dso)) {
+ void ProcessETMBranchList(const ETMBranchList& branch_list) {
+ if (!binary_filter_.Filter(branch_list.dso)) {
return;
}
- auto& branch_map = branch_list_binary_map_[branch_list.dso].branch_map;
+ auto& branch_map = etm_binary_map_[branch_list.dso].branch_map;
++branch_map[branch_list.addr][branch_list.branch];
}
- void ProcessAutoFDOBinaryInfo() {
- for (auto& p : autofdo_binary_map_) {
- Dso* dso = p.first;
- AutoFDOBinaryInfo& binary = p.second;
- binary.first_load_segment_addr = GetFirstLoadSegmentVaddr(dso);
- autofdo_callback_(BinaryKey(dso, 0), binary);
- }
- }
-
- void ProcessBranchListBinaryInfo() {
- for (auto& p : branch_list_binary_map_) {
+ void ProcessETMBinary() {
+ for (auto& p : etm_binary_map_) {
Dso* dso = p.first;
- BranchListBinaryInfo& binary = p.second;
+ ETMBinary& binary = p.second;
binary.dso_type = dso->type();
BinaryKey key(dso, 0);
if (binary.dso_type == DSO_KERNEL) {
@@ -382,114 +358,242 @@ class PerfDataReader {
key.kernel_start_addr = kernel_map_start_addr_;
}
}
- branch_list_callback_(key, binary);
+ etm_binary_callback_(key, binary);
}
}
- const std::string filename_;
- bool exclude_perf_;
ETMDumpOption etm_dump_option_;
- DsoFilter dso_filter_;
- AutoFDOBinaryCallback autofdo_callback_;
- BranchListBinaryCallback branch_list_callback_;
-
+ ETMThreadTreeWithFilter etm_thread_tree_;
std::vector<uint8_t> aux_data_buffer_;
std::unique_ptr<ETMDecoder> etm_decoder_;
- std::unique_ptr<RecordFileReader> record_file_reader_;
- ThreadTreeWithFilter thread_tree_;
uint64_t kernel_map_start_addr_ = 0;
- // Store results for AutoFDO.
- std::unordered_map<Dso*, AutoFDOBinaryInfo> autofdo_binary_map_;
- // Store results for BranchList.
- std::unordered_map<Dso*, BranchListBinaryInfo> branch_list_binary_map_;
+ // Store etm branch list data.
+ std::unordered_map<Dso*, ETMBinary> etm_binary_map_;
};
-// Read a protobuf file specified by etm_branch_list.proto, and generate BranchListBinaryInfo.
+static std::optional<std::vector<AutoFDOBinaryInfo>> ConvertLBRDataToAutoFDO(
+ const LBRData& lbr_data) {
+ std::vector<AutoFDOBinaryInfo> binaries(lbr_data.binaries.size());
+ for (const LBRSample& sample : lbr_data.samples) {
+ if (sample.binary_id != 0) {
+ if (sample.binary_id > binaries.size()) {
+ LOG(ERROR) << "binary_id out of range";
+ return std::nullopt;
+ }
+ binaries[sample.binary_id - 1].AddAddress(sample.vaddr_in_file);
+ }
+ for (size_t i = 0; i < sample.branches.size(); ++i) {
+ const LBRBranch& branch = sample.branches[i];
+ if (branch.from_binary_id == 0) {
+ continue;
+ }
+ if (branch.from_binary_id > binaries.size()) {
+ LOG(ERROR) << "binary_id out of range";
+ return std::nullopt;
+ }
+ if (branch.from_binary_id == branch.to_binary_id) {
+ binaries[branch.from_binary_id - 1].AddBranch(branch.from_vaddr_in_file,
+ branch.to_vaddr_in_file);
+ }
+ if (i > 0 && branch.from_binary_id == sample.branches[i - 1].to_binary_id) {
+ uint64_t begin = sample.branches[i - 1].to_vaddr_in_file;
+ uint64_t end = branch.from_vaddr_in_file;
+ // Use the same logic to skip bogus LBR data as AutoFDO.
+ if (end < begin || end - begin > (1 << 20)) {
+ continue;
+ }
+ binaries[branch.from_binary_id - 1].AddRange(begin, end);
+ }
+ }
+ }
+ return binaries;
+}
+
+class LBRPerfDataReader : public PerfDataReader {
+ public:
+ LBRPerfDataReader(std::unique_ptr<RecordFileReader> reader, bool exclude_perf,
+ const RegEx* binary_name_regex)
+ : PerfDataReader(std::move(reader), exclude_perf, binary_name_regex) {}
+
+ private:
+ bool ProcessRecord(Record& r) override {
+ thread_tree_.Update(r);
+ if (r.type() == PERF_RECORD_SAMPLE) {
+ auto& sr = static_cast<SampleRecord&>(r);
+ ThreadEntry* thread = thread_tree_.FindThread(sr.tid_data.tid);
+ if (thread == nullptr) {
+ return true;
+ }
+ auto& stack = sr.branch_stack_data;
+ lbr_data_.samples.resize(lbr_data_.samples.size() + 1);
+ LBRSample& sample = lbr_data_.samples.back();
+ std::pair<uint32_t, uint64_t> binary_addr = IpToBinaryAddr(*thread, sr.ip_data.ip);
+ sample.binary_id = binary_addr.first;
+ bool has_valid_binary_id = sample.binary_id != 0;
+ sample.vaddr_in_file = binary_addr.second;
+ sample.branches.resize(stack.stack_nr);
+ for (size_t i = 0; i < stack.stack_nr; ++i) {
+ uint64_t from_ip = stack.stack[i].from;
+ uint64_t to_ip = stack.stack[i].to;
+ LBRBranch& branch = sample.branches[i];
+ binary_addr = IpToBinaryAddr(*thread, from_ip);
+ branch.from_binary_id = binary_addr.first;
+ branch.from_vaddr_in_file = binary_addr.second;
+ binary_addr = IpToBinaryAddr(*thread, to_ip);
+ branch.to_binary_id = binary_addr.first;
+ branch.to_vaddr_in_file = binary_addr.second;
+ if (branch.from_binary_id != 0 || branch.to_binary_id != 0) {
+ has_valid_binary_id = true;
+ }
+ }
+ if (!has_valid_binary_id) {
+ lbr_data_.samples.pop_back();
+ }
+ }
+ return true;
+ }
+
+ bool PostProcess() override {
+ if (autofdo_callback_) {
+ std::optional<std::vector<AutoFDOBinaryInfo>> binaries = ConvertLBRDataToAutoFDO(lbr_data_);
+ if (!binaries) {
+ return false;
+ }
+ for (const auto& [dso, binary_id] : dso_map_) {
+ autofdo_binary_map_[dso] = std::move(binaries.value()[binary_id - 1]);
+ }
+ ProcessAutoFDOBinaryInfo();
+ } else if (lbr_data_callback_) {
+ lbr_data_callback_(lbr_data_);
+ }
+ return true;
+ }
+
+ std::pair<uint32_t, uint64_t> IpToBinaryAddr(ThreadEntry& thread, uint64_t ip) {
+ const MapEntry* map = thread_tree_.FindMap(&thread, ip);
+ Dso* dso = map->dso;
+ if (thread_tree_.IsUnknownDso(dso) || !binary_filter_.Filter(dso)) {
+ return std::make_pair(0, 0);
+ }
+ uint32_t binary_id = GetBinaryId(dso);
+ uint64_t vaddr_in_file = dso->IpToVaddrInFile(ip, map->start_addr, map->pgoff);
+ return std::make_pair(binary_id, vaddr_in_file);
+ }
+
+ uint32_t GetBinaryId(const Dso* dso) {
+ if (auto it = dso_map_.find(dso); it != dso_map_.end()) {
+ return it->second;
+ }
+ lbr_data_.binaries.emplace_back(dso, 0);
+ uint32_t binary_id = static_cast<uint32_t>(lbr_data_.binaries.size());
+ dso_map_[dso] = binary_id;
+ return binary_id;
+ }
+
+ LBRData lbr_data_;
+ // Map from dso to binary_id in lbr_data_.
+ std::unordered_map<const Dso*, uint32_t> dso_map_;
+};
+
+// Read a protobuf file specified by branch_list.proto.
class BranchListReader {
public:
BranchListReader(const std::string& filename, const RegEx* binary_name_regex)
- : filename_(filename), binary_name_regex_(binary_name_regex) {}
+ : filename_(filename), binary_filter_(binary_name_regex) {}
- void SetCallback(const BranchListBinaryCallback& callback) { callback_ = callback; }
+ void AddCallback(const ETMBinaryCallback& callback) { etm_binary_callback_ = callback; }
+ void AddCallback(const LBRDataCallback& callback) { lbr_data_callback_ = callback; }
bool Read() {
- auto fd = FileHelper::OpenReadOnly(filename_);
- if (!fd.ok()) {
- PLOG(ERROR) << "failed to open " << filename_;
+ std::string s;
+ if (!android::base::ReadFileToString(filename_, &s)) {
+ PLOG(ERROR) << "failed to read " << filename_;
return false;
}
-
- proto::ETMBranchList branch_list_proto;
- if (!branch_list_proto.ParseFromFileDescriptor(fd)) {
- PLOG(ERROR) << "failed to read msg from " << filename_;
+ ETMBinaryMap etm_data;
+ LBRData lbr_data;
+ if (!ParseBranchListData(s, etm_data, lbr_data)) {
+ PLOG(ERROR) << "file is in wrong format: " << filename_;
return false;
}
- if (branch_list_proto.magic() != ETM_BRANCH_LIST_PROTO_MAGIC) {
- PLOG(ERROR) << "file not in format etm_branch_list.proto: " << filename_;
- return false;
+ if (etm_binary_callback_ && !etm_data.empty()) {
+ ProcessETMData(etm_data);
}
-
- for (size_t i = 0; i < branch_list_proto.binaries_size(); i++) {
- const auto& binary_proto = branch_list_proto.binaries(i);
- if (binary_name_regex_ != nullptr && !binary_name_regex_->Search(binary_proto.path())) {
- continue;
- }
- BinaryKey key(binary_proto.path(), BuildId(binary_proto.build_id()));
- if (binary_proto.has_kernel_info()) {
- key.kernel_start_addr = binary_proto.kernel_info().kernel_start_addr();
- }
- BranchListBinaryInfo binary;
- auto dso_type = ToDsoType(binary_proto.type());
- if (!dso_type) {
- LOG(ERROR) << "invalid binary type in " << filename_;
- return false;
- }
- binary.dso_type = dso_type.value();
- binary.branch_map = BuildUnorderedBranchMap(binary_proto);
- callback_(key, binary);
+ if (lbr_data_callback_ && !lbr_data.samples.empty()) {
+ ProcessLBRData(lbr_data);
}
return true;
}
private:
- std::optional<DsoType> ToDsoType(proto::ETMBranchList_Binary::BinaryType binary_type) {
- switch (binary_type) {
- case proto::ETMBranchList_Binary::ELF_FILE:
- return DSO_ELF_FILE;
- case proto::ETMBranchList_Binary::KERNEL:
- return DSO_KERNEL;
- case proto::ETMBranchList_Binary::KERNEL_MODULE:
- return DSO_KERNEL_MODULE;
- default:
- LOG(ERROR) << "unexpected binary type " << binary_type;
- return std::nullopt;
+ void ProcessETMData(ETMBinaryMap& etm_data) {
+ for (auto& [key, binary] : etm_data) {
+ if (!binary_filter_.Filter(key.path)) {
+ continue;
+ }
+ etm_binary_callback_(key, binary);
}
}
- UnorderedBranchMap BuildUnorderedBranchMap(const proto::ETMBranchList_Binary& binary_proto) {
- UnorderedBranchMap branch_map;
- for (size_t i = 0; i < binary_proto.addrs_size(); i++) {
- const auto& addr_proto = binary_proto.addrs(i);
- auto& b_map = branch_map[addr_proto.addr()];
- for (size_t j = 0; j < addr_proto.branches_size(); j++) {
- const auto& branch_proto = addr_proto.branches(j);
- std::vector<bool> branch =
- ProtoStringToBranch(branch_proto.branch(), branch_proto.branch_size());
- b_map[branch] = branch_proto.count();
+ void ProcessLBRData(LBRData& lbr_data) {
+ // 1. Check if we need to remove binaries.
+ std::vector<uint32_t> new_ids(lbr_data.binaries.size());
+ uint32_t next_id = 1;
+
+ for (size_t i = 0; i < lbr_data.binaries.size(); ++i) {
+ if (!binary_filter_.Filter(lbr_data.binaries[i].path)) {
+ new_ids[i] = 0;
+ } else {
+ new_ids[i] = next_id++;
}
}
- return branch_map;
+
+ if (next_id <= lbr_data.binaries.size()) {
+ // 2. Modify lbr_data.binaries.
+ for (size_t i = 0; i < lbr_data.binaries.size(); ++i) {
+ if (new_ids[i] != 0) {
+ size_t new_pos = new_ids[i] - 1;
+ lbr_data.binaries[new_pos] = lbr_data.binaries[i];
+ }
+ }
+ lbr_data.binaries.resize(next_id - 1);
+
+ // 3. Modify lbr_data.samples.
+ auto convert_id = [&](uint32_t& binary_id) {
+ if (binary_id != 0) {
+ binary_id = (binary_id <= new_ids.size()) ? new_ids[binary_id - 1] : 0;
+ }
+ };
+ std::vector<LBRSample> new_samples;
+ for (LBRSample& sample : lbr_data.samples) {
+ convert_id(sample.binary_id);
+ bool has_valid_binary_id = sample.binary_id != 0;
+ for (LBRBranch& branch : sample.branches) {
+ convert_id(branch.from_binary_id);
+ convert_id(branch.to_binary_id);
+ if (branch.from_binary_id != 0 || branch.to_binary_id != 0) {
+ has_valid_binary_id = true;
+ }
+ }
+ if (has_valid_binary_id) {
+ new_samples.emplace_back(std::move(sample));
+ }
+ }
+ lbr_data.samples = std::move(new_samples);
+ }
+ lbr_data_callback_(lbr_data);
}
const std::string filename_;
- const RegEx* binary_name_regex_;
- BranchListBinaryCallback callback_;
+ BinaryFilter binary_filter_;
+ ETMBinaryCallback etm_binary_callback_;
+ LBRDataCallback lbr_data_callback_;
};
-// Convert BranchListBinaryInfo into AutoFDOBinaryInfo.
-class BranchListToAutoFDOConverter {
+// Convert ETMBinary into AutoFDOBinaryInfo.
+class ETMBranchListToAutoFDOConverter {
public:
- std::unique_ptr<AutoFDOBinaryInfo> Convert(const BinaryKey& key, BranchListBinaryInfo& binary) {
+ std::unique_ptr<AutoFDOBinaryInfo> Convert(const BinaryKey& key, ETMBinary& binary) {
BuildId build_id = key.build_id;
std::unique_ptr<Dso> dso = Dso::CreateDsoWithBuildId(binary.dso_type, key.path, build_id);
if (!dso || !CheckBuildId(dso.get(), key.build_id)) {
@@ -507,8 +611,8 @@ class BranchListToAutoFDOConverter {
autofdo_binary->AddInstrRange(range);
};
- auto result =
- ConvertBranchMapToInstrRanges(dso.get(), binary.GetOrderedBranchMap(), process_instr_range);
+ auto result = ConvertETMBranchMapToInstrRanges(dso.get(), binary.GetOrderedBranchMap(),
+ process_instr_range);
if (!result.ok()) {
LOG(WARNING) << "failed to build instr ranges for binary " << dso->Path() << ": "
<< result.error();
@@ -527,15 +631,14 @@ class BranchListToAutoFDOConverter {
build_id == expected_build_id;
}
- void ModifyBranchMapForKernel(Dso* dso, uint64_t kernel_start_addr,
- BranchListBinaryInfo& binary) {
+ void ModifyBranchMapForKernel(Dso* dso, uint64_t kernel_start_addr, ETMBinary& binary) {
if (kernel_start_addr == 0) {
// vmlinux has been provided when generating branch lists. Addresses in branch lists are
// already vaddrs in vmlinux.
return;
}
// Addresses are still kernel ip addrs in memory. Need to convert them to vaddrs in vmlinux.
- UnorderedBranchMap new_branch_map;
+ UnorderedETMBranchMap new_branch_map;
for (auto& p : binary.branch_map) {
uint64_t vaddr_in_file = dso->IpToVaddrInFile(p.first, kernel_start_addr, 0);
new_branch_map[vaddr_in_file] = std::move(p.second);
@@ -603,7 +706,12 @@ class AutoFDOWriter {
}
// Write addr_count_map.
- fprintf(output_fp.get(), "0\n");
+ std::map<uint64_t, uint64_t> address_count_map(binary.address_count_map.begin(),
+ binary.address_count_map.end());
+ fprintf(output_fp.get(), "%zu\n", address_count_map.size());
+ for (const auto& [addr, count] : address_count_map) {
+ fprintf(output_fp.get(), "%" PRIx64 ":%" PRIu64 "\n", to_offset(addr), count);
+ }
// Write branch_count_map.
std::map<AddrPair, uint64_t> branch_count_map(binary.branch_count_map.begin(),
@@ -618,6 +726,7 @@ class AutoFDOWriter {
}
// Write the binary path in comment.
+ fprintf(output_fp.get(), "// build_id: %s\n", key.build_id.ToString().c_str());
fprintf(output_fp.get(), "// %s\n\n", key.path.c_str());
}
return true;
@@ -627,96 +736,82 @@ class AutoFDOWriter {
std::unordered_map<BinaryKey, AutoFDOBinaryInfo, BinaryKeyHash> binary_map_;
};
-// Merge BranchListBinaryInfo.
+// Merge branch list data.
struct BranchListMerger {
- void AddBranchListBinary(const BinaryKey& key, BranchListBinaryInfo& binary) {
- auto it = binary_map.find(key);
- if (it == binary_map.end()) {
- binary_map[key] = std::move(binary);
- } else {
+ void AddETMBinary(const BinaryKey& key, ETMBinary& binary) {
+ if (auto it = etm_data_.find(key); it != etm_data_.end()) {
it->second.Merge(binary);
+ } else {
+ etm_data_[key] = std::move(binary);
}
}
- std::unordered_map<BinaryKey, BranchListBinaryInfo, BinaryKeyHash> binary_map;
-};
-
-// Write branch lists to a protobuf file specified by etm_branch_list.proto.
-class BranchListWriter {
- public:
- bool Write(const std::string& output_filename,
- const std::unordered_map<BinaryKey, BranchListBinaryInfo, BinaryKeyHash>& binary_map) {
- // Don't produce empty output file.
- if (binary_map.empty()) {
- LOG(INFO) << "Skip empty output file.";
- unlink(output_filename.c_str());
- return true;
- }
- std::unique_ptr<FILE, decltype(&fclose)> output_fp(fopen(output_filename.c_str(), "wb"),
- fclose);
- if (!output_fp) {
- PLOG(ERROR) << "failed to write to " << output_filename;
- return false;
+ void AddLBRData(LBRData& lbr_data) {
+ // 1. Merge binaries.
+ std::vector<uint32_t> new_ids(lbr_data.binaries.size());
+ for (size_t i = 0; i < lbr_data.binaries.size(); i++) {
+ const BinaryKey& key = lbr_data.binaries[i];
+ if (auto it = lbr_binary_id_map_.find(key); it != lbr_binary_id_map_.end()) {
+ new_ids[i] = it->second;
+ } else {
+ uint32_t next_id = static_cast<uint32_t>(lbr_binary_id_map_.size()) + 1;
+ new_ids[i] = next_id;
+ lbr_binary_id_map_[key] = next_id;
+ lbr_data_.binaries.emplace_back(key);
+ }
}
- proto::ETMBranchList branch_list_proto;
- branch_list_proto.set_magic(ETM_BRANCH_LIST_PROTO_MAGIC);
- std::vector<char> branch_buf;
- for (const auto& p : binary_map) {
- const BinaryKey& key = p.first;
- const BranchListBinaryInfo& binary = p.second;
- auto binary_proto = branch_list_proto.add_binaries();
-
- binary_proto->set_path(key.path);
- if (!key.build_id.IsEmpty()) {
- binary_proto->set_build_id(key.build_id.ToString().substr(2));
+ // 2. Merge samples.
+ auto convert_id = [&](uint32_t& binary_id) {
+ if (binary_id != 0) {
+ binary_id = (binary_id <= new_ids.size()) ? new_ids[binary_id - 1] : 0;
}
- auto opt_binary_type = ToProtoBinaryType(binary.dso_type);
- if (!opt_binary_type.has_value()) {
- return false;
+ };
+
+ for (LBRSample& sample : lbr_data.samples) {
+ convert_id(sample.binary_id);
+ for (LBRBranch& branch : sample.branches) {
+ convert_id(branch.from_binary_id);
+ convert_id(branch.to_binary_id);
}
- binary_proto->set_type(opt_binary_type.value());
+ lbr_data_.samples.emplace_back(std::move(sample));
+ }
+ }
- for (const auto& addr_p : binary.branch_map) {
- auto addr_proto = binary_proto->add_addrs();
- addr_proto->set_addr(addr_p.first);
+ ETMBinaryMap& GetETMData() { return etm_data_; }
- for (const auto& branch_p : addr_p.second) {
- const std::vector<bool>& branch = branch_p.first;
- auto branch_proto = addr_proto->add_branches();
+ LBRData& GetLBRData() { return lbr_data_; }
- branch_proto->set_branch(BranchToProtoString(branch));
- branch_proto->set_branch_size(branch.size());
- branch_proto->set_count(branch_p.second);
- }
- }
+ private:
+ ETMBinaryMap etm_data_;
+ LBRData lbr_data_;
+ std::unordered_map<BinaryKey, uint32_t, BinaryKeyHash> lbr_binary_id_map_;
+};
- if (binary.dso_type == DSO_KERNEL) {
- binary_proto->mutable_kernel_info()->set_kernel_start_addr(key.kernel_start_addr);
- }
+// Write branch lists to a protobuf file specified by branch_list.proto.
+static bool WriteBranchListFile(const std::string& output_filename, const ETMBinaryMap& etm_data,
+ const LBRData& lbr_data) {
+ std::string s;
+ if (!etm_data.empty()) {
+ if (!ETMBinaryMapToString(etm_data, s)) {
+ return false;
}
- if (!branch_list_proto.SerializeToFileDescriptor(fileno(output_fp.get()))) {
- PLOG(ERROR) << "failed to write to " << output_filename;
+ } else if (!lbr_data.samples.empty()) {
+ if (!LBRDataToString(lbr_data, s)) {
return false;
}
+ } else {
+ // Don't produce empty output file.
+ LOG(INFO) << "Skip empty output file.";
+ unlink(output_filename.c_str());
return true;
}
-
- private:
- std::optional<proto::ETMBranchList_Binary::BinaryType> ToProtoBinaryType(DsoType dso_type) {
- switch (dso_type) {
- case DSO_ELF_FILE:
- return proto::ETMBranchList_Binary::ELF_FILE;
- case DSO_KERNEL:
- return proto::ETMBranchList_Binary::KERNEL;
- case DSO_KERNEL_MODULE:
- return proto::ETMBranchList_Binary::KERNEL_MODULE;
- default:
- LOG(ERROR) << "unexpected dso type " << dso_type;
- return std::nullopt;
- }
+ if (!android::base::WriteStringToFile(s, output_filename)) {
+ PLOG(ERROR) << "failed to write to " << output_filename;
+ return false;
}
-};
+ return true;
+}
class InjectCommand : public Command {
public:
@@ -854,59 +949,94 @@ class InjectCommand : public Command {
return true;
}
+ bool ReadPerfDataFiles(const std::function<void(PerfDataReader&)> reader_callback) {
+ if (input_filenames_.empty()) {
+ return true;
+ }
+
+ std::string expected_data_type;
+ for (const auto& filename : input_filenames_) {
+ std::unique_ptr<RecordFileReader> file_reader = RecordFileReader::CreateInstance(filename);
+ if (!file_reader) {
+ return false;
+ }
+ std::string data_type = PerfDataReader::GetDataType(*file_reader);
+ if (expected_data_type.empty()) {
+ expected_data_type = data_type;
+ } else if (expected_data_type != data_type) {
+ LOG(ERROR) << "files have different data type: " << input_filenames_[0] << ", " << filename;
+ return false;
+ }
+ std::unique_ptr<PerfDataReader> reader;
+ if (data_type == "etm") {
+ reader.reset(new ETMPerfDataReader(std::move(file_reader), exclude_perf_,
+ binary_name_regex_.get(), etm_dump_option_));
+ } else if (data_type == "lbr") {
+ reader.reset(
+ new LBRPerfDataReader(std::move(file_reader), exclude_perf_, binary_name_regex_.get()));
+ } else {
+ LOG(ERROR) << "unsupported data type " << data_type << " in " << filename;
+ return false;
+ }
+ reader_callback(*reader);
+ if (!reader->Read()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
bool ConvertPerfDataToAutoFDO() {
AutoFDOWriter autofdo_writer;
- auto callback = [&](const BinaryKey& key, AutoFDOBinaryInfo& binary) {
+ auto afdo_callback = [&](const BinaryKey& key, AutoFDOBinaryInfo& binary) {
autofdo_writer.AddAutoFDOBinary(key, binary);
};
- for (const auto& input_filename : input_filenames_) {
- PerfDataReader reader(input_filename, exclude_perf_, etm_dump_option_,
- binary_name_regex_.get());
- reader.SetCallback(callback);
- if (!reader.Read()) {
- return false;
- }
+ auto reader_callback = [&](PerfDataReader& reader) { reader.AddCallback(afdo_callback); };
+ if (!ReadPerfDataFiles(reader_callback)) {
+ return false;
}
return autofdo_writer.Write(output_filename_);
}
bool ConvertPerfDataToBranchList() {
- BranchListMerger branch_list_merger;
- auto callback = [&](const BinaryKey& key, BranchListBinaryInfo& binary) {
- branch_list_merger.AddBranchListBinary(key, binary);
+ BranchListMerger merger;
+ auto etm_callback = [&](const BinaryKey& key, ETMBinary& binary) {
+ merger.AddETMBinary(key, binary);
};
- for (const auto& input_filename : input_filenames_) {
- PerfDataReader reader(input_filename, exclude_perf_, etm_dump_option_,
- binary_name_regex_.get());
- reader.SetCallback(callback);
- if (!reader.Read()) {
- return false;
- }
+ auto lbr_callback = [&](LBRData& lbr_data) { merger.AddLBRData(lbr_data); };
+
+ auto reader_callback = [&](PerfDataReader& reader) {
+ reader.AddCallback(etm_callback);
+ reader.AddCallback(lbr_callback);
+ };
+ if (!ReadPerfDataFiles(reader_callback)) {
+ return false;
}
- BranchListWriter branch_list_writer;
- return branch_list_writer.Write(output_filename_, branch_list_merger.binary_map);
+ return WriteBranchListFile(output_filename_, merger.GetETMData(), merger.GetLBRData());
}
bool ConvertBranchListToAutoFDO() {
// Step1 : Merge branch lists from all input files.
- BranchListMerger branch_list_merger;
- auto callback = [&](const BinaryKey& key, BranchListBinaryInfo& binary) {
- branch_list_merger.AddBranchListBinary(key, binary);
+ BranchListMerger merger;
+ auto etm_callback = [&](const BinaryKey& key, ETMBinary& binary) {
+ merger.AddETMBinary(key, binary);
};
+ auto lbr_callback = [&](LBRData& lbr_data) { merger.AddLBRData(lbr_data); };
for (const auto& input_filename : input_filenames_) {
BranchListReader reader(input_filename, binary_name_regex_.get());
- reader.SetCallback(callback);
+ reader.AddCallback(etm_callback);
+ reader.AddCallback(lbr_callback);
if (!reader.Read()) {
return false;
}
}
- // Step2: Convert BranchListBinaryInfo to AutoFDOBinaryInfo.
+ // Step2: Convert ETMBinary and LBRData to AutoFDOBinaryInfo.
AutoFDOWriter autofdo_writer;
- BranchListToAutoFDOConverter converter;
- for (auto& p : branch_list_merger.binary_map) {
+ ETMBranchListToAutoFDOConverter converter;
+ for (auto& p : merger.GetETMData()) {
const BinaryKey& key = p.first;
- BranchListBinaryInfo& binary = p.second;
+ ETMBinary& binary = p.second;
std::unique_ptr<AutoFDOBinaryInfo> autofdo_binary = converter.Convert(key, binary);
if (autofdo_binary) {
// Create new BinaryKey with kernel_start_addr = 0. Because AutoFDO output doesn't care
@@ -914,6 +1044,16 @@ class InjectCommand : public Command {
autofdo_writer.AddAutoFDOBinary(BinaryKey(key.path, key.build_id), *autofdo_binary);
}
}
+ if (!merger.GetLBRData().samples.empty()) {
+ LBRData& lbr_data = merger.GetLBRData();
+ std::optional<std::vector<AutoFDOBinaryInfo>> binaries = ConvertLBRDataToAutoFDO(lbr_data);
+ if (!binaries) {
+ return false;
+ }
+ for (size_t i = 0; i < binaries.value().size(); ++i) {
+ autofdo_writer.AddAutoFDOBinary(lbr_data.binaries[i], binaries.value()[i]);
+ }
+ }
// Step3: Write AutoFDOBinaryInfo.
return autofdo_writer.Write(output_filename_);
@@ -921,20 +1061,21 @@ class InjectCommand : public Command {
bool ConvertBranchListToBranchList() {
// Step1 : Merge branch lists from all input files.
- BranchListMerger branch_list_merger;
- auto callback = [&](const BinaryKey& key, BranchListBinaryInfo& binary) {
- branch_list_merger.AddBranchListBinary(key, binary);
+ BranchListMerger merger;
+ auto etm_callback = [&](const BinaryKey& key, ETMBinary& binary) {
+ merger.AddETMBinary(key, binary);
};
+ auto lbr_callback = [&](LBRData& lbr_data) { merger.AddLBRData(lbr_data); };
for (const auto& input_filename : input_filenames_) {
BranchListReader reader(input_filename, binary_name_regex_.get());
- reader.SetCallback(callback);
+ reader.AddCallback(etm_callback);
+ reader.AddCallback(lbr_callback);
if (!reader.Read()) {
return false;
}
}
- // Step2: Write BranchListBinaryInfo.
- BranchListWriter branch_list_writer;
- return branch_list_writer.Write(output_filename_, branch_list_merger.binary_map);
+ // Step2: Write ETMBinary.
+ return WriteBranchListFile(output_filename_, merger.GetETMData(), merger.GetLBRData());
}
std::unique_ptr<RegEx> binary_name_regex_;
diff --git a/simpleperf/cmd_inject_impl.h b/simpleperf/cmd_inject_impl.h
deleted file mode 100644
index 455a9d16..00000000
--- a/simpleperf/cmd_inject_impl.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2020 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.
- */
-
-#pragma once
-
-#include <string>
-#include <vector>
-
-namespace simpleperf {
-
-std::string BranchToProtoString(const std::vector<bool>& branch);
-
-std::vector<bool> ProtoStringToBranch(const std::string& s, size_t bit_size);
-
-} // namespace simpleperf \ No newline at end of file
diff --git a/simpleperf/cmd_inject_test.cpp b/simpleperf/cmd_inject_test.cpp
index 6c6161c1..b6e6456c 100644
--- a/simpleperf/cmd_inject_test.cpp
+++ b/simpleperf/cmd_inject_test.cpp
@@ -18,7 +18,6 @@
#include <android-base/test_utils.h>
#include <gtest/gtest.h>
-#include "cmd_inject_impl.h"
#include "command.h"
#include "get_test_data.h"
#include "test_util.h"
@@ -52,22 +51,24 @@ static bool RunInjectCmd(std::vector<std::string>&& args, std::string* output) {
return true;
}
-static void CheckMatchingExpectedData(std::string& data) {
+static void CheckMatchingExpectedData(const std::string& name, std::string& data) {
std::string expected_data;
ASSERT_TRUE(android::base::ReadFileToString(
- GetTestData(std::string("etm") + OS_PATH_SEPARATOR + "perf_inject.data"), &expected_data));
+ GetTestData(std::string("etm") + OS_PATH_SEPARATOR + name), &expected_data));
data.erase(std::remove(data.begin(), data.end(), '\r'), data.end());
ASSERT_EQ(data, expected_data);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_inject, smoke) {
std::string data;
ASSERT_TRUE(RunInjectCmd({}, &data));
// Test that we can find instr range in etm_test_loop binary.
ASSERT_NE(data.find("etm_test_loop"), std::string::npos);
- CheckMatchingExpectedData(data);
+ CheckMatchingExpectedData("perf_inject.data", data);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_inject, binary_option) {
// Test that data for etm_test_loop is generated when selected by --binary.
std::string data;
@@ -87,10 +88,12 @@ TEST(cmd_inject, binary_option) {
ASSERT_EQ(data.find("etm_test_loop"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_inject, exclude_perf_option) {
ASSERT_TRUE(RunInjectCmd({"--exclude-perf"}, nullptr));
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_inject, output_option) {
TemporaryFile tmpfile;
close(tmpfile.release());
@@ -98,23 +101,10 @@ TEST(cmd_inject, output_option) {
ASSERT_TRUE(RunInjectCmd({"--output", "branch-list", "-o", tmpfile.path}));
std::string autofdo_data;
ASSERT_TRUE(RunInjectCmd({"-i", tmpfile.path, "--output", "autofdo"}, &autofdo_data));
- CheckMatchingExpectedData(autofdo_data);
-}
-
-TEST(cmd_inject, branch_to_proto_string) {
- std::vector<bool> branch;
- for (size_t i = 0; i < 100; i++) {
- branch.push_back(i % 2 == 0);
- std::string s = BranchToProtoString(branch);
- for (size_t j = 0; j <= i; j++) {
- bool b = s[j >> 3] & (1 << (j & 7));
- ASSERT_EQ(b, branch[j]);
- }
- std::vector<bool> branch2 = ProtoStringToBranch(s, branch.size());
- ASSERT_EQ(branch, branch2);
- }
+ CheckMatchingExpectedData("perf_inject.data", autofdo_data);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_inject, skip_empty_output_file) {
TemporaryFile tmpfile;
close(tmpfile.release());
@@ -125,6 +115,7 @@ TEST(cmd_inject, skip_empty_output_file) {
tmpfile.DoNotRemove();
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_inject, inject_kernel_data) {
const std::string recording_file =
GetTestData(std::string("etm") + OS_PATH_SEPARATOR + "perf_kernel.data");
@@ -147,6 +138,7 @@ TEST(cmd_inject, inject_kernel_data) {
ASSERT_EQ(output, autofdo_output);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_inject, unformatted_trace) {
std::string data;
std::string perf_with_unformatted_trace =
@@ -154,9 +146,10 @@ TEST(cmd_inject, unformatted_trace) {
ASSERT_TRUE(RunInjectCmd({"-i", perf_with_unformatted_trace}, &data));
// Test that we can find instr range in etm_test_loop binary.
ASSERT_NE(data.find("etm_test_loop"), std::string::npos);
- CheckMatchingExpectedData(data);
+ CheckMatchingExpectedData("perf_inject.data", data);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_inject, multiple_input_files) {
std::string data;
std::string perf_data = GetTestData(PERF_DATA_ETM_TEST_LOOP);
@@ -180,6 +173,7 @@ TEST(cmd_inject, multiple_input_files) {
ASSERT_NE(data.find("106c->1074:200"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_inject, merge_branch_list_files) {
TemporaryFile tmpfile;
close(tmpfile.release());
@@ -193,6 +187,7 @@ TEST(cmd_inject, merge_branch_list_files) {
ASSERT_NE(autofdo_data.find("106c->1074:200"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_inject, report_warning_when_overflow) {
CapturedStderr capture;
std::vector<std::unique_ptr<TemporaryFile>> branch_list_files;
@@ -231,3 +226,63 @@ TEST(cmd_inject, report_warning_when_overflow) {
ASSERT_NE(capture.str().find(WARNING_MSG), std::string::npos);
ASSERT_NE(autofdo_data.find("106c->1074:18446744073709551615"), std::string::npos);
}
+
+// @CddTest = 6.1/C-0-2
+TEST(cmd_inject, accept_missing_aux_data) {
+ // Recorded with "-e cs-etm:u --user-buffer-size 64k sleep 1".
+ std::string perf_data = GetTestData("etm/perf_with_missing_aux_data.data");
+ TemporaryFile tmpfile;
+ close(tmpfile.release());
+ ASSERT_TRUE(RunInjectCmd({"--output", "branch-list", "-i", perf_data, "-o", tmpfile.path}));
+}
+
+// @CddTest = 6.1/C-0-2
+TEST(cmd_inject, read_lbr_data) {
+ // Convert perf.data to AutoFDO text format.
+ std::string perf_data_path = GetTestData("lbr/perf_lbr.data");
+ std::string data;
+ ASSERT_TRUE(RunInjectCmd({"-i", perf_data_path}, &data));
+ data.erase(std::remove(data.begin(), data.end(), '\r'), data.end());
+
+ std::string expected_data;
+ ASSERT_TRUE(android::base::ReadFileToString(
+ GetTestData(std::string("lbr") + OS_PATH_SEPARATOR + "inject_lbr.data"), &expected_data));
+ ASSERT_EQ(data, expected_data);
+
+ // Convert perf.data to branch_list.proto format.
+ // Then convert branch_list.proto format to AutoFDO text format.
+ TemporaryFile branch_list_file;
+ close(branch_list_file.release());
+ ASSERT_TRUE(
+ RunInjectCmd({"-i", perf_data_path, "--output", "branch-list", "-o", branch_list_file.path}));
+ ASSERT_TRUE(RunInjectCmd({"-i", branch_list_file.path}, &data));
+ ASSERT_EQ(data, expected_data);
+
+ // Test binary filter on LBR data.
+ ASSERT_TRUE(RunInjectCmd({"-i", perf_data_path, "--binary", "no_lbr_test_loop"}, &data));
+ ASSERT_EQ(data.find("lbr_test_loop"), data.npos);
+
+ // Test binary filter on branch list file.
+ ASSERT_TRUE(RunInjectCmd({"-i", branch_list_file.path, "--binary", "no_lbr_test_loop"}, &data));
+ ASSERT_EQ(data.find("lbr_test_loop"), data.npos);
+
+ // Test multiple input files.
+ ASSERT_TRUE(RunInjectCmd(
+ {
+ "-i",
+ std::string(branch_list_file.path) + "," + branch_list_file.path,
+ },
+ &data));
+ ASSERT_NE(data.find("194d->1940:706"), data.npos);
+}
+
+// @CddTest = 6.1/C-0-2
+TEST(cmd_inject, inject_small_binary) {
+ // etm_test_loop_small, a binary compiled with
+ // "-Wl,-z,noseparate-code", where the file is smaller than its text
+ // section mapped into memory.
+ std::string data;
+ std::string perf_data = GetTestData("etm/perf_for_small_binary.data");
+ ASSERT_TRUE(RunInjectCmd({"-i", perf_data}, &data));
+ CheckMatchingExpectedData("perf_inject_small.data", data);
+}
diff --git a/simpleperf/cmd_kmem.cpp b/simpleperf/cmd_kmem.cpp
index 81746ba5..847ffd63 100644
--- a/simpleperf/cmd_kmem.cpp
+++ b/simpleperf/cmd_kmem.cpp
@@ -532,10 +532,9 @@ bool KmemCommand::PrepareToBuildSampleTree() {
}
void KmemCommand::ReadEventAttrsFromRecordFile() {
- std::vector<EventAttrWithId> attrs = record_file_reader_->AttrSection();
- for (const auto& attr_with_id : attrs) {
+ for (const EventAttrWithId& attr_with_id : record_file_reader_->AttrSection()) {
EventAttrWithName attr;
- attr.attr = *attr_with_id.attr;
+ attr.attr = attr_with_id.attr;
attr.event_ids = attr_with_id.ids;
attr.name = GetEventNameByAttr(attr.attr);
event_attrs_.push_back(attr);
@@ -606,7 +605,11 @@ bool KmemCommand::ProcessTracingData(const std::vector<char>& data) {
if (attr.attr.type == PERF_TYPE_TRACEPOINT) {
uint64_t trace_event_id = attr.attr.config;
attr.name = tracing->GetTracingEventNameHavingId(trace_event_id);
- TracingFormat format = tracing->GetTracingFormatHavingId(trace_event_id);
+ std::optional<TracingFormat> opt_format = tracing->GetTracingFormatHavingId(trace_event_id);
+ if (!opt_format.has_value()) {
+ return false;
+ }
+ const TracingFormat& format = opt_format.value();
if (use_slab_) {
if (format.name == "kmalloc" || format.name == "kmem_cache_alloc" ||
format.name == "kmalloc_node" || format.name == "kmem_cache_alloc_node") {
diff --git a/simpleperf/cmd_kmem_test.cpp b/simpleperf/cmd_kmem_test.cpp
index b34f6148..7c66be41 100644
--- a/simpleperf/cmd_kmem_test.cpp
+++ b/simpleperf/cmd_kmem_test.cpp
@@ -85,15 +85,18 @@ static bool RunKmemRecordCmd(std::vector<std::string> v, const char* output_file
return KmemCmd()->Run(v);
}
+// @CddTest = 6.1/C-0-2
TEST(kmem_cmd, record_slab) {
TEST_IN_ROOT(ASSERT_TRUE(RunKmemRecordCmd({"--slab"})));
}
+// @CddTest = 6.1/C-0-2
TEST(kmem_cmd, record_fp_callchain_sampling) {
TEST_IN_ROOT(ASSERT_TRUE(RunKmemRecordCmd({"--slab", "-g"})));
TEST_IN_ROOT(ASSERT_TRUE(RunKmemRecordCmd({"--slab", "--call-graph", "fp"})));
}
+// @CddTest = 6.1/C-0-2
TEST(kmem_cmd, record_and_report) {
TemporaryFile tmp_file;
TEST_IN_ROOT({
@@ -104,6 +107,7 @@ TEST(kmem_cmd, record_and_report) {
});
}
+// @CddTest = 6.1/C-0-2
TEST(kmem_cmd, record_and_report_callgraph) {
TemporaryFile tmp_file;
TEST_IN_ROOT({
@@ -116,6 +120,7 @@ TEST(kmem_cmd, record_and_report_callgraph) {
#endif
+// @CddTest = 6.1/C-0-2
TEST(kmem_cmd, report) {
ReportResult result;
KmemReportFile(PERF_DATA_WITH_KMEM_SLAB_CALLGRAPH_RECORD, {}, &result);
@@ -124,6 +129,7 @@ TEST(kmem_cmd, report) {
ASSERT_NE(result.content.find("__alloc_skb"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(kmem_cmd, report_all_sort_options) {
ReportResult result;
KmemReportFile(
@@ -134,6 +140,7 @@ TEST(kmem_cmd, report_all_sort_options) {
ASSERT_NE(result.content.find("GfpFlags"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(kmem_cmd, report_callgraph) {
ReportResult result;
KmemReportFile(PERF_DATA_WITH_KMEM_SLAB_CALLGRAPH_RECORD, {"-g"}, &result);
diff --git a/simpleperf/cmd_list.cpp b/simpleperf/cmd_list.cpp
index 4953d9f1..926b7f7e 100644
--- a/simpleperf/cmd_list.cpp
+++ b/simpleperf/cmd_list.cpp
@@ -14,9 +14,13 @@
* limitations under the License.
*/
+#include <sched.h>
#include <stdio.h>
+
+#include <atomic>
#include <map>
#include <string>
+#include <thread>
#include <vector>
#include <android-base/file.h>
@@ -31,86 +35,244 @@
#include "event_type.h"
namespace simpleperf {
+
+extern std::unordered_map<std::string, std::unordered_set<int>> cpu_supported_raw_events;
+
+#if defined(__aarch64__) || defined(__arm__)
+extern std::unordered_map<uint64_t, std::string> arm64_cpuid_to_name;
+#endif // defined(__aarch64__) || defined(__arm__)
+
namespace {
-enum EventTypeStatus {
- NOT_SUPPORTED,
- MAY_NOT_SUPPORTED,
- SUPPORTED,
+struct RawEventTestThreadArg {
+ int cpu;
+ std::atomic<pid_t> tid;
+ std::atomic<bool> start;
+};
+
+static void RawEventTestThread(RawEventTestThreadArg* arg) {
+ cpu_set_t mask;
+ CPU_ZERO(&mask);
+ CPU_SET(arg->cpu, &mask);
+ int tid = gettid();
+ sched_setaffinity(tid, sizeof(mask), &mask);
+ arg->tid = tid;
+ while (!arg->start) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+ TemporaryFile tmpfile;
+ FILE* fp = fopen(tmpfile.path, "w");
+ if (fp == nullptr) {
+ return;
+ }
+ for (int i = 0; i < 10; ++i) {
+ fprintf(fp, "output some data\n");
+ }
+ fclose(fp);
+}
+
+struct RawEventSupportStatus {
+ std::vector<int> supported_cpus;
+ std::vector<int> may_supported_cpus;
};
-static EventTypeStatus IsEventTypeSupported(const EventType& event_type) {
- // Because PMU events are provided by kernel, we assume it's supported.
- if (event_type.IsPmuEvent()) {
- return EventTypeStatus::SUPPORTED;
+class RawEventSupportChecker {
+ public:
+ bool Init() {
+#if defined(__aarch64__) || defined(__arm__)
+ cpu_models_ = GetARMCpuModels();
+ if (cpu_models_.empty()) {
+ LOG(ERROR) << "can't get device cpu info";
+ return false;
+ }
+ for (const auto& model : cpu_models_) {
+ uint64_t cpu_id = (static_cast<uint64_t>(model.implementer) << 32) | model.partnum;
+ if (auto it = arm64_cpuid_to_name.find(cpu_id); it != arm64_cpuid_to_name.end()) {
+ cpu_model_names_.push_back(it->second);
+ } else {
+ cpu_model_names_.push_back("");
+ }
+ }
+#endif // defined(__aarch64__) || defined(__arm__)
+ return true;
+ }
+
+ RawEventSupportStatus GetCpusSupportingEvent(const EventType& event_type) {
+ RawEventSupportStatus status;
+ std::string required_cpu_model;
+ // For cpu model specific events, the limited_arch is like "arm64:Cortex-A520".
+ if (auto pos = event_type.limited_arch.find(':'); pos != std::string::npos) {
+ required_cpu_model = event_type.limited_arch.substr(pos + 1);
+ }
+
+ for (size_t i = 0; i < cpu_models_.size(); ++i) {
+ const ARMCpuModel& model = cpu_models_[i];
+ const std::string& model_name = cpu_model_names_[i];
+ bool supported = false;
+ bool may_supported = false;
+ if (!required_cpu_model.empty()) {
+ // This is a cpu model specific event, only supported on required_cpu_model.
+ supported = model_name == required_cpu_model;
+ } else if (!model_name.empty()) {
+ // We know events supported on this cpu model.
+ auto it = cpu_supported_raw_events.find(model_name);
+ CHECK(it != cpu_supported_raw_events.end()) << "no events configuration for " << model_name;
+ supported = it->second.count(event_type.config) > 0;
+ } else {
+ // We need to test the event support status.
+ TestEventSupportOnCpu(event_type, model.cpus[0], supported, may_supported);
+ }
+
+ if (supported) {
+ status.supported_cpus.insert(status.supported_cpus.end(), model.cpus.begin(),
+ model.cpus.end());
+ } else if (may_supported) {
+ status.may_supported_cpus.insert(status.may_supported_cpus.end(), model.cpus.begin(),
+ model.cpus.end());
+ }
+ }
+ return status;
}
- if (event_type.type != PERF_TYPE_RAW) {
+
+ private:
+ void TestEventSupportOnCpu(const EventType& event_type, int cpu, bool& supported,
+ bool& may_supported) {
+ // Because the kernel may not check whether the raw event is supported by the cpu pmu.
+ // We can't decide whether the raw event is supported by calling perf_event_open().
+ // Instead, we can check if it can collect some real number.
+ RawEventTestThreadArg test_thread_arg;
+ test_thread_arg.cpu = cpu;
+ test_thread_arg.tid = 0;
+ test_thread_arg.start = false;
+ std::thread test_thread(RawEventTestThread, &test_thread_arg);
+ while (test_thread_arg.tid == 0) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
perf_event_attr attr = CreateDefaultPerfEventAttr(event_type);
- // Exclude kernel to list supported events even when kernel recording isn't allowed.
- attr.exclude_kernel = 1;
- return IsEventAttrSupported(attr, event_type.name) ? EventTypeStatus::SUPPORTED
- : EventTypeStatus::NOT_SUPPORTED;
- }
- if (event_type.limited_arch == "arm" && GetTargetArch() != ARCH_ARM &&
- GetTargetArch() != ARCH_ARM64) {
- return EventTypeStatus::NOT_SUPPORTED;
- }
- // Because the kernel may not check whether the raw event is supported by the cpu pmu.
- // We can't decide whether the raw event is supported by calling perf_event_open().
- // Instead, we can check if it can collect some real number.
- perf_event_attr attr = CreateDefaultPerfEventAttr(event_type);
- std::unique_ptr<EventFd> event_fd =
- EventFd::OpenEventFile(attr, gettid(), -1, nullptr, event_type.name, false);
- if (event_fd == nullptr) {
- return EventTypeStatus::NOT_SUPPORTED;
- }
- auto work_function = []() {
- TemporaryFile tmpfile;
- FILE* fp = fopen(tmpfile.path, "w");
- if (fp == nullptr) {
+ std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(
+ attr, test_thread_arg.tid, test_thread_arg.cpu, nullptr, event_type.name, false);
+ test_thread_arg.start = true;
+ test_thread.join();
+ if (event_fd == nullptr) {
+ supported = may_supported = false;
+ return;
+ }
+ PerfCounter counter;
+ if (!event_fd->ReadCounter(&counter)) {
+ supported = may_supported = false;
return;
}
- for (int i = 0; i < 10; ++i) {
- fprintf(fp, "output some data\n");
+ if (counter.value != 0) {
+ supported = true;
+ may_supported = false;
+ } else {
+ supported = false;
+ may_supported = true;
}
- fclose(fp);
+ }
+
+ std::vector<ARMCpuModel> cpu_models_;
+ std::vector<std::string> cpu_model_names_;
+};
+
+static std::string ToCpuString(const std::vector<int>& cpus) {
+ std::string s;
+ if (cpus.empty()) {
+ return s;
+ }
+ s += std::to_string(cpus[0]);
+ int last_cpu = cpus[0];
+ bool added = true;
+ for (size_t i = 1; i < cpus.size(); ++i) {
+ if (cpus[i] == last_cpu + 1) {
+ last_cpu = cpus[i];
+ added = false;
+ } else {
+ s += "-" + std::to_string(last_cpu) + "," + std::to_string(cpus[i]);
+ last_cpu = cpus[i];
+ added = true;
+ }
+ }
+ if (!added) {
+ s += "-" + std::to_string(last_cpu);
+ }
+ return s;
+}
+
+static void PrintRawEventTypes(const std::string& type_desc) {
+ printf("List of %s:\n", type_desc.c_str());
+#if defined(__aarch64__) || defined(__arm__)
+ printf(
+ // clang-format off
+" # Please refer to \"PMU common architectural and microarchitectural event numbers\"\n"
+" # and \"ARM recommendations for IMPLEMENTATION DEFINED event numbers\" listed in\n"
+" # ARMv9 manual for details.\n"
+" # A possible link is https://developer.arm.com/documentation/ddi0487.\n"
+ // clang-format on
+ );
+#endif // defined(__aarch64__) || defined(__arm__)
+ RawEventSupportChecker support_checker;
+ if (!support_checker.Init()) {
+ return;
+ }
+ auto callback = [&](const EventType& event_type) {
+ if (event_type.type != PERF_TYPE_RAW) {
+ return true;
+ }
+ RawEventSupportStatus status = support_checker.GetCpusSupportingEvent(event_type);
+ if (status.supported_cpus.empty() && status.may_supported_cpus.empty()) {
+ return true;
+ }
+ std::string text = " " + event_type.name + " (";
+ if (!status.supported_cpus.empty()) {
+ text += "supported on cpu " + ToCpuString(status.supported_cpus);
+ if (!status.may_supported_cpus.empty()) {
+ text += ", ";
+ }
+ }
+ if (!status.may_supported_cpus.empty()) {
+ text += "may supported on cpu " + ToCpuString(status.may_supported_cpus);
+ }
+ text += ")";
+ printf("%s", text.c_str());
+ if (!event_type.description.empty()) {
+ printf("\t\t# %s", event_type.description.c_str());
+ }
+ printf("\n");
+ return true;
};
- work_function();
- PerfCounter counter;
- if (!event_fd->ReadCounter(&counter)) {
- return EventTypeStatus::NOT_SUPPORTED;
+ EventTypeManager::Instance().ForEachType(callback);
+ printf("\n");
+}
+
+static bool IsEventTypeSupported(const EventType& event_type) {
+ // PMU and tracepoint events are provided by kernel. So we assume they're supported.
+ if (event_type.IsPmuEvent() || event_type.IsTracepointEvent()) {
+ return true;
}
- // For raw events, we may not be able to detect whether it is supported on device.
- return (counter.value != 0u) ? EventTypeStatus::SUPPORTED : EventTypeStatus::MAY_NOT_SUPPORTED;
+ perf_event_attr attr = CreateDefaultPerfEventAttr(event_type);
+ // Exclude kernel to list supported events even when kernel recording isn't allowed.
+ attr.exclude_kernel = 1;
+ return IsEventAttrSupported(attr, event_type.name);
}
static void PrintEventTypesOfType(const std::string& type_name, const std::string& type_desc,
const std::function<bool(const EventType&)>& is_type_fn) {
+ if (type_name == "raw") {
+ return PrintRawEventTypes(type_desc);
+ }
printf("List of %s:\n", type_desc.c_str());
if (GetTargetArch() == ARCH_ARM || GetTargetArch() == ARCH_ARM64) {
- if (type_name == "raw") {
- printf(
- // clang-format off
-" # Please refer to \"PMU common architectural and microarchitectural event numbers\"\n"
-" # and \"ARM recommendations for IMPLEMENTATION DEFINED event numbers\" listed in\n"
-" # ARMv8 manual for details.\n"
-" # A possible link is https://developer.arm.com/docs/ddi0487/latest/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile.\n"
- // clang-format on
- );
- } else if (type_name == "cache") {
+ if (type_name == "cache") {
printf(" # More cache events are available in `simpleperf list raw`.\n");
}
}
auto callback = [&](const EventType& event_type) {
if (is_type_fn(event_type)) {
- EventTypeStatus status = IsEventTypeSupported(event_type);
- if (status == EventTypeStatus::NOT_SUPPORTED) {
+ if (!IsEventTypeSupported(event_type)) {
return true;
}
printf(" %s", event_type.name.c_str());
- if (status == EventTypeStatus::MAY_NOT_SUPPORTED) {
- printf(" (may not supported)");
- }
if (!event_type.description.empty()) {
printf("\t\t# %s", event_type.description.c_str());
}
diff --git a/simpleperf/cmd_list_test.cpp b/simpleperf/cmd_list_test.cpp
index e2f804da..940f7595 100644
--- a/simpleperf/cmd_list_test.cpp
+++ b/simpleperf/cmd_list_test.cpp
@@ -21,6 +21,7 @@
using namespace simpleperf;
+// @CddTest = 6.1/C-0-2
class ListCommandTest : public ::testing::Test {
protected:
virtual void SetUp() {
@@ -31,22 +32,27 @@ class ListCommandTest : public ::testing::Test {
std::unique_ptr<Command> list_cmd;
};
+// @CddTest = 6.1/C-0-2
TEST_F(ListCommandTest, no_options) {
ASSERT_TRUE(list_cmd->Run({}));
}
+// @CddTest = 6.1/C-0-2
TEST_F(ListCommandTest, one_option) {
ASSERT_TRUE(list_cmd->Run({"sw"}));
}
+// @CddTest = 6.1/C-0-2
TEST_F(ListCommandTest, multiple_options) {
ASSERT_TRUE(list_cmd->Run({"hw", "tracepoint"}));
}
+// @CddTest = 6.1/C-0-2
TEST_F(ListCommandTest, show_features_option) {
ASSERT_TRUE(list_cmd->Run({"--show-features"}));
}
+// @CddTest = 6.1/C-0-2
TEST_F(ListCommandTest, pmu_option) {
ASSERT_TRUE(list_cmd->Run({"pmu"}));
}
diff --git a/simpleperf/cmd_merge.cpp b/simpleperf/cmd_merge.cpp
index 6f9bc20c..1bf9833f 100644
--- a/simpleperf/cmd_merge.cpp
+++ b/simpleperf/cmd_merge.cpp
@@ -255,16 +255,16 @@ class MergeCommand : public Command {
// Check attr sections to know if recorded event types are the same.
bool CheckAttrSection() {
- std::vector<EventAttrWithId> attrs0 = readers_[0]->AttrSection();
+ const EventAttrIds& attrs0 = readers_[0]->AttrSection();
for (size_t i = 1; i < readers_.size(); i++) {
- std::vector<EventAttrWithId> attrs = readers_[i]->AttrSection();
+ const EventAttrIds& attrs = readers_[i]->AttrSection();
if (attrs.size() != attrs0.size()) {
LOG(ERROR) << input_files_[0] << " and " << input_files_[i]
<< " are not mergeable for recording different event types";
return false;
}
for (size_t attr_id = 0; attr_id < attrs.size(); attr_id++) {
- if (memcmp(attrs[attr_id].attr, attrs0[attr_id].attr, sizeof(perf_event_attr)) != 0) {
+ if (attrs[attr_id].attr != attrs0[attr_id].attr) {
LOG(ERROR) << input_files_[0] << " and " << input_files_[i]
<< " are not mergeable for recording different event types";
return false;
@@ -300,7 +300,7 @@ class MergeCommand : public Command {
// map event_ids in readers_[next_read_id] to event attrs. The map info is put into an
// EventIdRecord.
const std::unordered_map<uint64_t, size_t>& cur_map = readers_[prev_reader_id]->EventIdMap();
- std::vector<EventAttrWithId> attrs = readers_[next_reader_id]->AttrSection();
+ const EventAttrIds& attrs = readers_[next_reader_id]->AttrSection();
std::vector<uint64_t> event_id_data;
for (size_t attr_id = 0; attr_id < attrs.size(); attr_id++) {
for (size_t event_id : attrs[attr_id].ids) {
diff --git a/simpleperf/cmd_merge_test.cpp b/simpleperf/cmd_merge_test.cpp
index 482d0893..1a60c507 100644
--- a/simpleperf/cmd_merge_test.cpp
+++ b/simpleperf/cmd_merge_test.cpp
@@ -46,6 +46,7 @@ static std::string GetReport(const std::string& record_file) {
return data;
}
+// @CddTest = 6.1/C-0-2
TEST(merge_cmd, input_output_options) {
// missing arguments
ASSERT_FALSE(MergeCmd()->Run({}));
@@ -63,6 +64,7 @@ TEST(merge_cmd, input_output_options) {
ASSERT_TRUE(MergeCmd()->Run({"-i", input_file, "-i", input_file, "-o", tmpfile.path}));
}
+// @CddTest = 6.1/C-0-2
TEST(merge_cmd, merge_two_files) {
std::string input_file1 = GetTestData("perf_merge1.data");
std::string input_file2 = GetTestData("perf_merge2.data");
diff --git a/simpleperf/cmd_monitor.cpp b/simpleperf/cmd_monitor.cpp
index d81ccfea..0fd1e873 100644
--- a/simpleperf/cmd_monitor.cpp
+++ b/simpleperf/cmd_monitor.cpp
@@ -80,14 +80,15 @@ constexpr size_t DESIRED_PAGES_IN_MAPPED_BUFFER = 1024;
// buffer size on a 8 core system. For system-wide recording, it is 8K pages *
// 4K page_size * 8 cores = 256MB. For non system-wide recording, it is 1K pages
// * 4K page_size * 8 cores = 64MB.
-static constexpr size_t kRecordBufferSize = 64 * 1024 * 1024;
-static constexpr size_t kSystemWideRecordBufferSize = 256 * 1024 * 1024;
+static constexpr size_t kRecordBufferSize = 64 * kMegabyte;
+static constexpr size_t kSystemWideRecordBufferSize = 256 * kMegabyte;
class MonitorCommand : public Command {
public:
MonitorCommand()
- : Command("monitor", "monitor events and print their textual representations to stdout",
- // clang-format off
+ : Command(
+ "monitor", "monitor events and print their textual representations to stdout",
+ // clang-format off
"Usage: simpleperf monitor [options]\n"
" Gather sampling information and print the events on stdout.\n"
" For precise recording, prefer the record command.\n"
@@ -113,7 +114,7 @@ class MonitorCommand : public Command {
" samples every second. For non-tracepoint events, the default\n"
" option is -f 4000. A -f/-c option affects all event types\n"
" following it until meeting another -f/-c option. For example,\n"
-" for \"-f 1000 cpu-cycles -c 1 -e sched:sched_switch\", cpu-cycles\n"
+" for \"-f 1000 -e cpu-cycles -c 1 -e sched:sched_switch\", cpu-cycles\n"
" has sample freq 1000, sched:sched_switch event has sample period 1.\n"
"-c count Set event sample period. It means recording one sample when\n"
" [count] events happen. For tracepoint events, the default option\n"
@@ -132,8 +133,8 @@ class MonitorCommand : public Command {
"--exclude-perf Exclude samples for simpleperf process.\n"
RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING
"\n"
- // clang-format on
- ),
+ // clang-format on
+ ),
system_wide_collection_(false),
fp_callchain_sampling_(false),
dwarf_callchain_sampling_(false),
@@ -172,7 +173,6 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING
uint64_t max_sample_freq_ = DEFAULT_SAMPLE_FREQ_FOR_NONTRACEPOINT_EVENT;
size_t cpu_time_max_percent_ = 25;
- std::unique_ptr<SampleSpeed> sample_speed_;
bool system_wide_collection_;
bool fp_callchain_sampling_;
bool dwarf_callchain_sampling_;
@@ -232,14 +232,14 @@ bool MonitorCommand::PrepareMonitoring() {
}
// 3. Open perf event files and create mapped buffers.
- if (!event_selection_set_.OpenEventFiles({})) {
+ if (!event_selection_set_.OpenEventFiles()) {
return false;
}
size_t record_buffer_size =
system_wide_collection_ ? kSystemWideRecordBufferSize : kRecordBufferSize;
if (!event_selection_set_.MmapEventFiles(mmap_page_range_.first, mmap_page_range_.second,
0 /* aux_buffer_size */, record_buffer_size,
- false /* allow_cutting_samples */, exclude_perf_)) {
+ false /* allow_truncating_samples */, exclude_perf_)) {
return false;
}
auto callback = std::bind(&MonitorCommand::ProcessRecord, this, std::placeholders::_1);
@@ -252,7 +252,7 @@ bool MonitorCommand::PrepareMonitoring() {
// Use first perf_event_attr and first event id to dump mmap and comm records.
EventAttrWithId dumping_attr_id = event_selection_set_.GetEventAttrWithId()[0];
- map_record_reader_.emplace(*dumping_attr_id.attr, dumping_attr_id.ids[0],
+ map_record_reader_.emplace(dumping_attr_id.attr, dumping_attr_id.ids[0],
event_selection_set_.RecordNotExecutableMaps());
map_record_reader_->SetCallback([this](Record* r) { return ProcessRecord(r); });
@@ -351,8 +351,6 @@ bool MonitorCommand::ParseOptions(const std::vector<std::string>& args) {
CHECK(options.values.empty());
// Process ordered options.
- std::vector<size_t> wait_setting_speed_event_groups;
-
for (const auto& pair : ordered_options) {
const OptionName& name = pair.first;
const OptionValue& value = pair.second;
@@ -362,20 +360,17 @@ bool MonitorCommand::ParseOptions(const std::vector<std::string>& args) {
LOG(ERROR) << "invalid " << name << ": " << value.uint_value;
return false;
}
+ SampleRate rate;
if (name == "-c") {
- sample_speed_.reset(new SampleSpeed(0, value.uint_value));
+ rate.sample_period = value.uint_value;
} else {
if (value.uint_value >= INT_MAX) {
LOG(ERROR) << "sample freq can't be bigger than INT_MAX: " << value.uint_value;
return false;
}
- sample_speed_.reset(new SampleSpeed(value.uint_value, 0));
- }
-
- for (auto groud_id : wait_setting_speed_event_groups) {
- event_selection_set_.SetSampleSpeed(groud_id, *sample_speed_);
+ rate.sample_freq = value.uint_value;
}
- wait_setting_speed_event_groups.clear();
+ event_selection_set_.SetSampleRateForNewEvents(rate);
} else if (name == "--call-graph") {
std::vector<std::string> strs = android::base::Split(*value.str_value, ",");
@@ -407,15 +402,9 @@ bool MonitorCommand::ParseOptions(const std::vector<std::string>& args) {
} else if (name == "-e") {
std::vector<std::string> event_types = android::base::Split(*value.str_value, ",");
for (auto& event_type : event_types) {
- size_t group_id;
- if (!event_selection_set_.AddEventType(event_type, &group_id)) {
+ if (!event_selection_set_.AddEventType(event_type)) {
return false;
}
- if (sample_speed_) {
- event_selection_set_.SetSampleSpeed(group_id, *sample_speed_);
- } else {
- wait_setting_speed_event_groups.push_back(group_id);
- }
}
} else if (name == "-g") {
@@ -507,7 +496,7 @@ bool MonitorCommand::ProcessRecord(Record* record) {
// Record filter check should go after DumpMapsForRecord(). Otherwise, process/thread name
// filters don't work in system wide collection.
- if (!record_filter_.Check(&r)) {
+ if (!record_filter_.Check(r)) {
return true;
}
diff --git a/simpleperf/cmd_monitor_test.cpp b/simpleperf/cmd_monitor_test.cpp
index 963a9808..8264c966 100644
--- a/simpleperf/cmd_monitor_test.cpp
+++ b/simpleperf/cmd_monitor_test.cpp
@@ -59,15 +59,18 @@ static ::testing::AssertionResult RunMonitorCmd(std::vector<std::string> v, std:
return (result ? ::testing::AssertionSuccess() : ::testing::AssertionFailure());
}
+// @CddTest = 6.1/C-0-2
TEST(monitor_cmd, no_options) {
std::string output;
ASSERT_FALSE(RunMonitorCmd({}, output));
}
+// @CddTest = 6.1/C-0-2
TEST(monitor_cmd, no_event) {
ASSERT_FALSE(MonitorCmd()->Run({"-a", "--duration", "1"}));
}
+// @CddTest = 6.1/C-0-2
TEST(monitor_cmd, global) {
TEST_REQUIRE_ROOT();
std::string output;
@@ -75,6 +78,7 @@ TEST(monitor_cmd, global) {
ASSERT_GT(output.size(), 0);
}
+// @CddTest = 6.1/C-0-2
TEST(monitor_cmd, no_perf) {
TEST_REQUIRE_ROOT();
std::string output;
@@ -82,6 +86,7 @@ TEST(monitor_cmd, no_perf) {
ASSERT_GT(output.size(), 0);
}
+// @CddTest = 6.1/C-0-2
TEST(monitor_cmd, with_callchain) {
TEST_REQUIRE_ROOT();
std::string output;
@@ -89,6 +94,7 @@ TEST(monitor_cmd, with_callchain) {
ASSERT_GT(output.size(), 0);
}
+// @CddTest = 6.1/C-0-2
TEST(monitor_cmd, with_callchain_fp) {
TEST_REQUIRE_ROOT();
std::string output;
@@ -96,6 +102,7 @@ TEST(monitor_cmd, with_callchain_fp) {
ASSERT_GT(output.size(), 0);
}
+// @CddTest = 6.1/C-0-2
TEST(monitor_cmd, with_callchain_dwarf) {
TEST_REQUIRE_ROOT();
std::string output;
@@ -103,18 +110,21 @@ TEST(monitor_cmd, with_callchain_dwarf) {
ASSERT_GT(output.size(), 0);
}
+// @CddTest = 6.1/C-0-2
TEST(monitor_cmd, frequency) {
TEST_REQUIRE_ROOT();
std::string output;
ASSERT_TRUE(RunMonitorCmd({"-a", "-f", "1"}, output));
}
+// @CddTest = 6.1/C-0-2
TEST(monitor_cmd, count) {
TEST_REQUIRE_ROOT();
std::string output;
ASSERT_TRUE(RunMonitorCmd({"-a", "-c", "10000000"}, output));
}
+// @CddTest = 6.1/C-0-2
TEST(monitor_cmd, cpu_percent) {
TEST_REQUIRE_ROOT();
std::string output;
@@ -124,6 +134,7 @@ TEST(monitor_cmd, cpu_percent) {
ASSERT_FALSE(RunMonitorCmd({"-a", "--cpu-percent", "101"}, output));
}
+// @CddTest = 6.1/C-0-2
TEST(monitor_cmd, record_filter_options) {
TEST_REQUIRE_ROOT();
std::string output;
diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp
index 514f9061..e08b153b 100644
--- a/simpleperf/cmd_record.cpp
+++ b/simpleperf/cmd_record.cpp
@@ -22,6 +22,7 @@
#include <sys/utsname.h>
#include <time.h>
#include <unistd.h>
+#include <chrono>
#include <filesystem>
#include <optional>
#include <set>
@@ -33,7 +34,6 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
-#include <android-base/scopeguard.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
@@ -48,6 +48,7 @@
#endif
#include <unwindstack/Error.h>
+#include "BranchListFile.h"
#include "CallChainJoiner.h"
#include "ETMRecorder.h"
#include "IOEventLoop.h"
@@ -98,27 +99,21 @@ static std::unordered_map<std::string, int> clockid_map = {
// The max size of records dumped by kernel is 65535, and dump stack size
// should be a multiply of 8, so MAX_DUMP_STACK_SIZE is 65528.
-constexpr uint32_t MAX_DUMP_STACK_SIZE = 65528;
+static constexpr uint32_t MAX_DUMP_STACK_SIZE = 65528;
// The max allowed pages in mapped buffer is decided by rlimit(RLIMIT_MEMLOCK).
// Here 1024 is a desired value for pages in mapped buffer. If mapped
// successfully, the buffer size = 1024 * 4K (page size) = 4M.
-constexpr size_t DESIRED_PAGES_IN_MAPPED_BUFFER = 1024;
+static constexpr size_t DESIRED_PAGES_IN_MAPPED_BUFFER = 1024;
// Cache size used by CallChainJoiner to cache call chains in memory.
-constexpr size_t DEFAULT_CALL_CHAIN_JOINER_CACHE_SIZE = 8 * 1024 * 1024;
+static constexpr size_t DEFAULT_CALL_CHAIN_JOINER_CACHE_SIZE = 8 * kMegabyte;
-// Currently, the record buffer size in user-space is set to match the kernel buffer size on a
-// 8 core system. For system-wide recording, it is 8K pages * 4K page_size * 8 cores = 256MB.
-// For non system-wide recording, it is 1K pages * 4K page_size * 8 cores = 64MB.
-static constexpr size_t kDefaultRecordBufferSize = 64 * 1024 * 1024;
-static constexpr size_t kDefaultSystemWideRecordBufferSize = 256 * 1024 * 1024;
-
-static constexpr size_t kDefaultAuxBufferSize = 4 * 1024 * 1024;
+static constexpr size_t kDefaultAuxBufferSize = 4 * kMegabyte;
// On Pixel 3, it takes about 1ms to enable ETM, and 16-40ms to disable ETM and copy 4M ETM data.
-// So make default period to 100ms.
-static constexpr double kDefaultEtmDataFlushPeriodInSec = 0.1;
+// So make default interval to 100ms.
+static constexpr uint32_t kDefaultEtmDataFlushIntervalInMs = 100;
struct TimeStat {
uint64_t prepare_recording_time = 0;
@@ -128,6 +123,30 @@ struct TimeStat {
uint64_t post_process_time = 0;
};
+std::optional<size_t> GetDefaultRecordBufferSize(bool system_wide_recording) {
+ // Currently, the record buffer size in user-space is set to match the kernel buffer size on a
+ // 8 core system. For system-wide recording, it is 8K pages * 4K page_size * 8 cores = 256MB.
+ // For non system-wide recording, it is 1K pages * 4K page_size * 8 cores = 64MB.
+ // But on devices with memory >= 4GB, we increase buffer size to 256MB. This reduces the chance
+ // of cutting samples, which can cause broken callchains.
+ static constexpr size_t kLowMemoryRecordBufferSize = 64 * kMegabyte;
+ static constexpr size_t kHighMemoryRecordBufferSize = 256 * kMegabyte;
+ static constexpr size_t kSystemWideRecordBufferSize = 256 * kMegabyte;
+ // Ideally we can use >= 4GB here. But the memory size shown in /proc/meminfo is like to be 3.x GB
+ // on a device with 4GB memory. So we have to use <= 3GB.
+ static constexpr uint64_t kLowMemoryLimit = 3 * kGigabyte;
+
+ if (system_wide_recording) {
+ return kSystemWideRecordBufferSize;
+ }
+ auto device_memory = GetMemorySize();
+ if (!device_memory.has_value()) {
+ return std::nullopt;
+ }
+ return device_memory.value() <= kLowMemoryLimit ? kLowMemoryRecordBufferSize
+ : kHighMemoryRecordBufferSize;
+}
+
class RecordCommand : public Command {
public:
RecordCommand()
@@ -171,8 +190,8 @@ class RecordCommand : public Command {
"--kprobe kprobe_event1,kprobe_event2,...\n"
" Add kprobe events during recording. The kprobe_event format is in\n"
" Documentation/trace/kprobetrace.rst in the kernel. Examples:\n"
-" 'p:myprobe do_sys_open $arg2:string' - add event kprobes:myprobe\n"
-" 'r:myretprobe do_sys_open $retval:s64' - add event kprobes:myretprobe\n"
+" 'p:myprobe do_sys_openat2 $arg2:string' - add event kprobes:myprobe\n"
+" 'r:myretprobe do_sys_openat2 $retval:s64' - add event kprobes:myretprobe\n"
"--add-counter event1,event2,... Add additional event counts in record samples. For example,\n"
" we can use `-e cpu-cycles --add-counter instructions` to\n"
" get samples for cpu-cycles event, while having instructions\n"
@@ -183,7 +202,7 @@ class RecordCommand : public Command {
" samples every second. For non-tracepoint events, the default\n"
" option is -f 4000. A -f/-c option affects all event types\n"
" following it until meeting another -f/-c option. For example,\n"
-" for \"-f 1000 cpu-cycles -c 1 -e sched:sched_switch\", cpu-cycles\n"
+" for \"-f 1000 -e cpu-cycles -c 1 -e sched:sched_switch\", cpu-cycles\n"
" has sample freq 1000, sched:sched_switch event has sample period 1.\n"
"-c count Set event sample period. It means recording one sample when\n"
" [count] events happen. For tracepoint events, the default option\n"
@@ -191,15 +210,16 @@ class RecordCommand : public Command {
"--call-graph fp | dwarf[,<dump_stack_size>]\n"
" Enable call graph recording. Use frame pointer or dwarf debug\n"
" frame as the method to parse call graph in stack.\n"
-" Default is dwarf,65528.\n"
+" Default is no call graph. Default dump_stack_size with -g is 65528.\n"
"-g Same as '--call-graph dwarf'.\n"
"--clockid clock_id Generate timestamps of samples using selected clock.\n"
" Possible values are: realtime, monotonic,\n"
" monotonic_raw, boottime, perf. If supported, default\n"
" is monotonic, otherwise is perf.\n"
-"--cpu cpu_item1,cpu_item2,...\n"
-" Collect samples only on the selected cpus. cpu_item can be cpu\n"
-" number like 1, or cpu range like 0-3.\n"
+"--cpu cpu_item1,cpu_item2,... Monitor events on selected cpus. cpu_item can be a number like\n"
+" 1, or a range like 0-3. A --cpu option affects all event types\n"
+" following it until meeting another --cpu option.\n"
+"--delay time_in_ms Wait time_in_ms milliseconds before recording samples.\n"
"--duration time_in_sec Monitor for time_in_sec seconds instead of running\n"
" [command]. Here time_in_sec may be any positive\n"
" floating point number.\n"
@@ -220,31 +240,10 @@ class RecordCommand : public Command {
" It should be a power of 2. If not set, the max possible value <= 1024\n"
" will be used.\n"
"--user-buffer-size <buffer_size> Set buffer size in userspace to cache sample data.\n"
-" By default, it is 64M for process recording and 256M\n"
-" for system wide recording.\n"
-"--aux-buffer-size <buffer_size> Set aux buffer size, only used in cs-etm event type.\n"
-" Need to be power of 2 and page size aligned.\n"
-" Used memory size is (buffer_size * (cpu_count + 1).\n"
-" Default is 4M.\n"
+" By default, it is %s.\n"
"--no-inherit Don't record created child threads/processes.\n"
"--cpu-percent <percent> Set the max percent of cpu time used for recording.\n"
" percent is in range [1-100], default is 25.\n"
-"--addr-filter filter_str1,filter_str2,...\n"
-" Provide address filters for cs-etm instruction tracing.\n"
-" filter_str accepts below formats:\n"
-" 'filter <addr-range>' -- trace instructions in a range\n"
-" 'start <addr>' -- start tracing when ip is <addr>\n"
-" 'stop <addr>' -- stop tracing when ip is <addr>\n"
-" <addr-range> accepts below formats:\n"
-" <file_path> -- code sections in a binary file\n"
-" <vaddr_start>-<vaddr_end>@<file_path> -- part of a binary file\n"
-" <kernel_addr_start>-<kernel_addr_end> -- part of kernel space\n"
-" <addr> accepts below formats:\n"
-" <vaddr>@<file_path> -- virtual addr in a binary file\n"
-" <kernel_addr> -- a kernel address\n"
-" Examples:\n"
-" 'filter 0x456-0x480@/system/lib/libc.so'\n"
-" 'start 0x456@/system/lib/libc.so,stop 0x480@/system/lib/libc.so'\n"
"\n"
"--tp-filter filter_string Set filter_string for the previous tracepoint event.\n"
" Format is in Documentation/trace/events.rst in the kernel.\n"
@@ -266,10 +265,10 @@ class RecordCommand : public Command {
" When callchain joiner is used, set the matched nodes needed to join\n"
" callchains. The count should be >= 1. By default it is 1.\n"
"--no-cut-samples Simpleperf uses a record buffer to cache records received from the kernel.\n"
-" When the available space in the buffer reaches low level, it cuts part of\n"
-" the stack data in samples. When the available space reaches critical level,\n"
-" it drops all samples. This option makes simpleperf not cut samples when the\n"
-" available space reaches low level.\n"
+" When the available space in the buffer reaches low level, the stack data in\n"
+" samples is truncated to 1KB. When the available space reaches critical level,\n"
+" it drops all samples. This option makes simpleperf not truncate stack data\n"
+" when the available space reaches low level.\n"
"--keep-failed-unwinding-result Keep reasons for failed unwinding cases\n"
"--keep-failed-unwinding-debug-info Keep debug info for failed unwinding cases\n"
"\n"
@@ -291,6 +290,36 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING
" debug information, which are used for unwinding and dumping symbols.\n"
"--add-meta-info key=value Add extra meta info, which will be stored in the recording file.\n"
"\n"
+"ETM recording options:\n"
+"--addr-filter filter_str1,filter_str2,...\n"
+" Provide address filters for cs-etm instruction tracing.\n"
+" filter_str accepts below formats:\n"
+" 'filter <addr-range>' -- trace instructions in a range\n"
+" 'start <addr>' -- start tracing when ip is <addr>\n"
+" 'stop <addr>' -- stop tracing when ip is <addr>\n"
+" <addr-range> accepts below formats:\n"
+" <file_path> -- code sections in a binary file\n"
+" <vaddr_start>-<vaddr_end>@<file_path> -- part of a binary file\n"
+" <kernel_addr_start>-<kernel_addr_end> -- part of kernel space\n"
+" <addr> accepts below formats:\n"
+" <vaddr>@<file_path> -- virtual addr in a binary file\n"
+" <kernel_addr> -- a kernel address\n"
+" Examples:\n"
+" 'filter 0x456-0x480@/system/lib/libc.so'\n"
+" 'start 0x456@/system/lib/libc.so,stop 0x480@/system/lib/libc.so'\n"
+"--aux-buffer-size <buffer_size> Set aux buffer size, only used in cs-etm event type.\n"
+" Need to be power of 2 and page size aligned.\n"
+" Used memory size is (buffer_size * (cpu_count + 1).\n"
+" Default is 4M.\n"
+"--decode-etm Convert ETM data into branch lists while recording.\n"
+"--binary binary_name Used with --decode-etm to only generate data for binaries\n"
+" matching binary_name regex.\n"
+"--record-timestamp Generate timestamp packets in ETM stream.\n"
+"--record-cycles Generate cycle count packets in ETM stream.\n"
+"--cycle-threshold <threshold> Set cycle count counter threshold for ETM cycle count packets.\n"
+"--etm-flush-interval <interval> Set the interval between ETM data flushes from the ETR buffer\n"
+" to the perf event buffer (in milliseconds). Default is 100 ms.\n"
+"\n"
"Other options:\n"
"--exit-with-parent Stop recording when the thread starting simpleperf dies.\n"
"--use-cmd-exit-code Exit with the same exit code as the monitored cmdline.\n"
@@ -323,7 +352,6 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING
mmap_page_range_(std::make_pair(1, DESIRED_PAGES_IN_MAPPED_BUFFER)),
record_filename_("perf.data"),
sample_record_count_(0),
- lost_record_count_(0),
in_app_context_(false),
trace_offcpu_(false),
exclude_kernel_callchain_(false),
@@ -338,6 +366,7 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING
signal(SIGPIPE, SIG_IGN);
}
+ std::string LongHelpString() const override;
void Run(const std::vector<std::string>& args, int* exit_code) override;
bool Run(const std::vector<std::string>& args) override {
int exit_code;
@@ -347,7 +376,7 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING
private:
bool ParseOptions(const std::vector<std::string>& args, std::vector<std::string>* non_option_args,
- ProbeEvents* probe_events);
+ ProbeEvents& probe_events);
bool AdjustPerfEventLimit();
bool PrepareRecording(Workload* workload);
bool DoRecording(Workload* workload);
@@ -356,8 +385,8 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING
bool TraceOffCpu();
bool SetEventSelectionFlags();
bool CreateAndInitRecordFile();
- std::unique_ptr<RecordFileWriter> CreateRecordFile(
- const std::string& filename, const std::vector<EventAttrWithId>& override_attrs);
+ std::unique_ptr<RecordFileWriter> CreateRecordFile(const std::string& filename,
+ const EventAttrIds& attrs);
bool DumpKernelSymbol();
bool DumpTracingData();
bool DumpMaps();
@@ -370,7 +399,7 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING
bool SaveRecordForPostUnwinding(Record* record);
bool SaveRecordAfterUnwinding(Record* record);
bool SaveRecordWithoutUnwinding(Record* record);
- bool ProcessJITDebugInfo(const std::vector<JITDebugInfo>& debug_info, bool sync_kernel_records);
+ bool ProcessJITDebugInfo(std::vector<JITDebugInfo> debug_info, bool sync_kernel_records);
bool ProcessControlCmd(IOEventLoop* loop);
void UpdateRecord(Record* record);
bool UnwindRecord(SampleRecord& r);
@@ -388,8 +417,8 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING
bool DumpMetaInfoFeature(bool kernel_symbols_available);
bool DumpDebugUnwindFeature(const std::unordered_set<Dso*>& dso_set);
void CollectHitFileInfo(const SampleRecord& r, std::unordered_set<Dso*>* dso_set);
+ bool DumpETMBranchListFeature();
- std::unique_ptr<SampleSpeed> sample_speed_;
bool system_wide_collection_;
uint64_t branch_sampling_;
bool fp_callchain_sampling_;
@@ -401,11 +430,11 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING
bool keep_failed_unwinding_debug_info_ = false;
std::unique_ptr<OfflineUnwinder> offline_unwinder_;
bool child_inherit_;
+ uint64_t delay_in_ms_ = 0;
double duration_in_sec_;
bool can_dump_kernel_symbols_;
bool dump_symbols_;
std::string clockid_;
- std::vector<int> cpus_;
EventSelectionSet event_selection_set_;
std::pair<size_t, size_t> mmap_page_range_;
@@ -419,7 +448,6 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING
android::base::unique_fd stop_signal_fd_;
uint64_t sample_record_count_;
- uint64_t lost_record_count_;
android::base::unique_fd start_profiling_fd_;
bool stdio_controls_profiling_ = false;
@@ -435,7 +463,7 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING
bool allow_callchain_joiner_;
size_t callchain_joiner_min_matching_nodes_;
std::unique_ptr<CallChainJoiner> callchain_joiner_;
- bool allow_cutting_samples_ = true;
+ bool allow_truncating_samples_ = true;
std::unique_ptr<JITDebugReader> jit_debug_reader_;
uint64_t last_record_timestamp_; // used to insert Mmap2Records for JIT debug info
@@ -452,8 +480,33 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING
std::unordered_map<std::string, std::string> extra_meta_info_;
bool use_cmd_exit_code_ = false;
std::vector<std::string> add_counters_;
+
+ std::unique_ptr<ETMBranchListGenerator> etm_branch_list_generator_;
+ std::unique_ptr<RegEx> binary_name_regex_;
+ std::chrono::milliseconds etm_flush_interval_{kDefaultEtmDataFlushIntervalInMs};
};
+std::string RecordCommand::LongHelpString() const {
+ uint64_t process_buffer_size = 0;
+ uint64_t system_wide_buffer_size = 0;
+ if (auto size = GetDefaultRecordBufferSize(false); size) {
+ process_buffer_size = size.value() / kMegabyte;
+ }
+ if (auto size = GetDefaultRecordBufferSize(true); size) {
+ system_wide_buffer_size = size.value() / kMegabyte;
+ }
+ std::string buffer_size_str;
+ if (process_buffer_size == system_wide_buffer_size) {
+ buffer_size_str = android::base::StringPrintf("%" PRIu64 "M", process_buffer_size);
+ } else {
+ buffer_size_str =
+ android::base::StringPrintf("%" PRIu64 "M for process recording and %" PRIu64
+ "M\n for system wide recording",
+ process_buffer_size, system_wide_buffer_size);
+ }
+ return android::base::StringPrintf(long_help_string_.c_str(), buffer_size_str.c_str());
+}
+
void RecordCommand::Run(const std::vector<std::string>& args, int* exit_code) {
*exit_code = 1;
time_stat_.prepare_recording_time = GetSystemClock();
@@ -465,15 +518,8 @@ void RecordCommand::Run(const std::vector<std::string>& args, int* exit_code) {
AllowMoreOpenedFiles();
std::vector<std::string> workload_args;
- ProbeEvents probe_events;
- auto clear_probe_events_guard = android::base::make_scope_guard([this, &probe_events] {
- if (!probe_events.IsEmpty()) {
- // probe events can be deleted only when no perf event file is using them.
- event_selection_set_.CloseEventFiles();
- probe_events.Clear();
- }
- });
- if (!ParseOptions(args, &workload_args, &probe_events)) {
+ ProbeEvents probe_events(event_selection_set_);
+ if (!ParseOptions(args, &workload_args, probe_events)) {
return;
}
if (!AdjustPerfEventLimit()) {
@@ -528,7 +574,8 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
// 2. Add default event type.
if (event_selection_set_.empty()) {
std::string event_type = default_measured_event_type;
- if (GetTargetArch() == ARCH_X86_32 || GetTargetArch() == ARCH_X86_64) {
+ if (GetTargetArch() == ARCH_X86_32 || GetTargetArch() == ARCH_X86_64 ||
+ GetTargetArch() == ARCH_RISCV64) {
// Emulators may not support hardware events. So switch to cpu-clock when cpu-cycles isn't
// available.
if (!IsHardwareEventSupported()) {
@@ -536,13 +583,9 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
LOG(INFO) << "Hardware events are not available, switch to cpu-clock.";
}
}
- size_t group_id;
- if (!event_selection_set_.AddEventType(event_type, &group_id)) {
+ if (!event_selection_set_.AddEventType(event_type)) {
return false;
}
- if (sample_speed_) {
- event_selection_set_.SetSampleSpeed(group_id, *sample_speed_);
- }
}
// 3. Process options before opening perf event files.
@@ -578,7 +621,7 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
} else if (!event_selection_set_.HasMonitoredTarget()) {
if (workload != nullptr) {
event_selection_set_.AddMonitoredProcesses({workload->GetPid()});
- event_selection_set_.SetEnableOnExec(true);
+ event_selection_set_.SetEnableCondition(false, true);
} else if (!app_package_name_.empty()) {
// If app process is not created, wait for it. This allows simpleperf starts before
// app process. In this way, we can have a better support of app start-up time profiling.
@@ -592,6 +635,10 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
} else {
need_to_check_targets = true;
}
+ if (delay_in_ms_ != 0 || event_selection_set_.HasAuxTrace()) {
+ event_selection_set_.SetEnableCondition(false, false);
+ }
+
// Profiling JITed/interpreted Java code is supported starting from Android P.
// Also support profiling art interpreter on host.
if (GetAndroidVersion() >= kAndroidVersionP || GetAndroidVersion() == 0) {
@@ -609,19 +656,22 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
}
// 5. Open perf event files and create mapped buffers.
- if (!event_selection_set_.OpenEventFiles(cpus_)) {
+ if (!event_selection_set_.OpenEventFiles()) {
return false;
}
size_t record_buffer_size = 0;
if (user_buffer_size_.has_value()) {
record_buffer_size = user_buffer_size_.value();
} else {
- record_buffer_size =
- system_wide_collection_ ? kDefaultSystemWideRecordBufferSize : kDefaultRecordBufferSize;
+ auto default_size = GetDefaultRecordBufferSize(system_wide_collection_);
+ if (!default_size.has_value()) {
+ return false;
+ }
+ record_buffer_size = default_size.value();
}
if (!event_selection_set_.MmapEventFiles(mmap_page_range_.first, mmap_page_range_.second,
aux_buffer_size_, record_buffer_size,
- allow_cutting_samples_, exclude_perf_)) {
+ allow_truncating_samples_, exclude_perf_)) {
return false;
}
auto callback = std::bind(&RecordCommand::ProcessRecord, this, std::placeholders::_1);
@@ -656,6 +706,21 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
}
}
+ if (delay_in_ms_ != 0) {
+ auto delay_callback = [this]() {
+ if (!event_selection_set_.SetEnableEvents(true)) {
+ return false;
+ }
+ if (!system_wide_collection_) {
+ // Dump maps in case there are new maps created while delaying.
+ return DumpMaps();
+ }
+ return true;
+ };
+ if (!loop->AddOneTimeEvent(SecondToTimeval(delay_in_ms_ / 1000), delay_callback)) {
+ return false;
+ }
+ }
if (duration_in_sec_ != 0) {
if (!loop->AddPeriodicEvent(
SecondToTimeval(duration_in_sec_), [loop]() { return loop->ExitLoop(); },
@@ -669,8 +734,8 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
}
}
if (jit_debug_reader_) {
- auto callback = [this](const std::vector<JITDebugInfo>& debug_info, bool sync_kernel_records) {
- return ProcessJITDebugInfo(debug_info, sync_kernel_records);
+ auto callback = [this](std::vector<JITDebugInfo> debug_info, bool sync_kernel_records) {
+ return ProcessJITDebugInfo(std::move(debug_info), sync_kernel_records);
};
if (!jit_debug_reader_->RegisterDebugInfoCallback(loop, callback)) {
return false;
@@ -694,6 +759,12 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
}
}
if (event_selection_set_.HasAuxTrace()) {
+ // ETM events can only be enabled successfully after MmapEventFiles().
+ if (delay_in_ms_ == 0 && !event_selection_set_.IsEnabledOnExec()) {
+ if (!event_selection_set_.EnableETMEvents()) {
+ return false;
+ }
+ }
// ETM data is dumped to kernel buffer only when there is no thread traced by ETM. It happens
// either when all monitored threads are scheduled off cpu, or when all etm perf events are
// disabled.
@@ -701,12 +772,20 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
// makes less than expected data, especially in system wide recording. So add a periodic event
// to flush etm data by temporarily disable all perf events.
auto etm_flush = [this]() {
- return event_selection_set_.SetEnableEvents(false) &&
- event_selection_set_.SetEnableEvents(true);
+ return event_selection_set_.DisableETMEvents() && event_selection_set_.EnableETMEvents();
};
- if (!loop->AddPeriodicEvent(SecondToTimeval(kDefaultEtmDataFlushPeriodInSec), etm_flush)) {
+ if (!loop->AddPeriodicEvent(SecondToTimeval(etm_flush_interval_.count() / 1000.0), etm_flush)) {
return false;
}
+
+ if (etm_branch_list_generator_) {
+ if (exclude_perf_) {
+ etm_branch_list_generator_->SetExcludePid(getpid());
+ }
+ if (binary_name_regex_) {
+ etm_branch_list_generator_->SetBinaryFilter(binary_name_regex_.get());
+ }
+ }
}
return true;
}
@@ -730,6 +809,12 @@ bool RecordCommand::DoRecording(Workload* workload) {
return false;
}
time_stat_.stop_recording_time = GetSystemClock();
+ if (event_selection_set_.HasAuxTrace()) {
+ // Disable ETM events to flush the last ETM data.
+ if (!event_selection_set_.DisableETMEvents()) {
+ return false;
+ }
+ }
if (!event_selection_set_.SyncKernelBuffer()) {
return false;
}
@@ -806,28 +891,66 @@ bool RecordCommand::PostProcessRecording(const std::vector<std::string>& args) {
// 6. Show brief record result.
auto record_stat = event_selection_set_.GetRecordStat();
if (event_selection_set_.HasAuxTrace()) {
- LOG(INFO) << "Aux data traced: " << record_stat.aux_data_size;
+ LOG(INFO) << "Aux data traced: " << ReadableCount(record_stat.aux_data_size);
if (record_stat.lost_aux_data_size != 0) {
- LOG(INFO) << "Aux data lost in user space: " << record_stat.lost_aux_data_size;
+ LOG(INFO) << "Aux data lost in user space: " << ReadableCount(record_stat.lost_aux_data_size)
+ << ", consider increasing userspace buffer size(--user-buffer-size).";
}
} else {
- std::string cut_samples;
- if (record_stat.cut_stack_samples > 0) {
- cut_samples = android::base::StringPrintf(" (cut %zu)", record_stat.cut_stack_samples);
- }
- lost_record_count_ += record_stat.lost_samples + record_stat.lost_non_samples;
- LOG(INFO) << "Samples recorded: " << sample_record_count_ << cut_samples
- << ". Samples lost: " << lost_record_count_ << ".";
- LOG(DEBUG) << "In user space, dropped " << record_stat.lost_samples << " samples, "
- << record_stat.lost_non_samples << " non samples, cut stack of "
- << record_stat.cut_stack_samples << " samples.";
- if (sample_record_count_ + lost_record_count_ != 0) {
- double lost_percent =
- static_cast<double>(lost_record_count_) / (lost_record_count_ + sample_record_count_);
- constexpr double LOST_PERCENT_WARNING_BAR = 0.1;
- if (lost_percent >= LOST_PERCENT_WARNING_BAR) {
- LOG(WARNING) << "Lost " << (lost_percent * 100) << "% of samples, "
- << "consider increasing mmap_pages(-m), "
+ // Here we report all lost records as samples. This isn't accurate. Because records like
+ // MmapRecords are not samples. But It's easier for users to understand.
+ size_t userspace_lost_samples =
+ record_stat.userspace_lost_samples + record_stat.userspace_lost_non_samples;
+ size_t lost_samples = record_stat.kernelspace_lost_records + userspace_lost_samples;
+
+ std::stringstream os;
+ os << "Samples recorded: " << ReadableCount(sample_record_count_);
+ if (record_stat.userspace_truncated_stack_samples > 0) {
+ os << " (" << ReadableCount(record_stat.userspace_truncated_stack_samples)
+ << " with truncated stacks)";
+ }
+ os << ". Samples lost: " << ReadableCount(lost_samples);
+ if (lost_samples != 0) {
+ os << " (kernelspace: " << ReadableCount(record_stat.kernelspace_lost_records)
+ << ", userspace: " << ReadableCount(userspace_lost_samples) << ")";
+ }
+ os << ".";
+ LOG(INFO) << os.str();
+
+ LOG(DEBUG) << "Record stat: kernelspace_lost_records="
+ << ReadableCount(record_stat.kernelspace_lost_records)
+ << ", userspace_lost_samples=" << ReadableCount(record_stat.userspace_lost_samples)
+ << ", userspace_lost_non_samples="
+ << ReadableCount(record_stat.userspace_lost_non_samples)
+ << ", userspace_truncated_stack_samples="
+ << ReadableCount(record_stat.userspace_truncated_stack_samples);
+
+ if (sample_record_count_ + record_stat.kernelspace_lost_records != 0) {
+ double kernelspace_lost_percent =
+ static_cast<double>(record_stat.kernelspace_lost_records) /
+ (record_stat.kernelspace_lost_records + sample_record_count_);
+ constexpr double KERNELSPACE_LOST_PERCENT_WARNING_BAR = 0.1;
+ if (kernelspace_lost_percent >= KERNELSPACE_LOST_PERCENT_WARNING_BAR) {
+ LOG(WARNING) << "Lost " << (kernelspace_lost_percent * 100)
+ << "% of samples in kernel space, "
+ << "consider increasing kernel buffer size(-m), "
+ << "or decreasing sample frequency(-f), "
+ << "or increasing sample period(-c).";
+ }
+ }
+ size_t userspace_lost_truncated_samples =
+ userspace_lost_samples + record_stat.userspace_truncated_stack_samples;
+ size_t userspace_complete_samples =
+ sample_record_count_ - record_stat.userspace_truncated_stack_samples;
+ if (userspace_complete_samples + userspace_lost_truncated_samples != 0) {
+ double userspace_lost_percent =
+ static_cast<double>(userspace_lost_truncated_samples) /
+ (userspace_complete_samples + userspace_lost_truncated_samples);
+ constexpr double USERSPACE_LOST_PERCENT_WARNING_BAR = 0.1;
+ if (userspace_lost_percent >= USERSPACE_LOST_PERCENT_WARNING_BAR) {
+ LOG(WARNING) << "Lost/Truncated " << (userspace_lost_percent * 100)
+ << "% of samples in user space, "
+ << "consider increasing userspace buffer size(--user-buffer-size), "
<< "or decreasing sample frequency(-f), "
<< "or increasing sample period(-c).";
}
@@ -849,7 +972,7 @@ bool RecordCommand::PostProcessRecording(const std::vector<std::string>& args) {
bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
std::vector<std::string>* non_option_args,
- ProbeEvents* probe_events) {
+ ProbeEvents& probe_events) {
OptionValueMap options;
std::vector<std::pair<OptionName, OptionValue>> ordered_options;
@@ -900,6 +1023,13 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
branch_sampling_ = branch_sampling_type_map["any"];
}
+ if (auto value = options.PullValue("--binary"); value) {
+ binary_name_regex_ = RegEx::Create(*value->str_value);
+ if (binary_name_regex_ == nullptr) {
+ return false;
+ }
+ }
+
if (!options.PullUintValue("--callchain-joiner-min-matching-nodes",
&callchain_joiner_min_matching_nodes_, 1)) {
return false;
@@ -919,18 +1049,38 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
}
}
- if (auto value = options.PullValue("--cpu"); value) {
- if (auto cpus = GetCpusFromString(*value->str_value); cpus) {
- cpus_.assign(cpus->begin(), cpus->end());
- } else {
- return false;
- }
+ if (!options.PullUintValue("--cpu-percent", &cpu_time_max_percent_, 1, 100)) {
+ return false;
}
- if (!options.PullUintValue("--cpu-percent", &cpu_time_max_percent_, 1, 100)) {
+ if (options.PullBoolValue("--decode-etm")) {
+ etm_branch_list_generator_ = ETMBranchListGenerator::Create(system_wide_collection_);
+ }
+ uint32_t interval = 0;
+ if (options.PullUintValue("--etm-flush-interval", &interval)) {
+ etm_flush_interval_ = std::chrono::milliseconds(interval);
+ }
+
+ if (options.PullBoolValue("--record-timestamp")) {
+ ETMRecorder& recorder = ETMRecorder::GetInstance();
+ recorder.SetRecordTimestamp(true);
+ }
+
+ if (options.PullBoolValue("--record-cycles")) {
+ ETMRecorder& recorder = ETMRecorder::GetInstance();
+ recorder.SetRecordCycles(true);
+ }
+
+ if (!options.PullUintValue("--delay", &delay_in_ms_)) {
return false;
}
+ size_t cyc_threshold;
+ if (options.PullUintValue("--cycle-threshold", &cyc_threshold)) {
+ ETMRecorder& recorder = ETMRecorder::GetInstance();
+ recorder.SetCycleThreshold(cyc_threshold);
+ }
+
if (!options.PullDoubleValue("--duration", &duration_in_sec_, 1e-9)) {
return false;
}
@@ -966,7 +1116,7 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
for (const OptionValue& value : options.PullValues("--kprobe")) {
std::vector<std::string> cmds = android::base::Split(*value.str_value, ",");
for (const auto& cmd : cmds) {
- if (!probe_events->AddKprobe(cmd)) {
+ if (!probe_events.AddKprobe(cmd)) {
return false;
}
}
@@ -982,7 +1132,7 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
}
allow_callchain_joiner_ = !options.PullBoolValue("--no-callchain-joiner");
- allow_cutting_samples_ = !options.PullBoolValue("--no-cut-samples");
+ allow_truncating_samples_ = !options.PullBoolValue("--no-cut-samples");
can_dump_kernel_symbols_ = !options.PullBoolValue("--no-dump-kernel-symbols");
dump_symbols_ = !options.PullBoolValue("--no-dump-symbols");
if (auto value = options.PullValue("--no-inherit"); value) {
@@ -1070,8 +1220,6 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
CHECK(options.values.empty());
// Process ordered options.
- std::vector<size_t> wait_setting_speed_event_groups;
-
for (const auto& pair : ordered_options) {
const OptionName& name = pair.first;
const OptionValue& value = pair.second;
@@ -1081,20 +1229,17 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
LOG(ERROR) << "invalid " << name << ": " << value.uint_value;
return false;
}
+ SampleRate rate;
if (name == "-c") {
- sample_speed_.reset(new SampleSpeed(0, value.uint_value));
+ rate.sample_period = value.uint_value;
} else {
if (value.uint_value >= INT_MAX) {
LOG(ERROR) << "sample freq can't be bigger than INT_MAX: " << value.uint_value;
return false;
}
- sample_speed_.reset(new SampleSpeed(value.uint_value, 0));
- }
-
- for (auto groud_id : wait_setting_speed_event_groups) {
- event_selection_set_.SetSampleSpeed(groud_id, *sample_speed_);
+ rate.sample_freq = value.uint_value;
}
- wait_setting_speed_event_groups.clear();
+ event_selection_set_.SetSampleRateForNewEvents(rate);
} else if (name == "--call-graph") {
std::vector<std::string> strs = android::base::Split(*value.str_value, ",");
@@ -1123,53 +1268,43 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
}
}
+ } else if (name == "--cpu") {
+ if (auto cpus = GetCpusFromString(*value.str_value); cpus) {
+ event_selection_set_.SetCpusForNewEvents(
+ std::vector<int>(cpus.value().begin(), cpus.value().end()));
+ } else {
+ return false;
+ }
} else if (name == "-e") {
std::vector<std::string> event_types = android::base::Split(*value.str_value, ",");
for (auto& event_type : event_types) {
- if (probe_events->IsProbeEvent(event_type)) {
- if (!probe_events->CreateProbeEventIfNotExist(event_type)) {
- return false;
- }
- }
- size_t group_id;
- if (!event_selection_set_.AddEventType(event_type, &group_id)) {
+ if (!probe_events.CreateProbeEventIfNotExist(event_type)) {
return false;
}
- if (sample_speed_) {
- event_selection_set_.SetSampleSpeed(group_id, *sample_speed_);
- } else {
- wait_setting_speed_event_groups.push_back(group_id);
+ if (!event_selection_set_.AddEventType(event_type)) {
+ return false;
}
}
-
} else if (name == "-g") {
fp_callchain_sampling_ = false;
dwarf_callchain_sampling_ = true;
} else if (name == "--group") {
std::vector<std::string> event_types = android::base::Split(*value.str_value, ",");
for (const auto& event_type : event_types) {
- if (probe_events->IsProbeEvent(event_type)) {
- if (!probe_events->CreateProbeEventIfNotExist(event_type)) {
- return false;
- }
+ if (!probe_events.CreateProbeEventIfNotExist(event_type)) {
+ return false;
}
}
- size_t group_id;
- if (!event_selection_set_.AddEventGroup(event_types, &group_id)) {
+ if (!event_selection_set_.AddEventGroup(event_types)) {
return false;
}
- if (sample_speed_) {
- event_selection_set_.SetSampleSpeed(group_id, *sample_speed_);
- } else {
- wait_setting_speed_event_groups.push_back(group_id);
- }
-
} else if (name == "--tp-filter") {
if (!event_selection_set_.SetTracepointFilter(*value.str_value)) {
return false;
}
} else {
- CHECK(false) << "unprocessed option: " << name;
+ LOG(ERROR) << "unprocessed option: " << name;
+ return false;
}
}
@@ -1271,7 +1406,7 @@ bool RecordCommand::TraceOffCpu() {
<< android::base::Join(accepted_events, ' ');
return false;
}
- if (!event_selection_set_.AddEventType("sched:sched_switch")) {
+ if (!event_selection_set_.AddEventType("sched:sched_switch", SampleRate(0, 1))) {
return false;
}
if (IsSwitchRecordSupported()) {
@@ -1300,32 +1435,35 @@ bool RecordCommand::SetEventSelectionFlags() {
}
bool RecordCommand::CreateAndInitRecordFile() {
- record_file_writer_ =
- CreateRecordFile(record_filename_, event_selection_set_.GetEventAttrWithId());
+ EventAttrIds attrs = event_selection_set_.GetEventAttrWithId();
+ bool remove_regs_and_stacks = unwind_dwarf_callchain_ && !post_unwind_;
+ if (remove_regs_and_stacks) {
+ for (auto& attr : attrs) {
+ ReplaceRegAndStackWithCallChain(attr.attr);
+ }
+ }
+ record_file_writer_ = CreateRecordFile(record_filename_, attrs);
if (record_file_writer_ == nullptr) {
return false;
}
// Use first perf_event_attr and first event id to dump mmap and comm records.
- dumping_attr_id_ = event_selection_set_.GetEventAttrWithId()[0];
+ CHECK(!attrs.empty());
+ dumping_attr_id_ = attrs[0];
CHECK(!dumping_attr_id_.ids.empty());
- map_record_reader_.emplace(*dumping_attr_id_.attr, dumping_attr_id_.ids[0],
+ map_record_reader_.emplace(dumping_attr_id_.attr, dumping_attr_id_.ids[0],
event_selection_set_.RecordNotExecutableMaps());
map_record_reader_->SetCallback([this](Record* r) { return ProcessRecord(r); });
return DumpKernelSymbol() && DumpTracingData() && DumpMaps() && DumpAuxTraceInfo();
}
-std::unique_ptr<RecordFileWriter> RecordCommand::CreateRecordFile(
- const std::string& filename, const std::vector<EventAttrWithId>& attrs) {
+std::unique_ptr<RecordFileWriter> RecordCommand::CreateRecordFile(const std::string& filename,
+ const EventAttrIds& attrs) {
std::unique_ptr<RecordFileWriter> writer = RecordFileWriter::CreateInstance(filename);
- if (writer == nullptr) {
- return nullptr;
- }
-
- if (!writer->WriteAttrSection(attrs)) {
- return nullptr;
+ if (writer != nullptr && writer->WriteAttrSection(attrs)) {
+ return writer;
}
- return writer;
+ return nullptr;
}
bool RecordCommand::DumpKernelSymbol() {
@@ -1367,9 +1505,11 @@ bool RecordCommand::DumpMaps() {
// For system wide recording:
// If not aux tracing, only dump kernel maps. Maps of a process is dumped when needed (the
// first time a sample hits that process).
- // If aux tracing, we don't know which maps will be needed, so dump all process maps. To
- // reduce pre recording time, we dump process maps in map record thread while recording.
- if (event_selection_set_.HasAuxTrace()) {
+ // If aux tracing with decoding etm data, the maps are dumped by etm_branch_list_generator.
+ // If aux tracing without decoding etm data, we don't know which maps will be needed, so dump
+ // all process maps. To reduce pre recording time, we dump process maps in map record thread
+ // while recording.
+ if (event_selection_set_.HasAuxTrace() && !etm_branch_list_generator_) {
map_record_thread_.emplace(*map_record_reader_);
return true;
}
@@ -1424,7 +1564,16 @@ bool RecordCommand::ProcessRecord(Record* record) {
// Record filter check should go after DumpMapsForRecord(). Otherwise, process/thread name
// filters don't work in system wide collection.
if (record->type() == PERF_RECORD_SAMPLE) {
- if (!record_filter_.Check(static_cast<SampleRecord*>(record))) {
+ if (!record_filter_.Check(static_cast<SampleRecord&>(*record))) {
+ return true;
+ }
+ }
+ if (etm_branch_list_generator_) {
+ bool consumed = false;
+ if (!etm_branch_list_generator_->ProcessRecord(*record, consumed)) {
+ return false;
+ }
+ if (consumed) {
return true;
}
}
@@ -1506,8 +1655,6 @@ bool RecordCommand::SaveRecordAfterUnwinding(Record* record) {
return true;
}
sample_record_count_++;
- } else if (record->type() == PERF_RECORD_LOST) {
- lost_record_count_ += static_cast<LostRecord*>(record)->lost;
} else {
thread_tree_.Update(*record);
}
@@ -1525,30 +1672,32 @@ bool RecordCommand::SaveRecordWithoutUnwinding(Record* record) {
return true;
}
sample_record_count_++;
- } else if (record->type() == PERF_RECORD_LOST) {
- lost_record_count_ += static_cast<LostRecord*>(record)->lost;
}
return record_file_writer_->WriteRecord(*record);
}
-bool RecordCommand::ProcessJITDebugInfo(const std::vector<JITDebugInfo>& debug_info,
+bool RecordCommand::ProcessJITDebugInfo(std::vector<JITDebugInfo> debug_info,
bool sync_kernel_records) {
for (auto& info : debug_info) {
if (info.type == JITDebugInfo::JIT_DEBUG_JIT_CODE) {
uint64_t timestamp =
jit_debug_reader_->SyncWithRecords() ? info.timestamp : last_record_timestamp_;
- Mmap2Record record(*dumping_attr_id_.attr, false, info.pid, info.pid, info.jit_code_addr,
+ Mmap2Record record(dumping_attr_id_.attr, false, info.pid, info.pid, info.jit_code_addr,
info.jit_code_len, info.file_offset, map_flags::PROT_JIT_SYMFILE_MAP,
info.file_path, dumping_attr_id_.ids[0], timestamp);
if (!ProcessRecord(&record)) {
return false;
}
} else {
- if (info.extracted_dex_file_map) {
- ThreadMmap& map = *info.extracted_dex_file_map;
+ if (!info.symbols.empty()) {
+ Dso* dso = thread_tree_.FindUserDsoOrNew(info.file_path, 0, DSO_DEX_FILE);
+ dso->SetSymbols(&info.symbols);
+ }
+ if (info.dex_file_map) {
+ ThreadMmap& map = *info.dex_file_map;
uint64_t timestamp =
jit_debug_reader_->SyncWithRecords() ? info.timestamp : last_record_timestamp_;
- Mmap2Record record(*dumping_attr_id_.attr, false, info.pid, info.pid, map.start_addr,
+ Mmap2Record record(dumping_attr_id_.attr, false, info.pid, info.pid, map.start_addr,
map.len, map.pgoff, map.prot, map.name, dumping_attr_id_.ids[0],
timestamp);
if (!ProcessRecord(&record)) {
@@ -1656,9 +1805,11 @@ void RecordCommand::UpdateRecord(Record* record) {
}
bool RecordCommand::UnwindRecord(SampleRecord& r) {
- if ((r.sample_type & PERF_SAMPLE_CALLCHAIN) && (r.sample_type & PERF_SAMPLE_REGS_USER) &&
- (r.regs_user_data.reg_mask != 0) && (r.sample_type & PERF_SAMPLE_STACK_USER) &&
- (r.GetValidStackSize() > 0)) {
+ if (!(r.sample_type & PERF_SAMPLE_CALLCHAIN) && (r.sample_type & PERF_SAMPLE_REGS_USER) &&
+ (r.regs_user_data.reg_mask != 0) && (r.sample_type & PERF_SAMPLE_STACK_USER)) {
+ return true;
+ }
+ if (r.GetValidStackSize() > 0) {
ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
RegSet regs(r.regs_user_data.abi, r.regs_user_data.reg_mask, r.regs_user_data.regs);
std::vector<uint64_t> ips;
@@ -1687,6 +1838,9 @@ bool RecordCommand::UnwindRecord(SampleRecord& r) {
CallChainJoiner::ORIGINAL_OFFLINE, ips, sps)) {
return false;
}
+ } else {
+ // For kernel samples, we still need to remove user stack and register fields.
+ r.ReplaceRegAndStackWithCallChain({});
}
return true;
}
@@ -1711,11 +1865,15 @@ std::unique_ptr<RecordFileReader> RecordCommand::MoveRecordFile(const std::strin
return nullptr;
}
record_file_writer_.reset();
- {
- std::error_code ec;
- std::filesystem::rename(record_filename_, old_filename, ec);
- if (ec) {
- LOG(ERROR) << "Failed to rename: " << ec.message();
+ std::error_code ec;
+ std::filesystem::rename(record_filename_, old_filename, ec);
+ if (ec) {
+ LOG(DEBUG) << "Failed to rename: " << ec.message();
+ // rename() fails on Android N x86 emulator, which uses kernel 3.10. Because rename() in bionic
+ // uses renameat2 syscall, which isn't support on kernel < 3.15. So add a fallback to mv
+ // command. The mv command can also work with other situations when rename() doesn't work.
+ // So we'd like to keep it as a fallback to rename().
+ if (!Workload::RunCmd({"mv", record_filename_, old_filename})) {
return nullptr;
}
}
@@ -1774,8 +1932,16 @@ bool RecordCommand::PostUnwindRecords() {
if (!reader) {
return false;
}
+ // Write new event attrs without regs and stacks fields.
+ EventAttrIds attrs = reader->AttrSection();
+ for (auto& attr : attrs) {
+ ReplaceRegAndStackWithCallChain(attr.attr);
+ }
+ if (!record_file_writer_->WriteAttrSection(attrs)) {
+ return false;
+ }
+
sample_record_count_ = 0;
- lost_record_count_ = 0;
auto callback = [this](std::unique_ptr<Record> record) {
return SaveRecordAfterUnwinding(record.get());
};
@@ -1886,6 +2052,9 @@ bool RecordCommand::DumpAdditionalFeatures(const std::vector<std::string>& args)
if (keep_failed_unwinding_debug_info_) {
feature_count += 2;
}
+ if (etm_branch_list_generator_) {
+ feature_count++;
+ }
if (!record_file_writer_->BeginWriteFeatures(feature_count)) {
return false;
}
@@ -1928,6 +2097,9 @@ bool RecordCommand::DumpAdditionalFeatures(const std::vector<std::string>& args)
if (keep_failed_unwinding_debug_info_ && !DumpDebugUnwindFeature(debug_unwinding_files)) {
return false;
}
+ if (etm_branch_list_generator_ && !DumpETMBranchListFeature()) {
+ return false;
+ }
if (!record_file_writer_->EndWriteFeatures()) {
return false;
@@ -1945,34 +2117,9 @@ bool RecordCommand::DumpBuildIdFeature() {
if (!dso->HasDumpId() && !event_selection_set_.HasAuxTrace()) {
continue;
}
- if (dso->type() == DSO_KERNEL) {
- if (!GetKernelBuildId(&build_id)) {
- continue;
- }
- build_id_records.push_back(BuildIdRecord(true, UINT_MAX, build_id, dso->Path()));
- } else if (dso->type() == DSO_KERNEL_MODULE) {
- bool has_build_id = false;
- if (android::base::EndsWith(dso->Path(), ".ko")) {
- has_build_id = GetBuildIdFromDsoPath(dso->Path(), &build_id);
- } else if (const std::string& path = dso->Path();
- path.size() > 2 && path[0] == '[' && path.back() == ']') {
- // For kernel modules that we can't find the corresponding file, read build id from /sysfs.
- has_build_id = GetModuleBuildId(path.substr(1, path.size() - 2), &build_id);
- }
- if (has_build_id) {
- build_id_records.push_back(BuildIdRecord(true, UINT_MAX, build_id, dso->Path()));
- } else {
- LOG(DEBUG) << "Can't read build_id for module " << dso->Path();
- }
- } else if (dso->type() == DSO_ELF_FILE) {
- if (dso->Path() == DEFAULT_EXECNAME_FOR_THREAD_MMAP || dso->IsForJavaMethod()) {
- continue;
- }
- if (!GetBuildIdFromDsoPath(dso->Path(), &build_id)) {
- LOG(DEBUG) << "Can't read build_id from file " << dso->Path();
- continue;
- }
- build_id_records.push_back(BuildIdRecord(false, UINT_MAX, build_id, dso->Path()));
+ if (GetBuildId(*dso, build_id)) {
+ bool in_kernel = dso->type() == DSO_KERNEL || dso->type() == DSO_KERNEL_MODULE;
+ build_id_records.emplace_back(in_kernel, UINT_MAX, build_id, dso->Path());
}
}
if (!record_file_writer_->WriteBuildIdFeature(build_id_records)) {
@@ -2032,6 +2179,15 @@ bool RecordCommand::DumpMetaInfoFeature(bool kernel_symbols_available) {
if (dwarf_callchain_sampling_ && !unwind_dwarf_callchain_) {
OfflineUnwinder::CollectMetaInfo(&info_map);
}
+ auto record_stat = event_selection_set_.GetRecordStat();
+ info_map["record_stat"] = android::base::StringPrintf(
+ "sample_record_count=%" PRIu64
+ ",kernelspace_lost_records=%zu,userspace_lost_samples=%zu,"
+ "userspace_lost_non_samples=%zu,userspace_truncated_stack_samples=%zu",
+ sample_record_count_, record_stat.kernelspace_lost_records,
+ record_stat.userspace_lost_samples, record_stat.userspace_lost_non_samples,
+ record_stat.userspace_truncated_stack_samples);
+
return record_file_writer_->WriteMetaInfoFeature(info_map);
}
@@ -2065,6 +2221,13 @@ void RecordCommand::CollectHitFileInfo(const SampleRecord& r, std::unordered_set
const ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
size_t kernel_ip_count;
std::vector<uint64_t> ips = r.GetCallChain(&kernel_ip_count);
+ if ((r.sample_type & PERF_SAMPLE_BRANCH_STACK) != 0) {
+ for (uint64_t i = 0; i < r.branch_stack_data.stack_nr; ++i) {
+ const auto& item = r.branch_stack_data.stack[i];
+ ips.push_back(item.from);
+ ips.push_back(item.to);
+ }
+ }
for (size_t i = 0; i < ips.size(); i++) {
const MapEntry* map = thread_tree_.FindMap(thread, ips[i], i < kernel_ip_count);
Dso* dso = map->dso;
@@ -2083,6 +2246,16 @@ void RecordCommand::CollectHitFileInfo(const SampleRecord& r, std::unordered_set
}
}
+bool RecordCommand::DumpETMBranchListFeature() {
+ ETMBinaryMap binary_map = etm_branch_list_generator_->GetETMBinaryMap();
+ std::string s;
+ if (!ETMBinaryMapToString(binary_map, s)) {
+ return false;
+ }
+ return record_file_writer_->WriteFeature(PerfFileFormat::FEAT_ETM_BRANCH_LIST, s.data(),
+ s.size());
+}
+
} // namespace
static bool ConsumeStr(const char*& p, const char* s) {
diff --git a/simpleperf/cmd_record_impl.h b/simpleperf/cmd_record_impl.h
index 846bfbc9..e8561636 100644
--- a/simpleperf/cmd_record_impl.h
+++ b/simpleperf/cmd_record_impl.h
@@ -40,13 +40,21 @@ inline const OptionFormatMap& GetRecordCmdOptionFormats() {
{"--app", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::NOT_ALLOWED}},
{"--aux-buffer-size", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::ALLOWED}},
{"-b", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--binary", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::ALLOWED}},
{"-c", {OptionValueType::UINT, OptionType::ORDERED, AppRunnerType::ALLOWED}},
{"--call-graph", {OptionValueType::STRING, OptionType::ORDERED, AppRunnerType::ALLOWED}},
{"--callchain-joiner-min-matching-nodes",
{OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::ALLOWED}},
{"--clockid", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::ALLOWED}},
- {"--cpu", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--cpu", {OptionValueType::STRING, OptionType::ORDERED, AppRunnerType::ALLOWED}},
{"--cpu-percent", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--cycle-threshold", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--decode-etm", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--delay", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--etm-flush-interval",
+ {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--record-timestamp", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--record-cycles", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
{"--duration", {OptionValueType::DOUBLE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
{"-e", {OptionValueType::STRING, OptionType::ORDERED, AppRunnerType::ALLOWED}},
{"--exclude-perf", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp
index a2409fac..898d8756 100644
--- a/simpleperf/cmd_record_test.cpp
+++ b/simpleperf/cmd_record_test.cpp
@@ -34,6 +34,7 @@
#include <android-base/test_utils.h>
#include "ETMRecorder.h"
+#include "JITDebugReader.h"
#include "ProbeEvents.h"
#include "cmd_record_impl.h"
#include "command.h"
@@ -84,10 +85,12 @@ static bool RunRecordCmd(std::vector<std::string> v, const char* output_file = n
return RecordCmd()->Run(v);
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, no_options) {
ASSERT_TRUE(RunRecordCmd({}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, system_wide_option) {
TEST_IN_ROOT(ASSERT_TRUE(RunRecordCmd({"-a"})));
}
@@ -98,15 +101,15 @@ static void CheckEventType(const std::string& record_file, const std::string& ev
ASSERT_TRUE(type != nullptr);
std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(record_file);
ASSERT_TRUE(reader);
- std::vector<EventAttrWithId> attrs = reader->AttrSection();
- for (auto& attr : attrs) {
- if (attr.attr->type == type->type && attr.attr->config == type->config) {
- if (attr.attr->freq == 0) {
- ASSERT_EQ(sample_period, attr.attr->sample_period);
+ for (const auto& attr_with_id : reader->AttrSection()) {
+ const perf_event_attr& attr = attr_with_id.attr;
+ if (attr.type == type->type && attr.config == type->config) {
+ if (attr.freq == 0) {
+ ASSERT_EQ(sample_period, attr.sample_period);
ASSERT_EQ(sample_freq, 0u);
} else {
ASSERT_EQ(sample_period, 0u);
- ASSERT_EQ(sample_freq, attr.attr->sample_freq);
+ ASSERT_EQ(sample_freq, attr.sample_freq);
}
return;
}
@@ -114,16 +117,19 @@ static void CheckEventType(const std::string& record_file, const std::string& ev
FAIL();
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, sample_period_option) {
TemporaryFile tmpfile;
ASSERT_TRUE(RunRecordCmd({"-c", "100000"}, tmpfile.path));
CheckEventType(tmpfile.path, GetDefaultEvent(), 100000u, 0);
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, event_option) {
ASSERT_TRUE(RunRecordCmd({"-e", "cpu-clock"}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, freq_option) {
TemporaryFile tmpfile;
ASSERT_TRUE(RunRecordCmd({"-f", "99"}, tmpfile.path));
@@ -133,6 +139,7 @@ TEST(record_cmd, freq_option) {
ASSERT_FALSE(RunRecordCmd({"-f", std::to_string(UINT_MAX)}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, multiple_freq_or_sample_period_option) {
TemporaryFile tmpfile;
ASSERT_TRUE(RunRecordCmd({"-f", "99", "-e", "task-clock", "-c", "1000000", "-e", "cpu-clock"},
@@ -141,11 +148,13 @@ TEST(record_cmd, multiple_freq_or_sample_period_option) {
CheckEventType(tmpfile.path, "cpu-clock", 1000000u, 0u);
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, output_file_option) {
TemporaryFile tmpfile;
ASSERT_TRUE(RecordCmd()->Run({"-o", tmpfile.path, "-e", GetDefaultEvent(), "sleep", SLEEP_SEC}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, dump_kernel_mmap) {
TemporaryFile tmpfile;
ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
@@ -166,6 +175,7 @@ TEST(record_cmd, dump_kernel_mmap) {
ASSERT_TRUE(have_kernel_mmap);
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, dump_build_id_feature) {
TemporaryFile tmpfile;
ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
@@ -176,10 +186,12 @@ TEST(record_cmd, dump_build_id_feature) {
ASSERT_GT(reader->FeatureSectionDescriptors().size(), 0u);
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, tracepoint_event) {
TEST_IN_ROOT(ASSERT_TRUE(RunRecordCmd({"-a", "-e", "sched:sched_switch"})));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, rN_event) {
TEST_REQUIRE_HW_COUNTER();
OMIT_TEST_ON_NON_NATIVE_ABIS();
@@ -204,12 +216,13 @@ TEST(record_cmd, rN_event) {
ASSERT_TRUE(RunRecordCmd({"-e", event_name}, tmpfile.path));
std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
ASSERT_TRUE(reader);
- std::vector<EventAttrWithId> attrs = reader->AttrSection();
+ const EventAttrIds& attrs = reader->AttrSection();
ASSERT_EQ(1u, attrs.size());
- ASSERT_EQ(PERF_TYPE_RAW, attrs[0].attr->type);
- ASSERT_EQ(event_number, attrs[0].attr->config);
+ ASSERT_EQ(PERF_TYPE_RAW, attrs[0].attr.type);
+ ASSERT_EQ(event_number, attrs[0].attr.config);
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, branch_sampling) {
TEST_REQUIRE_HW_COUNTER();
if (IsBranchSamplingSupported()) {
@@ -224,14 +237,17 @@ TEST(record_cmd, branch_sampling) {
}
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, event_modifier) {
ASSERT_TRUE(RunRecordCmd({"-e", GetDefaultEvent() + std::string(":u")}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, fp_callchain_sampling) {
ASSERT_TRUE(RunRecordCmd({"--call-graph", "fp"}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, fp_callchain_sampling_warning_on_arm) {
if (GetTargetArch() != ARCH_ARM) {
GTEST_LOG_(INFO) << "This test does nothing as it only tests on arm arch.";
@@ -244,10 +260,12 @@ TEST(record_cmd, fp_callchain_sampling_warning_on_arm) {
testing::ExitedWithCode(0), "doesn't work well on arm");
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, system_wide_fp_callchain_sampling) {
TEST_IN_ROOT(ASSERT_TRUE(RunRecordCmd({"-a", "--call-graph", "fp"})));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, dwarf_callchain_sampling) {
OMIT_TEST_ON_NON_NATIVE_ABIS();
ASSERT_TRUE(IsDwarfCallChainSamplingSupported());
@@ -257,15 +275,26 @@ TEST(record_cmd, dwarf_callchain_sampling) {
ASSERT_TRUE(RunRecordCmd({"-p", pid, "--call-graph", "dwarf"}));
ASSERT_TRUE(RunRecordCmd({"-p", pid, "--call-graph", "dwarf,16384"}));
ASSERT_FALSE(RunRecordCmd({"-p", pid, "--call-graph", "dwarf,65536"}));
- ASSERT_TRUE(RunRecordCmd({"-p", pid, "-g"}));
+ TemporaryFile tmpfile;
+ ASSERT_TRUE(RunRecordCmd({"-p", pid, "-g"}, tmpfile.path));
+ auto reader = RecordFileReader::CreateInstance(tmpfile.path);
+ ASSERT_TRUE(reader);
+ const EventAttrIds& attrs = reader->AttrSection();
+ ASSERT_GT(attrs.size(), 0);
+ // Check that reg and stack fields are removed after unwinding.
+ for (const auto& attr : attrs) {
+ ASSERT_EQ(attr.attr.sample_type & (PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER), 0);
+ }
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, system_wide_dwarf_callchain_sampling) {
OMIT_TEST_ON_NON_NATIVE_ABIS();
ASSERT_TRUE(IsDwarfCallChainSamplingSupported());
TEST_IN_ROOT(RunRecordCmd({"-a", "--call-graph", "dwarf"}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, no_unwind_option) {
OMIT_TEST_ON_NON_NATIVE_ABIS();
ASSERT_TRUE(IsDwarfCallChainSamplingSupported());
@@ -273,6 +302,7 @@ TEST(record_cmd, no_unwind_option) {
ASSERT_FALSE(RunRecordCmd({"--no-unwind"}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, post_unwind_option) {
OMIT_TEST_ON_NON_NATIVE_ABIS();
ASSERT_TRUE(IsDwarfCallChainSamplingSupported());
@@ -284,6 +314,7 @@ TEST(record_cmd, post_unwind_option) {
ASSERT_TRUE(RunRecordCmd({"-p", pid, "--call-graph", "dwarf", "--post-unwind=no"}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, existing_processes) {
std::vector<std::unique_ptr<Workload>> workloads;
CreateProcesses(2, &workloads);
@@ -292,6 +323,7 @@ TEST(record_cmd, existing_processes) {
ASSERT_TRUE(RunRecordCmd({"-p", pid_list}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, existing_threads) {
std::vector<std::unique_ptr<Workload>> workloads;
CreateProcesses(2, &workloads);
@@ -301,17 +333,20 @@ TEST(record_cmd, existing_threads) {
ASSERT_TRUE(RunRecordCmd({"-t", tid_list}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, no_monitored_threads) {
TemporaryFile tmpfile;
ASSERT_FALSE(RecordCmd()->Run({"-o", tmpfile.path}));
ASSERT_FALSE(RecordCmd()->Run({"-o", tmpfile.path, ""}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, more_than_one_event_types) {
ASSERT_TRUE(RunRecordCmd({"-e", "task-clock,cpu-clock"}));
ASSERT_TRUE(RunRecordCmd({"-e", "task-clock", "-e", "cpu-clock"}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, mmap_page_option) {
ASSERT_TRUE(RunRecordCmd({"-m", "1"}));
ASSERT_FALSE(RunRecordCmd({"-m", "0"}));
@@ -335,6 +370,7 @@ static void CheckKernelSymbol(const std::string& path, bool need_kallsyms, bool*
*success = true;
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, kernel_symbol) {
TemporaryFile tmpfile;
ASSERT_TRUE(RunRecordCmd({"--no-dump-symbols"}, tmpfile.path));
@@ -380,6 +416,7 @@ static bool CheckDumpedSymbols(const std::string& path, bool allow_dumped_symbol
return true;
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, no_dump_symbols) {
TemporaryFile tmpfile;
ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
@@ -398,6 +435,7 @@ TEST(record_cmd, no_dump_symbols) {
ASSERT_TRUE(CheckDumpedSymbols(tmpfile.path, false));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, dump_kernel_symbols) {
TEST_REQUIRE_ROOT();
TemporaryFile tmpfile;
@@ -413,6 +451,7 @@ TEST(record_cmd, dump_kernel_symbols) {
ASSERT_TRUE(has_kernel_symbols);
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, group_option) {
ASSERT_TRUE(RunRecordCmd({"--group", "task-clock,cpu-clock", "-m", "16"}));
ASSERT_TRUE(
@@ -420,10 +459,12 @@ TEST(record_cmd, group_option) {
"--group", "task-clock:k,cpu-clock:k", "-m", "16"}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, symfs_option) {
ASSERT_TRUE(RunRecordCmd({"--symfs", "/"}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, duration_option) {
TemporaryFile tmpfile;
ASSERT_TRUE(RecordCmd()->Run({"--duration", "1.2", "-p", std::to_string(getpid()), "-o",
@@ -432,6 +473,7 @@ TEST(record_cmd, duration_option) {
{"--duration", "1", "-o", tmpfile.path, "-e", GetDefaultEvent(), "sleep", "2"}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, support_modifier_for_clock_events) {
for (const std::string& e : {"cpu-clock", "task-clock"}) {
for (const std::string& m : {"u", "k"}) {
@@ -440,6 +482,7 @@ TEST(record_cmd, support_modifier_for_clock_events) {
}
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, handle_SIGHUP) {
TemporaryFile tmpfile;
int pipefd[2];
@@ -460,6 +503,7 @@ TEST(record_cmd, handle_SIGHUP) {
ASSERT_STREQ(data, "STARTED");
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, stop_when_no_more_targets) {
TemporaryFile tmpfile;
std::atomic<int> tid(0);
@@ -474,6 +518,7 @@ TEST(record_cmd, stop_when_no_more_targets) {
{"-o", tmpfile.path, "-t", std::to_string(tid), "--in-app", "-e", GetDefaultEvent()}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, donot_stop_when_having_targets) {
std::vector<std::unique_ptr<Workload>> workloads;
CreateProcesses(1, &workloads);
@@ -486,6 +531,7 @@ TEST(record_cmd, donot_stop_when_having_targets) {
ASSERT_GT(end_time_in_ns - start_time_in_ns, static_cast<uint64_t>(2e9));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, start_profiling_fd_option) {
int pipefd[2];
ASSERT_EQ(0, pipe(pipefd));
@@ -504,6 +550,7 @@ TEST(record_cmd, start_profiling_fd_option) {
ASSERT_EQ("STARTED", s);
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, record_meta_info_feature) {
TemporaryFile tmpfile;
ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
@@ -512,6 +559,7 @@ TEST(record_cmd, record_meta_info_feature) {
auto& info_map = reader->GetMetaInfoFeature();
ASSERT_NE(info_map.find("simpleperf_version"), info_map.end());
ASSERT_NE(info_map.find("timestamp"), info_map.end());
+ ASSERT_NE(info_map.find("record_stat"), info_map.end());
#if defined(__ANDROID__)
ASSERT_NE(info_map.find("product_props"), info_map.end());
ASSERT_NE(info_map.find("android_version"), info_map.end());
@@ -519,6 +567,7 @@ TEST(record_cmd, record_meta_info_feature) {
}
// See http://b/63135835.
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, cpu_clock_for_a_long_time) {
std::vector<std::unique_ptr<Workload>> workloads;
CreateProcesses(1, &workloads);
@@ -528,6 +577,7 @@ TEST(record_cmd, cpu_clock_for_a_long_time) {
RecordCmd()->Run({"-e", "cpu-clock", "-o", tmpfile.path, "-p", pid, "--duration", "3"}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, dump_regs_for_tracepoint_events) {
TEST_REQUIRE_HOST_ROOT();
TEST_REQUIRE_TRACEPOINT_EVENTS();
@@ -538,6 +588,7 @@ TEST(record_cmd, dump_regs_for_tracepoint_events) {
ASSERT_TRUE(IsDumpingRegsForTracepointEventsSupported());
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, trace_offcpu_option) {
// On linux host, we need root privilege to read tracepoint events.
TEST_REQUIRE_HOST_ROOT();
@@ -551,7 +602,7 @@ TEST(record_cmd, trace_offcpu_option) {
auto info_map = reader->GetMetaInfoFeature();
ASSERT_EQ(info_map["trace_offcpu"], "true");
if (IsSwitchRecordSupported()) {
- ASSERT_EQ(reader->AttrSection()[0].attr->context_switch, 1);
+ ASSERT_EQ(reader->AttrSection()[0].attr.context_switch, 1);
}
// Release recording environment in perf.data, to avoid affecting tests below.
reader.reset();
@@ -563,10 +614,12 @@ TEST(record_cmd, trace_offcpu_option) {
ASSERT_FALSE(RunRecordCmd({"--trace-offcpu", "-e", "cpu-clock,task-clock"}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, exit_with_parent_option) {
ASSERT_TRUE(RunRecordCmd({"--exit-with-parent"}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, use_cmd_exit_code_option) {
TemporaryFile tmpfile;
int exit_code;
@@ -579,6 +632,7 @@ TEST(record_cmd, use_cmd_exit_code_option) {
ASSERT_NE(exit_code, 0);
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, clockid_option) {
if (!IsSettingClockIdSupported()) {
ASSERT_FALSE(RunRecordCmd({"--clockid", "monotonic"}));
@@ -592,6 +646,7 @@ TEST(record_cmd, clockid_option) {
}
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, generate_samples_by_hw_counters) {
TEST_REQUIRE_HW_COUNTER();
std::vector<std::string> events = {"cpu-cycles", "instructions"};
@@ -611,16 +666,19 @@ TEST(record_cmd, generate_samples_by_hw_counters) {
}
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, callchain_joiner_options) {
ASSERT_TRUE(RunRecordCmd({"--no-callchain-joiner"}));
ASSERT_TRUE(RunRecordCmd({"--callchain-joiner-min-matching-nodes", "2"}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, dashdash) {
TemporaryFile tmpfile;
ASSERT_TRUE(RecordCmd()->Run({"-o", tmpfile.path, "-e", GetDefaultEvent(), "--", "sleep", "1"}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, size_limit_option) {
std::vector<std::unique_ptr<Workload>> workloads;
CreateProcesses(1, &workloads);
@@ -635,6 +693,7 @@ TEST(record_cmd, size_limit_option) {
ASSERT_FALSE(RunRecordCmd({"--size-limit", "0"}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, support_mmap2) {
// mmap2 is supported in kernel >= 3.16. If not supported, please cherry pick below kernel
// patches:
@@ -643,6 +702,7 @@ TEST(record_cmd, support_mmap2) {
ASSERT_TRUE(IsMmap2Supported());
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, kernel_bug_making_zero_dyn_size) {
// Test a kernel bug that makes zero dyn_size in kernel < 3.13. If it fails, please cherry pick
// below kernel patch: 0a196848ca365e perf: Fix arch_perf_out_copy_user default
@@ -669,6 +729,7 @@ TEST(record_cmd, kernel_bug_making_zero_dyn_size) {
ASSERT_TRUE(has_sample);
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, kernel_bug_making_zero_dyn_size_for_kernel_samples) {
// Test a kernel bug that makes zero dyn_size for syscalls of 32-bit applications in 64-bit
// kernels. If it fails, please cherry pick below kernel patch:
@@ -698,6 +759,7 @@ TEST(record_cmd, kernel_bug_making_zero_dyn_size_for_kernel_samples) {
ASSERT_TRUE(has_sample);
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, cpu_percent_option) {
ASSERT_TRUE(RunRecordCmd({"--cpu-percent", "50"}));
ASSERT_FALSE(RunRecordCmd({"--cpu-percent", "0"}));
@@ -716,7 +778,7 @@ class RecordingAppHelper {
std::vector<std::string> args = android::base::Split(record_cmd, " ");
// record_cmd may end with child command. We should put output options before it.
args.emplace(args.begin(), "-o");
- args.emplace(args.begin() + 1, perf_data_file_.path);
+ args.emplace(args.begin() + 1, GetDataPath());
return RecordCmd()->Run(args);
}
@@ -728,15 +790,41 @@ class RecordingAppHelper {
}
return success;
};
- ProcessSymbolsInPerfDataFile(perf_data_file_.path, callback);
+ ProcessSymbolsInPerfDataFile(GetDataPath(), callback);
+ if (!success) {
+ if (IsInEmulator() && !HasSample()) {
+ // In emulator, the monitored app may not have a chance to run.
+ GTEST_LOG_(INFO) << "No samples are recorded. Skip checking symbols.";
+ return true;
+ }
+ DumpData();
+ }
return success;
}
- void DumpData() { CreateCommandInstance("report")->Run({"-i", perf_data_file_.path}); }
+ void DumpData() { CreateCommandInstance("report")->Run({"-i", GetDataPath()}); }
std::string GetDataPath() const { return perf_data_file_.path; }
private:
+ bool HasSample() {
+ std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(GetDataPath());
+ if (!reader) {
+ return false;
+ }
+ bool has_sample = false;
+ auto process_record = [&](std::unique_ptr<Record> r) {
+ if (r->type() == PERF_RECORD_SAMPLE) {
+ has_sample = true;
+ }
+ return true;
+ };
+ if (!reader->ReadDataSection(process_record)) {
+ return false;
+ }
+ return has_sample;
+ }
+
AppHelper app_helper_;
TemporaryFile perf_data_file_;
};
@@ -756,10 +844,7 @@ static void TestRecordingApps(const std::string& app_name, const std::string& ap
return strstr(name, expected_class_name.c_str()) != nullptr &&
strstr(name, expected_method_name.c_str()) != nullptr;
};
- if (!helper.CheckData(process_symbol)) {
- helper.DumpData();
- FAIL() << "Expected Java symbol doesn't exist in the profiling data";
- }
+ ASSERT_TRUE(helper.CheckData(process_symbol));
// Check app_package_name and app_type.
auto reader = RecordFileReader::CreateInstance(helper.GetDataPath());
@@ -779,7 +864,9 @@ static void TestRecordingApps(const std::string& app_name, const std::string& ap
}
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, app_option_for_debuggable_app) {
+ OMIT_TEST_ON_NON_NATIVE_ABIS();
TEST_REQUIRE_APPS();
SetRunInAppToolForTesting(true, false);
TestRecordingApps("com.android.simpleperf.debuggable", "debuggable");
@@ -787,7 +874,9 @@ TEST(record_cmd, app_option_for_debuggable_app) {
TestRecordingApps("com.android.simpleperf.debuggable", "debuggable");
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, app_option_for_profileable_app) {
+ OMIT_TEST_ON_NON_NATIVE_ABIS();
TEST_REQUIRE_APPS();
SetRunInAppToolForTesting(false, true);
TestRecordingApps("com.android.simpleperf.profileable", "profileable");
@@ -815,8 +904,10 @@ static void RecordJavaApp(RecordingAppHelper& helper) {
}
#endif // defined(__ANDROID__)
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, record_java_app) {
#if defined(__ANDROID__)
+ OMIT_TEST_ON_NON_NATIVE_ABIS();
RecordingAppHelper helper;
RecordJavaApp(helper);
@@ -825,15 +916,18 @@ TEST(record_cmd, record_java_app) {
}
// Check perf.data by looking for java symbols.
+ const char* java_symbols[] = {
+ "androidx.test.runner",
+ "androidx.test.espresso",
+ "android.app.ActivityThread.main",
+ };
auto process_symbol = [&](const char* name) {
-#if !defined(IN_CTS_TEST)
- const char* expected_name_with_keyguard = "androidx.test.runner"; // when screen is locked
- if (strstr(name, expected_name_with_keyguard) != nullptr) {
- return true;
+ for (const char* java_symbol : java_symbols) {
+ if (strstr(name, java_symbol) != nullptr) {
+ return true;
+ }
}
-#endif
- const char* expected_name = "androidx.test.espresso"; // when screen stays awake
- return strstr(name, expected_name) != nullptr;
+ return false;
};
ASSERT_TRUE(helper.CheckData(process_symbol));
#else
@@ -841,6 +935,7 @@ TEST(record_cmd, record_java_app) {
#endif
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, record_native_app) {
#if defined(__ANDROID__)
// In case of non-native ABI guest symbols are never directly executed, thus
@@ -876,9 +971,11 @@ TEST(record_cmd, record_native_app) {
#endif
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, check_trampoline_after_art_jni_methods) {
// Test if art jni methods are called by art_jni_trampoline.
#if defined(__ANDROID__)
+ OMIT_TEST_ON_NON_NATIVE_ABIS();
RecordingAppHelper helper;
RecordJavaApp(helper);
@@ -890,6 +987,7 @@ TEST(record_cmd, check_trampoline_after_art_jni_methods) {
auto reader = RecordFileReader::CreateInstance(helper.GetDataPath());
ASSERT_TRUE(reader);
ThreadTree thread_tree;
+ ASSERT_TRUE(reader->LoadBuildIdAndFileFeatures(thread_tree));
auto get_symbol_name = [&](ThreadEntry* thread, uint64_t ip) -> std::string {
const MapEntry* map = thread_tree.FindMap(thread, ip, false);
@@ -911,10 +1009,23 @@ TEST(record_cmd, check_trampoline_after_art_jni_methods) {
if (android::base::StartsWith(sym_name, "art::Method_invoke") && i + 1 < ips.size()) {
has_check = true;
std::string name = get_symbol_name(thread, ips[i + 1]);
- if (!android::base::EndsWith(name, "jni_trampoline")) {
- GTEST_LOG_(ERROR) << "unexpected symbol after art::Method_invoke: " << name;
- return false;
+ if (android::base::EndsWith(name, "jni_trampoline")) {
+ continue;
+ }
+ // When the jni_trampoline function is from JIT cache, we may not get map info in time.
+ // To avoid test flakiness, we accept this.
+ // Case 1: It doesn't hit any maps.
+ if (name == "unknown") {
+ continue;
+ }
+ // Case 2: It hits an old map for JIT cache.
+ if (const MapEntry* map = thread_tree.FindMap(thread, ips[i + 1], false);
+ JITDebugReader::IsPathInJITSymFile(map->dso->Path())) {
+ continue;
}
+
+ GTEST_LOG_(ERROR) << "unexpected symbol after art::Method_invoke: " << name;
+ return false;
}
}
}
@@ -927,10 +1038,12 @@ TEST(record_cmd, check_trampoline_after_art_jni_methods) {
#endif
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, no_cut_samples_option) {
ASSERT_TRUE(RunRecordCmd({"--no-cut-samples"}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, cs_etm_event) {
if (!ETMRecorder::GetInstance().CheckEtmSupport().ok()) {
GTEST_LOG_(INFO) << "Omit this test since etm isn't supported on this device";
@@ -943,9 +1056,9 @@ TEST(record_cmd, cs_etm_event) {
// cs-etm uses sample period instead of sample freq.
ASSERT_EQ(reader->AttrSection().size(), 1u);
- const perf_event_attr* attr = reader->AttrSection()[0].attr;
- ASSERT_EQ(attr->freq, 0);
- ASSERT_EQ(attr->sample_period, 1);
+ const perf_event_attr& attr = reader->AttrSection()[0].attr;
+ ASSERT_EQ(attr.freq, 0);
+ ASSERT_EQ(attr.sample_period, 1);
bool has_auxtrace_info = false;
bool has_auxtrace = false;
@@ -965,6 +1078,7 @@ TEST(record_cmd, cs_etm_event) {
ASSERT_TRUE(has_aux);
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, cs_etm_system_wide) {
TEST_REQUIRE_ROOT();
if (!ETMRecorder::GetInstance().CheckEtmSupport().ok()) {
@@ -974,6 +1088,7 @@ TEST(record_cmd, cs_etm_system_wide) {
ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "-a"}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, aux_buffer_size_option) {
if (!ETMRecorder::GetInstance().CheckEtmSupport().ok()) {
GTEST_LOG_(INFO) << "Omit this test since etm isn't supported on this device";
@@ -986,6 +1101,7 @@ TEST(record_cmd, aux_buffer_size_option) {
ASSERT_FALSE(RunRecordCmd({"-e", "cs-etm", "--aux-buffer-size", "12k"}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, addr_filter_option) {
TEST_REQUIRE_HW_COUNTER();
if (!ETMRecorder::GetInstance().CheckEtmSupport().ok()) {
@@ -1046,6 +1162,62 @@ TEST(record_cmd, addr_filter_option) {
ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--addr-filter", filter}));
}
+// @CddTest = 6.1/C-0-2
+TEST(record_cmd, decode_etm_option) {
+ if (!ETMRecorder::GetInstance().CheckEtmSupport().ok()) {
+ GTEST_LOG_(INFO) << "Omit this test since etm isn't supported on this device";
+ return;
+ }
+ ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--decode-etm"}));
+ ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--decode-etm", "--exclude-perf"}));
+}
+
+// @CddTest = 6.1/C-0-2
+TEST(record_cmd, record_timestamp) {
+ if (!ETMRecorder::GetInstance().CheckEtmSupport().ok()) {
+ GTEST_LOG_(INFO) << "Omit this test since etm isn't supported on this device";
+ return;
+ }
+ ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--record-timestamp"}));
+}
+
+// @CddTest = 6.1/C-0-2
+TEST(record_cmd, record_cycles) {
+ if (!ETMRecorder::GetInstance().CheckEtmSupport().ok()) {
+ GTEST_LOG_(INFO) << "Omit this test since etm isn't supported on this device";
+ return;
+ }
+ ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--record-cycles"}));
+}
+
+// @CddTest = 6.1/C-0-2
+TEST(record_cmd, cycle_threshold) {
+ if (!ETMRecorder::GetInstance().CheckEtmSupport().ok()) {
+ GTEST_LOG_(INFO) << "Omit this test since etm isn't supported on this device";
+ return;
+ }
+ ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--record-cycles", "--cycle-threshold", "8"}));
+}
+
+// @CddTest = 6.1/C-0-2
+TEST(record_cmd, binary_option) {
+ if (!ETMRecorder::GetInstance().CheckEtmSupport().ok()) {
+ GTEST_LOG_(INFO) << "Omit this test since etm isn't supported on this device";
+ return;
+ }
+ ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--decode-etm", "--binary", ".*"}));
+}
+
+// @CddTest = 6.1/C-0-2
+TEST(record_cmd, etm_flush_interval_option) {
+ if (!ETMRecorder::GetInstance().CheckEtmSupport().ok()) {
+ GTEST_LOG_(INFO) << "Omit this test since etm isn't supported on this device";
+ return;
+ }
+ ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--etm-flush-interval", "10"}));
+}
+
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, pmu_event_option) {
TEST_REQUIRE_PMU_COUNTER();
TEST_REQUIRE_HW_COUNTER();
@@ -1061,6 +1233,7 @@ TEST(record_cmd, pmu_event_option) {
TEST_IN_ROOT(ASSERT_TRUE(RunRecordCmd({"-e", event_string})));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, exclude_perf_option) {
ASSERT_TRUE(RunRecordCmd({"--exclude-perf"}));
if (IsRoot()) {
@@ -1081,6 +1254,7 @@ TEST(record_cmd, exclude_perf_option) {
}
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, tp_filter_option) {
TEST_REQUIRE_HOST_ROOT();
TEST_REQUIRE_TRACEPOINT_EVENTS();
@@ -1101,6 +1275,7 @@ TEST(record_cmd, tp_filter_option) {
}
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, ParseAddrFilterOption) {
auto option_to_str = [](const std::string& option) {
auto filters = ParseAddrFilterOption(option);
@@ -1133,19 +1308,22 @@ TEST(record_cmd, ParseAddrFilterOption) {
ASSERT_EQ(option_to_str("start 0x12345678,stop 0x1234567a"), "start 0x12345678,stop 0x1234567a");
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, kprobe_option) {
TEST_REQUIRE_ROOT();
- ProbeEvents probe_events;
+ EventSelectionSet event_selection_set(false);
+ ProbeEvents probe_events(event_selection_set);
if (!probe_events.IsKprobeSupported()) {
GTEST_LOG_(INFO) << "Skip this test as kprobe isn't supported by the kernel.";
return;
}
- ASSERT_TRUE(RunRecordCmd({"-e", "kprobes:myprobe", "--kprobe", "p:myprobe do_sys_open"}));
+ ASSERT_TRUE(RunRecordCmd({"-e", "kprobes:myprobe", "--kprobe", "p:myprobe do_sys_openat2"}));
// A default kprobe event is created if not given an explicit --kprobe option.
- ASSERT_TRUE(RunRecordCmd({"-e", "kprobes:do_sys_open"}));
- ASSERT_TRUE(RunRecordCmd({"--group", "kprobes:do_sys_open"}));
+ ASSERT_TRUE(RunRecordCmd({"-e", "kprobes:do_sys_openat2"}));
+ ASSERT_TRUE(RunRecordCmd({"--group", "kprobes:do_sys_openat2"}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, record_filter_options) {
ASSERT_TRUE(
RunRecordCmd({"--exclude-pid", "1,2", "--exclude-tid", "3,4", "--exclude-process-name",
@@ -1155,6 +1333,7 @@ TEST(record_cmd, record_filter_options) {
"processB", "--include-thread-name", "threadB", "--include-uid", "5,6"}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, keep_failed_unwinding_result_option) {
OMIT_TEST_ON_NON_NATIVE_ABIS();
std::vector<std::unique_ptr<Workload>> workloads;
@@ -1164,6 +1343,7 @@ TEST(record_cmd, keep_failed_unwinding_result_option) {
{"-p", pid, "-g", "--keep-failed-unwinding-result", "--keep-failed-unwinding-debug-info"}));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, kernel_address_warning) {
TEST_REQUIRE_NON_ROOT();
const std::string warning_msg = "Access to kernel symbol addresses is restricted.";
@@ -1188,6 +1368,7 @@ TEST(record_cmd, kernel_address_warning) {
ASSERT_EQ(output.find(warning_msg, pos + warning_msg.size()), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, add_meta_info_option) {
TemporaryFile tmpfile;
ASSERT_TRUE(RunRecordCmd({"--add-meta-info", "key1=value1", "--add-meta-info", "key2=value2"},
@@ -1209,6 +1390,7 @@ TEST(record_cmd, add_meta_info_option) {
ASSERT_FALSE(RunRecordCmd({"--add-meta-info", "=value1"}, tmpfile.path));
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, device_meta_info) {
#if defined(__ANDROID__)
TemporaryFile tmpfile;
@@ -1228,6 +1410,7 @@ TEST(record_cmd, device_meta_info) {
#endif
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, add_counter_option) {
TEST_REQUIRE_HW_COUNTER();
TemporaryFile tmpfile;
@@ -1249,6 +1432,33 @@ TEST(record_cmd, add_counter_option) {
ASSERT_TRUE(has_sample);
}
+// @CddTest = 6.1/C-0-2
TEST(record_cmd, user_buffer_size_option) {
ASSERT_TRUE(RunRecordCmd({"--user-buffer-size", "256M"}));
}
+
+// @CddTest = 6.1/C-0-2
+TEST(record_cmd, record_process_name) {
+ TemporaryFile tmpfile;
+ ASSERT_TRUE(RecordCmd()->Run({"-e", GetDefaultEvent(), "-o", tmpfile.path, "sleep", SLEEP_SEC}));
+ std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
+ ASSERT_TRUE(reader);
+ bool has_comm = false;
+ ASSERT_TRUE(reader->ReadDataSection([&](std::unique_ptr<Record> r) {
+ if (r->type() == PERF_RECORD_COMM) {
+ CommRecord* cr = static_cast<CommRecord*>(r.get());
+ if (strcmp(cr->comm, "sleep") == 0) {
+ has_comm = true;
+ }
+ }
+ return true;
+ }));
+ ASSERT_TRUE(has_comm);
+}
+
+// @CddTest = 6.1/C-0-2
+TEST(record_cmd, delay_option) {
+ TemporaryFile tmpfile;
+ ASSERT_TRUE(RecordCmd()->Run(
+ {"-o", tmpfile.path, "-e", GetDefaultEvent(), "--delay", "100", "sleep", "1"}));
+}
diff --git a/simpleperf/cmd_report.cpp b/simpleperf/cmd_report.cpp
index 2f2dab10..f2fb527f 100644
--- a/simpleperf/cmd_report.cpp
+++ b/simpleperf/cmd_report.cpp
@@ -449,12 +449,11 @@ class ReportCommand : public Command {
" The default sort keys are:\n"
" comm,pid,tid,dso,symbol\n"
"--symfs <dir> Look for files with symbols relative to this directory.\n"
+"--symdir <dir> Look for files with symbols in a directory recursively.\n"
"--vmlinux <file> Parse kernel symbols from <file>.\n"
"\n"
"Sample filter options:\n"
"--comms comm1,comm2,... Report only for threads with selected names.\n"
-"--cpu cpu_item1,cpu_item2,... Report samples on the selected cpus. cpu_item can be cpu\n"
-" number like 1, or cpu range like 0-3.\n"
"--dsos dso1,dso2,... Report only for selected dsos.\n"
"--pids pid1,pid2,... Same as '--include-pid'.\n"
"--symbols symbol1;symbol2;... Report only for selected symbols.\n"
@@ -587,6 +586,7 @@ bool ReportCommand::ParseOptions(const std::vector<std::string>& args) {
{"--sort", {OptionValueType::STRING, OptionType::SINGLE}},
{"--symbols", {OptionValueType::STRING, OptionType::MULTIPLE}},
{"--symfs", {OptionValueType::STRING, OptionType::SINGLE}},
+ {"--symdir", {OptionValueType::STRING, OptionType::SINGLE}},
{"--vmlinux", {OptionValueType::STRING, OptionType::SINGLE}},
};
OptionFormatMap record_filter_options = GetRecordFilterOptionFormats(false);
@@ -694,6 +694,11 @@ bool ReportCommand::ParseOptions(const std::vector<std::string>& args) {
return false;
}
}
+ if (auto value = options.PullValue("--symdir"); value) {
+ if (!Dso::AddSymbolDir(*value->str_value)) {
+ return false;
+ }
+ }
if (auto value = options.PullValue("--vmlinux"); value) {
Dso::SetVmlinux(*value->str_value);
}
@@ -856,9 +861,8 @@ bool ReportCommand::ReadMetaInfoFromRecordFile() {
}
bool ReportCommand::ReadEventAttrFromRecordFile() {
- std::vector<EventAttrWithId> attrs = record_file_reader_->AttrSection();
- for (const auto& attr_with_id : attrs) {
- const perf_event_attr& attr = *attr_with_id.attr;
+ for (const EventAttrWithId& attr_with_id : record_file_reader_->AttrSection()) {
+ const perf_event_attr& attr = attr_with_id.attr;
attr_names_.emplace_back(GetEventNameByAttr(attr));
// There are no samples for events added by --add-counter. So skip them.
@@ -955,7 +959,7 @@ bool ReportCommand::ReadSampleTreeFromRecordFile() {
bool ReportCommand::ProcessRecord(std::unique_ptr<Record> record) {
thread_tree_.Update(*record);
if (record->type() == PERF_RECORD_SAMPLE) {
- if (!record_filter_.Check(static_cast<SampleRecord*>(record.get()))) {
+ if (!record_filter_.Check(static_cast<SampleRecord&>(*record))) {
return true;
}
size_t attr_id = record_file_reader_->GetAttrIndexOfRecord(record.get());
diff --git a/simpleperf/cmd_report_sample.cpp b/simpleperf/cmd_report_sample.cpp
index 1026dbbe..94f799b0 100644
--- a/simpleperf/cmd_report_sample.cpp
+++ b/simpleperf/cmd_report_sample.cpp
@@ -121,6 +121,38 @@ static const char* ProtoUnwindingErrorCodeToString(
}
}
+struct SampleEntry {
+ uint64_t time;
+ uint64_t period;
+ uint32_t event_type_id;
+ bool is_complete_callchain;
+ std::vector<CallChainReportEntry> callchain;
+ std::optional<UnwindingResult> unwinding_result;
+};
+
+struct ThreadId {
+ uint32_t pid;
+ uint32_t tid;
+
+ ThreadId(uint32_t pid, uint32_t tid) : pid(pid), tid(tid) {}
+
+ bool operator==(const ThreadId& other) const { return pid == other.pid && tid == other.tid; }
+};
+
+struct ThreadIdHash {
+ size_t operator()(const ThreadId& thread_id) const noexcept {
+ size_t seed = 0;
+ HashCombine(seed, thread_id.pid);
+ HashCombine(seed, thread_id.tid);
+ return seed;
+ }
+};
+
+struct ThreadData {
+ std::string thread_name;
+ std::queue<SampleEntry> stack_gap_samples;
+};
+
class ReportSampleCommand : public Command {
public:
ReportSampleCommand()
@@ -128,22 +160,25 @@ class ReportSampleCommand : public Command {
"report-sample", "report raw sample information in perf.data",
// clang-format off
"Usage: simpleperf report-sample [options]\n"
-"--dump-protobuf-report <file>\n"
-" Dump report file generated by\n"
-" `simpleperf report-sample --protobuf -o <file>`.\n"
-"-i <file> Specify path of record file, default is perf.data.\n"
-"-o report_file_name Set report file name. Default report file name is\n"
-" report_sample.trace if --protobuf is used, otherwise\n"
-" the report is written to stdout.\n"
-"--proguard-mapping-file <file> Add proguard mapping file to de-obfuscate symbols.\n"
-"--protobuf Use protobuf format in cmd_report_sample.proto to output samples.\n"
-" Need to set a report_file_name when using this option.\n"
-"--show-callchain Print callchain samples.\n"
-"--remove-unknown-kernel-symbols Remove kernel callchains when kernel symbols\n"
-" are not available in perf.data.\n"
-"--show-art-frames Show frames of internal methods in the ART Java interpreter.\n"
-"--show-execution-type Show execution type of a method\n"
-"--symdir <dir> Look for files with symbols in a directory recursively.\n"
+"--dump-protobuf-report <file> Dump report file generated by\n"
+" `simpleperf report-sample --protobuf -o <file>`.\n"
+"-i <file> Specify path of record file, default is perf.data.\n"
+"-o report_file_name Set report file name. When --protobuf is used, default is\n"
+" report_sample.trace. Otherwise, default writes to stdout.\n"
+"--proguard-mapping-file <file> Add proguard mapping file to de-obfuscate symbols.\n"
+"--protobuf Use protobuf format in cmd_report_sample.proto to output\n"
+" samples.\n"
+"--remove-gaps MAX_GAP_LENGTH Ideally all callstacks are complete. But some may be broken\n"
+" for different reasons. To create a smooth view in Stack\n"
+" Chart, remove small gaps of broken callstacks. MAX_GAP_LENGTH\n"
+" is the max length of continuous broken-stack samples we want\n"
+" to remove. Default is 3.\n"
+"--remove-unknown-kernel-symbols Remove kernel callchains when kernel symbols are not\n"
+" available.\n"
+"--show-art-frames Show frames of internal methods in the ART Java interpreter.\n"
+"--show-callchain Show callchain with samples.\n"
+"--show-execution-type Show execution type of a method\n"
+"--symdir <dir> Look for files with symbols in a directory recursively.\n"
"\n"
"Sample filter options:\n"
RECORD_FILTER_OPTION_HELP_MSG_FOR_REPORTING
@@ -172,16 +207,18 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_REPORTING
bool ProcessRecord(std::unique_ptr<Record> record);
void UpdateThreadName(uint32_t pid, uint32_t tid);
bool ProcessSampleRecord(const SampleRecord& r);
- bool PrintSampleRecordInProtobuf(const SampleRecord& record,
- const std::vector<CallChainReportEntry>& entries);
- void AddUnwindingResultInProtobuf(proto::Sample_UnwindingResult* proto_unwinding_result);
+ bool ProcessSample(const ThreadEntry& thread, SampleEntry& sample);
+ bool ReportSample(const ThreadId& thread_id, const SampleEntry& sample, size_t stack_gap_length);
+ bool FinishReportSamples();
+ bool PrintSampleInProtobuf(const ThreadId& thread_id, const SampleEntry& sample);
+ void AddUnwindingResultInProtobuf(const UnwindingResult& unwinding_result,
+ proto::Sample_UnwindingResult* proto_unwinding_result);
bool ProcessSwitchRecord(Record* r);
bool WriteRecordInProtobuf(proto::Record& proto_record);
bool PrintLostSituationInProtobuf();
bool PrintFileInfoInProtobuf();
bool PrintThreadInfoInProtobuf();
- bool PrintSampleRecord(const SampleRecord& record,
- const std::vector<CallChainReportEntry>& entries);
+ bool PrintSample(const ThreadId& thread_id, const SampleEntry& sample);
void PrintLostSituation();
std::string record_filename_;
@@ -201,10 +238,10 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_REPORTING
bool kernel_symbols_available_;
bool show_execution_type_ = false;
CallChainReportBuilder callchain_report_builder_;
- // map from <pid, tid> to thread name
- std::map<uint64_t, const char*> thread_names_;
+ std::unordered_map<ThreadId, ThreadData, ThreadIdHash> per_thread_data_;
std::unique_ptr<UnwindingResultRecord> last_unwinding_result_;
RecordFilter record_filter_;
+ uint32_t max_remove_gap_length_ = 3;
};
bool ReportSampleCommand::Run(const std::vector<std::string>& args) {
@@ -266,6 +303,10 @@ bool ReportSampleCommand::Run(const std::vector<std::string>& args) {
return false;
}
+ if (!FinishReportSamples()) {
+ return false;
+ }
+
if (use_protobuf_) {
if (!PrintLostSituationInProtobuf()) {
return false;
@@ -301,6 +342,7 @@ bool ReportSampleCommand::ParseOptions(const std::vector<std::string>& args) {
{"--proguard-mapping-file", {OptionValueType::STRING, OptionType::MULTIPLE}},
{"--protobuf", {OptionValueType::NONE, OptionType::SINGLE}},
{"--show-callchain", {OptionValueType::NONE, OptionType::SINGLE}},
+ {"--remove-gaps", {OptionValueType::UINT, OptionType::SINGLE}},
{"--remove-unknown-kernel-symbols", {OptionValueType::NONE, OptionType::SINGLE}},
{"--show-art-frames", {OptionValueType::NONE, OptionType::SINGLE}},
{"--show-execution-type", {OptionValueType::NONE, OptionType::SINGLE}},
@@ -323,6 +365,9 @@ bool ReportSampleCommand::ParseOptions(const std::vector<std::string>& args) {
}
use_protobuf_ = options.PullBoolValue("--protobuf");
show_callchain_ = options.PullBoolValue("--show-callchain");
+ if (!options.PullUintValue("--remove-gaps", &max_remove_gap_length_)) {
+ return false;
+ }
remove_unknown_kernel_symbols_ = options.PullBoolValue("--remove-unknown-kernel-symbols");
if (options.PullBoolValue("--show-art-frames")) {
callchain_report_builder_.SetRemoveArtFrame(false);
@@ -522,7 +567,7 @@ bool ReportSampleCommand::OpenRecordFile() {
if (auto it = meta_info.find("trace_offcpu"); it != meta_info.end()) {
trace_offcpu_ = it->second == "true";
if (trace_offcpu_) {
- std::string event_name = GetEventNameByAttr(*record_file_reader_->AttrSection()[0].attr);
+ std::string event_name = GetEventNameByAttr(record_file_reader_->AttrSection()[0].attr);
if (!android::base::StartsWith(event_name, "cpu-clock") &&
!android::base::StartsWith(event_name, "task-clock")) {
LOG(ERROR) << "Recording file " << record_filename_ << " is no longer supported. "
@@ -537,8 +582,8 @@ bool ReportSampleCommand::OpenRecordFile() {
if (!record_filter_.CheckClock(record_file_reader_->GetClockId())) {
return false;
}
- for (EventAttrWithId& attr : record_file_reader_->AttrSection()) {
- event_types_.push_back(GetEventNameByAttr(*attr.attr));
+ for (const EventAttrWithId& attr : record_file_reader_->AttrSection()) {
+ event_types_.push_back(GetEventNameByAttr(attr.attr));
}
return true;
}
@@ -626,8 +671,19 @@ bool ReportSampleCommand::ProcessRecord(std::unique_ptr<Record> record) {
return result;
}
+static bool IsThreadStartPoint(CallChainReportEntry& entry) {
+ // Android studio wants a clear call chain end to notify whether a call chain is complete.
+ // For the main thread, the call chain ends at __libc_init in libc.so. For other threads,
+ // the call chain ends at __start_thread in libc.so.
+ // The call chain of the main thread can go beyond __libc_init, to _start (<= android O) or
+ // _start_main (> android O).
+ return entry.dso->FileName() == "libc.so" &&
+ (strcmp(entry.symbol->Name(), "__libc_init") == 0 ||
+ strcmp(entry.symbol->Name(), "__start_thread") == 0);
+}
+
bool ReportSampleCommand::ProcessSampleRecord(const SampleRecord& r) {
- if (!record_filter_.Check(&r)) {
+ if (!record_filter_.Check(r)) {
return true;
}
size_t kernel_ip_count;
@@ -643,37 +699,101 @@ bool ReportSampleCommand::ProcessSampleRecord(const SampleRecord& r) {
ips.resize(1);
kernel_ip_count = std::min(kernel_ip_count, static_cast<size_t>(1u));
}
- sample_count_++;
const ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
- std::vector<CallChainReportEntry> entries =
+ std::vector<CallChainReportEntry> callchain =
callchain_report_builder_.Build(thread, ips, kernel_ip_count);
- for (size_t i = 1; i < entries.size(); i++) {
- if (thread_tree_.IsUnknownDso(entries[i].dso)) {
- entries.resize(i);
+ bool complete_callchain = false;
+ for (size_t i = 1; i < callchain.size(); i++) {
+ // Stop at unknown callchain.
+ if (thread_tree_.IsUnknownDso(callchain[i].dso)) {
+ callchain.resize(i);
+ break;
+ }
+ // Stop at thread start point. Because Android studio wants a clear call chain end.
+ if (IsThreadStartPoint(callchain[i])) {
+ complete_callchain = true;
+ callchain.resize(i + 1);
break;
}
}
+ SampleEntry sample;
+ sample.time = r.time_data.time;
+ sample.period = r.period_data.period;
+ sample.event_type_id = record_file_reader_->GetAttrIndexOfRecord(&r);
+ sample.is_complete_callchain = complete_callchain;
+ sample.callchain = std::move(callchain);
+ // No need to add unwinding result for callchains fixed by callchain joiner.
+ if (!complete_callchain && last_unwinding_result_) {
+ sample.unwinding_result = last_unwinding_result_->unwinding_result;
+ }
+
+ return ProcessSample(*thread, sample);
+}
+
+bool ReportSampleCommand::ProcessSample(const ThreadEntry& thread, SampleEntry& sample) {
+ ThreadId thread_id(thread.pid, thread.tid);
+ ThreadData& data = per_thread_data_[thread_id];
+ if (data.thread_name != thread.comm) {
+ data.thread_name = thread.comm;
+ }
+
+ // If the sample has incomplete callchain, we push it to stack gap sample queue, to calculate
+ // stack gap length later.
+ if (!sample.is_complete_callchain) {
+ data.stack_gap_samples.push(std::move(sample));
+ return true;
+ }
+ // Otherwise, we can clean up stack gap sample queue and report the sample immediately.
+ size_t gap_length = data.stack_gap_samples.size();
+ while (!data.stack_gap_samples.empty()) {
+ if (!ReportSample(thread_id, data.stack_gap_samples.front(), gap_length)) {
+ return false;
+ }
+ data.stack_gap_samples.pop();
+ }
+ return ReportSample(thread_id, sample, 0);
+}
+
+bool ReportSampleCommand::ReportSample(const ThreadId& thread_id, const SampleEntry& sample,
+ size_t stack_gap_length) {
+ // Remove samples within a stack gap <= max_remove_gap_length_.
+ if (stack_gap_length > 0 && stack_gap_length <= max_remove_gap_length_) {
+ return true;
+ }
+ sample_count_++;
if (use_protobuf_) {
- uint64_t key = (static_cast<uint64_t>(r.tid_data.pid) << 32) | r.tid_data.tid;
- thread_names_[key] = thread->comm;
- return PrintSampleRecordInProtobuf(r, entries);
+ return PrintSampleInProtobuf(thread_id, sample);
}
- return PrintSampleRecord(r, entries);
+ return PrintSample(thread_id, sample);
}
-bool ReportSampleCommand::PrintSampleRecordInProtobuf(
- const SampleRecord& r, const std::vector<CallChainReportEntry>& entries) {
- proto::Record proto_record;
- proto::Sample* sample = proto_record.mutable_sample();
- sample->set_time(r.time_data.time);
- sample->set_event_count(r.period_data.period);
- sample->set_thread_id(r.tid_data.tid);
- sample->set_event_type_id(record_file_reader_->GetAttrIndexOfRecord(&r));
+bool ReportSampleCommand::FinishReportSamples() {
+ for (auto& p : per_thread_data_) {
+ const auto& thread_id = p.first;
+ auto& sample_queue = p.second.stack_gap_samples;
+ size_t gap_length = sample_queue.size();
+ while (!sample_queue.empty()) {
+ if (!ReportSample(thread_id, sample_queue.front(), gap_length)) {
+ return false;
+ }
+ sample_queue.pop();
+ }
+ }
+ return true;
+}
- bool complete_callchain = false;
- for (const auto& node : entries) {
- proto::Sample_CallChainEntry* callchain = sample->add_callchain();
+bool ReportSampleCommand::PrintSampleInProtobuf(const ThreadId& thread_id,
+ const SampleEntry& sample) {
+ proto::Record proto_record;
+ proto::Sample* proto_sample = proto_record.mutable_sample();
+ proto_sample->set_time(sample.time);
+ proto_sample->set_event_count(sample.period);
+ proto_sample->set_thread_id(thread_id.tid);
+ proto_sample->set_event_type_id(sample.event_type_id);
+
+ for (const auto& node : sample.callchain) {
+ proto::Sample_CallChainEntry* callchain = proto_sample->add_callchain();
uint32_t file_id;
if (!node.dso->GetDumpId(&file_id)) {
file_id = node.dso->CreateDumpId();
@@ -690,28 +810,17 @@ bool ReportSampleCommand::PrintSampleRecordInProtobuf(
if (show_execution_type_) {
callchain->set_execution_type(ToProtoExecutionType(node.execution_type));
}
-
- // Android studio wants a clear call chain end to notify whether a call chain is complete.
- // For the main thread, the call chain ends at __libc_init in libc.so. For other threads,
- // the call chain ends at __start_thread in libc.so.
- // The call chain of the main thread can go beyond __libc_init, to _start (<= android O) or
- // _start_main (> android O).
- if (node.dso->FileName() == "libc.so" && (strcmp(node.symbol->Name(), "__libc_init") == 0 ||
- strcmp(node.symbol->Name(), "__start_thread") == 0)) {
- complete_callchain = true;
- break;
- }
}
- // No need to add unwinding result for callchains fixed by callchain joiner.
- if (!complete_callchain && last_unwinding_result_) {
- AddUnwindingResultInProtobuf(sample->mutable_unwinding_result());
+ if (sample.unwinding_result.has_value()) {
+ AddUnwindingResultInProtobuf(sample.unwinding_result.value(),
+ proto_sample->mutable_unwinding_result());
}
return WriteRecordInProtobuf(proto_record);
}
void ReportSampleCommand::AddUnwindingResultInProtobuf(
+ const UnwindingResult& unwinding_result,
proto::Sample_UnwindingResult* proto_unwinding_result) {
- const UnwindingResult& unwinding_result = last_unwinding_result_->unwinding_result;
proto_unwinding_result->set_raw_error_code(unwinding_result.error_code);
proto_unwinding_result->set_error_addr(unwinding_result.error_addr);
proto::Sample_UnwindingResult_ErrorCode error_code;
@@ -840,14 +949,14 @@ bool ReportSampleCommand::PrintFileInfoInProtobuf() {
}
bool ReportSampleCommand::PrintThreadInfoInProtobuf() {
- for (const auto& p : thread_names_) {
- uint32_t pid = p.first >> 32;
- uint32_t tid = p.first & std::numeric_limits<uint32_t>::max();
+ for (const auto& p : per_thread_data_) {
+ const auto& thread_id = p.first;
+ const auto& thread_data = p.second;
proto::Record proto_record;
proto::Thread* proto_thread = proto_record.mutable_thread();
- proto_thread->set_thread_id(tid);
- proto_thread->set_process_id(pid);
- proto_thread->set_thread_name(p.second);
+ proto_thread->set_thread_id(thread_id.tid);
+ proto_thread->set_process_id(thread_id.pid);
+ proto_thread->set_thread_name(thread_data.thread_name);
if (!WriteRecordInProtobuf(proto_record)) {
return false;
}
@@ -855,16 +964,15 @@ bool ReportSampleCommand::PrintThreadInfoInProtobuf() {
return true;
}
-bool ReportSampleCommand::PrintSampleRecord(const SampleRecord& r,
- const std::vector<CallChainReportEntry>& entries) {
+bool ReportSampleCommand::PrintSample(const ThreadId& thread_id, const SampleEntry& sample) {
FprintIndented(report_fp_, 0, "sample:\n");
- FprintIndented(report_fp_, 1, "event_type: %s\n",
- event_types_[record_file_reader_->GetAttrIndexOfRecord(&r)].data());
- FprintIndented(report_fp_, 1, "time: %" PRIu64 "\n", r.time_data.time);
- FprintIndented(report_fp_, 1, "event_count: %" PRIu64 "\n", r.period_data.period);
- FprintIndented(report_fp_, 1, "thread_id: %d\n", r.tid_data.tid);
- const char* thread_name = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid)->comm;
- FprintIndented(report_fp_, 1, "thread_name: %s\n", thread_name);
+ FprintIndented(report_fp_, 1, "event_type: %s\n", event_types_[sample.event_type_id].data());
+ FprintIndented(report_fp_, 1, "time: %" PRIu64 "\n", sample.time);
+ FprintIndented(report_fp_, 1, "event_count: %" PRIu64 "\n", sample.period);
+ FprintIndented(report_fp_, 1, "thread_id: %d\n", thread_id.tid);
+ FprintIndented(report_fp_, 1, "thread_name: %s\n",
+ per_thread_data_[thread_id].thread_name.c_str());
+ const auto& entries = sample.callchain;
CHECK(!entries.empty());
FprintIndented(report_fp_, 1, "vaddr_in_file: %" PRIx64 "\n", entries[0].vaddr_in_file);
FprintIndented(report_fp_, 1, "file: %s\n", entries[0].dso->GetReportPath().data());
diff --git a/simpleperf/cmd_report_sample_test.cpp b/simpleperf/cmd_report_sample_test.cpp
index fc2f9f97..b544f7b5 100644
--- a/simpleperf/cmd_report_sample_test.cpp
+++ b/simpleperf/cmd_report_sample_test.cpp
@@ -29,16 +29,19 @@ static std::unique_ptr<Command> ReportSampleCmd() {
return CreateCommandInstance("report-sample");
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, text) {
ASSERT_TRUE(ReportSampleCmd()->Run({"-i", GetTestData(PERF_DATA_WITH_SYMBOLS)}));
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, output_option) {
TemporaryFile tmpfile;
ASSERT_TRUE(
ReportSampleCmd()->Run({"-i", GetTestData(PERF_DATA_WITH_SYMBOLS), "-o", tmpfile.path}));
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, show_callchain_option) {
TemporaryFile tmpfile;
ASSERT_TRUE(ReportSampleCmd()->Run(
@@ -58,6 +61,7 @@ static void GetProtobufReport(const std::string& test_data_file, std::string* pr
ASSERT_TRUE(android::base::ReadFileToString(tmpfile2.path, protobuf_report));
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, protobuf_option) {
std::string data;
GetProtobufReport(PERF_DATA_WITH_SYMBOLS, &data);
@@ -66,6 +70,7 @@ TEST(cmd_report_sample, protobuf_option) {
ASSERT_NE(data.find("file:"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, no_skipped_file_id) {
std::string data;
GetProtobufReport(PERF_DATA_WITH_WRONG_IP_IN_CALLCHAIN, &data);
@@ -73,12 +78,14 @@ TEST(cmd_report_sample, no_skipped_file_id) {
ASSERT_EQ(data.find("unknown"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, sample_has_event_count) {
std::string data;
GetProtobufReport(PERF_DATA_WITH_SYMBOLS, &data);
ASSERT_NE(data.find("event_count:"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, has_thread_record) {
std::string data;
GetProtobufReport(PERF_DATA_WITH_SYMBOLS, &data);
@@ -86,6 +93,7 @@ TEST(cmd_report_sample, has_thread_record) {
ASSERT_NE(data.find("thread_name: t2"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, trace_offcpu) {
std::string data;
GetProtobufReport("perf_with_trace_offcpu_v2.data", &data);
@@ -101,6 +109,7 @@ TEST(cmd_report_sample, trace_offcpu) {
}
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, have_clear_callchain_end_in_protobuf_output) {
std::string data;
GetProtobufReport("perf_with_trace_offcpu_v2.data", &data, {"--show-callchain"});
@@ -108,6 +117,7 @@ TEST(cmd_report_sample, have_clear_callchain_end_in_protobuf_output) {
ASSERT_EQ(data.find("_start_main"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, app_device_info_in_meta_info) {
std::string data;
GetProtobufReport("perf_with_meta_info.data", &data);
@@ -117,6 +127,7 @@ TEST(cmd_report_sample, app_device_info_in_meta_info) {
ASSERT_NE(data.find("android_build_type: userdebug"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, remove_unknown_kernel_symbols) {
std::string data;
// Test --remove-unknown-kernel-symbols on perf.data with kernel_symbols_available=false.
@@ -148,6 +159,7 @@ TEST(cmd_report_sample, remove_unknown_kernel_symbols) {
ASSERT_NE(data.find("path: /system/lib64/libc.so"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, show_art_frames_option) {
std::string data;
GetProtobufReport(PERF_DATA_WITH_INTERPRETER_FRAMES, &data, {"--show-callchain"});
@@ -157,6 +169,7 @@ TEST(cmd_report_sample, show_art_frames_option) {
ASSERT_NE(data.find("artMterpAsmInstructionStart"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, show_execution_type_option) {
std::string data;
GetProtobufReport("perf_display_bitmaps.data", &data,
@@ -174,6 +187,7 @@ TEST(cmd_report_sample, show_execution_type_option) {
ASSERT_NE(data.find("execution_type: art_method"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, show_symbols_before_and_after_demangle) {
std::string data;
GetProtobufReport(PERF_DATA_WITH_INTERPRETER_FRAMES, &data, {"--show-callchain"});
@@ -183,6 +197,7 @@ TEST(cmd_report_sample, show_symbols_before_and_after_demangle) {
std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, symdir_option) {
std::string data;
GetProtobufReport(PERF_DATA_FOR_BUILD_ID_CHECK, &data);
@@ -192,6 +207,7 @@ TEST(cmd_report_sample, symdir_option) {
ASSERT_NE(data.find("symbol: main"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, show_art_jni_methods) {
std::string data;
GetProtobufReport("perf_display_bitmaps.data", &data, {"--show-callchain"});
@@ -200,30 +216,35 @@ TEST(cmd_report_sample, show_art_jni_methods) {
ASSERT_EQ(data.find("art_jni_trampoline"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, show_unwinding_result) {
std::string data;
- GetProtobufReport("perf_with_failed_unwinding_debug_info.data", &data, {"--show-callchain"});
+ GetProtobufReport("perf_with_failed_unwinding_debug_info.data", &data,
+ {"--show-callchain", "--remove-gaps", "0"});
ASSERT_NE(data.find("error_code: ERROR_INVALID_MAP"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, proguard_mapping_file_option) {
std::string data;
// Symbols aren't de-obfuscated without proguard mapping file.
- GetProtobufReport("perf_need_proguard_mapping.data", &data, {"--show-callchain"});
+ GetProtobufReport("perf_need_proguard_mapping.data", &data,
+ {"--show-callchain", "--remove-gaps", "0"});
ASSERT_EQ(data.find("androidx.fragment.app.FragmentActivity.startActivityForResult"),
std::string::npos);
ASSERT_EQ(data.find("com.example.android.displayingbitmaps.ui.ImageGridFragment.onItemClick"),
std::string::npos);
// Symbols are de-obfuscated with proguard mapping file.
- GetProtobufReport(
- "perf_need_proguard_mapping.data", &data,
- {"--show-callchain", "--proguard-mapping-file", GetTestData("proguard_mapping.txt")});
+ GetProtobufReport("perf_need_proguard_mapping.data", &data,
+ {"--show-callchain", "--proguard-mapping-file",
+ GetTestData("proguard_mapping.txt"), "--remove-gaps", "0"});
ASSERT_NE(data.find("androidx.fragment.app.FragmentActivity.startActivityForResult"),
std::string::npos);
ASSERT_NE(data.find("com.example.android.displayingbitmaps.ui.ImageGridFragment.onItemClick"),
std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, exclude_include_pid_options) {
std::string data;
GetProtobufReport("perf_display_bitmaps.data", &data, {"--exclude-pid", "31850"});
@@ -233,6 +254,7 @@ TEST(cmd_report_sample, exclude_include_pid_options) {
ASSERT_NE(data.find("thread_id: 31850"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, exclude_include_tid_options) {
std::string data;
GetProtobufReport("perf_display_bitmaps.data", &data, {"--exclude-tid", "31881"});
@@ -242,6 +264,7 @@ TEST(cmd_report_sample, exclude_include_tid_options) {
ASSERT_NE(data.find("thread_id: 31881"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, exclude_include_process_name_options) {
std::string data;
GetProtobufReport("perf_display_bitmaps.data", &data,
@@ -253,6 +276,7 @@ TEST(cmd_report_sample, exclude_include_process_name_options) {
ASSERT_NE(data.find("thread_id: 31881"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, exclude_include_thread_name_options) {
std::string data;
GetProtobufReport("perf_display_bitmaps.data", &data,
@@ -264,6 +288,7 @@ TEST(cmd_report_sample, exclude_include_thread_name_options) {
ASSERT_NE(data.find("thread_id: 31850"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(cmd_report_sample, filter_file_option) {
std::string filter_data =
"GLOBAL_BEGIN 684943449406175\n"
@@ -275,3 +300,26 @@ TEST(cmd_report_sample, filter_file_option) {
ASSERT_NE(data.find("thread_id: 31881"), std::string::npos);
ASSERT_EQ(data.find("thread_id: 31850"), std::string::npos);
}
+
+// @CddTest = 6.1/C-0-2
+TEST(cmd_report_sample, remove_gaps_option) {
+ auto get_sample_count = [](const std::string& s) {
+ size_t count = 0;
+ ssize_t pos = 0;
+ while ((pos = s.find("\nsample ", pos)) != s.npos) {
+ ++count;
+ pos += strlen("\nsample ");
+ }
+ return count;
+ };
+
+ std::string data;
+ // By default, `--remove-gaps 3` is used.
+ GetProtobufReport("perf_display_bitmaps.data", &data, {"--show-callchain"});
+ ASSERT_EQ(get_sample_count(data), 517);
+ ASSERT_NE(data.find("sample_count: 517"), std::string::npos) << data;
+ // We can disable removing gaps by using `--remove-gaps 0`.
+ GetProtobufReport("perf_display_bitmaps.data", &data, {"--show-callchain", "--remove-gaps", "0"});
+ ASSERT_EQ(get_sample_count(data), 525);
+ ASSERT_NE(data.find("sample_count: 525"), std::string::npos);
+}
diff --git a/simpleperf/cmd_report_test.cpp b/simpleperf/cmd_report_test.cpp
index edca413f..9170beb2 100644
--- a/simpleperf/cmd_report_test.cpp
+++ b/simpleperf/cmd_report_test.cpp
@@ -37,19 +37,27 @@ static std::unique_ptr<Command> ReportCmd() {
return CreateCommandInstance("report");
}
+// @CddTest = 6.1/C-0-2
class ReportCommandTest : public ::testing::Test {
protected:
void Report(const std::string& perf_data,
- const std::vector<std::string>& add_args = std::vector<std::string>()) {
- ReportRaw(GetTestData(perf_data), add_args);
+ const std::vector<std::string>& add_args = std::vector<std::string>(),
+ bool with_symfs = true) {
+ ReportRaw(GetTestData(perf_data), add_args, with_symfs);
}
void ReportRaw(const std::string& perf_data,
- const std::vector<std::string>& add_args = std::vector<std::string>()) {
+ const std::vector<std::string>& add_args = std::vector<std::string>(),
+ bool with_symfs = true) {
success = false;
TemporaryFile tmp_file;
- std::vector<std::string> args = {"-i", perf_data, "--symfs", GetTestDataDir(),
- "-o", tmp_file.path};
+ std::vector<std::string> args = {"-i", perf_data, "-o", tmp_file.path};
+
+ if (with_symfs) {
+ args.emplace_back("--symfs");
+ args.emplace_back(GetTestDataDir());
+ }
+
args.insert(args.end(), add_args.begin(), add_args.end());
ASSERT_TRUE(ReportCmd()->Run(args));
ASSERT_TRUE(android::base::ReadFileToString(tmp_file.path, &content));
@@ -83,18 +91,21 @@ class ReportCommandTest : public ::testing::Test {
bool success;
};
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, no_option) {
Report(PERF_DATA);
ASSERT_TRUE(success);
ASSERT_NE(content.find("GlobalFunc"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, report_symbol_from_elf_file_with_mini_debug_info) {
Report(PERF_DATA_WITH_MINI_DEBUG_INFO);
ASSERT_TRUE(success);
ASSERT_NE(content.find("GlobalFunc"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, sort_option_pid) {
Report(PERF_DATA, {"--sort", "pid"});
ASSERT_TRUE(success);
@@ -105,6 +116,7 @@ TEST_F(ReportCommandTest, sort_option_pid) {
ASSERT_LT(line_index + 2, lines.size());
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, sort_option_more_than_one) {
Report(PERF_DATA, {"--sort", "comm,pid,dso,symbol"});
ASSERT_TRUE(success);
@@ -120,6 +132,7 @@ TEST_F(ReportCommandTest, sort_option_more_than_one) {
ASSERT_EQ(lines[line_index].find("Tid"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, children_option) {
Report(CALLGRAPH_FP_PERF_DATA, {"--children", "--sort", "symbol"});
ASSERT_TRUE(success);
@@ -164,6 +177,7 @@ static bool CheckCallerMode(std::vector<std::string>& lines) {
return found;
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, callgraph_option) {
Report(CALLGRAPH_FP_PERF_DATA, {"-g"});
ASSERT_TRUE(success);
@@ -201,6 +215,7 @@ static bool AllItemsWithString(std::vector<std::string>& lines,
return true;
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, pid_filter_option) {
Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--sort", "pid"});
ASSERT_TRUE(success);
@@ -221,6 +236,7 @@ TEST_F(ReportCommandTest, pid_filter_option) {
ASSERT_NE(content.find("17445"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, wrong_pid_filter_option) {
ASSERT_EXIT(
{
@@ -230,6 +246,7 @@ TEST_F(ReportCommandTest, wrong_pid_filter_option) {
testing::ExitedWithCode(1), "invalid pid: bogus");
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, tid_filter_option) {
Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--sort", "tid"});
ASSERT_TRUE(success);
@@ -243,6 +260,7 @@ TEST_F(ReportCommandTest, tid_filter_option) {
ASSERT_TRUE(AllItemsWithString(lines, {"17441", "17445"}));
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, wrong_tid_filter_option) {
ASSERT_EXIT(
{
@@ -252,6 +270,7 @@ TEST_F(ReportCommandTest, wrong_tid_filter_option) {
testing::ExitedWithCode(1), "Invalid tid 'bogus'");
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, comm_filter_option) {
Report(PERF_DATA, {"--sort", "comm"});
ASSERT_TRUE(success);
@@ -265,6 +284,7 @@ TEST_F(ReportCommandTest, comm_filter_option) {
ASSERT_TRUE(AllItemsWithString(lines, {"t1", "t2"}));
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, dso_filter_option) {
Report(PERF_DATA, {"--sort", "dso"});
ASSERT_TRUE(success);
@@ -278,6 +298,7 @@ TEST_F(ReportCommandTest, dso_filter_option) {
ASSERT_TRUE(AllItemsWithString(lines, {"/t1", "/t2"}));
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, symbol_filter_option) {
Report(PERF_DATA_WITH_SYMBOLS, {"--sort", "symbol"});
ASSERT_TRUE(success);
@@ -291,6 +312,7 @@ TEST_F(ReportCommandTest, symbol_filter_option) {
ASSERT_TRUE(AllItemsWithString(lines, {"main", "func2(int, int)"}));
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, dso_symbol_filter_with_children_option) {
// dso and symbol filter should filter different layers of the callchain separately.
Report("perf_display_bitmaps.data", {"--dsos", "/apex/com.android.runtime/lib64/libart.so",
@@ -305,6 +327,7 @@ TEST_F(ReportCommandTest, dso_symbol_filter_with_children_option) {
ASSERT_NE(content.find("51500000 2500000 MterpInvokeVirtual"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, use_branch_address) {
Report(BRANCH_PERF_DATA, {"-b", "--sort", "symbol_from,symbol_to"});
std::set<std::pair<std::string, std::string>> hit_set;
@@ -326,6 +349,7 @@ TEST_F(ReportCommandTest, use_branch_address) {
hit_set.end());
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, report_symbols_of_nativelib_in_apk) {
Report(NATIVELIB_IN_APK_PERF_DATA);
ASSERT_TRUE(success);
@@ -333,6 +357,7 @@ TEST_F(ReportCommandTest, report_symbols_of_nativelib_in_apk) {
ASSERT_NE(content.find("Func2"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, report_more_than_one_event_types) {
Report(PERF_DATA_WITH_TWO_EVENT_TYPES);
ASSERT_TRUE(success);
@@ -343,12 +368,14 @@ TEST_F(ReportCommandTest, report_more_than_one_event_types) {
ASSERT_NE(pos = content.find("Samples:", pos), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, report_kernel_symbol) {
Report(PERF_DATA_WITH_KERNEL_SYMBOL);
ASSERT_TRUE(success);
ASSERT_NE(content.find("perf_event_aux"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, report_dumped_symbols) {
Report(PERF_DATA_WITH_SYMBOLS);
ASSERT_TRUE(success);
@@ -358,6 +385,7 @@ TEST_F(ReportCommandTest, report_dumped_symbols) {
ASSERT_NE(content.find("memcpy"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, report_dumped_symbols_with_symfs_dir) {
// Check if we can report symbols when they appear both in perf.data and symfs dir.
Report(PERF_DATA_WITH_SYMBOLS, {"--symfs", GetTestDataDir()});
@@ -365,17 +393,28 @@ TEST_F(ReportCommandTest, report_dumped_symbols_with_symfs_dir) {
ASSERT_NE(content.find("main"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
+TEST_F(ReportCommandTest, report_dumped_symbols_with_symdir) {
+ // Check if we can report symbols by specifying symdir.
+ Report(PERF_DATA, {"--symdir", GetTestDataDir()}, false);
+ ASSERT_TRUE(success);
+ ASSERT_NE(content.find("GlobalFunc"), std::string::npos);
+}
+
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, report_without_symfs_dir) {
TemporaryFile tmpfile;
ASSERT_TRUE(ReportCmd()->Run({"-i", GetTestData(PERF_DATA), "-o", tmpfile.path}));
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, report_sort_vaddr_in_file) {
Report(PERF_DATA, {"--sort", "vaddr_in_file"});
ASSERT_TRUE(success);
ASSERT_NE(content.find("VaddrInFile"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, check_build_id) {
Report(PERF_DATA_FOR_BUILD_ID_CHECK, {"--symfs", GetTestData(CORRECT_SYMFS_FOR_BUILD_ID_CHECK)});
ASSERT_TRUE(success);
@@ -395,6 +434,7 @@ TEST_F(ReportCommandTest, check_build_id) {
testing::ExitedWithCode(0), "failed to read symbols from /elf_for_build_id_check");
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, no_show_ip_option) {
Report(PERF_DATA);
ASSERT_TRUE(success);
@@ -404,21 +444,7 @@ TEST_F(ReportCommandTest, no_show_ip_option) {
ASSERT_NE(content.find("unknown"), std::string::npos);
}
-TEST_F(ReportCommandTest, no_symbol_table_warning) {
- ASSERT_EXIT(
- {
- Report(PERF_DATA, {"--symfs", GetTestData(SYMFS_FOR_NO_SYMBOL_TABLE_WARNING)});
- if (!success) {
- exit(1);
- }
- if (content.find("GlobalFunc") != std::string::npos) {
- exit(2);
- }
- exit(0);
- },
- testing::ExitedWithCode(0), "elf doesn't contain symbol table");
-}
-
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, read_elf_file_warning) {
ASSERT_EXIT(
{
@@ -434,11 +460,13 @@ TEST_F(ReportCommandTest, read_elf_file_warning) {
testing::ExitedWithCode(0), "failed to read symbols from /elf: File not found");
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, report_data_generated_by_linux_perf) {
Report(PERF_DATA_GENERATED_BY_LINUX_PERF);
ASSERT_TRUE(success);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, max_stack_and_percent_limit_option) {
Report(PERF_DATA_MAX_STACK_AND_PERCENT_LIMIT, {"-g"});
ASSERT_TRUE(success);
@@ -459,6 +487,7 @@ TEST_F(ReportCommandTest, max_stack_and_percent_limit_option) {
ASSERT_NE(content.find("89.03"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, percent_limit_option) {
Report(PERF_DATA);
ASSERT_TRUE(success);
@@ -470,16 +499,19 @@ TEST_F(ReportCommandTest, percent_limit_option) {
ASSERT_EQ(content.find("3.23%"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, kallsyms_option) {
Report(PERF_DATA, {"--kallsyms", GetTestData("kallsyms")});
ASSERT_TRUE(success);
ASSERT_NE(content.find("FakeKernelSymbol"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, invalid_perf_data) {
ASSERT_FALSE(ReportCmd()->Run({"-i", GetTestData(INVALID_PERF_DATA)}));
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, raw_period_option) {
Report(PERF_DATA, {"--raw-period"});
ASSERT_TRUE(success);
@@ -487,6 +519,7 @@ TEST_F(ReportCommandTest, raw_period_option) {
ASSERT_EQ(content.find('%'), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, full_callgraph_option) {
Report(CALLGRAPH_FP_PERF_DATA, {"-g"});
ASSERT_TRUE(success);
@@ -496,6 +529,7 @@ TEST_F(ReportCommandTest, full_callgraph_option) {
ASSERT_EQ(content.find("skipped in brief callgraph mode"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, report_offcpu_time) {
Report(PERF_DATA_WITH_TRACE_OFFCPU, {"--children"});
ASSERT_TRUE(success);
@@ -511,11 +545,13 @@ TEST_F(ReportCommandTest, report_offcpu_time) {
ASSERT_TRUE(found);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, report_big_trace_data) {
Report(PERF_DATA_WITH_BIG_TRACE_DATA);
ASSERT_TRUE(success);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, csv_option) {
Report(PERF_DATA, {"--csv"});
ASSERT_TRUE(success);
@@ -526,6 +562,7 @@ TEST_F(ReportCommandTest, csv_option) {
ASSERT_NE(content.find("AccEventCount,SelfEventCount,EventName"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, csv_separator_option) {
Report(PERF_DATA, {"--csv", "--csv-separator", ";"});
ASSERT_TRUE(success);
@@ -533,6 +570,7 @@ TEST_F(ReportCommandTest, csv_separator_option) {
ASSERT_NE(content.find(";cpu-cycles"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, dso_path_for_jit_cache) {
Report("perf_with_jit_symbol.data", {"--sort", "dso"});
ASSERT_TRUE(success);
@@ -544,12 +582,14 @@ TEST_F(ReportCommandTest, dso_path_for_jit_cache) {
ASSERT_NE(content.find("[JIT app cache]"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, generic_jit_symbols) {
Report("perf_with_generic_git_symbols.data", {"--sort", "symbol"});
ASSERT_TRUE(success);
ASSERT_NE(std::string::npos, content.find("generic_jit_symbol_one"));
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, cpu_option) {
Report("perf.data");
ASSERT_TRUE(success);
@@ -566,6 +606,7 @@ TEST_F(ReportCommandTest, cpu_option) {
ASSERT_FALSE(ReportCmd()->Run({"-i", GetTestData("perf.data"), "--cpu", "-2"}));
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, print_event_count_option) {
// Report record file not recorded with --add-counter.
Report("perf.data", {"--print-event-count"});
@@ -598,6 +639,7 @@ TEST_F(ReportCommandTest, print_event_count_option) {
->Search(content));
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, exclude_include_pid_options) {
Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--sort", "pid", "--exclude-pid", "17441"});
ASSERT_TRUE(success);
@@ -607,6 +649,7 @@ TEST_F(ReportCommandTest, exclude_include_pid_options) {
ASSERT_TRUE(AllItemsWithString(lines, {"17441"}));
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, exclude_include_tid_options) {
Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS,
{"--sort", "tid", "--exclude-tid", "17441,17443,17444"});
@@ -618,6 +661,7 @@ TEST_F(ReportCommandTest, exclude_include_tid_options) {
ASSERT_TRUE(AllItemsWithString(lines, {"17441", "17443", "17444"}));
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, exclude_include_process_name_options) {
Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--sort", "comm", "--exclude-process-name", "t1"});
ASSERT_TRUE(success);
@@ -627,6 +671,7 @@ TEST_F(ReportCommandTest, exclude_include_process_name_options) {
ASSERT_TRUE(AllItemsWithString(lines, {"t1"}));
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, exclude_include_thread_name_options) {
Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--sort", "comm", "--exclude-thread-name", "t1"});
ASSERT_TRUE(success);
@@ -636,6 +681,7 @@ TEST_F(ReportCommandTest, exclude_include_thread_name_options) {
ASSERT_TRUE(AllItemsWithString(lines, {"t1"}));
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, filter_file_option) {
std::string filter_data =
"GLOBAL_BEGIN 684943449406175\n"
@@ -660,6 +706,7 @@ static std::unique_ptr<Command> RecordCmd() {
return CreateCommandInstance("record");
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, dwarf_callgraph) {
TEST_REQUIRE_HW_COUNTER();
OMIT_TEST_ON_NON_NATIVE_ABIS();
@@ -673,6 +720,7 @@ TEST_F(ReportCommandTest, dwarf_callgraph) {
ASSERT_TRUE(success);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, report_dwarf_callgraph_of_nativelib_in_apk) {
Report(NATIVELIB_IN_APK_PERF_DATA, {"-g"});
ASSERT_NE(content.find(GetUrlInApk(APK_FILE, NATIVELIB_IN_APK)), std::string::npos);
@@ -681,6 +729,7 @@ TEST_F(ReportCommandTest, report_dwarf_callgraph_of_nativelib_in_apk) {
ASSERT_NE(content.find("GlobalFunc"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ReportCommandTest, exclude_kernel_callchain) {
TEST_REQUIRE_HW_COUNTER();
TEST_REQUIRE_HOST_ROOT();
diff --git a/simpleperf/cmd_stat.cpp b/simpleperf/cmd_stat.cpp
index 009afc3c..19fb4891 100644
--- a/simpleperf/cmd_stat.cpp
+++ b/simpleperf/cmd_stat.cpp
@@ -34,6 +34,7 @@
#include <android-base/unique_fd.h>
#include "IOEventLoop.h"
+#include "ProbeEvents.h"
#include "cmd_stat_impl.h"
#include "command.h"
#include "environment.h"
@@ -91,6 +92,18 @@ static const std::unordered_map<std::string_view, std::pair<std::string_view, st
{"raw-l2d-tlb-refill-rd", {"raw-l2d-tlb-rd", "level 2 data TLB refill rate, read"}},
};
+std::string CounterSummary::ReadableCountValue(bool csv) {
+ if (type_name == "cpu-clock" || type_name == "task-clock") {
+ // Convert nanoseconds to milliseconds.
+ double value = count / 1e6;
+ return android::base::StringPrintf("%lf(ms)", value);
+ }
+ if (csv) {
+ return android::base::StringPrintf("%" PRIu64, count);
+ }
+ return ReadableCount(count);
+}
+
const CounterSummary* CounterSummaries::FindSummary(const std::string& type_name,
const std::string& modifier,
const ThreadInfo* thread, int cpu) {
@@ -350,9 +363,9 @@ class StatCommand : public Command {
" On non-rooted devices, the app must be debuggable,\n"
" because we use run-as to switch to the app's context.\n"
#endif
-"--cpu cpu_item1,cpu_item2,...\n"
-" Collect information only on the selected cpus. cpu_item can\n"
-" be a cpu number like 1, or a cpu range like 0-3.\n"
+"--cpu cpu_item1,cpu_item2,... Monitor events on selected cpus. cpu_item can be a number like\n"
+" 1, or a range like 0-3. A --cpu option affects all event types\n"
+" following it until meeting another --cpu option.\n"
"--csv Write report in comma separate form.\n"
"--duration time_in_sec Monitor for time_in_sec seconds instead of running\n"
" [command]. Here time_in_sec may be any positive\n"
@@ -376,6 +389,11 @@ class StatCommand : public Command {
" Similar to -e option. But events specified in the same --group\n"
" option are monitored as a group, and scheduled in and out at the\n"
" same time.\n"
+"--kprobe kprobe_event1,kprobe_event2,...\n"
+" Add kprobe events during stating. The kprobe_event format is in\n"
+" Documentation/trace/kprobetrace.rst in the kernel. Examples:\n"
+" 'p:myprobe do_sys_openat2 $arg2:string' - add event kprobes:myprobe\n"
+" 'r:myretprobe do_sys_openat2 $retval:s64' - add event kprobes:myretprobe\n"
"--no-inherit Don't stat created child threads/processes.\n"
"-o output_filename Write report to output_filename instead of standard output.\n"
"--per-core Print counters for each cpu core.\n"
@@ -384,6 +402,9 @@ class StatCommand : public Command {
" Stat events on existing processes. Processes are searched either by pid\n"
" or process name regex. Mutually exclusive with -a.\n"
"-t tid1,tid2,... Stat events on existing threads. Mutually exclusive with -a.\n"
+"--tp-filter filter_string Set filter_string for the previous tracepoint event.\n"
+" Format is in Documentation/trace/events.rst in the kernel.\n"
+" An example: 'prev_comm != \"simpleperf\" && (prev_pid > 1)'.\n"
"--print-hw-counter Test and print CPU PMU hardware counters available on the device.\n"
"--sort key1,key2,... Select keys used to sort the report, used when --per-thread\n"
" or --per-core appears. The appearance order of keys decides\n"
@@ -433,8 +454,8 @@ class StatCommand : public Command {
bool Run(const std::vector<std::string>& args);
private:
- bool ParseOptions(const std::vector<std::string>& args,
- std::vector<std::string>* non_option_args);
+ bool ParseOptions(const std::vector<std::string>& args, std::vector<std::string>* non_option_args,
+ ProbeEvents& probe_events);
void PrintHardwareCounters();
bool AddDefaultMeasuredEventTypes();
void SetEventSelectionFlags();
@@ -451,7 +472,6 @@ class StatCommand : public Command {
double interval_in_ms_;
bool interval_only_values_;
std::vector<std::vector<CounterSum>> last_sum_values_;
- std::vector<int> cpus_;
EventSelectionSet event_selection_set_;
std::string output_filename_;
android::base::unique_fd out_fd_;
@@ -479,7 +499,8 @@ bool StatCommand::Run(const std::vector<std::string>& args) {
// 1. Parse options, and use default measured event types if not given.
std::vector<std::string> workload_args;
- if (!ParseOptions(args, &workload_args)) {
+ ProbeEvents probe_events(event_selection_set_);
+ if (!ParseOptions(args, &workload_args, probe_events)) {
return false;
}
if (print_hw_counter_) {
@@ -523,7 +544,7 @@ bool StatCommand::Run(const std::vector<std::string>& args) {
} else if (!event_selection_set_.HasMonitoredTarget()) {
if (workload != nullptr) {
event_selection_set_.AddMonitoredProcesses({workload->GetPid()});
- event_selection_set_.SetEnableOnExec(true);
+ event_selection_set_.SetEnableCondition(false, true);
} else if (!app_package_name_.empty()) {
std::set<pid_t> pids = WaitForAppProcesses(app_package_name_);
event_selection_set_.AddMonitoredProcesses(pids);
@@ -540,7 +561,7 @@ bool StatCommand::Run(const std::vector<std::string>& args) {
}
// 3. Open perf_event_files and output file if defined.
- if (!event_selection_set_.OpenEventFiles(cpus_)) {
+ if (!event_selection_set_.OpenEventFiles()) {
return false;
}
std::unique_ptr<FILE, decltype(&fclose)> fp_holder(nullptr, fclose);
@@ -561,11 +582,6 @@ bool StatCommand::Run(const std::vector<std::string>& args) {
// 4. Add signal/periodic Events.
IOEventLoop* loop = event_selection_set_.GetIOEventLoop();
- if (interval_in_ms_ != 0) {
- if (!loop->UsePreciseTimer()) {
- return false;
- }
- }
std::chrono::time_point<std::chrono::steady_clock> start_time;
std::vector<CountersInfo> counters;
if (need_to_check_targets && !event_selection_set_.StopWhenNoMoreTargets()) {
@@ -632,7 +648,8 @@ bool StatCommand::Run(const std::vector<std::string>& args) {
}
bool StatCommand::ParseOptions(const std::vector<std::string>& args,
- std::vector<std::string>* non_option_args) {
+ std::vector<std::string>* non_option_args,
+ ProbeEvents& probe_events) {
OptionValueMap options;
std::vector<std::pair<OptionName, OptionValue>> ordered_options;
@@ -647,14 +664,6 @@ bool StatCommand::ParseOptions(const std::vector<std::string>& args,
if (auto value = options.PullValue("--app"); value) {
app_package_name_ = *value->str_value;
}
- if (auto value = options.PullValue("--cpu"); value) {
- if (auto cpus = GetCpusFromString(*value->str_value); cpus) {
- cpus_.assign(cpus->begin(), cpus->end());
- } else {
- return false;
- }
- }
-
csv_ = options.PullBoolValue("--csv");
if (!options.PullDoubleValue("--duration", &duration_in_sec_, 1e-9)) {
@@ -665,21 +674,14 @@ bool StatCommand::ParseOptions(const std::vector<std::string>& args,
}
interval_only_values_ = options.PullBoolValue("--interval-only-values");
- for (const OptionValue& value : options.PullValues("-e")) {
- for (const auto& event_type : Split(*value.str_value, ",")) {
- if (!event_selection_set_.AddEventType(event_type)) {
+ in_app_context_ = options.PullBoolValue("--in-app");
+ for (const OptionValue& value : options.PullValues("--kprobe")) {
+ for (const auto& cmd : Split(*value.str_value, ",")) {
+ if (!probe_events.AddKprobe(cmd)) {
return false;
}
}
}
-
- for (const OptionValue& value : options.PullValues("--group")) {
- if (!event_selection_set_.AddEventGroup(Split(*value.str_value, ","))) {
- return false;
- }
- }
-
- in_app_context_ = options.PullBoolValue("--in-app");
child_inherit_ = !options.PullBoolValue("--no-inherit");
if (auto value = options.PullValue("-o"); value) {
@@ -727,7 +729,47 @@ bool StatCommand::ParseOptions(const std::vector<std::string>& args,
verbose_mode_ = options.PullBoolValue("--verbose");
CHECK(options.values.empty());
- CHECK(ordered_options.empty());
+
+ // Process ordered options.
+ for (const auto& pair : ordered_options) {
+ const OptionName& name = pair.first;
+ const OptionValue& value = pair.second;
+
+ if (name == "--cpu") {
+ if (auto v = GetCpusFromString(*value.str_value); v) {
+ std::set<int>& cpus = v.value();
+ event_selection_set_.SetCpusForNewEvents(std::vector<int>(cpus.begin(), cpus.end()));
+ } else {
+ return false;
+ }
+ } else if (name == "-e") {
+ for (const auto& event_type : Split(*value.str_value, ",")) {
+ if (!probe_events.CreateProbeEventIfNotExist(event_type)) {
+ return false;
+ }
+ if (!event_selection_set_.AddEventType(event_type)) {
+ return false;
+ }
+ }
+ } else if (name == "--group") {
+ std::vector<std::string> event_types = Split(*value.str_value, ",");
+ for (const auto& event_type : event_types) {
+ if (!probe_events.CreateProbeEventIfNotExist(event_type)) {
+ return false;
+ }
+ }
+ if (!event_selection_set_.AddEventGroup(event_types)) {
+ return false;
+ }
+ } else if (name == "--tp-filter") {
+ if (!event_selection_set_.SetTracepointFilter(*value.str_value)) {
+ return false;
+ }
+ } else {
+ LOG(ERROR) << "unprocessed option: " << name;
+ return false;
+ }
+ }
if (system_wide_collection_ && event_selection_set_.HasMonitoredTarget()) {
LOG(ERROR) << "Stat system wide and existing processes/threads can't be "
@@ -749,6 +791,9 @@ bool StatCommand::ParseOptions(const std::vector<std::string>& args,
}
std::optional<bool> CheckHardwareCountersOnCpu(int cpu, size_t counters) {
+ if (counters == 0) {
+ return true;
+ }
const EventType* event = FindEventTypeByName("cpu-cycles", true);
if (event == nullptr) {
return std::nullopt;
@@ -929,20 +974,7 @@ bool StatCommand::ShowCounters(const std::vector<CountersInfo>& counters, double
}
void StatCommand::CheckHardwareCounterMultiplexing() {
- size_t hardware_events = 0;
- for (const EventType* event : event_selection_set_.GetEvents()) {
- if (event->IsHardwareEvent()) {
- hardware_events++;
- }
- }
- if (hardware_events == 0) {
- return;
- }
- std::vector<int> cpus = cpus_;
- if (cpus.empty()) {
- cpus = GetOnlineCpus();
- }
- for (int cpu : cpus) {
+ for (const auto& [cpu, hardware_events] : event_selection_set_.GetHardwareCountersForCpus()) {
std::optional<bool> result = CheckHardwareCountersOnCpu(cpu, hardware_events);
if (result.has_value() && !result.value()) {
LOG(WARNING) << "It seems the number of hardware events are more than the number of\n"
diff --git a/simpleperf/cmd_stat_impl.h b/simpleperf/cmd_stat_impl.h
index 515412bf..a7397477 100644
--- a/simpleperf/cmd_stat_impl.h
+++ b/simpleperf/cmd_stat_impl.h
@@ -131,28 +131,7 @@ struct CounterSummary {
}
private:
- std::string ReadableCountValue(bool csv) {
- if (type_name == "cpu-clock" || type_name == "task-clock") {
- // Convert nanoseconds to milliseconds.
- double value = count / 1e6;
- return android::base::StringPrintf("%lf(ms)", value);
- } else {
- // Convert big numbers to human friendly mode. For example,
- // 1000000 will be converted to 1,000,000.
- std::string s = android::base::StringPrintf("%" PRIu64, count);
- if (csv) {
- return s;
- } else {
- for (size_t i = s.size() - 1, j = 1; i > 0; --i, ++j) {
- if (j == 3) {
- s.insert(s.begin() + i, ',');
- j = 0;
- }
- }
- return s;
- }
- }
- }
+ std::string ReadableCountValue(bool csv);
};
BUILD_COMPARE_VALUE_FUNCTION_REVERSE(CompareSummaryCount, count);
@@ -328,15 +307,16 @@ inline const OptionFormatMap& GetStatCmdOptionFormats() {
static const OptionFormatMap option_formats = {
{"-a", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::NOT_ALLOWED}},
{"--app", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::NOT_ALLOWED}},
- {"--cpu", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--cpu", {OptionValueType::STRING, OptionType::ORDERED, AppRunnerType::ALLOWED}},
{"--csv", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
{"--duration", {OptionValueType::DOUBLE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
{"--interval", {OptionValueType::DOUBLE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
{"--interval-only-values",
{OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
- {"-e", {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}},
- {"--group", {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}},
+ {"-e", {OptionValueType::STRING, OptionType::ORDERED, AppRunnerType::ALLOWED}},
+ {"--group", {OptionValueType::STRING, OptionType::ORDERED, AppRunnerType::ALLOWED}},
{"--in-app", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--kprobe", {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::NOT_ALLOWED}},
{"--no-inherit", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
{"-o", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::NOT_ALLOWED}},
{"--out-fd", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::CHECK_FD}},
@@ -347,6 +327,7 @@ inline const OptionFormatMap& GetStatCmdOptionFormats() {
{"--sort", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::ALLOWED}},
{"--stop-signal-fd", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::CHECK_FD}},
{"-t", {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}},
+ {"--tp-filter", {OptionValueType::STRING, OptionType::ORDERED, AppRunnerType::ALLOWED}},
{"--tracepoint-events",
{OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::CHECK_PATH}},
{"--use-devfreq-counters",
diff --git a/simpleperf/cmd_stat_test.cpp b/simpleperf/cmd_stat_test.cpp
index dff42a21..c47ad729 100644
--- a/simpleperf/cmd_stat_test.cpp
+++ b/simpleperf/cmd_stat_test.cpp
@@ -22,6 +22,7 @@
#include <thread>
+#include "ProbeEvents.h"
#include "cmd_stat_impl.h"
#include "command.h"
#include "environment.h"
@@ -35,26 +36,32 @@ static std::unique_ptr<Command> StatCmd() {
return CreateCommandInstance("stat");
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, no_options) {
ASSERT_TRUE(StatCmd()->Run({"sleep", "1"}));
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, event_option) {
ASSERT_TRUE(StatCmd()->Run({"-e", "cpu-clock,task-clock", "sleep", "1"}));
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, system_wide_option) {
TEST_IN_ROOT(ASSERT_TRUE(StatCmd()->Run({"-a", "sleep", "1"})));
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, verbose_option) {
ASSERT_TRUE(StatCmd()->Run({"--verbose", "sleep", "1"}));
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, tracepoint_event) {
TEST_IN_ROOT(ASSERT_TRUE(StatCmd()->Run({"-a", "-e", "sched:sched_switch", "sleep", "1"})));
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, rN_event) {
TEST_REQUIRE_HW_COUNTER();
OMIT_TEST_ON_NON_NATIVE_ABIS();
@@ -78,6 +85,7 @@ TEST(stat_cmd, rN_event) {
ASSERT_TRUE(StatCmd()->Run({"-e", event_name, "sleep", "1"}));
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, pmu_event) {
TEST_REQUIRE_PMU_COUNTER();
TEST_REQUIRE_HW_COUNTER();
@@ -95,6 +103,7 @@ TEST(stat_cmd, pmu_event) {
TEST_IN_ROOT(ASSERT_TRUE(StatCmd()->Run({"-a", "-e", event_string, "sleep", "1"})));
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, event_modifier) {
TEST_REQUIRE_HW_COUNTER();
ASSERT_TRUE(StatCmd()->Run({"-e", "cpu-cycles:u,cpu-cycles:k", "sleep", "1"}));
@@ -120,6 +129,7 @@ void CreateProcesses(size_t count, std::vector<std::unique_ptr<Workload>>* workl
}
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, existing_processes) {
std::vector<std::unique_ptr<Workload>> workloads;
CreateProcesses(2, &workloads);
@@ -128,6 +138,7 @@ TEST(stat_cmd, existing_processes) {
ASSERT_TRUE(StatCmd()->Run({"-p", pid_list, "sleep", "1"}));
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, existing_threads) {
std::vector<std::unique_ptr<Workload>> workloads;
CreateProcesses(2, &workloads);
@@ -137,11 +148,13 @@ TEST(stat_cmd, existing_threads) {
ASSERT_TRUE(StatCmd()->Run({"-t", tid_list, "sleep", "1"}));
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, no_monitored_threads) {
ASSERT_FALSE(StatCmd()->Run({}));
ASSERT_FALSE(StatCmd()->Run({""}));
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, group_option) {
TEST_REQUIRE_HW_COUNTER();
ASSERT_TRUE(StatCmd()->Run({"--group", "cpu-clock,page-faults", "sleep", "1"}));
@@ -150,6 +163,7 @@ TEST(stat_cmd, group_option) {
"cpu-cycles:k,instructions:k", "sleep", "1"}));
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, auto_generated_summary) {
TEST_REQUIRE_HW_COUNTER();
TemporaryFile tmp_file;
@@ -166,11 +180,13 @@ TEST(stat_cmd, auto_generated_summary) {
ASSERT_NE(s.npos, s.find("instructions", pos));
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, duration_option) {
ASSERT_TRUE(StatCmd()->Run({"--duration", "1.2", "-p", std::to_string(getpid()), "--in-app"}));
ASSERT_TRUE(StatCmd()->Run({"--duration", "1", "sleep", "2"}));
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, interval_option) {
TemporaryFile tmp_file;
ASSERT_TRUE(StatCmd()->Run(
@@ -187,16 +203,19 @@ TEST(stat_cmd, interval_option) {
ASSERT_EQ(count, 2UL);
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, interval_option_in_system_wide) {
TEST_IN_ROOT(ASSERT_TRUE(StatCmd()->Run({"-a", "--interval", "100", "--duration", "0.3"})));
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, interval_only_values_option) {
ASSERT_TRUE(StatCmd()->Run({"--interval", "500", "--interval-only-values", "sleep", "2"}));
TEST_IN_ROOT(ASSERT_TRUE(
StatCmd()->Run({"-a", "--interval", "100", "--interval-only-values", "--duration", "0.3"})));
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, no_modifier_for_clock_events) {
for (const std::string& e : {"cpu-clock", "task-clock"}) {
for (const std::string& m : {"u", "k"}) {
@@ -206,6 +225,7 @@ TEST(stat_cmd, no_modifier_for_clock_events) {
}
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, handle_SIGHUP) {
std::thread thread([]() {
sleep(1);
@@ -215,6 +235,7 @@ TEST(stat_cmd, handle_SIGHUP) {
ASSERT_TRUE(StatCmd()->Run({"sleep", "1000000"}));
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, stop_when_no_more_targets) {
std::atomic<int> tid(0);
std::thread thread([&]() {
@@ -227,21 +248,24 @@ TEST(stat_cmd, stop_when_no_more_targets) {
ASSERT_TRUE(StatCmd()->Run({"-t", std::to_string(tid), "--in-app"}));
}
-TEST(stat_cmd, sample_speed_should_be_zero) {
+// @CddTest = 6.1/C-0-2
+TEST(stat_cmd, sample_rate_should_be_zero) {
TEST_REQUIRE_HW_COUNTER();
EventSelectionSet set(true);
ASSERT_TRUE(set.AddEventType("cpu-cycles"));
set.AddMonitoredProcesses({getpid()});
- ASSERT_TRUE(set.OpenEventFiles({-1}));
- std::vector<EventAttrWithId> attrs = set.GetEventAttrWithId();
+ set.SetCpusForNewEvents({-1});
+ ASSERT_TRUE(set.OpenEventFiles());
+ const EventAttrIds& attrs = set.GetEventAttrWithId();
ASSERT_GT(attrs.size(), 0u);
for (auto& attr : attrs) {
- ASSERT_EQ(attr.attr->sample_period, 0u);
- ASSERT_EQ(attr.attr->sample_freq, 0u);
- ASSERT_EQ(attr.attr->freq, 0u);
+ ASSERT_EQ(attr.attr.sample_period, 0u);
+ ASSERT_EQ(attr.attr.sample_freq, 0u);
+ ASSERT_EQ(attr.attr.freq, 0u);
}
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, calculating_cpu_frequency) {
TEST_REQUIRE_HW_COUNTER();
CaptureStdout capture;
@@ -267,6 +291,7 @@ TEST(stat_cmd, calculating_cpu_frequency) {
ASSERT_NEAR(cpu_frequency, calculated_frequency, 1e-3);
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, set_comm_in_another_thread) {
// Test a kernel bug which was fixed in 3.15. If kernel panic happens, please cherry pick kernel
// patch: e041e328c4b41e perf: Fix perf_event_comm() vs. exec() assumption
@@ -289,12 +314,14 @@ TEST(stat_cmd, set_comm_in_another_thread) {
EventSelectionSet set(true);
ASSERT_TRUE(set.AddEventType("cpu-cycles"));
set.AddMonitoredThreads({child_tid});
- ASSERT_TRUE(set.OpenEventFiles({-1}));
+ set.SetCpusForNewEvents({-1});
+ ASSERT_TRUE(set.OpenEventFiles());
EventSelectionSet set2(true);
ASSERT_TRUE(set2.AddEventType("instructions"));
set2.AddMonitoredThreads({gettid()});
- ASSERT_TRUE(set2.OpenEventFiles({-1}));
+ set2.SetCpusForNewEvents({-1});
+ ASSERT_TRUE(set2.OpenEventFiles());
// For kernels with the bug, setting comm will make the monitored events of the child thread
// on the cpu of the current thread.
@@ -317,6 +344,7 @@ static void TestStatingApps(const std::string& app_name) {
ASSERT_TRUE(StatCmd()->Run({"--app", app_name, "--duration", "3"}));
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, app_option_for_debuggable_app) {
TEST_REQUIRE_APPS();
SetRunInAppToolForTesting(true, false);
@@ -325,12 +353,14 @@ TEST(stat_cmd, app_option_for_debuggable_app) {
TestStatingApps("com.android.simpleperf.debuggable");
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, app_option_for_profileable_app) {
TEST_REQUIRE_APPS();
SetRunInAppToolForTesting(false, true);
TestStatingApps("com.android.simpleperf.profileable");
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, use_devfreq_counters_option) {
#if defined(__ANDROID__)
TEST_IN_ROOT(StatCmd()->Run({"--use-devfreq-counters", "sleep", "0.1"}));
@@ -339,21 +369,25 @@ TEST(stat_cmd, use_devfreq_counters_option) {
#endif
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, per_thread_option) {
ASSERT_TRUE(StatCmd()->Run({"--per-thread", "sleep", "0.1"}));
TEST_IN_ROOT(StatCmd()->Run({"--per-thread", "-a", "--duration", "0.1"}));
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, per_core_option) {
ASSERT_TRUE(StatCmd()->Run({"--per-core", "sleep", "0.1"}));
TEST_IN_ROOT(StatCmd()->Run({"--per-core", "-a", "--duration", "0.1"}));
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, sort_option) {
ASSERT_TRUE(
StatCmd()->Run({"--per-thread", "--per-core", "--sort", "cpu,count", "sleep", "0.1"}));
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, counter_sum) {
PerfCounter counter;
counter.value = 1;
@@ -378,10 +412,63 @@ TEST(stat_cmd, counter_sum) {
ASSERT_EQ(counter.time_running, 6);
}
+// @CddTest = 6.1/C-0-2
TEST(stat_cmd, print_hw_counter_option) {
ASSERT_TRUE(StatCmd()->Run({"--print-hw-counter"}));
}
+// @CddTest = 6.1/C-0-2
+TEST(stat_cmd, record_different_counters_for_different_cpus) {
+ std::vector<int> online_cpus = GetOnlineCpus();
+ ASSERT_FALSE(online_cpus.empty());
+ std::string cpu0 = std::to_string(online_cpus[0]);
+ std::string cpu1 = std::to_string(online_cpus.back());
+
+ CaptureStdout capture;
+ ASSERT_TRUE(capture.Start());
+ ASSERT_TRUE(StatCmd()->Run({"--csv", "--cpu", cpu0, "-e", "cpu-clock", "--cpu", cpu1, "-e",
+ "task-clock", "--verbose", "sleep", SLEEP_SEC}));
+ std::string output = capture.Finish();
+ bool has_cpu_clock = false;
+ bool has_task_clock = false;
+ for (auto& line : android::base::Split(output, "\n")) {
+ if (android::base::StartsWith(line, "cpu-clock,")) {
+ ASSERT_NE(line.find("cpu," + cpu0 + ","), line.npos) << output;
+ has_cpu_clock = true;
+ } else if (android::base::StartsWith(line, "task-clock,")) {
+ ASSERT_NE(line.find("cpu," + cpu1 + ","), line.npos) << output;
+ has_task_clock = true;
+ }
+ }
+ ASSERT_TRUE(has_cpu_clock) << output;
+ ASSERT_TRUE(has_task_clock) << output;
+}
+
+// @CddTest = 6.1/C-0-2
+TEST(stat_cmd, kprobe_option) {
+ TEST_REQUIRE_ROOT();
+ EventSelectionSet event_selection_set(false);
+ ProbeEvents probe_events(event_selection_set);
+ if (!probe_events.IsKprobeSupported()) {
+ GTEST_LOG_(INFO) << "Skip this test as kprobe isn't supported by the kernel.";
+ return;
+ }
+ ASSERT_TRUE(StatCmd()->Run({"-e", "kprobes:myprobe", "--kprobe", "p:myprobe do_sys_openat2", "-a",
+ "--duration", SLEEP_SEC}));
+ // A default kprobe event is created if not given an explicit --kprobe option.
+ ASSERT_TRUE(StatCmd()->Run({"-e", "kprobes:do_sys_openat2", "-a", "--duration", SLEEP_SEC}));
+ ASSERT_TRUE(StatCmd()->Run({"--group", "kprobes:do_sys_openat2", "-a", "--duration", SLEEP_SEC}));
+}
+
+// @CddTest = 6.1/C-0-2
+TEST(stat_cmd, tp_filter_option) {
+ TEST_REQUIRE_HOST_ROOT();
+ TEST_REQUIRE_TRACEPOINT_EVENTS();
+ ASSERT_TRUE(StatCmd()->Run(
+ {"-e", "sched:sched_switch", "--tp-filter", "prev_comm != sleep", "sleep", SLEEP_SEC}));
+}
+
+// @CddTest = 6.1/C-0-2
class StatCmdSummaryBuilderTest : public ::testing::Test {
protected:
struct CounterArg {
@@ -433,6 +520,7 @@ class StatCmdSummaryBuilderTest : public ::testing::Test {
std::vector<std::string> sort_keys_;
};
+// @CddTest = 6.1/C-0-2
TEST_F(StatCmdSummaryBuilderTest, multiple_events) {
AddCounter({.event_id = 0, .value = 1, .time_enabled = 1, .time_running = 1});
AddCounter({.event_id = 1, .value = 2, .time_enabled = 2, .time_running = 2});
@@ -446,6 +534,7 @@ TEST_F(StatCmdSummaryBuilderTest, multiple_events) {
ASSERT_NEAR(summaries[1].scale, 1.0, 1e-5);
}
+// @CddTest = 6.1/C-0-2
TEST_F(StatCmdSummaryBuilderTest, default_aggregate) {
AddCounter({.tid = 0, .cpu = 0, .value = 1, .time_enabled = 1, .time_running = 1});
AddCounter({.tid = 0, .cpu = 1, .value = 1, .time_enabled = 1, .time_running = 1});
@@ -457,6 +546,7 @@ TEST_F(StatCmdSummaryBuilderTest, default_aggregate) {
ASSERT_NEAR(summaries[0].scale, 1.25, 1e-5);
}
+// @CddTest = 6.1/C-0-2
TEST_F(StatCmdSummaryBuilderTest, per_thread_aggregate) {
AddCounter({.tid = 0, .cpu = 0, .value = 1, .time_enabled = 1, .time_running = 1});
AddCounter({.tid = 0, .cpu = 1, .value = 1, .time_enabled = 1, .time_running = 1});
@@ -474,6 +564,7 @@ TEST_F(StatCmdSummaryBuilderTest, per_thread_aggregate) {
ASSERT_NEAR(summaries[1].scale, 1.0, 1e-5);
}
+// @CddTest = 6.1/C-0-2
TEST_F(StatCmdSummaryBuilderTest, per_core_aggregate) {
AddCounter({.tid = 0, .cpu = 0, .value = 1, .time_enabled = 1, .time_running = 1});
AddCounter({.tid = 0, .cpu = 1, .value = 1, .time_enabled = 1, .time_running = 1});
@@ -491,6 +582,7 @@ TEST_F(StatCmdSummaryBuilderTest, per_core_aggregate) {
ASSERT_NEAR(summaries[1].scale, 1.5, 1e-5);
}
+// @CddTest = 6.1/C-0-2
TEST_F(StatCmdSummaryBuilderTest, per_thread_core_aggregate) {
AddCounter({.tid = 0, .cpu = 0, .value = 1, .time_enabled = 1, .time_running = 1});
AddCounter({.tid = 0, .cpu = 1, .value = 2, .time_enabled = 1, .time_running = 1});
@@ -516,6 +608,7 @@ TEST_F(StatCmdSummaryBuilderTest, per_thread_core_aggregate) {
ASSERT_NEAR(summaries[3].scale, 1.0, 1e-5);
}
+// @CddTest = 6.1/C-0-2
TEST_F(StatCmdSummaryBuilderTest, sort_key_count) {
sort_keys_ = {"count"};
AddCounter({.tid = 0, .cpu = 0, .value = 1});
@@ -525,6 +618,7 @@ TEST_F(StatCmdSummaryBuilderTest, sort_key_count) {
ASSERT_EQ(summaries[1].count, 1);
}
+// @CddTest = 6.1/C-0-2
TEST_F(StatCmdSummaryBuilderTest, sort_key_count_per_thread) {
sort_keys_ = {"count_per_thread", "count"};
AddCounter({.tid = 0, .cpu = 0, .value = 1});
@@ -536,6 +630,7 @@ TEST_F(StatCmdSummaryBuilderTest, sort_key_count_per_thread) {
ASSERT_EQ(summaries[2].count, 3);
}
+// @CddTest = 6.1/C-0-2
TEST_F(StatCmdSummaryBuilderTest, sort_key_cpu) {
sort_keys_ = {"cpu"};
AddCounter({.tid = 0, .cpu = 1, .value = 2});
@@ -545,6 +640,7 @@ TEST_F(StatCmdSummaryBuilderTest, sort_key_cpu) {
ASSERT_EQ(summaries[1].cpu, 1);
}
+// @CddTest = 6.1/C-0-2
TEST_F(StatCmdSummaryBuilderTest, sort_key_pid_tid_name) {
AddCounter({.tid = 0, .cpu = 0, .value = 1});
AddCounter({.tid = 1, .cpu = 0, .value = 2});
@@ -557,6 +653,7 @@ TEST_F(StatCmdSummaryBuilderTest, sort_key_pid_tid_name) {
}
}
+// @CddTest = 6.1/C-0-2
class StatCmdSummariesTest : public ::testing::Test {
protected:
void AddSummary(const std::string event_name, pid_t tid, int cpu, uint64_t count,
@@ -585,6 +682,7 @@ class StatCmdSummariesTest : public ::testing::Test {
std::unique_ptr<CounterSummaries> summaries_;
};
+// @CddTest = 6.1/C-0-2
TEST_F(StatCmdSummariesTest, task_clock_comment) {
AddSummary("task-clock", -1, -1, 1e9, 0);
AddSummary("task-clock", 0, -1, 2e9, 0);
@@ -596,6 +694,7 @@ TEST_F(StatCmdSummariesTest, task_clock_comment) {
ASSERT_EQ(*GetComment(3), "3.000000 cpus used");
}
+// @CddTest = 6.1/C-0-2
TEST_F(StatCmdSummariesTest, cpu_cycles_comment) {
AddSummary("cpu-cycles", -1, -1, 100, 100);
AddSummary("cpu-cycles", 0, -1, 200, 100);
@@ -607,6 +706,7 @@ TEST_F(StatCmdSummariesTest, cpu_cycles_comment) {
ASSERT_EQ(*GetComment(3), "3.000000 GHz");
}
+// @CddTest = 6.1/C-0-2
TEST_F(StatCmdSummariesTest, rate_comment) {
AddSummary("branch-misses", -1, -1, 1e9, 1e9);
AddSummary("branch-misses", 0, -1, 1e6, 1e9);
diff --git a/simpleperf/cmd_trace_sched.cpp b/simpleperf/cmd_trace_sched.cpp
index 607fb2a4..e04c54b2 100644
--- a/simpleperf/cmd_trace_sched.cpp
+++ b/simpleperf/cmd_trace_sched.cpp
@@ -192,9 +192,9 @@ bool TraceSchedCommand::ParseSchedEvents(const std::string& record_file_path) {
return false;
}
const EventType* event = FindEventTypeByName("sched:sched_stat_runtime");
- std::vector<EventAttrWithId> attrs = reader->AttrSection();
- if (attrs.size() != 1u || attrs[0].attr->type != event->type ||
- attrs[0].attr->config != event->config) {
+ const EventAttrIds& attrs = reader->AttrSection();
+ if (attrs.size() != 1u || attrs[0].attr.type != event->type ||
+ attrs[0].attr.config != event->config) {
LOG(ERROR) << "sched:sched_stat_runtime isn't recorded in " << record_file_path;
return false;
}
@@ -237,9 +237,12 @@ bool TraceSchedCommand::ProcessRecord(Record& record) {
}
const EventType* event = FindEventTypeByName("sched:sched_stat_runtime");
CHECK(event != nullptr);
- TracingFormat format = tracing->GetTracingFormatHavingId(event->config);
- format.GetField("comm", tracing_field_comm_);
- format.GetField("runtime", tracing_field_runtime_);
+ std::optional<TracingFormat> format = tracing->GetTracingFormatHavingId(event->config);
+ if (!format.has_value()) {
+ return false;
+ }
+ format.value().GetField("comm", tracing_field_comm_);
+ format.value().GetField("runtime", tracing_field_runtime_);
break;
}
}
diff --git a/simpleperf/cmd_trace_sched_test.cpp b/simpleperf/cmd_trace_sched_test.cpp
index 11ba3ba5..a48f97cb 100644
--- a/simpleperf/cmd_trace_sched_test.cpp
+++ b/simpleperf/cmd_trace_sched_test.cpp
@@ -43,10 +43,12 @@ static std::unique_ptr<Command> TraceSchedCmd() {
return CreateCommandInstance("trace-sched");
}
+// @CddTest = 6.1/C-0-2
TEST(trace_sched_cmd, smoke) {
TEST_IN_ROOT({ ASSERT_TRUE(TraceSchedCmd()->Run({"--duration", "1"})); });
}
+// @CddTest = 6.1/C-0-2
TEST(trace_sched_cmd, report_smoke) {
CaptureStdout capture;
ASSERT_TRUE(capture.Start());
diff --git a/simpleperf/command.cpp b/simpleperf/command.cpp
index f48b4649..71c1e519 100644
--- a/simpleperf/command.cpp
+++ b/simpleperf/command.cpp
@@ -183,32 +183,14 @@ const std::vector<std::string> GetAllCommandNames() {
return names;
}
-extern void RegisterBootRecordCommand();
-extern void RegisterDumpRecordCommand();
-extern void RegisterHelpCommand();
-extern void RegisterInjectCommand();
-extern void RegisterListCommand();
-extern void RegisterKmemCommand();
-extern void RegisterMergeCommand();
-extern void RegisterRecordCommand();
-extern void RegisterReportCommand();
-extern void RegisterReportSampleCommand();
-extern void RegisterStatCommand();
-extern void RegisterDebugUnwindCommand();
-extern void RegisterTraceSchedCommand();
-extern void RegisterAPICommands();
-extern void RegisterMonitorCommand();
-
-class CommandRegister {
- public:
- CommandRegister() {
- RegisterDumpRecordCommand();
- RegisterHelpCommand();
- RegisterInjectCommand();
- RegisterKmemCommand();
- RegisterMergeCommand();
- RegisterReportCommand();
- RegisterReportSampleCommand();
+void RegisterAllCommands() {
+ RegisterDumpRecordCommand();
+ RegisterHelpCommand();
+ RegisterInjectCommand();
+ RegisterKmemCommand();
+ RegisterMergeCommand();
+ RegisterReportCommand();
+ RegisterReportSampleCommand();
#if defined(__linux__)
RegisterListCommand();
RegisterRecordCommand();
@@ -221,10 +203,7 @@ class CommandRegister {
RegisterBootRecordCommand();
#endif
#endif
- }
-};
-
-CommandRegister command_register;
+}
static void StderrLogger(android::base::LogId, android::base::LogSeverity severity, const char*,
const char* file, unsigned int line, const char* message) {
diff --git a/simpleperf/command.h b/simpleperf/command.h
index 1616bc40..c1c6cb68 100644
--- a/simpleperf/command.h
+++ b/simpleperf/command.h
@@ -169,7 +169,7 @@ class Command {
const std::string& ShortHelpString() const { return short_help_string_; }
- const std::string LongHelpString() const { return long_help_string_; }
+ virtual std::string LongHelpString() const { return long_help_string_; }
virtual bool Run(const std::vector<std::string>&) { return false; }
virtual void Run(const std::vector<std::string>& args, int* exit_code) {
@@ -203,7 +203,6 @@ class Command {
bool NextArgumentOrError(const std::vector<std::string>& args, size_t* pi);
void ReportUnknownOption(const std::vector<std::string>& args, size_t i);
- private:
const std::string name_;
const std::string short_help_string_;
const std::string long_help_string_;
@@ -213,7 +212,24 @@ class Command {
void RegisterCommand(const std::string& cmd_name,
const std::function<std::unique_ptr<Command>(void)>& callback);
+void RegisterBootRecordCommand();
+void RegisterDumpRecordCommand();
+void RegisterHelpCommand();
+void RegisterInjectCommand();
+void RegisterListCommand();
+void RegisterKmemCommand();
+void RegisterMergeCommand();
+void RegisterRecordCommand();
+void RegisterReportCommand();
+void RegisterReportSampleCommand();
+void RegisterStatCommand();
+void RegisterDebugUnwindCommand();
+void RegisterTraceSchedCommand();
+void RegisterAPICommands();
+void RegisterMonitorCommand();
+void RegisterAllCommands();
void UnRegisterCommand(const std::string& cmd_name);
+
std::unique_ptr<Command> CreateCommandInstance(const std::string& cmd_name);
const std::vector<std::string> GetAllCommandNames();
bool RunSimpleperfCmd(int argc, char** argv);
diff --git a/simpleperf/command_test.cpp b/simpleperf/command_test.cpp
index 392ec8ac..a8c59655 100644
--- a/simpleperf/command_test.cpp
+++ b/simpleperf/command_test.cpp
@@ -27,6 +27,7 @@ class MockCommand : public Command {
bool Run(const std::vector<std::string>&) override { return true; }
};
+// @CddTest = 6.1/C-0-2
TEST(command, CreateCommandInstance) {
ASSERT_TRUE(CreateCommandInstance("mock1") == nullptr);
RegisterCommand("mock1", [] { return std::unique_ptr<Command>(new MockCommand); });
@@ -35,6 +36,7 @@ TEST(command, CreateCommandInstance) {
ASSERT_TRUE(CreateCommandInstance("mock1") == nullptr);
}
+// @CddTest = 6.1/C-0-2
TEST(command, GetAllCommands) {
size_t command_count = GetAllCommandNames().size();
RegisterCommand("mock1", [] { return std::unique_ptr<Command>(new MockCommand); });
@@ -43,6 +45,7 @@ TEST(command, GetAllCommands) {
ASSERT_EQ(command_count, GetAllCommandNames().size());
}
+// @CddTest = 6.1/C-0-2
TEST(command, GetValueForOption) {
MockCommand command;
uint64_t value;
@@ -70,6 +73,7 @@ TEST(command, GetValueForOption) {
ASSERT_DOUBLE_EQ(double_value, 3.2);
}
+// @CddTest = 6.1/C-0-2
TEST(command, PreprocessOptions) {
MockCommand cmd;
OptionValueMap options;
@@ -156,6 +160,7 @@ TEST(command, PreprocessOptions) {
&ordered_options, nullptr));
}
+// @CddTest = 6.1/C-0-2
TEST(command, OptionValueMap) {
OptionValue value;
value.uint_value = 10;
diff --git a/simpleperf/cpu_hotplug_test.cpp b/simpleperf/cpu_hotplug_test.cpp
index 9b78c644..526ab87a 100644
--- a/simpleperf/cpu_hotplug_test.cpp
+++ b/simpleperf/cpu_hotplug_test.cpp
@@ -497,7 +497,7 @@ int main(int argc, char** argv) {
verbose_mode = true;
}
}
- android::base::InitLogging(argv, android::base::StderrLogger);
testing::InitGoogleTest(&argc, argv);
+ android::base::InitLogging(argv, android::base::StderrLogger);
return RUN_ALL_TESTS();
}
diff --git a/simpleperf/doc/README.md b/simpleperf/doc/README.md
index f989b785..2efa323b 100644
--- a/simpleperf/doc/README.md
+++ b/simpleperf/doc/README.md
@@ -10,8 +10,8 @@ profile both Java and C++ code on Android. The simpleperf executable can run on
and Python scripts can be used on Android >= N.
Simpleperf is part of the Android Open Source Project.
-The source code is [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/).
-The latest document is [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/README.md).
+The source code is [here](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/).
+The latest document is [here](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/doc/README.md).
[TOC]
@@ -106,84 +106,104 @@ See [view_the_profile.md](./view_the_profile.md).
## Answers to common issues
-### Why we suggest profiling on Android >= N devices?
-
-1. Running on a device reflects a real running situation, so we suggest
- profiling on real devices instead of emulators.
-2. To profile Java code, we need ART running in oat mode, which is only
- available >= L for rooted devices, and >= N for non-rooted devices.
-3. Old Android versions are likely to be shipped with old kernels (< 3.18),
- which may not support profiling features like recording dwarf based call graphs.
-4. Old Android versions are likely to be shipped with Arm32 chips. In Arm32
- mode, recording stack frame based call graphs doesn't work well.
-
-### Suggestions about recording call graphs
-
-Below is our experiences of dwarf based call graphs and stack frame based call graphs.
-
-dwarf based call graphs:
-1. Need support of debug information in binaries.
-2. Behave normally well on both ARM and ARM64, for both fully compiled Java code and C++ code.
-3. Can only unwind 64K stack for each sample. So usually can't show complete flamegraph. But
- probably is enough for users to identify hot places.
-4. Take more CPU time than stack frame based call graphs. So the sample frequency is suggested
- to be 1000 Hz. Thus at most 1000 samples per second.
-
-stack frame based call graphs:
-1. Need support of stack frame registers.
-2. Don't work well on ARM. Because ARM is short of registers, and ARM and THUMB code have different
- stack frame registers. So the kernel can't unwind user stack containing both ARM/THUMB code.
-3. Also don't work well on fully compiled Java code on ARM64. Because the ART compiler doesn't
- reserve stack frame registers.
-4. Work well when profiling native programs on ARM64. One example is profiling surfacelinger. And
+### Support on different Android versions
+
+On Android < N, the kernel may be too old (< 3.18) to support features like recording DWARF
+based call graphs.
+On Android M - O, we can only profile C++ code and fully compiled Java code.
+On Android >= P, the ART interpreter supports DWARF based unwinding. So we can profile Java code.
+On Android >= Q, we can used simpleperf shipped on device to profile released Android apps, with
+ `<profileable android:shell="true" />`.
+
+
+### Comparing DWARF based and stack frame based call graphs
+
+Simpleperf supports two ways recording call stacks with samples. One is DWARF based call graph,
+the other is stack frame based call graph. Below is their comparison:
+
+Recording DWARF based call graph:
+1. Needs support of debug information in binaries.
+2. Behaves normally well on both ARM and ARM64, for both Java code and C++ code.
+3. Can only unwind 64K stack for each sample. So it isn't always possible to unwind to the bottom.
+ However, this is alleviated in simpleperf, as explained in the next section.
+4. Takes more CPU time than stack frame based call graphs. So it has higher overhead, and can't
+ sample at very high frequency (usually <= 4000 Hz).
+
+Recording stack frame based call graph:
+1. Needs support of stack frame registers.
+2. Doesn't work well on ARM. Because ARM is short of registers, and ARM and THUMB code have
+ different stack frame registers. So the kernel can't unwind user stack containing both ARM and
+ THUMB code.
+3. Also doesn't work well on Java code. Because the ART compiler doesn't reserve stack frame
+ registers. And it can't get frames for interpreted Java code.
+4. Works well when profiling native programs on ARM64. One example is profiling surfacelinger. And
usually shows complete flamegraph when it works well.
-5. Take less CPU time than dwarf based call graphs. So the sample frequency can be 4000 Hz or
+5. Takes much less CPU time than DWARF based call graphs. So the sample frequency can be 10000 Hz or
higher.
-So if you need to profile code on ARM or profile fully compiled Java code, dwarf based call graphs
-may be better. If you need to profile C++ code on ARM64, stack frame based call graphs may be
-better. After all, you can always try dwarf based call graph first, because it always produces
-reasonable results when given unstripped binaries properly. If it doesn't work well enough, then
-try stack frame based call graphs instead.
+So if you need to profile code on ARM or profile Java code, DWARF based call graph is better. If you
+need to profile C++ code on ARM64, stack frame based call graphs may be better. After all, you can
+fisrt try DWARF based call graph, which is also the default option when `-g` is used. Because it
+always produces reasonable results. If it doesn't work well enough, then try stack frame based call
+graph instead.
-Simpleperf may need unstripped native binaries on the device to generate good dwarf based call
-graphs. It can be supported by downloading unstripped native libraries on device, as [here](#fix-broken-callchain-stopped-at-c-functions).
-### Why we can't always get complete DWARF-based call graphs?
+### Fix broken DWARF based call graph
-DWARF-based call graphs are generated by unwinding thread stacks. When a sample is generated, up to
-64KB stack data is dumped by the kernel. By unwinding the stack based on dwarf information, we get
-a callchain. But the thread stack can be much longer than 64KB. In that case, we can't unwind to
-the thread start point.
+A DWARF-based call graph is generated by unwinding thread stacks. When a sample is recorded, a
+kernel dumps up to 64 kilobytes of stack data. By unwinding the stack based on DWARF information,
+we can get a call stack.
-To alleviate the problem, simpleperf joins callchains after recording them. If two callchains of
-a thread have an entry containing the same ip and sp address, then simpleperf tries to join them to
-make the callchains longer. In that case, the longer we run, the more samples we get. This makes it
-more likely to get complete callchains, but it's still not guaranteed to get complete call graphs.
+Two reasons may cause a broken call stack:
+1. The kernel can only dump up to 64 kilobytes of stack data for each sample, but a thread can have
+ much larger stack. In this case, we can't unwind to the thread start point.
-### How to solve missing symbols in report?
+2. We need binaries containing DWARF call frame information to unwind stack frames. The binary
+ should have one of the following sections: .eh_frame, .debug_frame, .ARM.exidx or .gnu_debugdata.
-The simpleperf record command collects symbols on device in perf.data. But if the native libraries
-you use on device are stripped, this will result in a lot of unknown symbols in the report. A
-solution is to build binary_cache on host.
+To mitigate these problems,
-```sh
-# Collect binaries needed by perf.data in binary_cache/.
-$ ./binary_cache_builder.py -lib NATIVE_LIB_DIR,...
-```
-The NATIVE_LIB_DIRs passed in -lib option are the directories containing unstripped native
-libraries on host. After running it, the native libraries containing symbol tables are collected
-in binary_cache/ for use when reporting.
+For the missing stack data problem:
+1. To alleviate it, simpleperf joins callchains (call stacks) after recording. If two callchains of
+ a thread have an entry containing the same ip and sp address, then simpleperf tries to join them
+ to make the callchains longer. So we can get more complete callchains by recording longer and
+ joining more samples. This doesn't guarantee to get complete call graphs. But it usually works
+ well.
+
+2. Simpleperf stores samples in a buffer before unwinding them. If the bufer is low in free space,
+ simpleperf may decide to truncate stack data for a sample to 1K. Hopefully, this can be recovered
+ by callchain joiner. But when a high percentage of samples are truncated, many callchains can be
+ broken. We can tell if many samples are truncated in the record command output, like:
```sh
-$ ./report.py --symfs binary_cache
+$ simpleperf record ...
+simpleperf I cmd_record.cpp:809] Samples recorded: 105584 (cut 86291). Samples lost: 6501.
-# report_html.py searches binary_cache/ automatically, so you don't need to
-# pass it any argument.
-$ ./report_html.py
+$ simpleperf record ...
+simpleperf I cmd_record.cpp:894] Samples recorded: 7,365 (1,857 with truncated stacks).
```
+ There are two ways to avoid truncating samples. One is increasing the buffer size, like
+ `--user-buffer-size 1G`. But `--user-buffer-size` is only available on latest simpleperf. If that
+ option isn't available, we can use `--no-cut-samples` to disable truncating samples.
+
+For the missing DWARF call frame info problem:
+1. Most C++ code generates binaries containing call frame info, in .eh_frame or .ARM.exidx sections.
+ These sections are not stripped, and are usually enough for stack unwinding.
+
+2. For C code and a small percentage of C++ code that the compiler is sure will not generate
+ exceptions, the call frame info is generated in .debug_frame section. .debug_frame section is
+ usually stripped with other debug sections. One way to fix it, is to download unstripped binaries
+ on device, as [here](#fix-broken-callchain-stopped-at-c-functions).
+
+3. The compiler doesn't generate unwind instructions for function prologue and epilogue. Because
+ they operates stack frames and will not generate exceptions. But profiling may hit these
+ instructions, and fails to unwind them. This usually doesn't matter in a frame graph. But in a
+ time based Stack Chart (like in Android Studio and Firefox profiler), this causes stack gaps once
+ in a while. We can remove stack gaps via `--remove-gaps`, which is already enabled by default.
+
+
### Fix broken callchain stopped at C functions
When using dwarf based call graphs, simpleperf generates callchains during recording to save space.
@@ -207,6 +227,31 @@ To use app_profiler.py:
$ ./app_profiler.py -lib <unstripped_dir>
```
+
+### How to solve missing symbols in report?
+
+The simpleperf record command collects symbols on device in perf.data. But if the native libraries
+you use on device are stripped, this will result in a lot of unknown symbols in the report. A
+solution is to build binary_cache on host.
+
+```sh
+# Collect binaries needed by perf.data in binary_cache/.
+$ ./binary_cache_builder.py -lib NATIVE_LIB_DIR,...
+```
+
+The NATIVE_LIB_DIRs passed in -lib option are the directories containing unstripped native
+libraries on host. After running it, the native libraries containing symbol tables are collected
+in binary_cache/ for use when reporting.
+
+```sh
+$ ./report.py --symfs binary_cache
+
+# report_html.py searches binary_cache/ automatically, so you don't need to
+# pass it any argument.
+$ ./report_html.py
+```
+
+
### Show annotated source code and disassembly
To show hot places at source code and instruction level, we need to show source code and
@@ -219,32 +264,65 @@ disassembly for C++ code and fully compiled Java code. Simpleperf supports two w
libs with debug info. Do it with
`binary_cache_builder.py -i perf.data -lib <dir_of_lib_with_debug_info>`.
3) Use report_html.py to generate report.html with annotated source code and disassembly,
- as described [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/scripts_reference.md#report_html_py).
+ as described [here](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/doc/scripts_reference.md#report_html_py).
2. Through pprof.
1) Generate perf.data and binary_cache as above.
2) Use pprof_proto_generator.py to generate pprof proto file. `pprof_proto_generator.py`.
- 3) Use pprof to report a function with annotated source code, as described [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/scripts_reference.md#pprof_proto_generator_py).
+ 3) Use pprof to report a function with annotated source code, as described [here](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/doc/scripts_reference.md#pprof_proto_generator_py).
+
+
+### Reduce lost samples and samples with truncated stack
+
+When using `simpleperf record`, we may see lost samples or samples with truncated stack data. Before
+saving samples to a file, simpleperf uses two buffers to cache samples in memory. One is a kernel
+buffer, the other is a userspace buffer. The kernel puts samples to the kernel buffer. Simpleperf
+moves samples from the kernel buffer to the userspace buffer before processing them. If a buffer
+overflows, we lose samples or get samples with truncated stack data. Below is an example.
+
+```sh
+$ simpleperf record -a --duration 1 -g --user-buffer-size 100k
+simpleperf I cmd_record.cpp:799] Recorded for 1.00814 seconds. Start post processing.
+simpleperf I cmd_record.cpp:894] Samples recorded: 79 (16 with truncated stacks).
+ Samples lost: 2,129 (kernelspace: 18, userspace: 2,111).
+simpleperf W cmd_record.cpp:911] Lost 18.5567% of samples in kernel space, consider increasing
+ kernel buffer size(-m), or decreasing sample frequency(-f), or
+ increasing sample period(-c).
+simpleperf W cmd_record.cpp:928] Lost/Truncated 97.1233% of samples in user space, consider
+ increasing userspace buffer size(--user-buffer-size), or
+ decreasing sample frequency(-f), or increasing sample period(-c).
+```
+
+In the above example, we get 79 samples, 16 of them are with truncated stack data. We lose 18
+samples in the kernel buffer, and lose 2111 samples in the userspace buffer.
+
+To reduce lost samples in the kernel buffer, we can increase kernel buffer size via `-m`. To reduce
+lost samples in the userspace buffer, or reduce samples with truncated stack data, we can increase
+userspace buffer size via `--user-buffer-size`.
+
+We can also reduce samples generated in a fixed time period, like reducing sample frequency using
+`-f`, reducing monitored threads, not monitoring multiple perf events at the same time.
+
## Bugs and contribution
Bugs and feature requests can be submitted at https://github.com/android/ndk/issues.
Patches can be uploaded to android-review.googlesource.com as [here](https://source.android.com/setup/contribute/),
-or sent to email addresses listed [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/OWNERS).
+or sent to email addresses listed [here](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/OWNERS).
If you want to compile simpleperf C++ source code, follow below steps:
1. Download AOSP main branch as [here](https://source.android.com/setup/build/requirements).
2. Build simpleperf.
```sh
$ . build/envsetup.sh
-$ lunch aosp_arm64-userdebug
+$ lunch aosp_arm64-trunk_staging-userdebug
$ mmma system/extras/simpleperf -j30
```
If built successfully, out/target/product/generic_arm64/system/bin/simpleperf is for ARM64, and
out/target/product/generic_arm64/system/bin/simpleperf32 is for ARM.
-The source code of simpleperf python scripts is in [system/extras/simpleperf/scripts](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/scripts/).
+The source code of simpleperf python scripts is in [system/extras/simpleperf/scripts](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/scripts/).
Most scripts rely on simpleperf binaries to work. To update binaries for scripts (using linux
x86_64 host and android arm64 target as an example):
```sh
diff --git a/simpleperf/doc/android_application_profiling.md b/simpleperf/doc/android_application_profiling.md
index 5ee46a51..c0bcb645 100644
--- a/simpleperf/doc/android_application_profiling.md
+++ b/simpleperf/doc/android_application_profiling.md
@@ -1,7 +1,7 @@
# Android application profiling
This section shows how to profile an Android application.
-Some examples are [Here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/demo/README.md).
+Some examples are [Here](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/demo/README.md).
Profiling an Android application involves three steps:
1. Prepare an Android application.
@@ -104,7 +104,7 @@ To compile java code, we can pass app_profiler.py the --compile_java_code option
On Android <= M, simpleperf doesn't support profiling Java code.
-Below I use application [SimpleperfExampleCpp](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/demo/SimpleperfExampleCpp).
+Below I use application [SimpleperfExampleCpp](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/demo/SimpleperfExampleCpp).
It builds an app-debug.apk for profiling.
```sh
@@ -303,7 +303,7 @@ Simpleperf supports controlling recording from application code. Below is the wo
3. Run `api_profiler.py collect -p <package_name>` to collect profiling data files to host.
-Examples are CppApi and JavaApi in [demo](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/demo).
+Examples are CppApi and JavaApi in [demo](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/demo).
## Parse profiling data manually
diff --git a/simpleperf/doc/android_platform_profiling.md b/simpleperf/doc/android_platform_profiling.md
index f2a31cf0..52bccda7 100644
--- a/simpleperf/doc/android_platform_profiling.md
+++ b/simpleperf/doc/android_platform_profiling.md
@@ -80,27 +80,27 @@ can be used to borrow from the counters used by the kernel.
On userdebug/eng devices, we can get boot-time profile via simpleperf.
-Step 1. In adb root, set options used to record boot-time profile. Simpleperf stores the options in
-a persist property `persist.simpleperf.boot_record`.
+Step 1. Customize the configuration if needed. By default, simpleperf tracks all processes
+except for itself, starts at `early-init`, and stops when `sys.boot_completed` is set.
+You can customize it by changing the trigger or command line flags in
+`system/extras/simpleperf/simpleperf.rc`.
+Step 2. Add `androidboot.simpleperf.boot_record=1` to the kernel command line.
+For example, on Pixel devices, you can do
```
-# simpleperf boot-record --enable "-a -g --duration 10 --exclude-perf"
+$ fastboot oem cmdline add androidboot.simpleperf.boot_record=1
```
-Step 2. Reboot the device. When booting, init finds that the persist property is set, so it forks
-a background process to run simpleperf to record boot-time profile. init starts simpleperf at
-zygote-start stage, right after zygote is started.
+Step 3. Reboot the device. When booting, init finds that the kernel command line flag is set,
+so it forks a background process to run simpleperf to record boot-time profile.
+init starts simpleperf at `early-init` stage, which is very soon after second-stage init starts.
-```
-$ adb reboot
-```
-
-Step 3. After boot, the boot-time profile is stored in /data/simpleperf_boot_data. Then we can pull
+Step 4. After boot, the boot-time profile is stored in /tmp/boot_perf.data. Then we can pull
the profile to host to report.
```
-$ adb shell ls /data/simpleperf_boot_data
-perf-20220126-11-47-51.data
+$ adb shell ls /tmp/boot_perf.data
+/tmp/boot_perf.data
```
Following is a boot-time profile example. From timestamp, the first sample is generated at about
diff --git a/simpleperf/doc/collect_etm_data_for_autofdo.md b/simpleperf/doc/collect_etm_data_for_autofdo.md
index 5313c4e6..e86f1bba 100644
--- a/simpleperf/doc/collect_etm_data_for_autofdo.md
+++ b/simpleperf/doc/collect_etm_data_for_autofdo.md
@@ -81,20 +81,15 @@ branch with count info for binary2
We need to split perf_inject.data, and make sure one file only contains info for one binary.
-Then we can use [AutoFDO](https://github.com/google/autofdo) to create profile. AutoFDO only works
-for binaries having an executable segment as its first loadable segment. But binaries built in
-Android may not follow this rule. Simpleperf inject command knows how to work around this problem.
-But there is a check in AutoFDO forcing binaries to start with an executable segment. We need to
-disable the check in AutoFDO, by commenting out L127-L136 in
-https://github.com/google/autofdo/commit/188db2834ce74762ed17108ca344916994640708#diff-2d132ecbb5e4f13e0da65419f6d1759dd27d6b696786dd7096c0c34d499b1710R127-R136.
-Then we can use `create_llvm_prof` in AutoFDO to create profiles used by clang.
+Then we can use [AutoFDO](https://github.com/google/autofdo) to create profile. Follow README.md
+in AutoFDO to build create_llvm_prof, then use `create_llvm_prof` to create profiles for clang.
```sh
# perf_inject_binary1.data is split from perf_inject.data, and only contains branch info for binary1.
-host $ autofdo/create_llvm_prof -profile perf_inject_binary1.data -profiler text -binary path_of_binary1 -out a.prof -format binary
+host $ create_llvm_prof -profile perf_inject_binary1.data -profiler text -binary path_of_binary1 -out a.prof -format binary
# perf_inject_kernel.data is split from perf_inject.data, and only contains branch info for [kernel.kallsyms].
-host $ autofdo/create_llvm_prof -profile perf_inject_kernel.data -profiler text -binary vmlinux -out a.prof -format binary
+host $ create_llvm_prof -profile perf_inject_kernel.data -profiler text -binary vmlinux -out a.prof -format binary
```
Then we can use a.prof for PGO during compilation, via `-fprofile-sample-use=a.prof`.
@@ -103,15 +98,15 @@ Then we can use a.prof for PGO during compilation, via `-fprofile-sample-use=a.p
### A complete example: etm_test_loop.cpp
`etm_test_loop.cpp` is an example to show the complete process.
-The source code is in [etm_test_loop.cpp](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/runtest/etm_test_loop.cpp).
-The build script is in [Android.bp](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/runtest/Android.bp).
+The source code is in [etm_test_loop.cpp](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/runtest/etm_test_loop.cpp).
+The build script is in [Android.bp](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/runtest/Android.bp).
It builds an executable called `etm_test_loop`, which runs on device.
Step 1: Build `etm_test_loop` binary.
```sh
(host) <AOSP>$ . build/envsetup.sh
-(host) <AOSP>$ lunch aosp_arm64-userdebug
+(host) <AOSP>$ lunch aosp_arm64-trunk_staging-userdebug
(host) <AOSP>$ make etm_test_loop
```
@@ -121,14 +116,13 @@ Step 2: Run `etm_test_loop` on device, and collect ETM data for its running.
(host) <AOSP>$ adb push out/target/product/generic_arm64/system/bin/etm_test_loop /data/local/tmp
(host) <AOSP>$ adb root
(host) <AOSP>$ adb shell
-(device) / # cd /data/local/tmp
-(device) /data/local/tmp # chmod a+x etm_test_loop
-(device) /data/local/tmp # simpleperf record -e cs-etm:u ./etm_test_loop
-simpleperf I cmd_record.cpp:729] Recorded for 0.0370068 seconds. Start post processing.
-simpleperf I cmd_record.cpp:799] Aux data traced: 1689136
-(device) /data/local/tmp # simpleperf inject -i perf.data --output branch-list -o branch_list.data
-simpleperf W dso.cpp:557] failed to read min virtual address of [vdso]: File not found
-(device) /data/local/tmp # exit
+(device) / $ cd /data/local/tmp
+(device) /data/local/tmp $ chmod a+x etm_test_loop
+(device) /data/local/tmp $ simpleperf record -e cs-etm:u ./etm_test_loop
+simpleperf I cmd_record.cpp:809] Recorded for 0.033556 seconds. Start post processing.
+simpleperf I cmd_record.cpp:879] Aux data traced: 1,134,720
+(device) /data/local/tmp $ simpleperf inject -i perf.data --output branch-list -o branch_list.data
+(device) /data/local/tmp $ exit
(host) <AOSP>$ adb pull /data/local/tmp/branch_list.data
```
@@ -137,28 +131,43 @@ Step 3: Convert ETM data to AutoFDO data.
```sh
# Build simpleperf tool on host.
(host) <AOSP>$ make simpleperf_ndk
-(host) <AOSP>$ simpleperf_ndk64 inject -i branch_list.data -o perf_inject_etm_test_loop.data --symdir out/target/product/generic_arm64/symbols/system/bin
-simpleperf W cmd_inject.cpp:505] failed to build instr ranges for binary [vdso]: File not found
+(host) <AOSP>$ simpleperf inject -i branch_list.data -o perf_inject_etm_test_loop.data --symdir out/target/product/generic_arm64/symbols/system/bin
(host) <AOSP>$ cat perf_inject_etm_test_loop.data
-13
-1000-1010:1
-1014-1050:1
+14
+4000-4010:1
+4014-4048:1
...
-112c->0:1
+418c->0:1
+// build_id: 0xa6fc5b506adf9884cdb680b4893c505a00000000
// /data/local/tmp/etm_test_loop
(host) <AOSP>$ create_llvm_prof -profile perf_inject_etm_test_loop.data -profiler text -binary out/target/product/generic_arm64/symbols/system/bin/etm_test_loop -out etm_test_loop.afdo -format binary
(host) <AOSP>$ ls -lh etm_test_loop.afdo
-rw-r--r-- 1 user group 241 Aug 29 16:04 etm_test_loop.afdo
+rw-r--r-- 1 user group 241 Apr 30 09:52 etm_test_loop.afdo
```
Step 4: Use AutoFDO data to build optimized binary.
```sh
-(host) <AOSP>$ mkdir toolchain/pgo-profiles/sampling/
(host) <AOSP>$ cp etm_test_loop.afdo toolchain/pgo-profiles/sampling/
+(host) <AOSP>$ vi toolchain/pgo-profiles/sampling/Android.bp
+# Edit Android.bp to add a fdo_profile module:
+#
+# fdo_profile {
+# name: "etm_test_loop",
+# profile: "etm_test_loop.afdo"
+# }
+(host) <AOSP>$ vi toolchain/pgo-profiles/sampling/afdo_profiles.mk
+# Edit afdo_profiles.mk to add etm_test_loop profile mapping:
+#
+# AFDO_PROFILES += keystore2://toolchain/pgo-profiles/sampling:keystore2 \
+# ...
+# server_configurable_flags://toolchain/pgo-profiles/sampling:server_configurable_flags \
+# etm_test_loop://toolchain/pgo-profiles/sampling:etm_test_loop
+#
(host) <AOSP>$ vi system/extras/simpleperf/runtest/Android.bp
-# edit Android.bp to enable afdo for etm_test_loop.
+# Edit Android.bp to enable afdo for etm_test_loop:
+#
# cc_binary {
# name: "etm_test_loop",
# srcs: ["etm_test_loop.cpp"],
@@ -167,6 +176,14 @@ Step 4: Use AutoFDO data to build optimized binary.
(host) <AOSP>$ make etm_test_loop
```
+We can check if `etm_test_loop.afdo` is used when building etm_test_loop.
+
+```sh
+(host) <AOSP>$ gzip -d out/verbose.log.gz
+(host) <AOSP>$ cat out/verbose.log | grep etm_test_loop.afdo
+ ... -fprofile-sample-use=toolchain/pgo-profiles/sampling/etm_test_loop.afdo ...
+```
+
If comparing the disassembly of `out/target/product/generic_arm64/symbols/system/bin/etm_test_loop`
before and after optimizing with AutoFDO data, we can see different preferences when branching.
@@ -174,7 +191,7 @@ before and after optimizing with AutoFDO data, we can see different preferences
## Collect ETM data with a daemon
Android also has a daemon collecting ETM data periodically. It only runs on userdebug and eng
-devices. The source code is in https://android.googlesource.com/platform/system/extras/+/master/profcollectd/.
+devices. The source code is in https://android.googlesource.com/platform/system/extras/+/main/profcollectd/.
## Support ETM in the kernel
diff --git a/simpleperf/doc/debug_dwarf_unwinding.md b/simpleperf/doc/debug_dwarf_unwinding.md
index 2abb9825..6a5e4a55 100644
--- a/simpleperf/doc/debug_dwarf_unwinding.md
+++ b/simpleperf/doc/debug_dwarf_unwinding.md
@@ -2,7 +2,7 @@
Dwarf unwinding is the default way of getting call graphs in simpleperf. In this process,
simpleperf asks the kernel to add stack and register data to each sample. Then it uses
-[libunwindstack](https://cs.android.com/android/platform/superproject/+/master:system/unwinding/libunwindstack/)
+[libunwindstack](https://cs.android.com/android/platform/superproject/+/main:system/unwinding/libunwindstack/)
to unwind the call stack. libunwindstack uses dwarf sections (like .debug_frame or .eh_frame) in
elf files to know how to unwind the stack.
@@ -74,6 +74,6 @@ $ simpleperf record -g -o perf_unwind.data simpleperf debug-unwind --unwind-samp
$ report_html.py -i perf_unwind.data
# We can also add source code annotation in report.html.
-$ binary_cache_builder.py -i perf_unwind.data -lib <path to aosp-master>/out/target/product/<device-name>/symbols/system
-$ report_html.py -i perf_unwind.data --add_source_code --source_dirs <path to aosp-master>/system/
+$ binary_cache_builder.py -i perf_unwind.data -lib <path to aosp-main>/out/target/product/<device-name>/symbols/system
+$ report_html.py -i perf_unwind.data --add_source_code --source_dirs <path to aosp-main>/system/
```
diff --git a/simpleperf/doc/executable_commands_reference.md b/simpleperf/doc/executable_commands_reference.md
index 0e29fbf7..b5c051a3 100644
--- a/simpleperf/doc/executable_commands_reference.md
+++ b/simpleperf/doc/executable_commands_reference.md
@@ -333,6 +333,27 @@ $ su 0 simpleperf stat --per-core -a --duration 1
$ su 0 simpleperf stat -e cpu-cycles -a --per-thread --per-core --duration 1
```
+### Monitor different events on different cores
+
+Android devices usually have big and little cores. Different cores may support different events.
+Therefore, we may want to monitor different events on different cores. We can do this using
+the `--cpu` option. The `--cpu` option selects the cores on which to monitor events. A `--cpu`
+option affects all the following events until meeting another `--cpu` option. The first `--cpu`
+option also affects all events before it. Following are some examples:
+
+```sh
+# By default, cpu-cycles and instructions are monitored on all cpus.
+$ su 0 simpleperf stat -e cpu-cycles,instructions -a --duration 1 --per-core
+
+# Use one `--cpu` option to monitor cpu-cycles and instructions only on cpu 0-3,8.
+$ su 0 simpleperf stat -e cpu-cycles --cpu 0-3,8 -e instructions -a --duration 1 --per-core
+
+# Use two `--cpu` options to monitor raw-l3d-cache-refill-rd on cpu 0-3, and raw-l3d-cache-refill on
+# cpu 4-8.
+$ su 0 simpleperf stat --cpu 0-3 -e raw-l3d-cache-refill-rd --cpu 4-8 -e raw-l3d-cache-refill \
+ -a --duration 1 --per-core
+```
+
## The record command
The record command is used to dump samples of the profiled processes. Each sample can contain
diff --git a/simpleperf/doc/jit_symbols.md b/simpleperf/doc/jit_symbols.md
index 27f87e52..e50d34fb 100644
--- a/simpleperf/doc/jit_symbols.md
+++ b/simpleperf/doc/jit_symbols.md
@@ -44,6 +44,9 @@ For example:
0x20002004 0x8 jit_symbol_three
```
+All characters after the symbol size and until the end of the line are parsed as the symbol name,
+with leading and trailing spaces removed. This means spaces are allowed in symbol names themselves.
+
### Known issues
Current implementation gets confused if memory pages where JIT symbols reside are reused by mapping
diff --git a/simpleperf/doc/sample_filter.md b/simpleperf/doc/sample_filter.md
index 3755e760..421c9758 100644
--- a/simpleperf/doc/sample_filter.md
+++ b/simpleperf/doc/sample_filter.md
@@ -7,7 +7,8 @@ ranges. To filter samples, we can pass filter options to the report commands or
## filter file format
To filter samples based on time ranges, simpleperf accepts a filter file when reporting. The filter
-file is in text format, containing a list of lines. Each line is a filter command.
+file is in text format, containing a list of lines. Each line is a filter command. The filter file
+can be generated by `sample_filter.py`, and passed to report scripts via `--filter-file`.
```
filter_command1 command_args
diff --git a/simpleperf/doc/scripts_reference.md b/simpleperf/doc/scripts_reference.md
index 31dee02d..af74458e 100644
--- a/simpleperf/doc/scripts_reference.md
+++ b/simpleperf/doc/scripts_reference.md
@@ -151,6 +151,9 @@ $ ./report_html.py --add_disassembly
# Adding disassembly for all binaries can cost a lot of time. So we can choose to only add
# disassembly for selected binaries.
$ ./report_html.py --add_disassembly --binary_filter libgame.so
+# Add disassembly and source code for binaries belonging to an app with package name
+# com.example.myapp.
+$ ./report_html.py --add_source_code --add_disassembly --binary_filter com.example.myapp
# report_html.py accepts more than one recording data file.
$ ./report_html.py -i perf1.data perf2.data
@@ -320,3 +323,38 @@ Then we can read all samples through GetNextSample(). For each sample, we can re
Examples of using `simpleperf_report_lib.py` are in `report_sample.py`, `report_html.py`,
`pprof_proto_generator.py` and `inferno/inferno.py`.
+
+## ipc.py
+`ipc.py`captures the instructions per cycle (IPC) of the system during a specified duration.
+
+Example:
+```sh
+./ipc.py
+./ipc.py 2 20 # Set interval to 2 secs and total duration to 20 secs
+./ipc.py -p 284 -C 4 # Only profile the PID 284 while running on core 4
+./ipc.py -c 'sleep 5' # Only profile the command to run
+```
+
+The results look like:
+```
+K_CYCLES K_INSTR IPC
+36840 14138 0.38
+70701 27743 0.39
+104562 41350 0.40
+138264 54916 0.40
+```
+
+## sample_filter.py
+
+`sample_filter.py` generates sample filter files as documented in [sample_filter.md](https://android.googlesource.com/platform/system/extras/+/refs/heads/main/simpleperf/doc/sample_filter.md).
+A filter file can be passed in `--filter-file` when running report scripts.
+
+For example, it can be used to split a large recording file into several report files.
+
+```sh
+$ sample_filter.py -i perf.data --split-time-range 2 -o sample_filter
+$ gecko_profile_generator.py -i perf.data --filter-file sample_filter_part1 \
+ | gzip >profile-part1.json.gz
+$ gecko_profile_generator.py -i perf.data --filter-file sample_filter_part2 \
+ | gzip >profile-part2.json.gz
+```
diff --git a/simpleperf/doc/view_the_profile.md b/simpleperf/doc/view_the_profile.md
index 8a0e07e7..ccf44724 100644
--- a/simpleperf/doc/view_the_profile.md
+++ b/simpleperf/doc/view_the_profile.md
@@ -39,18 +39,27 @@ pprof.
This will print some debug logs about Failed to read symbols: this is usually OK, unless those
symbols are hotspots.
-Upload pprof.profile to http://pprof/ UI:
+The continuous pprof server has a file upload size limit of 50MB. To get around this limit, compress
+the profile before uploading:
+
+```
+gzip pprof.profile
+```
+
+After compressing, you can upload the `pprof.profile.gz` file to either http://pprof/ or
+http://pprofng/. Both websites have an 'Upload' tab for this purpose. Alternatively, you can use
+the following `pprof` command to upload the compressed profile:
```
# Upload all threads in profile, grouped by threadpool.
# This is usually a good default, combining threads with similar names.
-pprof --flame --tagroot threadpool pprof.profile
+pprof --flame --tagroot threadpool pprof.profile.gz
# Upload all threads in profile, grouped by individual thread name.
-pprof --flame --tagroot thread pprof.profile
+pprof --flame --tagroot thread pprof.profile.gz
# Upload all threads in profile, without grouping by thread.
-pprof --flame pprof.profile
+pprof --flame pprof.profile.gz
This will output a URL, example: https://pprof.corp.google.com/?id=589a60852306144c880e36429e10b166
```
@@ -58,6 +67,7 @@ This will output a URL, example: https://pprof.corp.google.com/?id=589a608523061
We can view Android profiles using Firefox Profiler: https://profiler.firefox.com/. This does not
require Firefox installation -- Firefox Profiler is just a website, you can open it in any browser.
+There is also an internal Google-Hosted Firefox Profiler, at go/profiler or go/firefox-profiler.
![Example](./pictures/firefox_profiler.png)
@@ -310,7 +320,7 @@ Showing nodes accounting for 1.05s, 4.88% of 21.46s total
1.05s 4.88% 4.88% 1.05s 4.88% com.example.android.displayingbitmaps.util.ImageCache.addBitmapToCache
```
-For more information, see the [pprof README](https://github.com/google/pprof/blob/master/doc/README.md#interactive-terminal-use).
+For more information, see the [pprof README](https://github.com/google/pprof/blob/main/doc/README.md#interactive-terminal-use).
## Simpleperf Report Command Line
diff --git a/simpleperf/dso.cpp b/simpleperf/dso.cpp
index 32b6731e..1380ada9 100644
--- a/simpleperf/dso.cpp
+++ b/simpleperf/dso.cpp
@@ -501,6 +501,11 @@ class DexFileDso : public Dso {
std::vector<Symbol> LoadSymbolsImpl() override {
std::vector<Symbol> symbols;
+ if (StartsWith(path_, kDexFileInMemoryPrefix)) {
+ // For dex file in memory, the symbols should already be set via SetSymbols().
+ return symbols;
+ }
+
const std::string& debug_file_path = GetDebugFilePath();
auto tuple = SplitUrlInApk(debug_file_path);
// Symbols of dex files are collected on device. If the dex file doesn't exist, probably
@@ -573,8 +578,10 @@ class ElfDso : public Dso {
if (elf) {
min_vaddr_ = elf->ReadMinExecutableVaddr(&file_offset_of_min_vaddr_);
} else {
- LOG(WARNING) << "failed to read min virtual address of " << GetDebugFilePath() << ": "
- << status;
+ // This is likely to be a file wrongly thought of as an ELF file, due to stack unwinding.
+ // No need to report it by default.
+ LOG(DEBUG) << "failed to read min virtual address of " << GetDebugFilePath() << ": "
+ << status;
}
}
*min_vaddr = min_vaddr_;
@@ -637,14 +644,13 @@ class ElfDso : public Dso {
status = elf->ParseSymbols(symbol_callback);
}
android::base::LogSeverity log_level = android::base::WARNING;
- if (!symbols_.empty()) {
+ if (!symbols_.empty() || !symbols.empty()) {
// We already have some symbols when recording.
log_level = android::base::DEBUG;
}
if ((status == ElfStatus::FILE_NOT_FOUND || status == ElfStatus::FILE_MALFORMED) &&
build_id.IsEmpty()) {
- // This is likely to be a file wongly thought of as an ELF file, maybe due to stack
- // unwinding.
+ // This is likely to be a file wrongly thought of as an ELF file, due to stack unwinding.
log_level = android::base::DEBUG;
}
ReportReadElfSymbolResult(status, path_, GetDebugFilePath(), log_level);
@@ -663,17 +669,7 @@ class ElfDso : public Dso {
class KernelDso : public Dso {
public:
- KernelDso(const std::string& path) : Dso(DSO_KERNEL, path) {
- debug_file_path_ = FindDebugFilePath();
- if (!vmlinux_.empty()) {
- // Use vmlinux as the kernel debug file.
- BuildId build_id = GetExpectedBuildId();
- ElfStatus status;
- if (ElfFile::Open(vmlinux_, &build_id, &status)) {
- debug_file_path_ = vmlinux_;
- }
- }
- }
+ KernelDso(const std::string& path) : Dso(DSO_KERNEL, path) {}
// IpToVaddrInFile() and LoadSymbols() must be consistent in fixing addresses changed by kernel
// address space layout randomization.
@@ -696,6 +692,13 @@ class KernelDso : public Dso {
protected:
std::string FindDebugFilePath() const override {
BuildId build_id = GetExpectedBuildId();
+ if (!vmlinux_.empty()) {
+ // Use vmlinux as the kernel debug file.
+ ElfStatus status;
+ if (ElfFile::Open(vmlinux_, &build_id, &status)) {
+ return vmlinux_;
+ }
+ }
return debug_elf_file_finder_.FindDebugFile(path_, false, build_id);
}
@@ -712,7 +715,7 @@ class KernelDso : public Dso {
}
#endif // defined(__linux__)
SortAndFixSymbols(symbols);
- if (!symbols.empty()) {
+ if (!symbols.empty() && symbols.back().len == 0) {
symbols.back().len = std::numeric_limits<uint64_t>::max() - symbols.back().addr;
}
return symbols;
@@ -872,8 +875,8 @@ class KernelModuleDso : public Dso {
if (elf) {
status = elf->ParseSymbols(symbol_callback);
}
- ReportReadElfSymbolResult(status, path_, GetDebugFilePath(),
- symbols_.empty() ? android::base::WARNING : android::base::DEBUG);
+ // Don't warn when a kernel module is missing. As a backup, we read symbols from /proc/kallsyms.
+ ReportReadElfSymbolResult(status, path_, GetDebugFilePath(), android::base::DEBUG);
SortAndFixSymbols(symbols);
return symbols;
}
@@ -894,6 +897,10 @@ class KernelModuleDso : public Dso {
// and its vaddr_in_file from the kernel module file. Then other symbols in .text section can
// be mapped in the same way. Below we use the second method.
+ if (!IsRegularFile(GetDebugFilePath())) {
+ return;
+ }
+
// 1. Select a module symbol in /proc/kallsyms.
kernel_dso_->LoadSymbols();
const auto& kernel_symbols = kernel_dso_->GetSymbols();
@@ -1024,4 +1031,30 @@ bool GetBuildIdFromDsoPath(const std::string& dso_path, BuildId* build_id) {
return false;
}
+bool GetBuildId(const Dso& dso, BuildId& build_id) {
+ if (dso.type() == DSO_KERNEL) {
+ if (GetKernelBuildId(&build_id)) {
+ return true;
+ }
+ } else if (dso.type() == DSO_KERNEL_MODULE) {
+ bool has_build_id = false;
+ if (android::base::EndsWith(dso.Path(), ".ko")) {
+ return GetBuildIdFromDsoPath(dso.Path(), &build_id);
+ }
+ if (const std::string& path = dso.Path();
+ path.size() > 2 && path[0] == '[' && path.back() == ']') {
+ // For kernel modules that we can't find the corresponding file, read build id from /sysfs.
+ return GetModuleBuildId(path.substr(1, path.size() - 2), &build_id);
+ }
+ } else if (dso.type() == DSO_ELF_FILE) {
+ if (dso.Path() == DEFAULT_EXECNAME_FOR_THREAD_MMAP || dso.IsForJavaMethod()) {
+ return false;
+ }
+ if (GetBuildIdFromDsoPath(dso.Path(), &build_id)) {
+ return true;
+ }
+ }
+ return false;
+}
+
} // namespace simpleperf
diff --git a/simpleperf/dso.h b/simpleperf/dso.h
index 30427766..41cab757 100644
--- a/simpleperf/dso.h
+++ b/simpleperf/dso.h
@@ -228,6 +228,7 @@ class Dso {
const char* DsoTypeToString(DsoType dso_type);
bool GetBuildIdFromDsoPath(const std::string& dso_path, BuildId* build_id);
+bool GetBuildId(const Dso& dso, BuildId& build_id);
} // namespace simpleperf
diff --git a/simpleperf/dso_test.cpp b/simpleperf/dso_test.cpp
index 11bcde19..f9199e41 100644
--- a/simpleperf/dso_test.cpp
+++ b/simpleperf/dso_test.cpp
@@ -30,6 +30,7 @@
using namespace simpleperf;
using namespace simpleperf_dso_impl;
+// @CddTest = 6.1/C-0-2
TEST(DebugElfFileFinder, use_build_id_list) {
// Create a temp symdir with build_id_list.
TemporaryDir tmpdir;
@@ -57,6 +58,7 @@ static std::string ConvertPathSeparator(const std::string& path) {
return result;
}
+// @CddTest = 6.1/C-0-2
TEST(DebugElfFileFinder, concatenating_symfs_dir) {
DebugElfFileFinder finder;
ASSERT_TRUE(finder.SetSymFsDir(GetTestDataDir()));
@@ -73,6 +75,7 @@ TEST(DebugElfFileFinder, concatenating_symfs_dir) {
GetTestDataDir() + apk_path + "!/" + NATIVELIB_IN_APK);
}
+// @CddTest = 6.1/C-0-2
TEST(DebugElfFileFinder, use_vdso) {
DebugElfFileFinder finder;
std::string fake_vdso32 = "fake_vdso32";
@@ -84,6 +87,7 @@ TEST(DebugElfFileFinder, use_vdso) {
ASSERT_EQ(finder.FindDebugFile("[vdso]", true, build_id), fake_vdso64);
}
+// @CddTest = 6.1/C-0-2
TEST(DebugElfFileFinder, add_symbol_dir) {
DebugElfFileFinder finder;
ASSERT_FALSE(finder.AddSymbolDir(GetTestDataDir() + "dir_not_exist"));
@@ -94,6 +98,7 @@ TEST(DebugElfFileFinder, add_symbol_dir) {
symfs_dir + OS_PATH_SEPARATOR + "elf_for_build_id_check");
}
+// @CddTest = 6.1/C-0-2
TEST(DebugElfFileFinder, build_id_list) {
DebugElfFileFinder finder;
// Find file in symfs dir with correct build_id_list.
@@ -109,6 +114,7 @@ TEST(DebugElfFileFinder, build_id_list) {
ASSERT_EQ(finder.FindDebugFile("elf", false, CHECK_ELF_FILE_BUILD_ID), "elf");
}
+// @CddTest = 6.1/C-0-2
TEST(DebugElfFileFinder, no_build_id) {
DebugElfFileFinder finder;
// If not given a build id, we should match an elf in symfs without build id.
@@ -118,6 +124,7 @@ TEST(DebugElfFileFinder, no_build_id) {
ASSERT_EQ(finder.FindDebugFile("elf", false, build_id), symfs_dir + OS_PATH_SEPARATOR + "elf");
}
+// @CddTest = 6.1/C-0-2
TEST(DebugElfFileFinder, find_basename_in_symfs_dir) {
DebugElfFileFinder finder;
// Find normal elf file.
@@ -136,6 +143,7 @@ TEST(DebugElfFileFinder, find_basename_in_symfs_dir) {
symfs_dir + OS_PATH_SEPARATOR + "elf");
}
+// @CddTest = 6.1/C-0-2
TEST(DebugElfFileFinder, build_id_mismatch) {
DebugElfFileFinder finder;
finder.SetSymFsDir(GetTestDataDir());
@@ -148,6 +156,7 @@ TEST(DebugElfFileFinder, build_id_mismatch) {
ASSERT_NE(stderr_output.find("build id mismatch"), std::string::npos);
}
+// @CddTest = 6.1/C-0-2
TEST(dso, dex_file_dso) {
#if defined(__linux__)
for (DsoType dso_type : {DSO_DEX_FILE, DSO_ELF_FILE}) {
@@ -177,6 +186,7 @@ TEST(dso, dex_file_dso) {
#endif // defined(__linux__)
}
+// @CddTest = 6.1/C-0-2
TEST(dso, dex_file_offsets) {
std::unique_ptr<Dso> dso = Dso::CreateDso(DSO_DEX_FILE, "");
ASSERT_TRUE(dso);
@@ -186,6 +196,7 @@ TEST(dso, dex_file_offsets) {
ASSERT_EQ(*dso->DexFileOffsets(), std::vector<uint64_t>({0x1, 0x2, 0x3, 0x4, 0x5}));
}
+// @CddTest = 6.1/C-0-2
TEST(dso, embedded_elf) {
const std::string file_path = GetUrlInApk(GetTestData(APK_FILE), NATIVELIB_IN_APK);
std::unique_ptr<Dso> dso = Dso::CreateDso(DSO_ELF_FILE, file_path);
@@ -205,12 +216,14 @@ TEST(dso, embedded_elf) {
ASSERT_EQ(build_id, native_lib_build_id);
}
+// @CddTest = 6.1/C-0-2
TEST(dso, IpToVaddrInFile) {
std::unique_ptr<Dso> dso = Dso::CreateDso(DSO_ELF_FILE, GetTestData("libc.so"));
ASSERT_TRUE(dso);
ASSERT_EQ(0xa5140, dso->IpToVaddrInFile(0xe9201140, 0xe9201000, 0xa5000));
}
+// @CddTest = 6.1/C-0-2
TEST(dso, kernel_address_randomization) {
// Use ELF_FILE as a fake kernel vmlinux.
const std::string vmlinux_path = GetTestData(ELF_FILE);
@@ -234,6 +247,7 @@ TEST(dso, kernel_address_randomization) {
ASSERT_STREQ(symbol->Name(), "GlobalFunc");
}
+// @CddTest = 6.1/C-0-2
TEST(dso, find_vmlinux_in_symdirs) {
// Create a symdir.
TemporaryDir tmpdir;
@@ -260,6 +274,7 @@ TEST(dso, find_vmlinux_in_symdirs) {
ASSERT_EQ(0x400927, dso->IpToVaddrInFile(0x800527, 0x800000, 0));
}
+// @CddTest = 6.1/C-0-2
TEST(dso, kernel_module) {
// Test finding debug files for kernel modules.
Dso::SetSymFsDir(GetTestDataDir());
@@ -272,14 +287,16 @@ TEST(dso, kernel_module) {
ASSERT_EQ(dso->GetDebugFilePath(), GetTestData(ELF_FILE));
}
+// @CddTest = 6.1/C-0-2
TEST(dso, kernel_module_CalculateMinVaddr) {
// Create fake Dso objects.
auto kernel_dso = Dso::CreateDso(DSO_KERNEL, DEFAULT_KERNEL_MMAP_NAME);
ASSERT_TRUE(kernel_dso);
const uint64_t module_memory_start = 0xffffffa9bc790000ULL;
const uint64_t module_memory_size = 0x8d7000ULL;
+ TemporaryFile tmpfile;
auto module_dso =
- Dso::CreateKernelModuleDso("fake_module.ko", module_memory_start,
+ Dso::CreateKernelModuleDso(tmpfile.path, module_memory_start,
module_memory_start + module_memory_size, kernel_dso.get());
ASSERT_TRUE(module_dso);
@@ -302,6 +319,7 @@ TEST(dso, kernel_module_CalculateMinVaddr) {
ASSERT_EQ(module_dso->IpToVaddrInFile(0xffffffa9bc7a64e8ULL, module_memory_start, 0), 0x144e8);
}
+// @CddTest = 6.1/C-0-2
TEST(dso, symbol_map_file) {
auto dso = Dso::CreateDso(DSO_SYMBOL_MAP_FILE, "perf-123.map");
ASSERT_TRUE(dso);
@@ -310,6 +328,7 @@ TEST(dso, symbol_map_file) {
ASSERT_EQ(0x12345678, dso->IpToVaddrInFile(0x12345678, 0xe9201000, 0xa5000));
}
+// @CddTest = 6.1/C-0-2
TEST(dso, FunctionName) {
Symbol symbol = Symbol("void ctep.v(cteo, ctgc, ctbn)", 0x0, 0x1);
ASSERT_EQ(symbol.FunctionName(), "ctep.v");
@@ -319,6 +338,7 @@ TEST(dso, FunctionName) {
ASSERT_EQ(symbol.FunctionName(), "ctep.v");
}
+// @CddTest = 6.1/C-0-2
TEST(dso, search_debug_file_only_when_needed) {
Dso::SetBuildIds({std::make_pair("/elf", BuildId("1b12a384a9f4a3f3659b7171ca615dbec3a81f71"))});
Dso::SetSymFsDir(GetTestDataDir());
@@ -330,6 +350,7 @@ TEST(dso, search_debug_file_only_when_needed) {
capture.Stop();
}
+// @CddTest = 6.1/C-0-2
TEST(dso, read_symbol_warning) {
{
// Don't warn when the file may not be an ELF file.
@@ -370,6 +391,7 @@ TEST(dso, read_symbol_warning) {
}
}
+// @CddTest = 6.1/C-0-2
TEST(dso, demangle) {
ASSERT_EQ(Dso::Demangle("main"), "main");
ASSERT_EQ(Dso::Demangle("_ZN4main4main17h2a68d4d833d7495aE"), "main::main::h2a68d4d833d7495a");
diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp
index 8c4b9576..55074d8a 100644
--- a/simpleperf/environment.cpp
+++ b/simpleperf/environment.cpp
@@ -26,6 +26,7 @@
#include <unistd.h>
#include <limits>
+#include <optional>
#include <set>
#include <unordered_map>
#include <vector>
@@ -74,6 +75,9 @@ std::vector<int> GetOnlineCpus() {
static void GetAllModuleFiles(const std::string& path,
std::unordered_map<std::string, std::string>* module_file_map) {
+ if (!IsDir(path)) {
+ return;
+ }
for (const auto& name : GetEntriesInDir(path)) {
std::string entry_path = path + "/" + name;
if (IsRegularFile(entry_path) && android::base::EndsWith(name, ".ko")) {
@@ -93,9 +97,13 @@ static std::vector<KernelMmap> GetModulesInUse() {
}
std::unordered_map<std::string, std::string> module_file_map;
#if defined(__ANDROID__)
- // Search directories listed in "File locations" section in
- // https://source.android.com/devices/architecture/kernel/modular-kernels.
- for (const auto& path : {"/vendor/lib/modules", "/odm/lib/modules", "/lib/modules"}) {
+ // On Android, kernel modules are stored in /system/lib/modules, /vendor/lib/modules,
+ // /odm/lib/modules.
+ // See https://source.android.com/docs/core/architecture/partitions/gki-partitions and
+ // https://source.android.com/docs/core/architecture/partitions/vendor-odm-dlkm-partition.
+ // They can also be stored in vendor_kernel_ramdisk.img, which isn't accessible from userspace.
+ // See https://source.android.com/docs/core/architecture/kernel/kernel-module-support.
+ for (const auto& path : {"/system/lib/modules", "/vendor/lib/modules", "/odm/lib/modules"}) {
GetAllModuleFiles(path, &module_file_map);
}
#else
@@ -212,18 +220,19 @@ bool GetModuleBuildId(const std::string& module_name, BuildId* build_id,
*/
static const char* perf_event_allow_path = "/proc/sys/kernel/perf_event_paranoid";
-static bool ReadPerfEventAllowStatus(int* value) {
+static std::optional<int> ReadPerfEventAllowStatus() {
std::string s;
if (!android::base::ReadFileToString(perf_event_allow_path, &s)) {
PLOG(DEBUG) << "failed to read " << perf_event_allow_path;
- return false;
+ return std::nullopt;
}
s = android::base::Trim(s);
- if (!android::base::ParseInt(s.c_str(), value)) {
+ int value;
+ if (!android::base::ParseInt(s.c_str(), &value)) {
PLOG(ERROR) << "failed to parse " << perf_event_allow_path << ": " << s;
- return false;
+ return std::nullopt;
}
- return true;
+ return value;
}
bool CanRecordRawData() {
@@ -236,11 +245,20 @@ bool CanRecordRawData() {
// users.
return false;
#else
- int value;
- return ReadPerfEventAllowStatus(&value) && value == -1;
+ return ReadPerfEventAllowStatus() == -1;
#endif
}
+std::optional<uint64_t> GetMemorySize() {
+ std::unique_ptr<FILE, decltype(&fclose)> fp(fopen("/proc/meminfo", "r"), fclose);
+ uint64_t size;
+ if (fp && fscanf(fp.get(), "MemTotal:%" PRIu64 " k", &size) == 1) {
+ return size * kKilobyte;
+ }
+ PLOG(ERROR) << "failed to get memory size";
+ return std::nullopt;
+}
+
static const char* GetLimitLevelDescription(int limit_level) {
switch (limit_level) {
case -1:
@@ -259,11 +277,16 @@ static const char* GetLimitLevelDescription(int limit_level) {
}
bool CheckPerfEventLimit() {
+ std::optional<int> old_level = ReadPerfEventAllowStatus();
+
// Root is not limited by perf_event_allow_path. However, the monitored threads
// may create child processes not running as root. To make sure the child processes have
// enough permission to create inherited tracepoint events, write -1 to perf_event_allow_path.
// See http://b/62230699.
if (IsRoot()) {
+ if (old_level == -1) {
+ return true;
+ }
if (android::base::WriteStringToFile("-1", perf_event_allow_path)) {
return true;
}
@@ -273,9 +296,7 @@ bool CheckPerfEventLimit() {
return false;
#endif
}
- int limit_level;
- bool can_read_allow_file = ReadPerfEventAllowStatus(&limit_level);
- if (can_read_allow_file && limit_level <= 1) {
+ if (old_level.has_value() && old_level <= 1) {
return true;
}
#if defined(__ANDROID__)
@@ -291,23 +312,24 @@ bool CheckPerfEventLimit() {
// Try to enable perf events by setprop security.perf_harden=0.
if (android::base::SetProperty(prop_name, "0")) {
sleep(1);
- if (can_read_allow_file && ReadPerfEventAllowStatus(&limit_level) && limit_level <= 1) {
+ // Check the result of setprop, by reading allow status or the property value.
+ if (auto level = ReadPerfEventAllowStatus(); level.has_value() && level <= 1) {
return true;
}
if (android::base::GetProperty(prop_name, "") == "0") {
return true;
}
}
- if (can_read_allow_file) {
- LOG(ERROR) << perf_event_allow_path << " is " << limit_level << ", "
- << GetLimitLevelDescription(limit_level) << ".";
+ if (old_level.has_value()) {
+ LOG(ERROR) << perf_event_allow_path << " is " << old_level.value() << ", "
+ << GetLimitLevelDescription(old_level.value()) << ".";
}
LOG(ERROR) << "Try using `adb shell setprop security.perf_harden 0` to allow profiling.";
return false;
#else
- if (can_read_allow_file) {
- LOG(ERROR) << perf_event_allow_path << " is " << limit_level << ", "
- << GetLimitLevelDescription(limit_level) << ". Try using `echo -1 >"
+ if (old_level.has_value()) {
+ LOG(ERROR) << perf_event_allow_path << " is " << old_level.value() << ", "
+ << GetLimitLevelDescription(old_level.value()) << ". Try using `echo -1 >"
<< perf_event_allow_path << "` to enable profiling.";
return false;
}
@@ -333,9 +355,10 @@ bool SetPerfEventLimits(uint64_t sample_freq, size_t cpu_percent, uint64_t mlock
}
// Wait for init process to change perf event limits based on properties.
const size_t max_wait_us = 3 * 1000000;
+ const size_t interval_us = 10000;
int finish_mask = 0;
- for (size_t i = 0; i < max_wait_us && finish_mask != 7; ++i) {
- usleep(1); // Wait 1us to avoid busy loop.
+ for (size_t i = 0; i < max_wait_us && finish_mask != 7; i += interval_us) {
+ usleep(interval_us); // Wait 10ms to avoid busy loop.
if ((finish_mask & 1) == 0) {
uint64_t freq;
if (!GetMaxSampleFrequency(&freq) || freq == sample_freq) {
@@ -955,7 +978,9 @@ std::string GetCompleteProcessName(pid_t pid) {
if (pos != std::string::npos) {
argv0.resize(pos);
}
- return android::base::Basename(argv0);
+ // argv0 can be empty if the process is in zombie state. In that case, we don't want to pass argv0
+ // to Basename(), which returns ".".
+ return argv0.empty() ? std::string() : android::base::Basename(argv0);
}
const char* GetTraceFsDir() {
@@ -1011,4 +1036,54 @@ std::optional<uid_t> GetProcessUid(pid_t pid) {
return std::nullopt;
}
+std::vector<ARMCpuModel> GetARMCpuModels() {
+ std::vector<ARMCpuModel> cpu_models;
+ LineReader reader("/proc/cpuinfo");
+ if (!reader.Ok()) {
+ return cpu_models;
+ }
+ auto add_cpu = [&](uint32_t processor, uint32_t implementer, uint32_t partnum) {
+ for (auto& model : cpu_models) {
+ if (model.implementer == implementer && model.partnum == partnum) {
+ model.cpus.push_back(processor);
+ return;
+ }
+ }
+ cpu_models.resize(cpu_models.size() + 1);
+ ARMCpuModel& model = cpu_models.back();
+ model.implementer = implementer;
+ model.partnum = partnum;
+ model.cpus.push_back(processor);
+ };
+
+ uint32_t processor = 0;
+ uint32_t implementer = 0;
+ uint32_t partnum = 0;
+ int parsed = 0;
+ std::string* line;
+ while ((line = reader.ReadLine()) != nullptr) {
+ std::vector<std::string> strs = android::base::Split(*line, ":");
+ if (strs.size() != 2) {
+ continue;
+ }
+ std::string name = android::base::Trim(strs[0]);
+ std::string value = android::base::Trim(strs[1]);
+ if (name == "processor") {
+ if (android::base::ParseUint(value, &processor)) {
+ parsed |= 1;
+ }
+ } else if (name == "CPU implementer") {
+ if (android::base::ParseUint(value, &implementer)) {
+ parsed |= 2;
+ }
+ } else if (name == "CPU part") {
+ if (android::base::ParseUint(value, &partnum) && parsed == 0x3) {
+ add_cpu(processor, implementer, partnum);
+ }
+ parsed = 0;
+ }
+ }
+ return cpu_models;
+}
+
} // namespace simpleperf
diff --git a/simpleperf/environment.h b/simpleperf/environment.h
index 34ce7fa5..b9447fec 100644
--- a/simpleperf/environment.h
+++ b/simpleperf/environment.h
@@ -85,6 +85,7 @@ bool SetCpuTimeMaxPercent(size_t percent);
bool GetPerfEventMlockKb(uint64_t* mlock_kb);
bool SetPerfEventMlockKb(uint64_t mlock_kb);
bool CanRecordRawData();
+std::optional<uint64_t> GetMemorySize();
ArchType GetMachineArch();
void PrepareVdsoFile();
@@ -151,6 +152,14 @@ static inline int gettid() {
}
#endif
+struct ARMCpuModel {
+ uint32_t implementer = 0;
+ uint32_t partnum = 0;
+ std::vector<int> cpus;
+};
+
+std::vector<ARMCpuModel> GetARMCpuModels();
+
#endif // defined(__linux__)
std::optional<uint32_t> GetProcessUid(pid_t pid);
diff --git a/simpleperf/environment_test.cpp b/simpleperf/environment_test.cpp
index a95caca8..49a9bb53 100644
--- a/simpleperf/environment_test.cpp
+++ b/simpleperf/environment_test.cpp
@@ -29,6 +29,7 @@
namespace fs = std::filesystem;
using namespace simpleperf;
+// @CddTest = 6.1/C-0-2
TEST(environment, PrepareVdsoFile) {
std::string content;
ASSERT_TRUE(android::base::ReadFileToString("/proc/self/maps", &content));
@@ -46,6 +47,7 @@ TEST(environment, PrepareVdsoFile) {
ASSERT_NE(dso->GetDebugFilePath(), "[vdso]");
}
+// @CddTest = 6.1/C-0-2
TEST(environment, GetHardwareFromCpuInfo) {
std::string cpu_info =
"CPU revision : 10\n\n"
@@ -54,6 +56,7 @@ TEST(environment, GetHardwareFromCpuInfo) {
GetHardwareFromCpuInfo(cpu_info));
}
+// @CddTest = 6.1/C-0-2
TEST(environment, MappedFileOnlyExistInMemory) {
ASSERT_TRUE(MappedFileOnlyExistInMemory(""));
ASSERT_TRUE(MappedFileOnlyExistInMemory("[stack]"));
@@ -66,6 +69,7 @@ TEST(environment, MappedFileOnlyExistInMemory) {
ASSERT_FALSE(MappedFileOnlyExistInMemory("/system/lib64/libc.so"));
}
+// @CddTest = 6.1/C-0-2
TEST(environment, SetPerfEventLimits) {
#if defined(__ANDROID__)
if (GetAndroidVersion() <= kAndroidVersionP) {
@@ -101,10 +105,12 @@ TEST(environment, SetPerfEventLimits) {
#endif
}
+// @CddTest = 6.1/C-0-2
TEST(environment, GetKernelVersion) {
ASSERT_TRUE(GetKernelVersion());
}
+// @CddTest = 6.1/C-0-2
TEST(environment, GetModuleBuildId) {
BuildId build_id;
fs::path dir(GetTestData("sysfs/module/fake_kernel_module/notes"));
@@ -114,6 +120,7 @@ TEST(environment, GetModuleBuildId) {
ASSERT_EQ(build_id, BuildId("3e0ba155286f3454"));
}
+// @CddTest = 6.1/C-0-2
TEST(environment, GetKernelAndModuleMmaps) {
TEST_REQUIRE_ROOT();
KernelMmap kernel_mmap;
@@ -124,15 +131,34 @@ TEST(environment, GetKernelAndModuleMmaps) {
ASSERT_GT(kernel_mmap.start_addr, 0);
}
+// @CddTest = 6.1/C-0-2
TEST(environment, GetProcessUid) {
std::optional<uid_t> uid = GetProcessUid(getpid());
ASSERT_TRUE(uid.has_value());
ASSERT_EQ(uid.value(), getuid());
}
+// @CddTest = 6.1/C-0-2
TEST(environment, GetAppType) {
TEST_REQUIRE_APPS();
ASSERT_EQ(GetAppType("com.android.simpleperf.debuggable"), "debuggable");
ASSERT_EQ(GetAppType("com.android.simpleperf.profileable"), "profileable");
ASSERT_EQ(GetAppType("com.android.simpleperf.app_not_exist"), "not_exist");
}
+
+// @CddTest = 6.1/C-0-2
+TEST(environment, GetMemorySize) {
+ auto value = GetMemorySize();
+ ASSERT_TRUE(value);
+ ASSERT_GT(value.value(), 0);
+}
+
+// @CddTest = 6.1/C-0-2
+TEST(environment, GetARMCpuModels) {
+#if defined(__aarch64__) && defined(__ANDROID__)
+ auto models = GetARMCpuModels();
+ ASSERT_FALSE(models.empty());
+ ASSERT_FALSE(models[0].cpus.empty());
+ ASSERT_EQ(models[0].cpus[0], 0);
+#endif // defined(__aarch64__) && defined(__ANDROID__)
+}
diff --git a/simpleperf/etm_branch_list.proto b/simpleperf/etm_branch_list.proto
deleted file mode 100644
index c66b0d5e..00000000
--- a/simpleperf/etm_branch_list.proto
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2020 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.
- */
-
-syntax = "proto3";
-
-package simpleperf.proto;
-
-message ETMBranchList {
-
- message Binary {
- string path = 1;
- string build_id = 2;
-
- message Address {
- // vaddr in binary, instr addr before the first branch
- uint64 addr = 1;
-
- message Branch {
- // Each bit represents a branch: 0 for not branch, 1 for branch.
- // Bit 0 comes first, bit 7 comes last.
- bytes branch = 1;
- uint32 branch_size = 2;
- uint64 count = 3;
- }
-
- repeated Branch branches = 2;
- }
-
- repeated Address addrs = 3;
-
- enum BinaryType {
- ELF_FILE = 0;
- KERNEL = 1;
- KERNEL_MODULE = 2;
- }
- BinaryType type = 4;
-
- message KernelBinaryInfo {
- // kernel_start_addr is used to convert kernel ip address to vaddr in vmlinux.
- // If it is zero, the Address in KERNEL binary has been converted to vaddr. Otherwise,
- // the Address in KERNEL binary is still ip address, and need to be converted later.
- uint64 kernel_start_addr = 1;
- }
-
- KernelBinaryInfo kernel_info = 5;
-
- }
-
- // Used to identify format in generated proto files.
- // Should always be "simpleperf:EtmBranchList".
- string magic = 1;
- repeated Binary binaries = 2;
-}
diff --git a/simpleperf/event_attr.cpp b/simpleperf/event_attr.cpp
index dea77a94..b8acc341 100644
--- a/simpleperf/event_attr.cpp
+++ b/simpleperf/event_attr.cpp
@@ -145,7 +145,7 @@ void DumpPerfEventAttr(const perf_event_attr& attr, size_t indent) {
PrintIndented(indent + 1, "sample_stack_user 0x%" PRIx64 "\n", attr.sample_stack_user);
}
-bool GetCommonEventIdPositionsForAttrs(std::vector<perf_event_attr>& attrs,
+bool GetCommonEventIdPositionsForAttrs(const EventAttrIds& attrs,
size_t* event_id_pos_in_sample_records,
size_t* event_id_reverse_pos_in_non_sample_records) {
// When there are more than one perf_event_attrs, we need to read event id
@@ -153,7 +153,7 @@ bool GetCommonEventIdPositionsForAttrs(std::vector<perf_event_attr>& attrs,
// we need to determine the event id position in a record here.
std::vector<uint64_t> sample_types;
for (const auto& attr : attrs) {
- sample_types.push_back(attr.sample_type);
+ sample_types.push_back(attr.attr.sample_type);
}
// First determine event_id_pos_in_sample_records.
// If PERF_SAMPLE_IDENTIFIER is enabled, it is just after perf_event_header.
@@ -192,7 +192,7 @@ bool GetCommonEventIdPositionsForAttrs(std::vector<perf_event_attr>& attrs,
// also be the same.
bool sample_id_all_enabled = true;
for (const auto& attr : attrs) {
- if (attr.sample_id_all == 0) {
+ if (attr.attr.sample_id_all == 0) {
sample_id_all_enabled = false;
}
}
@@ -254,4 +254,11 @@ std::string GetEventNameByAttr(const perf_event_attr& attr) {
return name;
}
+void ReplaceRegAndStackWithCallChain(perf_event_attr& attr) {
+ attr.sample_type &= ~(PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER);
+ attr.exclude_callchain_user = 0;
+ attr.sample_regs_user = 0;
+ attr.sample_stack_user = 0;
+}
+
} // namespace simpleperf
diff --git a/simpleperf/event_attr.h b/simpleperf/event_attr.h
index a9d8bba7..5521f328 100644
--- a/simpleperf/event_attr.h
+++ b/simpleperf/event_attr.h
@@ -18,6 +18,7 @@
#define SIMPLE_PERF_EVENT_ATTR_H_
#include <stddef.h>
+#include <string.h>
#include <string>
#include <vector>
@@ -29,15 +30,17 @@ namespace simpleperf {
struct EventType;
struct EventAttrWithId {
- const perf_event_attr* attr;
+ perf_event_attr attr;
std::vector<uint64_t> ids;
};
+using EventAttrIds = std::vector<EventAttrWithId>;
+
inline constexpr uint64_t INFINITE_SAMPLE_PERIOD = 1ULL << 62;
perf_event_attr CreateDefaultPerfEventAttr(const EventType& event_type);
void DumpPerfEventAttr(const perf_event_attr& attr, size_t indent = 0);
-bool GetCommonEventIdPositionsForAttrs(std::vector<perf_event_attr>& attrs,
+bool GetCommonEventIdPositionsForAttrs(const EventAttrIds& attrs,
size_t* event_id_pos_in_sample_records,
size_t* event_id_reverse_pos_in_non_sample_records);
bool IsTimestampSupported(const perf_event_attr& attr);
@@ -45,6 +48,15 @@ bool IsCpuSupported(const perf_event_attr& attr);
// Return event name with modifier if the event is found, otherwise return "unknown".
// This function is slow for using linear search, so only used when reporting.
std::string GetEventNameByAttr(const perf_event_attr& attr);
+void ReplaceRegAndStackWithCallChain(perf_event_attr& attr);
+
+inline bool operator==(const perf_event_attr& attr1, const perf_event_attr& attr2) {
+ return memcmp(&attr1, &attr2, sizeof(perf_event_attr)) == 0;
+}
+
+inline bool operator!=(const perf_event_attr& attr1, const perf_event_attr& attr2) {
+ return !(attr1 == attr2);
+}
} // namespace simpleperf
diff --git a/simpleperf/event_selection_set.cpp b/simpleperf/event_selection_set.cpp
index 58b4b956..1a7cdef8 100644
--- a/simpleperf/event_selection_set.cpp
+++ b/simpleperf/event_selection_set.cpp
@@ -233,6 +233,8 @@ bool EventSelectionSet::BuildAndCheckEventSelection(const std::string& event_nam
// enabling/disabling etm devices. So don't adjust frequency by default.
selection->event_attr.freq = 0;
selection->event_attr.sample_period = 1;
+ // An ETM event can't be enabled without mmap aux buffer. So disable it by default.
+ selection->event_attr.disabled = 1;
} else {
selection->event_attr.freq = 1;
// Set default sample freq here may print msg "Adjust sample freq to max allowed sample
@@ -264,7 +266,7 @@ bool EventSelectionSet::BuildAndCheckEventSelection(const std::string& event_nam
selection->event_fds.clear();
for (const auto& group : groups_) {
- for (const auto& sel : group) {
+ for (const auto& sel : group.selections) {
if (sel.event_type_modifier.name == selection->event_type_modifier.name) {
LOG(ERROR) << "Event type '" << sel.event_type_modifier.name << "' appears more than once";
return false;
@@ -274,12 +276,19 @@ bool EventSelectionSet::BuildAndCheckEventSelection(const std::string& event_nam
return true;
}
-bool EventSelectionSet::AddEventType(const std::string& event_name, size_t* group_id) {
- return AddEventGroup(std::vector<std::string>(1, event_name), group_id);
+bool EventSelectionSet::AddEventType(const std::string& event_name) {
+ return AddEventGroup(std::vector<std::string>(1, event_name));
}
-bool EventSelectionSet::AddEventGroup(const std::vector<std::string>& event_names,
- size_t* group_id) {
+bool EventSelectionSet::AddEventType(const std::string& event_name, const SampleRate& sample_rate) {
+ if (!AddEventGroup(std::vector<std::string>(1, event_name))) {
+ return false;
+ }
+ SetSampleRateForGroup(groups_.back(), sample_rate);
+ return true;
+}
+
+bool EventSelectionSet::AddEventGroup(const std::vector<std::string>& event_names) {
EventSelectionGroup group;
bool first_event = groups_.empty();
bool first_in_group = true;
@@ -299,13 +308,16 @@ bool EventSelectionSet::AddEventGroup(const std::vector<std::string>& event_name
}
first_event = false;
first_in_group = false;
- group.push_back(std::move(selection));
+ group.selections.emplace_back(std::move(selection));
}
- groups_.push_back(std::move(group));
- UnionSampleType();
- if (group_id != nullptr) {
- *group_id = groups_.size() - 1;
+ if (sample_rate_) {
+ SetSampleRateForGroup(group, sample_rate_.value());
}
+ if (cpus_) {
+ group.cpus = cpus_.value();
+ }
+ groups_.emplace_back(std::move(group));
+ UnionSampleType();
return true;
}
@@ -324,10 +336,10 @@ bool EventSelectionSet::AddCounters(const std::vector<std::string>& event_names)
selection.event_attr.freq = 0;
selection.event_attr.sample_period = INFINITE_SAMPLE_PERIOD;
selection.event_attr.inherit = 0;
- groups_[0].emplace_back(std::move(selection));
+ groups_[0].selections.emplace_back(std::move(selection));
}
// Add counters in each sample.
- for (auto& selection : groups_[0]) {
+ for (auto& selection : groups_[0].selections) {
selection.event_attr.sample_type |= PERF_SAMPLE_READ;
selection.event_attr.read_format |= PERF_FORMAT_GROUP;
}
@@ -337,7 +349,7 @@ bool EventSelectionSet::AddCounters(const std::vector<std::string>& event_names)
std::vector<const EventType*> EventSelectionSet::GetEvents() const {
std::vector<const EventType*> result;
for (const auto& group : groups_) {
- for (const auto& selection : group) {
+ for (const auto& selection : group.selections) {
result.push_back(&selection.event_type_modifier.event_type);
}
}
@@ -347,7 +359,7 @@ std::vector<const EventType*> EventSelectionSet::GetEvents() const {
std::vector<const EventType*> EventSelectionSet::GetTracepointEvents() const {
std::vector<const EventType*> result;
for (const auto& group : groups_) {
- for (const auto& selection : group) {
+ for (const auto& selection : group.selections) {
if (selection.event_type_modifier.event_type.type == PERF_TYPE_TRACEPOINT) {
result.push_back(&selection.event_type_modifier.event_type);
}
@@ -358,7 +370,7 @@ std::vector<const EventType*> EventSelectionSet::GetTracepointEvents() const {
bool EventSelectionSet::ExcludeKernel() const {
for (const auto& group : groups_) {
- for (const auto& selection : group) {
+ for (const auto& selection : group.selections) {
if (!selection.event_type_modifier.exclude_kernel) {
return false;
}
@@ -367,16 +379,17 @@ bool EventSelectionSet::ExcludeKernel() const {
return true;
}
-std::vector<EventAttrWithId> EventSelectionSet::GetEventAttrWithId() const {
- std::vector<EventAttrWithId> result;
+EventAttrIds EventSelectionSet::GetEventAttrWithId() const {
+ EventAttrIds result;
for (const auto& group : groups_) {
- for (const auto& selection : group) {
- EventAttrWithId attr_id;
- attr_id.attr = &selection.event_attr;
+ for (const auto& selection : group.selections) {
+ std::vector<uint64_t> ids;
for (const auto& fd : selection.event_fds) {
- attr_id.ids.push_back(fd->Id());
+ ids.push_back(fd->Id());
}
- result.push_back(attr_id);
+ result.resize(result.size() + 1);
+ result.back().attr = selection.event_attr;
+ result.back().ids = std::move(ids);
}
}
return result;
@@ -385,7 +398,7 @@ std::vector<EventAttrWithId> EventSelectionSet::GetEventAttrWithId() const {
std::unordered_map<uint64_t, std::string> EventSelectionSet::GetEventNamesById() const {
std::unordered_map<uint64_t, std::string> result;
for (const auto& group : groups_) {
- for (const auto& selection : group) {
+ for (const auto& selection : group.selections) {
for (const auto& fd : selection.event_fds) {
result[fd->Id()] = selection.event_type_modifier.name;
}
@@ -394,45 +407,66 @@ std::unordered_map<uint64_t, std::string> EventSelectionSet::GetEventNamesById()
return result;
}
+std::unordered_map<uint64_t, int> EventSelectionSet::GetCpusById() const {
+ std::unordered_map<uint64_t, int> result;
+ for (const auto& group : groups_) {
+ for (const auto& selection : group.selections) {
+ for (const auto& fd : selection.event_fds) {
+ result[fd->Id()] = fd->Cpu();
+ }
+ }
+ }
+ return result;
+}
+
+std::map<int, size_t> EventSelectionSet::GetHardwareCountersForCpus() const {
+ std::map<int, size_t> cpu_map;
+ std::vector<int> online_cpus = GetOnlineCpus();
+
+ for (const auto& group : groups_) {
+ size_t hardware_events = 0;
+ for (const auto& selection : group.selections) {
+ if (selection.event_type_modifier.event_type.IsHardwareEvent()) {
+ hardware_events++;
+ }
+ }
+ const std::vector<int>* pcpus = group.cpus.empty() ? &online_cpus : &group.cpus;
+ for (int cpu : *pcpus) {
+ cpu_map[cpu] += hardware_events;
+ }
+ }
+ return cpu_map;
+}
+
// Union the sample type of different event attrs can make reading sample
// records in perf.data easier.
void EventSelectionSet::UnionSampleType() {
uint64_t sample_type = 0;
for (const auto& group : groups_) {
- for (const auto& selection : group) {
+ for (const auto& selection : group.selections) {
sample_type |= selection.event_attr.sample_type;
}
}
for (auto& group : groups_) {
- for (auto& selection : group) {
+ for (auto& selection : group.selections) {
selection.event_attr.sample_type = sample_type;
}
}
}
-void EventSelectionSet::SetEnableOnExec(bool enable) {
+void EventSelectionSet::SetEnableCondition(bool enable_on_open, bool enable_on_exec) {
for (auto& group : groups_) {
- for (auto& selection : group) {
- // If sampling is enabled on exec, then it is disabled at startup,
- // otherwise it should be enabled at startup. Don't use
- // ioctl(PERF_EVENT_IOC_ENABLE) to enable it after perf_event_open().
- // Because some android kernels can't handle ioctl() well when cpu-hotplug
- // happens. See http://b/25193162.
- if (enable) {
- selection.event_attr.enable_on_exec = 1;
- selection.event_attr.disabled = 1;
- } else {
- selection.event_attr.enable_on_exec = 0;
- selection.event_attr.disabled = 0;
- }
+ for (auto& selection : group.selections) {
+ selection.event_attr.disabled = !enable_on_open;
+ selection.event_attr.enable_on_exec = enable_on_exec;
}
}
}
-bool EventSelectionSet::GetEnableOnExec() {
+bool EventSelectionSet::IsEnabledOnExec() const {
for (const auto& group : groups_) {
- for (const auto& selection : group) {
- if (selection.event_attr.enable_on_exec == 0) {
+ for (const auto& selection : group.selections) {
+ if (!selection.event_attr.enable_on_exec) {
return false;
}
}
@@ -442,21 +476,40 @@ bool EventSelectionSet::GetEnableOnExec() {
void EventSelectionSet::SampleIdAll() {
for (auto& group : groups_) {
- for (auto& selection : group) {
+ for (auto& selection : group.selections) {
selection.event_attr.sample_id_all = 1;
}
}
}
-void EventSelectionSet::SetSampleSpeed(size_t group_id, const SampleSpeed& speed) {
- CHECK_LT(group_id, groups_.size());
- for (auto& selection : groups_[group_id]) {
- if (speed.UseFreq()) {
+void EventSelectionSet::SetSampleRateForNewEvents(const SampleRate& rate) {
+ sample_rate_ = rate;
+ for (auto& group : groups_) {
+ if (!group.set_sample_rate) {
+ SetSampleRateForGroup(group, rate);
+ }
+ }
+}
+
+void EventSelectionSet::SetCpusForNewEvents(const std::vector<int>& cpus) {
+ cpus_ = cpus;
+ for (auto& group : groups_) {
+ if (group.cpus.empty()) {
+ group.cpus = cpus_.value();
+ }
+ }
+}
+
+void EventSelectionSet::SetSampleRateForGroup(EventSelectionSet::EventSelectionGroup& group,
+ const SampleRate& rate) {
+ group.set_sample_rate = true;
+ for (auto& selection : group.selections) {
+ if (rate.UseFreq()) {
selection.event_attr.freq = 1;
- selection.event_attr.sample_freq = speed.sample_freq;
+ selection.event_attr.sample_freq = rate.sample_freq;
} else {
selection.event_attr.freq = 0;
- selection.event_attr.sample_period = speed.sample_period;
+ selection.event_attr.sample_period = rate.sample_period;
}
}
}
@@ -473,7 +526,7 @@ bool EventSelectionSet::SetBranchSampling(uint64_t branch_sample_type) {
return false;
}
for (auto& group : groups_) {
- for (auto& selection : group) {
+ for (auto& selection : group.selections) {
perf_event_attr& attr = selection.event_attr;
if (branch_sample_type != 0) {
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
@@ -488,7 +541,7 @@ bool EventSelectionSet::SetBranchSampling(uint64_t branch_sample_type) {
void EventSelectionSet::EnableFpCallChainSampling() {
for (auto& group : groups_) {
- for (auto& selection : group) {
+ for (auto& selection : group.selections) {
selection.event_attr.sample_type |= PERF_SAMPLE_CALLCHAIN;
}
}
@@ -500,7 +553,7 @@ bool EventSelectionSet::EnableDwarfCallChainSampling(uint32_t dump_stack_size) {
return false;
}
for (auto& group : groups_) {
- for (auto& selection : group) {
+ for (auto& selection : group.selections) {
selection.event_attr.sample_type |=
PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
selection.event_attr.exclude_callchain_user = 1;
@@ -513,7 +566,7 @@ bool EventSelectionSet::EnableDwarfCallChainSampling(uint32_t dump_stack_size) {
void EventSelectionSet::SetInherit(bool enable) {
for (auto& group : groups_) {
- for (auto& selection : group) {
+ for (auto& selection : group.selections) {
selection.event_attr.inherit = (enable ? 1 : 0);
}
}
@@ -521,7 +574,7 @@ void EventSelectionSet::SetInherit(bool enable) {
void EventSelectionSet::SetClockId(int clock_id) {
for (auto& group : groups_) {
- for (auto& selection : group) {
+ for (auto& selection : group.selections) {
selection.event_attr.use_clockid = 1;
selection.event_attr.clockid = clock_id;
}
@@ -534,20 +587,20 @@ bool EventSelectionSet::NeedKernelSymbol() const {
void EventSelectionSet::SetRecordNotExecutableMaps(bool record) {
// We only need to dump non-executable mmap records for the first event type.
- groups_[0][0].event_attr.mmap_data = record ? 1 : 0;
+ groups_[0].selections[0].event_attr.mmap_data = record ? 1 : 0;
}
bool EventSelectionSet::RecordNotExecutableMaps() const {
- return groups_[0][0].event_attr.mmap_data == 1;
+ return groups_[0].selections[0].event_attr.mmap_data == 1;
}
void EventSelectionSet::EnableSwitchRecord() {
- groups_[0][0].event_attr.context_switch = 1;
+ groups_[0].selections[0].event_attr.context_switch = 1;
}
void EventSelectionSet::WakeupPerSample() {
for (auto& group : groups_) {
- for (auto& selection : group) {
+ for (auto& selection : group.selections) {
selection.event_attr.watermark = 0;
selection.event_attr.wakeup_events = 1;
}
@@ -559,9 +612,9 @@ bool EventSelectionSet::SetTracepointFilter(const std::string& filter) {
EventSelection* selection = nullptr;
if (!groups_.empty()) {
auto& group = groups_.back();
- if (group.size() == 1) {
- if (group[0].event_attr.type == PERF_TYPE_TRACEPOINT) {
- selection = &group[0];
+ if (group.selections.size() == 1) {
+ if (group.selections[0].event_attr.type == PERF_TYPE_TRACEPOINT) {
+ selection = &group.selections[0];
}
}
}
@@ -603,24 +656,13 @@ bool EventSelectionSet::SetTracepointFilter(const std::string& filter) {
return true;
}
-static bool CheckIfCpusOnline(const std::vector<int>& cpus) {
- std::vector<int> online_cpus = GetOnlineCpus();
- for (const auto& cpu : cpus) {
- if (std::find(online_cpus.begin(), online_cpus.end(), cpu) == online_cpus.end()) {
- LOG(ERROR) << "cpu " << cpu << " is not online.";
- return false;
- }
- }
- return true;
-}
-
bool EventSelectionSet::OpenEventFilesOnGroup(EventSelectionGroup& group, pid_t tid, int cpu,
std::string* failed_event_type) {
std::vector<std::unique_ptr<EventFd>> event_fds;
// Given a tid and cpu, events on the same group should be all opened
// successfully or all failed to open.
EventFd* group_fd = nullptr;
- for (auto& selection : group) {
+ for (auto& selection : group.selections) {
std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(
selection.event_attr, tid, cpu, group_fd, selection.event_type_modifier.name, false);
if (!event_fd) {
@@ -628,13 +670,13 @@ bool EventSelectionSet::OpenEventFilesOnGroup(EventSelectionGroup& group, pid_t
return false;
}
LOG(VERBOSE) << "OpenEventFile for " << event_fd->Name();
- event_fds.push_back(std::move(event_fd));
+ event_fds.emplace_back(std::move(event_fd));
if (group_fd == nullptr) {
group_fd = event_fds.back().get();
}
}
- for (size_t i = 0; i < group.size(); ++i) {
- group[i].event_fds.push_back(std::move(event_fds[i]));
+ for (size_t i = 0; i < group.selections.size(); ++i) {
+ group.selections[i].event_fds.emplace_back(std::move(event_fds[i]));
}
return true;
}
@@ -649,29 +691,39 @@ static std::set<pid_t> PrepareThreads(const std::set<pid_t>& processes,
return result;
}
-bool EventSelectionSet::OpenEventFiles(const std::vector<int>& cpus) {
- std::vector<int> monitored_cpus;
- if (cpus.empty()) {
- monitored_cpus = GetOnlineCpus();
- } else if (cpus.size() == 1 && cpus[0] == -1) {
- monitored_cpus = {-1};
- } else {
- if (!CheckIfCpusOnline(cpus)) {
- return false;
+bool EventSelectionSet::OpenEventFiles() {
+ std::vector<int> online_cpus = GetOnlineCpus();
+
+ auto check_if_cpus_online = [&](const std::vector<int>& cpus) {
+ if (cpus.size() == 1 && cpus[0] == -1) {
+ return true;
}
- monitored_cpus = cpus;
- }
+ for (int cpu : cpus) {
+ if (std::find(online_cpus.begin(), online_cpus.end(), cpu) == online_cpus.end()) {
+ LOG(ERROR) << "cpu " << cpu << " is not online.";
+ return false;
+ }
+ }
+ return true;
+ };
+
std::set<pid_t> threads = PrepareThreads(processes_, threads_);
for (auto& group : groups_) {
+ const std::vector<int>* pcpus = &group.cpus;
+ if (!group.selections[0].allowed_cpus.empty()) {
+ // override cpu list if event's PMU has a cpumask as those PMUs are
+ // agnostic to cpu and it's meaningless to specify cpus for them.
+ pcpus = &group.selections[0].allowed_cpus;
+ }
+ if (pcpus->empty()) {
+ pcpus = &online_cpus;
+ } else if (!check_if_cpus_online(*pcpus)) {
+ return false;
+ }
+
size_t success_count = 0;
std::string failed_event_type;
for (const auto tid : threads) {
- const std::vector<int>* pcpus = &monitored_cpus;
- if (!group[0].allowed_cpus.empty()) {
- // override cpu list if event's PMU has a cpumask as those PMUs are
- // agnostic to cpu and it's meaningless to specify cpus for them.
- pcpus = &group[0].allowed_cpus;
- }
for (const auto& cpu : *pcpus) {
if (OpenEventFilesOnGroup(group, tid, cpu, &failed_event_type)) {
success_count++;
@@ -730,7 +782,7 @@ bool EventSelectionSet::ApplyAddrFilters() {
}
for (auto& group : groups_) {
- for (auto& selection : group) {
+ for (auto& selection : group.selections) {
if (IsEtmEventType(selection.event_type_modifier.event_type.type)) {
for (auto& event_fd : selection.event_fds) {
if (!event_fd->SetFilter(filter_str)) {
@@ -745,7 +797,7 @@ bool EventSelectionSet::ApplyAddrFilters() {
bool EventSelectionSet::ApplyTracepointFilters() {
for (auto& group : groups_) {
- for (auto& selection : group) {
+ for (auto& selection : group.selections) {
if (!selection.tracepoint_filter.empty()) {
for (auto& event_fd : selection.event_fds) {
if (!event_fd->SetFilter(selection.tracepoint_filter)) {
@@ -770,7 +822,7 @@ static bool ReadCounter(EventFd* event_fd, CounterInfo* counter) {
bool EventSelectionSet::ReadCounters(std::vector<CountersInfo>* counters) {
counters->clear();
for (size_t i = 0; i < groups_.size(); ++i) {
- for (auto& selection : groups_[i]) {
+ for (auto& selection : groups_[i].selections) {
CountersInfo counters_info;
counters_info.group_id = i;
counters_info.event_name = selection.event_type_modifier.event_type.name;
@@ -791,10 +843,10 @@ bool EventSelectionSet::ReadCounters(std::vector<CountersInfo>* counters) {
bool EventSelectionSet::MmapEventFiles(size_t min_mmap_pages, size_t max_mmap_pages,
size_t aux_buffer_size, size_t record_buffer_size,
- bool allow_cutting_samples, bool exclude_perf) {
+ bool allow_truncating_samples, bool exclude_perf) {
record_read_thread_.reset(new simpleperf::RecordReadThread(
- record_buffer_size, groups_[0][0].event_attr, min_mmap_pages, max_mmap_pages, aux_buffer_size,
- allow_cutting_samples, exclude_perf));
+ record_buffer_size, groups_[0].selections[0].event_attr, min_mmap_pages, max_mmap_pages,
+ aux_buffer_size, allow_truncating_samples, exclude_perf));
return true;
}
@@ -807,7 +859,7 @@ bool EventSelectionSet::PrepareToReadMmapEventData(const std::function<bool(Reco
}
std::vector<EventFd*> event_fds;
for (auto& group : groups_) {
- for (auto& selection : group) {
+ for (auto& selection : group.selections) {
for (auto& event_fd : selection.event_fds) {
event_fds.push_back(event_fd.get());
}
@@ -848,7 +900,7 @@ void EventSelectionSet::CloseEventFiles() {
record_read_thread_->StopReadThread();
}
for (auto& group : groups_) {
- for (auto& event : group) {
+ for (auto& event : group.selections) {
event.event_fds.clear();
}
}
@@ -878,7 +930,7 @@ bool EventSelectionSet::CheckMonitoredTargets() {
bool EventSelectionSet::HasSampler() {
for (auto& group : groups_) {
- for (auto& sel : group) {
+ for (auto& sel : group.selections) {
if (!sel.event_fds.empty()) {
return true;
}
@@ -889,7 +941,7 @@ bool EventSelectionSet::HasSampler() {
bool EventSelectionSet::SetEnableEvents(bool enable) {
for (auto& group : groups_) {
- for (auto& sel : group) {
+ for (auto& sel : group.selections) {
for (auto& fd : sel.event_fds) {
if (!fd->SetEnableEvent(enable)) {
return false;
@@ -900,4 +952,61 @@ bool EventSelectionSet::SetEnableEvents(bool enable) {
return true;
}
+bool EventSelectionSet::EnableETMEvents() {
+ for (auto& group : groups_) {
+ for (auto& sel : group.selections) {
+ if (!sel.event_type_modifier.event_type.IsEtmEvent()) {
+ continue;
+ }
+ for (auto& fd : sel.event_fds) {
+ if (!fd->SetEnableEvent(true)) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+bool EventSelectionSet::DisableETMEvents() {
+ for (auto& group : groups_) {
+ for (auto& sel : group.selections) {
+ if (!sel.event_type_modifier.event_type.IsEtmEvent()) {
+ continue;
+ }
+ // When using ETR, ETM data is flushed to the aux buffer of the last cpu disabling ETM events.
+ // To avoid overflowing the aux buffer for one cpu, rotate the last cpu disabling ETM events.
+ if (etm_event_cpus_.empty()) {
+ for (const auto& fd : sel.event_fds) {
+ etm_event_cpus_.insert(fd->Cpu());
+ }
+ if (etm_event_cpus_.empty()) {
+ continue;
+ }
+ etm_event_cpus_it_ = etm_event_cpus_.begin();
+ }
+ int last_disabled_cpu = *etm_event_cpus_it_;
+ if (++etm_event_cpus_it_ == etm_event_cpus_.end()) {
+ etm_event_cpus_it_ = etm_event_cpus_.begin();
+ }
+
+ for (auto& fd : sel.event_fds) {
+ if (fd->Cpu() != last_disabled_cpu) {
+ if (!fd->SetEnableEvent(false)) {
+ return false;
+ }
+ }
+ }
+ for (auto& fd : sel.event_fds) {
+ if (fd->Cpu() == last_disabled_cpu) {
+ if (!fd->SetEnableEvent(false)) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+ return true;
+}
+
} // namespace simpleperf
diff --git a/simpleperf/event_selection_set.h b/simpleperf/event_selection_set.h
index 07754fd2..a892d51e 100644
--- a/simpleperf/event_selection_set.h
+++ b/simpleperf/event_selection_set.h
@@ -52,15 +52,15 @@ struct CountersInfo {
std::vector<CounterInfo> counters;
};
-struct SampleSpeed {
- // There are two ways to set sample speed:
+struct SampleRate {
+ // There are two ways to set sample rate:
// 1. sample_freq: take [sample_freq] samples every second.
// 2. sample_period: take one sample every [sample_period] events happen.
uint64_t sample_freq;
uint64_t sample_period;
- SampleSpeed(uint64_t freq = 0, uint64_t period = 0) : sample_freq(freq), sample_period(period) {}
+ SampleRate(uint64_t freq = 0, uint64_t period = 0) : sample_freq(freq), sample_period(period) {}
bool UseFreq() const {
- // Only use one way to set sample speed.
+ // Only use one way to set sample rate.
CHECK_NE(sample_freq != 0u, sample_period != 0u);
return sample_freq != 0u;
}
@@ -107,21 +107,27 @@ class EventSelectionSet {
bool empty() const { return groups_.empty(); }
- bool AddEventType(const std::string& event_name, size_t* group_id = nullptr);
- bool AddEventGroup(const std::vector<std::string>& event_names, size_t* group_id = nullptr);
+ bool AddEventType(const std::string& event_name);
+ bool AddEventType(const std::string& event_name, const SampleRate& sample_rate);
+ bool AddEventGroup(const std::vector<std::string>& event_names);
// For each sample generated for the existing event group, add counters for selected events.
bool AddCounters(const std::vector<std::string>& event_names);
std::vector<const EventType*> GetEvents() const;
std::vector<const EventType*> GetTracepointEvents() const;
bool ExcludeKernel() const;
bool HasAuxTrace() const { return has_aux_trace_; }
- std::vector<EventAttrWithId> GetEventAttrWithId() const;
+ EventAttrIds GetEventAttrWithId() const;
std::unordered_map<uint64_t, std::string> GetEventNamesById() const;
+ std::unordered_map<uint64_t, int> GetCpusById() const;
+ std::map<int, size_t> GetHardwareCountersForCpus() const;
- void SetEnableOnExec(bool enable);
- bool GetEnableOnExec();
+ void SetEnableCondition(bool enable_on_open, bool enable_on_exec);
+ bool IsEnabledOnExec() const;
void SampleIdAll();
- void SetSampleSpeed(size_t group_id, const SampleSpeed& speed);
+ // Only set sample rate for events that haven't set sample rate.
+ void SetSampleRateForNewEvents(const SampleRate& rate);
+ // Set on which cpus to monitor events. Only set cpus for events that haven't set before.
+ void SetCpusForNewEvents(const std::vector<int>& cpus);
bool SetBranchSampling(uint64_t branch_sample_type);
void EnableFpCallChainSampling();
bool EnableDwarfCallChainSampling(uint32_t dump_stack_size);
@@ -158,13 +164,10 @@ class EventSelectionSet {
IOEventLoop* GetIOEventLoop() { return loop_.get(); }
- // If cpus = {}, monitor on all cpus, with a perf event file for each cpu.
- // If cpus = {-1}, monitor on all cpus, with a perf event file shared by all cpus.
- // Otherwise, monitor on selected cpus, with a perf event file for each cpu.
- bool OpenEventFiles(const std::vector<int>& cpus);
+ bool OpenEventFiles();
bool ReadCounters(std::vector<CountersInfo>* counters);
bool MmapEventFiles(size_t min_mmap_pages, size_t max_mmap_pages, size_t aux_buffer_size,
- size_t record_buffer_size, bool allow_cutting_samples, bool exclude_perf);
+ size_t record_buffer_size, bool allow_truncating_samples, bool exclude_perf);
bool PrepareToReadMmapEventData(const std::function<bool(Record*)>& callback);
bool SyncKernelBuffer();
bool FinishReadMmapEventData();
@@ -177,6 +180,8 @@ class EventSelectionSet {
double check_interval_in_sec = DEFAULT_PERIOD_TO_CHECK_MONITORED_TARGETS_IN_SEC);
bool SetEnableEvents(bool enable);
+ bool EnableETMEvents();
+ bool DisableETMEvents();
private:
struct EventSelection {
@@ -188,11 +193,22 @@ class EventSelectionSet {
std::vector<int> allowed_cpus;
std::string tracepoint_filter;
};
- typedef std::vector<EventSelection> EventSelectionGroup;
+
+ struct EventSelectionGroup {
+ std::vector<EventSelection> selections;
+ bool set_sample_rate = false;
+ // Select on which cpus to monitor this event group:
+ // If cpus = {}, monitor on all cpus, with a perf event file for each cpu. This is the default
+ // option.
+ // If cpus = {-1}, monitor on all cpus, with a perf event file shared by all cpus.
+ // Otherwise, monitor on selected cpus, with a perf event file for each cpu.
+ std::vector<int> cpus;
+ };
bool BuildAndCheckEventSelection(const std::string& event_name, bool first_event,
EventSelection* selection);
void UnionSampleType();
+ void SetSampleRateForGroup(EventSelectionGroup& group, const SampleRate& rate);
bool OpenEventFilesOnGroup(EventSelectionGroup& group, pid_t tid, int cpu,
std::string* failed_event_type);
bool ApplyFilters();
@@ -216,6 +232,11 @@ class EventSelectionSet {
bool has_aux_trace_ = false;
std::vector<AddrFilter> addr_filters_;
+ std::optional<SampleRate> sample_rate_;
+ std::optional<std::vector<int>> cpus_;
+
+ std::set<int> etm_event_cpus_;
+ std::set<int>::const_iterator etm_event_cpus_it_;
DISALLOW_COPY_AND_ASSIGN(EventSelectionSet);
};
diff --git a/simpleperf/event_selection_set_test.cpp b/simpleperf/event_selection_set_test.cpp
new file mode 100644
index 00000000..e60d81a8
--- /dev/null
+++ b/simpleperf/event_selection_set_test.cpp
@@ -0,0 +1,97 @@
+/*
+ * 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 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "event_selection_set.h"
+
+using namespace simpleperf;
+
+// @CddTest = 6.1/C-0-2
+TEST(EventSelectionSet, set_sample_rate_for_new_events) {
+ EventSelectionSet event_selection_set(false);
+ ASSERT_TRUE(event_selection_set.AddEventType("cpu-clock:u"));
+ event_selection_set.SetSampleRateForNewEvents(SampleRate(100, 0));
+ ASSERT_TRUE(event_selection_set.AddEventType("page-faults:u"));
+ event_selection_set.SetSampleRateForNewEvents(SampleRate(200, 0));
+ ASSERT_TRUE(event_selection_set.AddEventGroup({"context-switches:u", "task-clock:u"}));
+ EventAttrIds attrs = event_selection_set.GetEventAttrWithId();
+ ASSERT_EQ(attrs.size(), 4);
+ ASSERT_EQ(GetEventNameByAttr(attrs[0].attr), "cpu-clock:u");
+ ASSERT_EQ(attrs[0].attr.freq, 1);
+ ASSERT_EQ(attrs[0].attr.sample_freq, 100);
+ ASSERT_EQ(GetEventNameByAttr(attrs[1].attr), "page-faults:u");
+ ASSERT_EQ(attrs[1].attr.freq, 1);
+ ASSERT_EQ(attrs[1].attr.sample_freq, 100);
+ ASSERT_EQ(GetEventNameByAttr(attrs[2].attr), "context-switches:u");
+ ASSERT_EQ(attrs[2].attr.freq, 1);
+ ASSERT_EQ(attrs[2].attr.sample_freq, 200);
+ ASSERT_EQ(GetEventNameByAttr(attrs[3].attr), "task-clock:u");
+ ASSERT_EQ(attrs[3].attr.freq, 1);
+ ASSERT_EQ(attrs[3].attr.sample_freq, 200);
+}
+
+// @CddTest = 6.1/C-0-2
+TEST(EventSelectionSet, add_event_with_sample_rate) {
+ EventSelectionSet event_selection_set(false);
+ ASSERT_TRUE(event_selection_set.AddEventType("cpu-clock:u"));
+ ASSERT_TRUE(event_selection_set.AddEventType("context-switches", SampleRate(0, 1)));
+ EventAttrIds attrs = event_selection_set.GetEventAttrWithId();
+ ASSERT_EQ(attrs.size(), 2);
+ ASSERT_EQ(GetEventNameByAttr(attrs[0].attr), "cpu-clock:u");
+ ASSERT_EQ(attrs[0].attr.freq, 1);
+ ASSERT_EQ(attrs[0].attr.sample_freq, 4000);
+ ASSERT_EQ(GetEventNameByAttr(attrs[1].attr), "context-switches");
+ ASSERT_EQ(attrs[1].attr.freq, 0);
+ ASSERT_EQ(attrs[1].attr.sample_period, 1);
+}
+
+// @CddTest = 6.1/C-0-2
+TEST(EventSelectionSet, set_cpus_for_new_events) {
+ EventSelectionSet event_selection_set(false);
+ std::vector<int> online_cpus = GetOnlineCpus();
+ ASSERT_FALSE(online_cpus.empty());
+ ASSERT_TRUE(event_selection_set.AddEventType("cpu-clock:u"));
+ event_selection_set.SetCpusForNewEvents({online_cpus[0]});
+ ASSERT_TRUE(event_selection_set.AddEventType("page-faults:u"));
+ event_selection_set.SetCpusForNewEvents({online_cpus.back()});
+ ASSERT_TRUE(event_selection_set.AddEventGroup({"context-switches:u", "task-clock:u"}));
+ event_selection_set.AddMonitoredThreads({gettid()});
+ ASSERT_TRUE(event_selection_set.OpenEventFiles());
+
+ std::unordered_map<uint64_t, int> id_to_cpu = event_selection_set.GetCpusById();
+ auto get_cpu = [&](int id) {
+ if (auto it = id_to_cpu.find(id); it != id_to_cpu.end()) {
+ return it->second;
+ }
+ return -2;
+ };
+
+ EventAttrIds attrs = event_selection_set.GetEventAttrWithId();
+ ASSERT_EQ(attrs.size(), 4);
+ ASSERT_EQ(GetEventNameByAttr(attrs[0].attr), "cpu-clock:u");
+ ASSERT_EQ(attrs[0].ids.size(), 1);
+ ASSERT_EQ(get_cpu(attrs[0].ids[0]), online_cpus[0]);
+ ASSERT_EQ(GetEventNameByAttr(attrs[1].attr), "page-faults:u");
+ ASSERT_EQ(attrs[1].ids.size(), 1);
+ ASSERT_EQ(get_cpu(attrs[1].ids[0]), online_cpus[0]);
+ ASSERT_EQ(GetEventNameByAttr(attrs[2].attr), "context-switches:u");
+ ASSERT_EQ(attrs[2].ids.size(), 1);
+ ASSERT_EQ(get_cpu(attrs[2].ids[0]), online_cpus.back());
+ ASSERT_EQ(GetEventNameByAttr(attrs[3].attr), "task-clock:u");
+ ASSERT_EQ(attrs[3].ids.size(), 1);
+ ASSERT_EQ(get_cpu(attrs[3].ids[0]), online_cpus.back());
+}
diff --git a/simpleperf/event_table.json b/simpleperf/event_table.json
new file mode 100644
index 00000000..acff512c
--- /dev/null
+++ b/simpleperf/event_table.json
@@ -0,0 +1,1042 @@
+{
+ "arm64": {
+ "events": [
+ ["0x0000", "SW_INCR", "Instruction architecturally executed, Condition code check pass, software increment"],
+ ["0x0001", "L1I_CACHE_REFILL", "Level 1 instruction cache refill"],
+ ["0x0002", "L1I_TLB_REFILL", "Level 1 instruction TLB refill"],
+ ["0x0003", "L1D_CACHE_REFILL", "Level 1 data cache refill"],
+ ["0x0004", "L1D_CACHE", "Level 1 data cache access"],
+ ["0x0005", "L1D_TLB_REFILL", "Level 1 data TLB refill"],
+ ["0x0006", "LD_RETIRED", "Instruction architecturally executed, Condition code check pass, load"],
+ ["0x0007", "ST_RETIRED", "Instruction architecturally executed, Condition code check pass, store"],
+ ["0x0008", "INST_RETIRED", "Instruction architecturally executed"],
+ ["0x0009", "EXC_TAKEN", "Exception taken"],
+ ["0x000A", "EXC_RETURN", " Instruction architecturally executed, Condition code check pass, exception return"],
+ ["0x000B", "CID_WRITE_RETIRED", "Instruction architecturally executed, Condition code check pass, write to CONTEXTIDR"],
+ ["0x000C", "PC_WRITE_RETIRED", "D, Instruction architecturally executed, Condition code check pass, Software change of the PC"],
+ ["0x000D", "BR_IMMED_RETIRED", "Branch Instruction architecturally executed, immediate"],
+ ["0x000E", "BR_RETURN_RETIRED", "Branch Instruction architecturally executed, procedure return, taken"],
+ ["0x000F", "UNALIGNED_LDST_RETIRED", "Instruction architecturally executed, Condition code check pass, unaligned load or store"],
+ ["0x0010", "BR_MIS_PRED", "Branch instruction Speculatively executed, mispredicted or not predicted "],
+ ["0x0011", "CPU_CYCLES", "Cycle"],
+ ["0x0012", "BR_PRED", "Predictable branch instruction Speculatively executed"],
+ ["0x0013", "MEM_ACCESS", " Data memory access"],
+ ["0x0014", "L1I_CACHE", " Level 1 instruction cache access "],
+ ["0x0015", "L1D_CACHE_WB", " Level 1 data cache write-back"],
+ ["0x0016", "L2D_CACHE", " Level 2 data cache access"],
+ ["0x0017", "L2D_CACHE_REFILL", "Level 2 data cache refill"],
+ ["0x0018", "L2D_CACHE_WB", "Level 2 data cache write-back"],
+ ["0x0019", "BUS_ACCESS", "Bus access"],
+ ["0x001A", "MEMORY_ERROR", "Local memory error"],
+ ["0x001B", "INST_SPEC", "Operation speculatively executed"],
+ ["0x001C", "TTBR_WRITE_RETIRED", "Instruction architecturally executed, Condition code check pass, write to TTBR"],
+ ["0x001D", "BUS_CYCLES", "Bus cycle "],
+ ["0x001F", "L1D_CACHE_ALLOCATE", " Level 1 data cache allocation without refill"],
+ ["0x0020", "L2D_CACHE_ALLOCATE", "Level 2 data cache allocation without refill"],
+ ["0x0021", "BR_RETIRED", "Instruction architecturally executed, branch"],
+ ["0x0022", "BR_MIS_PRED_RETIRED", "Branch Instruction architecturally executed, mispredicted "],
+ ["0x0023", "STALL_FRONTEND", "No operation sent for execution due to the frontend"],
+ ["0x0024", "STALL_BACKEND", "No operation sent for execution due to the backend "],
+ ["0x0025", "L1D_TLB", "Level 1 data TLB access"],
+ ["0x0026", "L1I_TLB", "Level 1 instruction TLB access"],
+ ["0x0027", "L2I_CACHE", "Level 2 instruction cache access "],
+ ["0x0028", "L2I_CACHE_REFILL", "Level 2 instruction cache refill"],
+ ["0x0029", "L3D_CACHE_ALLOCATE", "Level 3 data cache allocation without refill"],
+ ["0x002A", "L3D_CACHE_REFILL", "Level 3 data cache refill"],
+ ["0x002B", "L3D_CACHE", "Level 3 data cache access "],
+ ["0x002C", "L3D_CACHE_WB", "Level 3 data cache write-back"],
+ ["0x002D", "L2D_TLB_REFILL", "Level 2 data TLB refill "],
+ ["0x002E", "L2I_TLB_REFILL", "Level 2 instruction TLB refill "],
+ ["0x002F", "L2D_TLB", "Level 2 data TLB access "],
+ ["0x0030", "L2I_TLB", "Level 2 instruction TLB access "],
+ ["0x0031", "REMOTE_ACCESS", "Access to another socket in a multi-socket system "],
+ ["0x0032", "LL_CACHE", "Last level cache access"],
+ ["0x0033", "LL_CACHE_MISS", "Last level cache miss"],
+ ["0x0034", "DTLB_WALK", "Data TLB access with at least one translation table walk"],
+ ["0x0035", "ITLB_WALK", "Instruction TLB access with at least one translation table walk"],
+ ["0x0036", "LL_CACHE_RD", "Last level cache access, read"],
+ ["0x0037", "LL_CACHE_MISS_RD", "Last level cache miss, read "],
+ ["0x0038", "REMOTE_ACCESS_RD", "Access to another socket in a multi-socket system, read"],
+ ["0x0039", "L1D_CACHE_LMISS_RD", "Level 1 data cache long-latency read miss"],
+ ["0x003A", "OP_RETIRED", "Micro-operation architecturally executed"],
+ ["0x003B", "OP_SPEC", "Micro-operation Speculatively executed"],
+ ["0x003C", "STALL", "No operation sent for execution"],
+ ["0x003D", "STALL_SLOT_BACKEND", "No operation sent for execution on a Slot due to the backend"],
+ ["0x003E", "STALL_SLOT_FRONTEND", "No operation sent for execution on a Slot due to the frontend "],
+ ["0x003F", "STALL_SLOT", "No operation sent for execution on a Slot "],
+ ["0x0040", "L1D_CACHE_RD", "Level 1 data cache access, read"],
+ ["0x0041", "L1D_CACHE_WR", "Level 1 data cache access, write"],
+ ["0x0042", "L1D_CACHE_REFILL_RD", "Level 1 data cache refill, read"],
+ ["0x0043", "L1D_CACHE_REFILL_WR", "Level 1 data cache refill, write"],
+ ["0x0044", "L1D_CACHE_REFILL_INNER", "Level 1 data cache refill, inner"],
+ ["0x0045", "L1D_CACHE_REFILL_OUTER", "Level 1 data cache refill, outer"],
+ ["0x0046", "L1D_CACHE_WB_VICTIM", "Level 1 data cache write-back, victim"],
+ ["0x0047", "L1D_CACHE_WB_CLEAN", "Level 1 data cache write-back, cleaning and coherency"],
+ ["0x0048", "L1D_CACHE_INVAL", "Level 1 data cache invalidate"],
+ ["0x004C", "L1D_TLB_REFILL_RD", "Level 1 data TLB refill, read"],
+ ["0x004D", "L1D_TLB_REFILL_WR", "Level 1 data TLB refill, write"],
+ ["0x004E", "L1D_TLB_RD", "Level 1 data TLB access, read"],
+ ["0x004F", "L1D_TLB_WR", "Level 1 data TLB access, write"],
+ ["0x0050", "L2D_CACHE_RD", "Level 2 data cache access, read"],
+ ["0x0051", "L2D_CACHE_WR", "Level 2 data cache access, write"],
+ ["0x0052", "L2D_CACHE_REFILL_RD", "Level 2 data cache refill, read"],
+ ["0x0053", "L2D_CACHE_REFILL_WR", "Level 2 data cache refill, write"],
+ ["0x0056", "L2D_CACHE_WB_VICTIM", "Level 2 data cache write-back, victim"],
+ ["0x0057", "L2D_CACHE_WB_CLEAN", "Level 2 data cache write-back, cleaning and coherency"],
+ ["0x0058", "L2D_CACHE_INVAL", "Level 2 data cache invalidate"],
+ ["0x005C", "L2D_TLB_REFILL_RD", "Level 2 data TLB refill, read"],
+ ["0x005D", "L2D_TLB_REFILL_WR", "Level 2 data TLB refill, write"],
+ ["0x005E", "L2D_TLB_RD", "Level 2 data TLB access, read"],
+ ["0x005F", "L2D_TLB_WR", "Level 2 data TLB access, write"],
+ ["0x0060", "BUS_ACCESS_RD", "Bus access, read"],
+ ["0x0061", "BUS_ACCESS_WR", "Bus access, write"],
+ ["0x0062", "BUS_ACCESS_SHARED", "Bus access, Normal, Cacheable, Shareable"],
+ ["0x0063", "BUS_ACCESS_NOT_SHARED", "Bus access, not Normal, Cacheable, Shareable"],
+ ["0x0064", "BUS_ACCESS_NORMAL", "Bus access, normal"],
+ ["0x0065", "BUS_ACCESS_PERIPH", "Bus access, peripheral"],
+ ["0x0066", "MEM_ACCESS_RD", "Data memory access, read"],
+ ["0x0067", "MEM_ACCESS_WR", "Data memory access, write"],
+ ["0x0068", "UNALIGNED_LD_SPEC", "Unaligned access, read"],
+ ["0x0069", "UNALIGNED_ST_SPEC", "Unaligned access, write"],
+ ["0x006A", "UNALIGNED_LDST_SPEC", "Unaligned access"],
+ ["0x006C", "LDREX_SPEC", "Exclusive operation Speculatively executed, Load-Exclusive"],
+ ["0x006D", "STREX_PASS_SPEC", "Exclusive operation Speculatively executed, Store-Exclusive pass"],
+ ["0x006E", "STREX_FAIL_SPEC", "Exclusive operation Speculatively executed, Store-Exclusive fail"],
+ ["0x006F", "STREX_SPEC", "Exclusive operation Speculatively executed, Store-Exclusive"],
+ ["0x0070", "LD_SPEC", "Operation speculatively executed, load"],
+ ["0x0071", "ST_SPEC", "Operation speculatively executed, store"],
+ ["0x0072", "LDST_SPEC", "Operation speculatively executed, load or store"],
+ ["0x0073", "DP_SPEC", "Operation speculatively executed, integer data processing"],
+ ["0x0074", "ASE_SPEC", "Operation speculatively executed, Advanced SIMD"],
+ ["0x0075", "VFP_SPEC", "Operation speculatively executed, scalar floating-point"],
+ ["0x0076", "PC_WRITE_SPEC", "Operation speculatively executed, Software change of the PC"],
+ ["0x0077", "CRYPTO_SPEC", "Operation speculatively executed, Cryptographic instruction"],
+ ["0x0078", "BR_IMMED_SPEC", "Branch Speculatively executed, immediate branch"],
+ ["0x0079", "BR_RETURN_SPEC", "Branch Speculatively executed, procedure return"],
+ ["0x007A", "BR_INDIRECT_SPEC", "Branch Speculatively executed, indirect branch"],
+ ["0x007C", "ISB_SPEC", "Barrier Speculatively executed, ISB"],
+ ["0x007D", "DSB_SPEC", "Barrier Speculatively executed, DSB"],
+ ["0x007E", "DMB_SPEC", "Barrier Speculatively executed, DMB"],
+ ["0x007F", "CSDB_SPEC", "Barrier Speculatively executed, CSDB"],
+ ["0x0081", "EXC_UNDEF", "Exception taken, other synchronous"],
+ ["0x0082", "EXC_SVC", "Exception taken, Supervisor Call"],
+ ["0x0083", "EXC_PABORT", "Exception taken, Instruction Abort"],
+ ["0x0084", "EXC_DABORT", "Exception taken, Data Abort or SError"],
+ ["0x0086", "EXC_IRQ", "Exception taken, IRQ"],
+ ["0x0087", "EXC_FIQ", "Exception taken, FIQ"],
+ ["0x0088", "EXC_SMC", "Exception taken, Secure Monitor Call"],
+ ["0x008A", "EXC_HVC", "Exception taken, Hypervisor Call"],
+ ["0x008B", "EXC_TRAP_PABORT", "Exception taken, Instruction Abort not Taken locally"],
+ ["0x008C", "EXC_TRAP_DABORT", "Exception taken, Data Abort or SError not Taken locally"],
+ ["0x008D", "EXC_TRAP_OTHER", "Exception taken, other traps not Taken locally"],
+ ["0x008E", "EXC_TRAP_IRQ", "Exception taken, IRQ not Taken locally"],
+ ["0x008F", "EXC_TRAP_FIQ", "Exception taken, FIQ not Taken locally"],
+ ["0x0090", "RC_LD_SPEC", "Release consistency operation Speculatively executed, Load-Acquire"],
+ ["0x0091", "RC_ST_SPEC", "Release consistency operation Speculatively executed, Store-Release"],
+ ["0x00A0", "L3D_CACHE_RD", "Level 3 data cache access, read"],
+ ["0x00A1", "L3D_CACHE_WR", "Level 3 data cache access, write"],
+ ["0x00A2", "L3D_CACHE_REFILL_RD", "Level 3 data cache refill, read"],
+ ["0x00A3", "L3D_CACHE_REFILL_WR", "Level 3 data cache refill, write"],
+ ["0x00A6", "L3D_CACHE_WB_VICTIM", "Level 3 data cache write-back, victim"],
+ ["0x00A7", "L3D_CACHE_WB_CLEAN", "Level 3 data cache write-back, cleaning and coherency"],
+ ["0x00A8", "L3D_CACHE_INVAL", "Level 3 data cache invalidate"],
+ ["0x4000", "SAMPLE_POP", "Statistical Profiling sample population"],
+ ["0x4001", "SAMPLE_FEED", "Statistical Profiling sample taken"],
+ ["0x4002", "SAMPLE_FILTRATE", "Statistical Profiling sample taken and not removed by filtering"],
+ ["0x4003", "SAMPLE_COLLISION", "Statistical Profiling sample collided with previous sample"],
+ ["0x4004", "CNT_CYCLES", "Constant frequency cycles"],
+ ["0x4005", "STALL_BACKEND_MEM", "Memory stall cycles"],
+ ["0x4006", "L1I_CACHE_LMISS", "Level 1 instruction cache long-latency miss"],
+ ["0x4009", "L2D_CACHE_LMISS_RD", "Level 2 data cache long-latency read miss"],
+ ["0x400A", "L2I_CACHE_LMISS", "Level 2 instruction cache long-latency miss"],
+ ["0x400B", "L3D_CACHE_LMISS_RD", "Level 3 data cache long-latency read miss"],
+ ["0x400C", "TRB_WRAP", "Trace buffer current write pointer wrapped"],
+ ["0x400E", "TRB_TRIG", "Trace buffer Trigger Event "],
+ ["0x4010", "TRCEXTOUT0", "Trace unit external output 0"],
+ ["0x4011", "TRCEXTOUT1", "Trace unit external output 1"],
+ ["0x4012", "TRCEXTOUT2", "Trace unit external output 2"],
+ ["0x4013", "TRCEXTOUT3", "Trace unit external output 3 "],
+ ["0x4018", "CTI_TRIGOUT4", "Cross-trigger Interface output trigger 4"],
+ ["0x4019", "CTI_TRIGOUT5", "Cross-trigger Interface output trigger 5"],
+ ["0x401A", "CTI_TRIGOUT6", "Cross-trigger Interface output trigger 6"],
+ ["0x401B", "CTI_TRIGOUT7", "Cross-trigger Interface output trigger 7"],
+ ["0x4020", "LDST_ALIGN_LAT", "Access with additional latency from alignment"],
+ ["0x4021", "LD_ALIGN_LAT", "Load with additional latency from alignment"],
+ ["0x4022", "ST_ALIGN_LAT", "Store with additional latency from alignment"],
+ ["0x4024", "MEM_ACCESS_CHECKED", "Checked data memory access"],
+ ["0x4025", "MEM_ACCESS_RD_CHECKED", "Checked data memory access, read"],
+ ["0x4026", "MEM_ACCESS_WR_CHECKED", "Checked data memory access, write "],
+ ["0x8000", "SIMD_INST_RETIRED", "Instruction architecturally executed, SIMD"],
+ ["0x8001", "ASE_INST_RETIRED", "Instruction architecturally executed, Advanced SIMD"],
+ ["0x8002", "SVE_INST_RETIRED", "Instruction architecturally executed, SVE"],
+ ["0x8003", "ASE_SVE_INST_RETIRED", "Instruction architecturally executed, Advanced SIMD or SVE"],
+ ["0x8004", "SIMD_INST_SPEC", "Operation speculatively executed, SIMD"],
+ ["0x8005", "ASE_INST_SPEC", "Operation speculatively executed, Advanced SIMD"],
+ ["0x8006", "SVE_INST_SPEC", "Operation speculatively executed, SVE, including load and store"],
+ ["0x8007", "ASE_SVE_INST_SPEC", "Operation speculatively executed, Advanced SIMD or SVE"],
+ ["0x8008", "UOP_SPEC", "Microarchitectural operation speculatively executed"],
+ ["0x8009", "ASE_UOP_SPEC", "Microarchitectural operation speculatively executed, Advanced SIMD"],
+ ["0x800A", "SVE_UOP_SPEC", "Microarchitectural operation speculatively executed, SVE"],
+ ["0x800B", "ASE_SVE_UOP_SPEC", "Microarchitectural operation speculatively executed, Advanced SIMD or SVE"],
+ ["0x800C", "SIMD_UOP_SPEC", "Microarchitectural operation speculatively executed, SIMD"],
+ ["0x800E", "SVE_MATH_SPEC", "Operation speculatively executed, SVE math accelerator"],
+ ["0x8010", "FP_SPEC", "Floating-point operation speculatively executed, including SIMD"],
+ ["0x8011", "ASE_FP_SPEC", "Floating-point operation speculatively executed, Advanced SIMD"],
+ ["0x8012", "SVE_FP_SPEC", "Floating-point operation speculatively executed, SVE"],
+ ["0x8013", "ASE_SVE_FP_SPEC", "Floating-point operation speculatively executed, Advanced SIMD or SVE"],
+ ["0x8014", "FP_HP_SPEC", "Floating-point operation speculatively executed, half precision"],
+ ["0x8015", "ASE_FP_HP_SPEC", "Floating-point operation speculatively executed, Advanced SIMD half precision"],
+ ["0x8016", "SVE_FP_HP_SPEC", "Floating-point operation speculatively executed, SVE half precision"],
+ ["0x8017", "ASE_SVE_FP_HP_SPEC", "Floating-point operation speculatively executed, Advanced SIMD or SVE half precision"],
+ ["0x8018", "FP_SP_SPEC", "Floating-point operation speculatively executed, single precision"],
+ ["0x8019", "ASE_FP_SP_SPEC", "Floating-point operation speculatively executed, Advanced SIMD single precision"],
+ ["0x801A", "SVE_FP_SP_SPEC", "Floating-point operation speculatively executed, SVE single precision"],
+ ["0x801B", "ASE_SVE_FP_SP_SPEC", "Floating-point operation speculatively executed, Advanced SIMD or SVE single precision"],
+ ["0x801C", "FP_DP_SPEC", "Floating-point operation speculatively executed, double precision"],
+ ["0x801D", "ASE_FP_DP_SPEC", "Floating-point operation speculatively executed, Advanced SIMD double precision"],
+ ["0x801E", "SVE_FP_DP_SPEC", "Floating-point operation speculatively executed, SVE double precision"],
+ ["0x801F", "ASE_SVE_FP_DP_SPEC", "Floating-point operation speculatively executed, Advanced SIMD or SVE double precision"],
+ ["0x8020", "FP_DIV_SPEC", "Floating-point operation speculatively executed, divide"],
+ ["0x8021", "ASE_FP_DIV_SPEC", "Floating-point operation speculatively executed, Advanced SIMD divide"],
+ ["0x8022", "SVE_FP_DIV_SPEC", "Floating-point operation speculatively executed, SVE divide"],
+ ["0x8023", "ASE_SVE_FP_DIV_SPEC", "Floating-point operation speculatively executed, Advanced SIMD or SVE divide"],
+ ["0x8024", "FP_SQRT_SPEC", "Floating-point operation speculatively executed, square root"],
+ ["0x8025", "ASE_FP_SQRT_SPEC", "Floating-point operation speculatively executed, Advanced SIMD square root"],
+ ["0x8026", "SVE_FP_SQRT_SPEC", "Floating-point operation speculatively executed, SVE square root"],
+ ["0x8027", "ASE_SVE_FP_SQRT_SPEC", "Floating-point operation speculatively executed, Advanced SIMD or SVE square-root"],
+ ["0x8028", "FP_FMA_SPEC", "Floating-point operation speculatively executed, FMA"],
+ ["0x8029", "ASE_FP_FMA_SPEC", "Floating-point operation speculatively executed, Advanced SIMD FMA"],
+ ["0x802A", "SVE_FP_FMA_SPEC", "Floating-point operation speculatively executed, SVE FMA"],
+ ["0x802B", "ASE_SVE_FP_FMA_SPEC", "Floating-point operation speculatively executed, Advanced SIMD or SVE FMA"],
+ ["0x802C", "FP_MUL_SPEC", "Floating-point operation speculatively executed, multiply"],
+ ["0x802D", "ASE_FP_MUL_SPEC", "Floating-point operation speculatively executed, Advanced SIMD multiply"],
+ ["0x802E", "SVE_FP_MUL_SPEC", "Floating-point operation speculatively executed, SVE multiply"],
+ ["0x802F", "ASE_SVE_FP_MUL_SPEC", "Floating-point operation speculatively executed, Advanced SIMD or SVE multiply"],
+ ["0x8030", "FP_ADDSUB_SPEC", "Floating-point operation speculatively executed, add or subtract"],
+ ["0x8031", "ASE_FP_ADDSUB_SPEC", "Floating-point operation speculatively executed, Advanced SIMD add or subtract"],
+ ["0x8032", "SVE_FP_ADDSUB_SPEC", "Floating-point operation speculatively executed, SVE add or subtract"],
+ ["0x8033", "ASE_SVE_FP_ADDSUB_SPEC", "Floating-point operation speculatively executed, Advanced SIMD or SVE add or subtract"],
+ ["0x8034", "FP_RECPE_SPEC", "Floating-point operation speculatively executed, reciprocal estimate"],
+ ["0x8035", "ASE_FP_RECPE_SPEC", "Floating-point operation speculatively executed, Advanced SIMD reciprocal estimate"],
+ ["0x8036", "SVE_FP_RECPE_SPEC", "Floating-point operation speculatively executed, SVE reciprocal estimate"],
+ ["0x8037", "ASE_SVE_FP_RECPE_SPEC", "Floating-point operation speculatively executed, Advanced SIMD or SVE reciprocal estimate"],
+ ["0x8038", "FP_CVT_SPEC", "Floating-point operation speculatively executed, convert"],
+ ["0x8039", "ASE_FP_CVT_SPEC", "Floating-point operation speculatively executed, Advanced SIMD convert"],
+ ["0x803A", "SVE_FP_CVT_SPEC", "Floating-point operation speculatively executed, SVE convert"],
+ ["0x803B", "ASE_SVE_FP_CVT_SPEC", "Floating-point operation speculatively executed, Advanced SIMD or SVE convert"],
+ ["0x803C", "SVE_FP_AREDUCE_SPEC", "Floating-point operation speculatively executed, SVE accumulating reduction"],
+ ["0x803D", "ASE_FP_PREDUCE_SPEC", "Floating-point operation speculatively executed, Advanced SIMD pairwise add step"],
+ ["0x803E", "SVE_FP_VREDUCE_SPEC", "Floating-point operation speculatively executed, SVE vector reduction"],
+ ["0x803F", "ASE_SVE_FP_VREDUCE_SPEC", "Floating-point operation speculatively executed, Advanced SIMD or SVE vector reduction"],
+ ["0x8040", "INT_SPEC", "Integer operation speculatively executed"],
+ ["0x8041", "ASE_INT_SPEC", "Integer operation speculatively executed, Advanced SIMD"],
+ ["0x8042", "SVE_INT_SPEC", "Integer operation speculatively executed, SVE"],
+ ["0x8043", "ASE_SVE_INT_SPEC", "Integer operation speculatively executed, Advanced SIMD or SVE"],
+ ["0x8044", "INT_DIV_SPEC", "Integer operation speculatively executed, divide"],
+ ["0x8045", "INT_DIV64_SPEC", "Integer operation speculatively executed, 64-bit divide"],
+ ["0x8046", "SVE_INT_DIV_SPEC", "Integer operation speculatively executed, SVE divide"],
+ ["0x8047", "SVE_INT_DIV64_SPEC", "Integer operation speculatively executed, SVE 64-bit divide"],
+ ["0x8048", "INT_MUL_SPEC", "Integer operation speculatively executed, multiply"],
+ ["0x8049", "ASE_INT_MUL_SPEC", "Integer operation speculatively executed, Advanced SIMD multiply"],
+ ["0x804A", "SVE_INT_MUL_SPEC", "Integer operation speculatively executed, SVE multiply"],
+ ["0x804B", "ASE_SVE_INT_MUL_SPEC", "Integer operation speculatively executed, Advanced SIMD or SVE multiply"],
+ ["0x804C", "INT_MUL64_SPEC", "Integer operation speculatively executed, 64x64 multiply"],
+ ["0x804D", "SVE_INT_MUL64_SPEC", "Integer operation speculatively executed, SVE 64x64 multiply"],
+ ["0x804E", "INT_MULH64_SPEC", "Integer operation speculatively executed, 64x64 multiply returning high part"],
+ ["0x804F", "SVE_INT_MULH64_SPEC", "Integer operation speculatively executed, SVE 64x64 multiply high part"],
+ ["0x8058", "NONFP_SPEC", "Non-floating-point operation speculatively executed"],
+ ["0x8059", "ASE_NONFP_SPEC", "Non-floating-point operation speculatively executed, Advanced SIMD"],
+ ["0x805A", "SVE_NONFP_SPEC", "Non-floating-point operation speculatively executed, SVE"],
+ ["0x805B", "ASE_SVE_NONFP_SPEC", "Non-floating-point operation speculatively executed, Advanced SIMD or SVE"],
+ ["0x805D", "ASE_INT_VREDUCE_SPEC", "Integer operation speculatively executed, Advanced SIMD reduction"],
+ ["0x805E", "SVE_INT_VREDUCE_SPEC", "Integer operation speculatively executed, SVE reduction"],
+ ["0x805F", "ASE_SVE_INT_VREDUCE_SPEC", "Integer operation speculatively executed, Advanced SIMD or SVE reduction"],
+ ["0x8060", "SVE_PERM_SPEC", "Operation speculatively executed, SVE permute"],
+ ["0x8061", "SVE_PERM_IGRANULE_SPEC", "Operation speculatively executed, SVE intra-granule permute"],
+ ["0x8062", "SVE_PERM_XGRANULE_SPEC", "Operation speculatively executed, SVE cross-granule permute"],
+ ["0x8063", "SVE_PERM_VARIABLE_SPEC", "Operation speculatively executed, SVE programmable permute"],
+ ["0x8064", "SVE_XPIPE_SPEC", "Operation speculatively executed, SVE cross-pipe"],
+ ["0x8065", "SVE_XPIPE_Z2R_SPEC", "Operation speculatively executed, SVE vector to scalar cross-pipe"],
+ ["0x8066", "SVE_XPIPE_R2Z_SPEC", "Operation speculatively executed, SVE scalar to vector cross-pipe"],
+ ["0x8067", "SVE_PGEN_NVEC_SPEC", "Operation speculatively executed, SVE predicate-only"],
+ ["0x8068", "SVE_PGEN_SPEC", "Operation speculatively executed, SVE predicate generating"],
+ ["0x8069", "SVE_PGEN_FLG_SPEC", "Operation speculatively executed, SVE predicate flag setting"],
+ ["0x806A", "SVE_PGEN_CMP_SPEC", "Operation speculatively executed, SVE vector compare"],
+ ["0x806B", "SVE_PGEN_FCM_SPEC", "Floating-point operation speculatively executed, SVE vector compare"],
+ ["0x806C", "SVE_PGEN_LOGIC_SPEC", "Operation speculatively executed, SVE predicate logical"],
+ ["0x806D", "SVE_PPERM_SPEC", "Operation speculatively executed, SVE predicate permute"],
+ ["0x806E", "SVE_PSCAN_SPEC", "Operation speculatively executed, SVE predicate scan"],
+ ["0x806F", "SVE_PCNT_SPEC", "Operation speculatively executed, SVE predicate count"],
+ ["0x8070", "SVE_PLOOP_WHILE_SPEC", "Operation speculatively executed, SVE predicate loop while"],
+ ["0x8071", "SVE_PLOOP_TEST_SPEC", "Operation speculatively executed, SVE predicate loop test"],
+ ["0x8072", "SVE_PLOOP_ELTS_SPEC", "Operation speculatively executed, SVE predicate loop elements"],
+ ["0x8073", "SVE_PLOOP_TERM_SPEC", "Operation speculatively executed, SVE predicate loop termination"],
+ ["0x8074", "SVE_PRED_SPEC", "Operation speculatively executed, SVE predicated"],
+ ["0x8075", "SVE_PRED_EMPTY_SPEC", "Operation speculatively executed, SVE predicated with no active predicates"],
+ ["0x8076", "SVE_PRED_FULL_SPEC", "Operation speculatively executed, SVE predicated with all active predicates"],
+ ["0x8077", "SVE_PRED_PARTIAL_SPEC", "Operation speculatively executed, SVE predicated with partially active predicates"],
+ ["0x8078", "SVE_UNPRED_SPEC", "Operation speculatively executed, SVE unpredicated"],
+ ["0x8079", "SVE_PRED_NOT_FULL_SPEC", "SVE predicated operations Speculatively executed with no active or partially active predicates"],
+ ["0x807C", "SVE_MOVPRFX_SPEC", "Operation speculatively executed, SVE MOVPRFX"],
+ ["0x807D", "SVE_MOVPRFX_Z_SPEC", "Operation speculatively executed, SVE MOVPRFX zeroing predication"],
+ ["0x807E", "SVE_MOVPRFX_M_SPEC", "Operation speculatively executed, SVE MOVPRFX merging predication"],
+ ["0x807F", "SVE_MOVPRFX_U_SPEC", "Operation speculatively executed, SVE MOVPRFX unfused"],
+ ["0x8080", "SVE_LDST_SPEC", "Operation speculatively executed, SVE load, store, or prefetch"],
+ ["0x8081", "SVE_LD_SPEC", "Operation speculatively executed, SVE load"],
+ ["0x8082", "SVE_ST_SPEC", "Operation speculatively executed, SVE store"],
+ ["0x8083", "SVE_PRF_SPEC", "Operation speculatively executed, SVE prefetch"],
+ ["0x8084", "ASE_SVE_LDST_SPEC", "Operation speculatively executed, Advanced SIMD or SVE load or store"],
+ ["0x8085", "ASE_SVE_LD_SPEC", "Operation speculatively executed, Advanced SIMD or SVE load"],
+ ["0x8086", "ASE_SVE_ST_SPEC", "Operation speculatively executed, Advanced SIMD or SVE store"],
+ ["0x8087", "PRF_SPEC", "Operation speculatively executed, Prefetch"],
+ ["0x8088", "BASE_LDST_REG_SPEC", "Operation speculatively executed, general-purpose register load, store, or prefetch"],
+ ["0x8089", "BASE_LD_REG_SPEC", "Operation speculatively executed, general-purpose register load"],
+ ["0x808A", "BASE_ST_REG_SPEC", "Operation speculatively executed, general-purpose register store"],
+ ["0x808B", "BASE_PRF_SPEC", "Operation speculatively executed, general-purpose register prefetch"],
+ ["0x808C", "FPASE_LDST_REG_SPEC", "Operation speculatively executed, SIMD&FP register load or store"],
+ ["0x808D", "FPASE_LD_REG_SPEC", "Operation speculatively executed, SIMD&FP register load"],
+ ["0x808E", "FPASE_ST_REG_SPEC", "Operation speculatively executed, SIMD&FP register store"],
+ ["0x8090", "SVE_LDST_REG_SPEC", "Operation speculatively executed, SVE unpredicated load or store register"],
+ ["0x8091", "SVE_LDR_REG_SPEC", "Operation speculatively executed, SVE unpredicated load register"],
+ ["0x8092", "SVE_STR_REG_SPEC", "Operation speculatively executed, SVE unpredicated store register"],
+ ["0x8094", "SVE_LDST_PREG_SPEC", "Operation speculatively executed, SVE load or store predicate register"],
+ ["0x8095", "SVE_LDR_PREG_SPEC", "Operation speculatively executed, SVE load predicate register"],
+ ["0x8096", "SVE_STR_PREG_SPEC", "Operation speculatively executed, SVE store predicate register"],
+ ["0x8098", "SVE_LDST_ZREG_SPEC", "Operation speculatively executed, SVE load or store vector register"],
+ ["0x8099", "SVE_LDR_ZREG_SPEC", "Operation speculatively executed, SVE load vector register"],
+ ["0x809A", "SVE_STR_ZREG_SPEC", "Operation speculatively executed, SVE store vector register"],
+ ["0x809C", "SVE_LDST_CONTIG_SPEC", "Operation speculatively executed, SVE contiguous load, store, or prefetch element"],
+ ["0x809D", "SVE_LD_CONTIG_SPEC", "Operation speculatively executed, SVE contiguous load element"],
+ ["0x809E", "SVE_ST_CONTIG_SPEC", "Operation speculatively executed, SVE contiguous store element"],
+ ["0x809F", "SVE_PRF_CONTIG_SPEC", "Operation speculatively executed, SVE contiguous prefetch element"],
+ ["0x80A0", "SVE_LDSTNT_CONTIG_SPEC", "Operation speculatively executed, SVE non-temporal contiguous load or store element"],
+ ["0x80A1", "SVE_LDNT_CONTIG_SPEC", "Operation speculatively executed, SVE non-temporal contiguous load element"],
+ ["0x80A2", "SVE_STNT_CONTIG_SPEC", "Operation speculatively executed, SVE non-temporal contiguous store element"],
+ ["0x80A4", "ASE_SVE_LDST_MULTI_SPEC", "Operation speculatively executed, Advanced SIMD or SVE contiguous load or store multiple vector"],
+ ["0x80A5", "ASE_SVE_LD_MULTI_SPEC", "Operation speculatively executed, Advanced SIMD or SVE contiguous load multiple vector"],
+ ["0x80A6", "ASE_SVE_ST_MULTI_SPEC", "Operation speculatively executed, Advanced SIMD or SVE contiguous store multiple vector"],
+ ["0x80A8", "SVE_LDST_MULTI_SPEC", "Operation speculatively executed, SVE contiguous load or store multiple vector"],
+ ["0x80A9", "SVE_LD_MULTI_SPEC", "Operation speculatively executed, SVE contiguous load multiple vector"],
+ ["0x80AA", "SVE_ST_MULTI_SPEC", "Operation speculatively executed, SVE contiguous store multiple vector"],
+ ["0x80AC", "SVE_LDST_NONCONTIG_SPEC", "Operation speculatively executed, SVE non-contiguous load, store, or prefetch"],
+ ["0x80AD", "SVE_LD_GATHER_SPEC", "Operation speculatively executed, SVE gather-load"],
+ ["0x80AE", "SVE_ST_SCATTER_SPEC", "Operation speculatively executed, SVE scatter-store"],
+ ["0x80AF", "SVE_PRF_GATHER_SPEC", "Operation speculatively executed, SVE gather-prefetch"],
+ ["0x80B0", "SVE_LDST64_NONCONTIG_SPEC", "Operation speculatively executed, SVE 64-bit non-contiguous load, store, or prefetch"],
+ ["0x80B1", "SVE_LD64_GATHER_SPEC", "Operation speculatively executed, SVE 64-bit gather-load"],
+ ["0x80B2", "SVE_ST64_SCATTER_SPEC", "Operation speculatively executed, SVE 64-bit scatter-store"],
+ ["0x80B3", "SVE_PRF64_GATHER_SPEC", "Operation speculatively executed, SVE 64-bit gather-prefetch"],
+ ["0x80B4", "ASE_SVE_UNALIGNED_LDST_SPEC", "Advanced SIMD or SVE unaligned accesses"],
+ ["0x80B5", "ASE_SVE_UNALIGNED_LD_SPEC", "Advanced SIMD or SVE unaligned read accesses"],
+ ["0x80B6", "ASE_SVE_UNALIGNED_ST_SPEC", "Advanced SIMD or SVE unaligned write accesses"],
+ ["0x80B8", "ASE_SVE_UNALIGNED_CONTIG_LDST_SPEC", "Advanced SIMD or SVE unaligned contiguous accesses"],
+ ["0x80B9", "ASE_SVE_UNALIGNED_CONTIG_LD_SPEC", "Advanced SIMD or SVE unaligned contiguous read accesses"],
+ ["0x80BA", "ASE_SVE_UNALIGNED_CONTIG_ST_SPEC", "Advanced SIMD or SVE unaligned contiguous write accesses"],
+ ["0x80BC", "SVE_LDFF_SPEC", "Operation speculatively executed, SVE first-fault load"],
+ ["0x80BD", "SVE_LDFF_FAULT_SPEC", "Operation speculatively executed, SVE first-fault load which set FFR bit to 0b0"],
+ ["0x80C0", "FP_SCALE_OPS_SPEC", "Scalable floating-point element ALU operations Speculatively executed"],
+ ["0x80C1", "FP_FIXED_OPS_SPEC", "Non-scalable floating-point element ALU operations Speculatively executed"],
+ ["0x80C2", "FP_HP_SCALE_OPS_SPEC", "Scalable half-precision floating-point element ALU operations Speculatively executed"],
+ ["0x80C3", "FP_HP_FIXED_OPS_SPEC", "Non-scalable half-precision floating-point element ALU operations Speculatively executed"],
+ ["0x80C4", "FP_SP_SCALE_OPS_SPEC", "Scalable single-precision floating-point element ALU operations Speculatively executed"],
+ ["0x80C5", "FP_SP_FIXED_OPS_SPEC", "Non-scalable single-precision floating-point element ALU operations Speculatively executed"],
+ ["0x80C6", "FP_DP_SCALE_OPS_SPEC", "Scalable double-precision floating-point element ALU operations Speculatively executed"],
+ ["0x80C7", "FP_DP_FIXED_OPS_SPEC", "Non-scalable double-precision floating-point element ALU operations Speculatively executed"],
+ ["0x80C8", "INT_SCALE_OPS_SPEC", "Scalable integer element ALU operations Speculatively executed"],
+ ["0x80C9", "INT_FIXED_OPS_SPEC", "Non-scalable integer element ALU operations Speculatively executed"],
+ ["0x80CA", "LDST_SCALE_OPS_SPEC", "Scalable load or store element Operations speculatively executed"],
+ ["0x80CB", "LDST_FIXED_OPS_SPEC", "Non-scalable load or store element Operations speculatively executed"],
+ ["0x80CC", "LD_SCALE_OPS_SPEC", "Scalable load element Operations speculatively executed"],
+ ["0x80CD", "LD_FIXED_OPS_SPEC", "Non-scalable load element Operations speculatively executed"],
+ ["0x80CE", "ST_SCALE_OPS_SPEC", "Scalable store element Operations speculatively executed"],
+ ["0x80CF", "ST_FIXED_OPS_SPEC", "Non-scalable store element Operations speculatively executed"],
+ ["0x80DA", "LDST_SCALE_BYTES_SPEC", "Scalable load and store bytes Speculatively executed"],
+ ["0x80DB", "LDST_FIXED_BYTES_SPEC", "Non-scalable load and store bytes Speculatively executed"],
+ ["0x80DC", "LD_SCALE_BYTES_SPEC", "Scalable load bytes Speculatively executed"],
+ ["0x80DD", "LD_FIXED_BYTES_SPEC", "Non-scalable load bytes Speculatively executed"],
+ ["0x80DE", "ST_SCALE_BYTES_SPEC", "Scalable store bytes Speculatively executed"],
+ ["0x80DF", "ST_FIXED_BYTES_SPEC", "Non-scalable store bytes Speculatively executed"],
+ ["0x80E1", "ASE_INT8_SPEC", "Integer operation speculatively executed, Advanced SIMD 8-bit"],
+ ["0x80E2", "SVE_INT8_SPEC", "Integer operation speculatively executed, SVE 8-bit"],
+ ["0x80E3", "ASE_SVE_INT8_SPEC", "Integer operation speculatively executed, Advanced SIMD or SVE 8-bit"],
+ ["0x80E5", "ASE_INT16_SPEC", "Integer operation speculatively executed, Advanced SIMD 16-bit"],
+ ["0x80E6", "SVE_INT16_SPEC", "Integer operation speculatively executed, SVE 16-bit"],
+ ["0x80E7", "ASE_SVE_INT16_SPEC", "Integer operation speculatively executed, Advanced SIMD or SVE 16-bit"],
+ ["0x80E9", "ASE_INT32_SPEC", "Integer operation speculatively executed, Advanced SIMD 32-bit"],
+ ["0x80EA", "SVE_INT32_SPEC", "Integer operation speculatively executed, SVE 32-bit"],
+ ["0x80EB", "ASE_SVE_INT32_SPEC", "Integer operation speculatively executed, Advanced SIMD or SVE 32-bit"],
+ ["0x80ED", "ASE_INT64_SPEC", "Integer operation speculatively executed, Advanced SIMD 64-bit"],
+ ["0x80EE", "SVE_INT64_SPEC", "Integer operation speculatively executed, SVE 64-bit"],
+ ["0x80EF", "ASE_SVE_INT64_SPEC", "Integer operation speculatively executed, Advanced SIMD or SVE 64-bit"],
+ ["0x80F1", "ASE_FP_DOT_SPEC", "Floating-point operation speculatively executed, Advanced SIMD dot-product"],
+ ["0x80F2", "SVE_FP_DOT_SPEC", "Floating-point operation speculatively executed, SVE dot-product"],
+ ["0x80F3", "ASE_SVE_FP_DOT_SPEC", "Floating-point operation speculatively executed, Advanced SIMD or SVE dot-product"],
+ ["0x80F5", "ASE_FP_MMLA_SPEC", "Floating-point operation speculatively executed, Advanced SIMD matrix multiply"],
+ ["0x80F6", "SVE_FP_MMLA_SPEC", "Floating-point operation speculatively executed, SVE matrix multiply"],
+ ["0x80F7", "ASE_SVE_FP_MMLA_SPEC", "Floating-point operation speculatively executed, Advanced SIMD or SVE matrix multiply"],
+ ["0x80F9", "ASE_INT_DOT_SPEC", "Operation speculatively executed, Advanced SIMD integer dot-product"],
+ ["0x80FA", "SVE_INT_DOT_SPEC", "Integer operation speculatively executed, SVE dot-product"],
+ ["0x80FB", "ASE_SVE_INT_DOT_SPEC", "Integer operation speculatively executed, Advanced SIMD or SVE dot-product"],
+ ["0x80FD", "ASE_INT_MMLA_SPEC", "Integer operation speculatively executed, Advanced SIMD matrix multiply"],
+ ["0x80FE", "SVE_INT_MMLA_SPEC", "Integer operation speculatively executed, SVE matrix multiply"],
+ ["0x80FF", "ASE_SVE_INT_MMLA_SPEC", "Integer operation speculatively executed, Advanced SIMD or SVE matrix multiply"],
+ ["0x8107", "BR_SKIP_RETIRED", "Branch Instruction architecturally executed, not taken"],
+ ["0x8108", "BR_IMMED_TAKEN_RETIRED", "Branch Instruction architecturally executed, immediate, taken"],
+ ["0x8109", "BR_IMMED_SKIP_RETIRED", "Branch Instruction architecturally executed, immediate, not taken"],
+ ["0x810A", "BR_IND_TAKEN_RETIRED", "Branch Instruction architecturally executed, indirect, taken"],
+ ["0x810B", "BR_IND_SKIP_RETIRED", "Branch Instruction architecturally executed, indirect, not taken"],
+ ["0x810C", "BR_INDNR_TAKEN_RETIRED", "Branch Instruction architecturally executed, indirect excluding procedure return, taken"],
+ ["0x810D", "BR_INDNR_SKIP_RETIRED", "Branch Instruction architecturally executed, indirect excluding procedure return, not taken"],
+ ["0x810E", "BR_RETURN_ANY_RETIRED", "Branch Instruction architecturally executed, procedure return"],
+ ["0x810F", "BR_RETURN_SKIP_RETIRED", "Branch Instruction architecturally executed, procedure return, not taken"],
+ ["0x8110", "BR_IMMED_PRED_RETIRED", "Branch Instruction architecturally executed, predicted immediate"],
+ ["0x8111", "BR_IMMED_MIS_PRED_RETIRED", "Branch Instruction architecturally executed, mispredicted immediate"],
+ ["0x8112", "BR_IND_PRED_RETIRED", "Branch Instruction architecturally executed, predicted indirect"],
+ ["0x8113", "BR_IND_MIS_PRED_RETIRED", "Branch Instruction architecturally executed, mispredicted indirect"],
+ ["0x8114", "BR_RETURN_PRED_RETIRED", "Branch Instruction architecturally executed, predicted procedure return"],
+ ["0x8115", "BR_RETURN_MIS_PRED_RETIRED", "Branch Instruction architecturally executed, mispredicted procedure return"],
+ ["0x8116", "BR_INDNR_PRED_RETIRED", "Branch Instruction architecturally executed, predicted indirect excluding procedure return"],
+ ["0x8117", "BR_INDNR_MIS_PRED_RETIRED", "Branch Instruction architecturally executed, mispredicted indirect excluding procedure return"],
+ ["0x8118", "BR_TAKEN_PRED_RETIRED", "Branch Instruction architecturally executed, predicted branch, taken"],
+ ["0x8119", "BR_TAKEN_MIS_PRED_RETIRED", "Branch Instruction architecturally executed, mispredicted branch, taken"],
+ ["0x811A", "BR_SKIP_PRED_RETIRED", "Branch Instruction architecturally executed, predicted branch, not taken"],
+ ["0x811B", "BR_SKIP_MIS_PRED_RETIRED", "Branch Instruction architecturally executed, mispredicted branch, not taken"],
+ ["0x811C", "BR_PRED_RETIRED", "Branch Instruction architecturally executed, predicted branch"],
+ ["0x811D", "BR_IND_RETIRED", "Instruction architecturally executed, indirect branch"],
+ ["0x811E", "BR_INDNR_RETIRED", "Branch Instruction architecturally executed, indirect excluding procedure return"],
+ ["0x811F", "BRB_FILTRATE", "Branch Record captured"],
+ ["0x8120", "INST_FETCH_PERCYC", "Event in progress, INST_FETCH"],
+ ["0x8121", "MEM_ACCESS_RD_PERCYC", "Event in progress, MEM_ACCESS_RD"],
+ ["0x8124", "INST_FETCH", "Instruction memory access"],
+ ["0x8125", "BUS_REQ_RD_PERCYC", "Bus read transactions in progress"],
+ ["0x8126", "BUS_REQ_WR_PERCYC", "Bus write transactions in progress"],
+ ["0x8128", "DTLB_WALK_PERCYC", "Event in progress, DTLB_WALK"],
+ ["0x8129", "ITLB_WALK_PERCYC", "Event in progress, ITLB_WALK"],
+ ["0x812A", "SAMPLE_FEED_BR", "Statistical Profiling sample taken, branch"],
+ ["0x812B", "SAMPLE_FEED_LD", "Statistical Profiling sample taken, load"],
+ ["0x812C", "SAMPLE_FEED_ST", "Statistical Profiling sample taken, store"],
+ ["0x812D", "SAMPLE_FEED_OP", "Statistical Profiling sample taken, matching operation type"],
+ ["0x812E", "SAMPLE_FEED_EVENT", "Statistical Profiling sample taken, matching events"],
+ ["0x812F", "SAMPLE_FEED_LAT", "Statistical Profiling sample taken, exceeding minimum latency"],
+ ["0x8130", "L1D_TLB_RW", "Level 1 data TLB demand access"],
+ ["0x8131", "L1I_TLB_RD", "Level 1 instruction TLB demand access"],
+ ["0x8132", "L1D_TLB_PRFM", "Level 1 data TLB software preload"],
+ ["0x8133", "L1I_TLB_PRFM", "Level 1 instruction TLB software preload"],
+ ["0x8134", "DTLB_HWUPD", "Data TLB hardware update of translation table"],
+ ["0x8135", "ITLB_HWUPD", "Instruction TLB hardware update of translation table"],
+ ["0x8136", "DTLB_STEP", "Data TLB translation table walk, step"],
+ ["0x8137", "ITLB_STEP", "Instruction TLB translation table walk, step"],
+ ["0x8138", "DTLB_WALK_LARGE", "Data TLB large page translation table walk"],
+ ["0x8139", "ITLB_WALK_LARGE", "Instruction TLB large page translation table walk"],
+ ["0x813A", "DTLB_WALK_SMALL", "Data TLB small page translation table walk"],
+ ["0x813B", "ITLB_WALK_SMALL", "Instruction TLB small page translation table walk"],
+ ["0x813C", "DTLB_WALK_RW", "Data TLB demand access with at least one translation table walk"],
+ ["0x813D", "ITLB_WALK_RD", "Instruction TLB demand access with at least one translation table walk"],
+ ["0x813E", "DTLB_WALK_PRFM", "Data TLB software preload access with at least one translation table walk"],
+ ["0x813F", "ITLB_WALK_PRFM", "Instruction TLB software preload access with at least one translation table walk"],
+ ["0x8140", "L1D_CACHE_RW", "Level 1 data cache demand access"],
+ ["0x8141", "L1I_CACHE_RD", "Level 1 instruction cache demand fetch"],
+ ["0x8142", "L1D_CACHE_PRFM", "Level 1 data cache software preload"],
+ ["0x8143", "L1I_CACHE_PRFM", "Level 1 instruction cache software preload"],
+ ["0x8144", "L1D_CACHE_MISS", "Level 1 data cache demand access miss"],
+ ["0x8145", "L1I_CACHE_HWPRF", "Level 1 instruction cache hardware prefetch"],
+ ["0x8146", "L1D_CACHE_REFILL_PRFM", "Level 1 data cache refill, software preload"],
+ ["0x8147", "L1I_CACHE_REFILL_PRFM", "Level 1 instruction cache refill, software preload"],
+ ["0x8148", "L2D_CACHE_RW", "Level 2 data cache demand access"],
+ ["0x8149", "L2I_CACHE_RD", "Level 2 instruction cache demand fetch"],
+ ["0x814A", "L2D_CACHE_PRFM", "Level 2 data cache software preload"],
+ ["0x814B", "L2I_CACHE_PRFM", "Level 2 instruction cache software preload"],
+ ["0x814C", "L2D_CACHE_MISS", "Level 2 data cache demand access miss"],
+ ["0x814D", "L2I_CACHE_HWPRF", "Level 2 instruction cache hardware prefetch"],
+ ["0x814E", "L2D_CACHE_REFILL_PRFM", "Level 2 data cache refill, software preload"],
+ ["0x814F", "L2I_CACHE_REFILL_PRFM", "Level 2 instruction cache refill, software preload"],
+ ["0x8150", "L3D_CACHE_RW", "Level 3 data cache demand access"],
+ ["0x8151", "L3D_CACHE_PRFM", "Level 3 data cache software preload"],
+ ["0x8152", "L3D_CACHE_MISS", "Level 3 data cache demand access miss"],
+ ["0x8153", "L3D_CACHE_REFILL_PRFM", "Level 3 data cache refill, software preload"],
+ ["0x8154", "L1D_CACHE_HWPRF", "Level 1 data cache hardware prefetch"],
+ ["0x8155", "L2D_CACHE_HWPRF", "Level 2 data cache hardware prefetch"],
+ ["0x8156", "L3D_CACHE_HWPRF", "Level 3 data cache hardware prefetch"],
+ ["0x8157", "LL_CACHE_HWPRF", "Last level cache hardware prefetch"],
+ ["0x8158", "STALL_FRONTEND_MEMBOUND", "Frontend stall cycles, memory bound"],
+ ["0x8159", "STALL_FRONTEND_L1I", "Frontend stall cycles, level 1 instruction cache"],
+ ["0x815A", "STALL_FRONTEND_L2I", "Frontend stall cycles, level 2 instruction cache"],
+ ["0x815B", "STALL_FRONTEND_MEM", "Frontend stall cycles, last level PE cache or memory"],
+ ["0x815C", "STALL_FRONTEND_TLB", "Frontend stall cycles, TLB"],
+ ["0x8160", "STALL_FRONTEND_CPUBOUND", "Frontend stall cycles, processor bound"],
+ ["0x8161", "STALL_FRONTEND_FLOW", "Frontend stall cycles, flow control"],
+ ["0x8162", "STALL_FRONTEND_FLUSH", "Frontend stall cycles, flush recovery"],
+ ["0x8163", "STALL_FRONTEND_RENAME", "Frontend stall cycles, rename full"],
+ ["0x8164", "STALL_BACKEND_MEMBOUND", "Backend stall cycles, memory bound"],
+ ["0x8165", "STALL_BACKEND_L1D", "Backend stall cycles, level 1 data cache"],
+ ["0x8166", "STALL_BACKEND_L2D", "Backend stall cycles, level 2 data cache"],
+ ["0x8167", "STALL_BACKEND_TLB", "Backend stall cycles, TLB"],
+ ["0x8168", "STALL_BACKEND_ST", "Backend stall cycles, store"],
+ ["0x816A", "STALL_BACKEND_CPUBOUND", "Backend stall cycles, processor bound"],
+ ["0x816B", "STALL_BACKEND_BUSY", "Backend stall cycles, backend busy"],
+ ["0x816C", "STALL_BACKEND_ILOCK", "Backend stall cycles, input dependency"],
+ ["0x816D", "STALL_BACKEND_RENAME", "Backend stall cycles, rename full"],
+ ["0x816E", "STALL_BACKEND_ATOMIC", "Backend stall cycles, atomic operation"],
+ ["0x816F", "STALL_BACKEND_MEMCPYSET", "Backend stall cycles, Memory Copy or Set operation"],
+ ["0x8170", "CAS_NEAR_FAIL", "Atomic memory Operation speculatively executed, Compare and Swap fail"],
+ ["0x8171", "CAS_NEAR_PASS", "Atomic memory Operation speculatively executed, Compare and Swap pass"],
+ ["0x8172", "CAS_NEAR_SPEC", "Atomic memory Operation speculatively executed, Compare and Swap near"],
+ ["0x8173", "CAS_FAR_SPEC", "Atomic memory Operation speculatively executed, Compare and Swap far"],
+ ["0x8174", "CAS_SPEC", "Atomic memory Operation speculatively executed, Compare and Swap"],
+ ["0x8175", "LSE_LD_SPEC", "Atomic memory Operation speculatively executed, load"],
+ ["0x8176", "LSE_ST_SPEC", "Atomic memory Operation speculatively executed, store"],
+ ["0x8177", "LSE_LDST_SPEC", "Atomic memory Operation speculatively executed, load or store"],
+ ["0x8178", "REMOTE_ACCESS_WR", "Access to another socket in a multi-socket system, write"],
+ ["0x8179", "BRNL_INDNR_TAKEN_RETIRED", "Branch Instruction architecturally executed, indirect branch without link excluding procedure return, taken"],
+ ["0x817A", "BL_TAKEN_RETIRED", "Branch Instruction architecturally executed, branch with link, taken"],
+ ["0x817B", "BRNL_TAKEN_RETIRED", "Branch Instruction architecturally executed, branch without link, taken"],
+ ["0x817C", "BL_IND_TAKEN_RETIRED", "Branch Instruction architecturally executed, indirect branch with link, taken"],
+ ["0x817D", "BRNL_IND_TAKEN_RETIRED", "Branch Instruction architecturally executed, indirect branch without link, taken"],
+ ["0x817E", "BL_IMMED_TAKEN_RETIRED", "Branch Instruction architecturally executed, direct branch with link, taken"],
+ ["0x817F", "BRNL_IMMED_TAKEN_RETIRED", "Branch Instruction architecturally executed, direct branch without link, taken"],
+ ["0x8180", "BR_UNCOND_RETIRED", "Branch Instruction architecturally executed, unconditional branch"],
+ ["0x8181", "BR_COND_RETIRED", "Branch Instruction architecturally executed, conditional branch"],
+ ["0x8182", "BR_COND_TAKEN_RETIRED", "Branch Instruction architecturally executed, conditional branch, taken"],
+ ["0x8183", "BR_HINT_COND_RETIRED", "Branch Instruction architecturally executed, hinted conditional"],
+ ["0x8184", "BR_HINT_COND_PRED_RETIRED", "Branch Instruction architecturally executed, predicted hinted conditional"],
+ ["0x8185", "BR_HINT_COND_MIS_PRED_RETIRED", "Branch Instruction architecturally executed, mispredicted hinted conditional"],
+ ["0x8186", "UOP_RETIRED", "Micro-operation architecturally executed"],
+ ["0x8188", "DTLB_WALK_BLOCK", "Data TLB block translation table walk"],
+ ["0x8189", "ITLB_WALK_BLOCK", "Instruction TLB block translation table walk"],
+ ["0x818A", "DTLB_WALK_PAGE", "Data TLB page translation table walk"],
+ ["0x818B", "ITLB_WALK_PAGE", "Instruction TLB page translation table walk"],
+ ["0x818D", "BUS_REQ_RD", "Bus request, read"],
+ ["0x818E", "BUS_REQ_WR", "Bus request, write"],
+ ["0x818F", "BUS_REQ", "Bus request"],
+ ["0x8190", "ISNP_HIT_RD", "Snoop hit, demand instruction fetch"],
+ ["0x8191", "ISNP_HIT_NEAR_RD", "Snoop hit in near cache, demand instruction fetch"],
+ ["0x8192", "ISNP_HIT_FAR_RD", "Snoop hit in far cache, demand instruction fetch"],
+ ["0x8193", "ISNP_HIT_REMOTE_RD", "Snoop hit in remote cache, demand instruction fetch"],
+ ["0x8194", "DSNP_HIT_RD", "Snoop hit, demand data read"],
+ ["0x8195", "DSNP_HIT_NEAR_RD", "Snoop hit in near cache, demand data read"],
+ ["0x8196", "DSNP_HIT_FAR_RD", "Snoop hit in far cache, demand data read"],
+ ["0x8197", "DSNP_HIT_REMOTE_RD", "Snoop hit in remote cache, demand data read"],
+ ["0x8198", "DSNP_HIT_WR", "Snoop hit, demand data write"],
+ ["0x8199", "DSNP_HIT_NEAR_WR", "Snoop hit in near cache, demand data write"],
+ ["0x819A", "DSNP_HIT_FAR_WR", "Snoop hit in far cache, demand data write"],
+ ["0x819B", "DSNP_HIT_REMOTE_WR", "Snoop hit in remote cache, demand data write"],
+ ["0x819C", "DSNP_HIT_RW", "Snoop hit, demand data access"],
+ ["0x819D", "DSNP_HIT_NEAR_RW", "Snoop hit in near cache, demand data access"],
+ ["0x819E", "DSNP_HIT_FAR_RW", "Snoop hit in far cache, demand data access"],
+ ["0x819F", "DSNP_HIT_REMOTE_RW", "Snoop hit in remote cache, demand data access"],
+ ["0x81A0", "DSNP_HIT_PRFM", "Snoop hit, software data preload"],
+ ["0x81A1", "DSNP_HIT_NEAR_PRFM", "Snoop hit in near cache, software data preload"],
+ ["0x81A2", "DSNP_HIT_FAR_PRFM", "Snoop hit in far cache, software data preload"],
+ ["0x81A3", "DSNP_HIT_REMOTE_PRFM", "Snoop hit in remote cache, software data preload"],
+ ["0x81A4", "DSNP_HIT_HWPRF", "Snoop hit, hardware data prefetch"],
+ ["0x81A5", "DSNP_HIT_NEAR_HWPRF", "Snoop hit in near cache, hardware data prefetch"],
+ ["0x81A6", "DSNP_HIT_FAR_HWPRF", "Snoop hit in far cache, hardware data prefetch"],
+ ["0x81A7", "DSNP_HIT_REMOTE_HWPRF", "Snoop hit in remote cache, hardware data prefetch"],
+ ["0x81A8", "ISNP_HIT_PRFM", "Snoop hit, software instruction preload"],
+ ["0x81A9", "ISNP_HIT_NEAR_PRFM", "Snoop hit in near cache, software instruction preload"],
+ ["0x81AA", "ISNP_HIT_FAR_PRFM", "Snoop hit in far cache, software instruction preload"],
+ ["0x81AB", "ISNP_HIT_REMOTE_PRFM", "Snoop hit in remote cache, software instruction preload"],
+ ["0x81AC", "ISNP_HIT_HWPRF", "Snoop hit, hardware instruction prefetch"],
+ ["0x81AD", "ISNP_HIT_NEAR_HWPRF", "Snoop hit in near cache, hardware instruction prefetch"],
+ ["0x81AE", "ISNP_HIT_FAR_HWPRF", "Snoop hit in far cache, hardware instruction prefetch"],
+ ["0x81AF", "ISNP_HIT_REMOTE_HWPRF", "Snoop hit in remote cache, hardware instruction prefetch"],
+ ["0x81B0", "ISNP_HIT", "Snoop hit, instruction"],
+ ["0x81B1", "ISNP_HIT_NEAR", "Snoop hit in near cache, instruction"],
+ ["0x81B2", "ISNP_HIT_FAR", "Snoop hit in far cache, instruction"],
+ ["0x81B3", "ISNP_HIT_REMOTE", "Snoop hit in remote cache, instruction"],
+ ["0x81B4", "DSNP_HIT", "Snoop hit, data"],
+ ["0x81B5", "DSNP_HIT_NEAR", "Snoop hit in near cache, data"],
+ ["0x81B6", "DSNP_HIT_FAR", "Snoop hit in far cache, data"],
+ ["0x81B7", "DSNP_HIT_REMOTE", "Snoop hit in remote cache, data"],
+ ["0x81B8", "L1I_CACHE_REFILL_HWPRF", "Level 1 instruction cache refill, hardware prefetch"],
+ ["0x81B9", "L2I_CACHE_REFILL_HWPRF", "Level 2 instruction cache refill, hardware prefetch"],
+ ["0x81BC", "L1D_CACHE_REFILL_HWPRF", "Level 1 data cache refill, hardware prefetch"],
+ ["0x81BD", "L2D_CACHE_REFILL_HWPRF", "Level 2 data cache refill, hardware prefetch"],
+ ["0x81BE", "L3D_CACHE_REFILL_HWPRF", "Level 3 data cache refill, hardware prefetch"],
+ ["0x81BF", "LL_CACHE_REFILL_HWPRF", "Last level cache refill, hardware prefetch"],
+ ["0x81C0", "L1I_CACHE_HIT_RD", "Level 1 instruction cache demand fetch hit"],
+ ["0x81C1", "L2I_CACHE_HIT_RD", "Level 2 instruction cache demand fetch hit"],
+ ["0x81C4", "L1D_CACHE_HIT_RD", "Level 1 data cache demand hit, read"],
+ ["0x81C5", "L2D_CACHE_HIT_RD", "Level 2 data cache demand hit, read"],
+ ["0x81C6", "L3D_CACHE_HIT_RD", "Level 3 data cache demand hit, read"],
+ ["0x81C7", "LL_CACHE_HIT_RD", "Last level cache demand hit, read"],
+ ["0x81C8", "L1D_CACHE_HIT_WR", "Level 1 data cache demand access hit, write"],
+ ["0x81C9", "L2D_CACHE_HIT_WR", "Level 2 data cache demand access hit, write"],
+ ["0x81CA", "L3D_CACHE_HIT_WR", "Level 3 data cache demand access hit, write"],
+ ["0x81CB", "LL_CACHE_HIT_WR", "Last level cache demand access hit, write"],
+ ["0x81CC", "L1D_CACHE_HIT_RW", "Level 1 data cache demand access hit"],
+ ["0x81CD", "L2D_CACHE_HIT_RW", "Level 2 data cache demand access hit"],
+ ["0x81CE", "L3D_CACHE_HIT_RW", "Level 3 data cache demand access hit"],
+ ["0x81CF", "LL_CACHE_HIT_RW", "Last level cache demand access hit"],
+ ["0x81D0", "L1I_CACHE_HIT_RD_FPRFM", "Level 1 instruction cache demand fetch first hit, fetched by software preload"],
+ ["0x81D1", "L2I_CACHE_HIT_RD_FPRFM", "Level 2 instruction cache demand fetch first hit, fetched by software preload"],
+ ["0x81D4", "L1D_CACHE_HIT_RD_FPRFM", "Level 1 data cache demand first hit, read, fetched by software preload"],
+ ["0x81D5", "L2D_CACHE_HIT_RD_FPRFM", "Level 2 data cache demand first hit, read, fetched by software preload"],
+ ["0x81D6", "L3D_CACHE_HIT_RD_FPRFM", "Level 3 data cache demand first hit, read, fetched by software preload"],
+ ["0x81D7", "LL_CACHE_HIT_RD_FPRFM", "Last level cache demand first hit, read, fetched by software preload"],
+ ["0x81D8", "L1D_CACHE_HIT_WR_FPRFM", "Level 1 data cache demand access first hit, write, fetched by software preload"],
+ ["0x81D9", "L2D_CACHE_HIT_WR_FPRFM", "Level 2 data cache demand access first hit, write, fetched by software preload"],
+ ["0x81DA", "L3D_CACHE_HIT_WR_FPRFM", "Level 3 data cache demand access first hit, write, fetched by software preload"],
+ ["0x81DB", "LL_CACHE_HIT_WR_FPRFM", "Last level cache demand access first hit, write, fetched by software preload"],
+ ["0x81DC", "L1D_CACHE_HIT_RW_FPRFM", "Level 1 data cache demand access first hit, fetched by software preload"],
+ ["0x81DD", "L2D_CACHE_HIT_RW_FPRFM", "Level 2 data cache demand access first hit, fetched by software preload"],
+ ["0x81DE", "L3D_CACHE_HIT_RW_FPRFM", "Level 3 data cache demand access first hit, fetched by software preload"],
+ ["0x81DF", "LL_CACHE_HIT_RW_FPRFM", "Last level cache demand access first hit, fetched by software preload"],
+ ["0x81E0", "L1I_CACHE_HIT_RD_FHWPRF", "Level 1 instruction cache demand fetch first hit, fetched by hardware prefetcher"],
+ ["0x81E1", "L2I_CACHE_HIT_RD_FHWPRF", "Level 2 instruction cache demand fetch first hit, fetched by hardware prefetcher"],
+ ["0x81E4", "L1D_CACHE_HIT_RD_FHWPRF", "Level 1 data cache demand first hit, read, fetched by hardware prefetcher"],
+ ["0x81E5", "L2D_CACHE_HIT_RD_FHWPRF", "Level 2 data cache demand first hit, read, fetched by hardware prefetcher"],
+ ["0x81E6", "L3D_CACHE_HIT_RD_FHWPRF", "Level 3 data cache demand first hit, read, fetched by hardware prefetcher"],
+ ["0x81E7", "LL_CACHE_HIT_RD_FHWPRF", "Last level cache demand first hit, read, fetched by hardware prefetcher"],
+ ["0x81E8", "L1D_CACHE_HIT_WR_FHWPRF", "Level 1 data cache demand access first hit, write, fetched by hardware prefetcher"],
+ ["0x81E9", "L2D_CACHE_HIT_WR_FHWPRF", "Level 2 data cache demand access first hit, write, fetched by hardware prefetcher"],
+ ["0x81EA", "L3D_CACHE_HIT_WR_FHWPRF", "Level 3 data cache demand access first hit, write, fetched by hardware prefetcher"],
+ ["0x81EB", "LL_CACHE_HIT_WR_FHWPRF", "Last level cache demand access first hit, write, fetched by hardware prefetcher"],
+ ["0x81EC", "L1D_CACHE_HIT_RW_FHWPRF", "Level 1 data cache demand access first hit, fetched by hardware prefetcher"],
+ ["0x81ED", "L2D_CACHE_HIT_RW_FHWPRF", "Level 2 data cache demand access first hit, fetched by hardware prefetcher"],
+ ["0x81EE", "L3D_CACHE_HIT_RW_FHWPRF", "Level 3 data cache demand access first hit, fetched by hardware prefetcher"],
+ ["0x81EF", "LL_CACHE_HIT_RW_FHWPRF", "Last level cache demand access first hit, fetched by hardware prefetcher"],
+ ["0x81F0", "L1I_CACHE_HIT_RD_FPRF", "Level 1 instruction cache demand fetch first hit, fetched by preload or prefetch"],
+ ["0x81F1", "L2I_CACHE_HIT_RD_FPRF", "Level 2 instruction cache demand fetch first hit, fetched by preload or prefetch"],
+ ["0x81F4", "L1D_CACHE_HIT_RD_FPRF", "Level 1 data cache demand first hit, read, fetched by preload or prefetch"],
+ ["0x81F5", "L2D_CACHE_HIT_RD_FPRF", "Level 2 data cache demand first hit, read, fetched by preload or prefetch"],
+ ["0x81F6", "L3D_CACHE_HIT_RD_FPRF", "Level 3 data cache demand first hit, read, fetched by preload or prefetch"],
+ ["0x81F7", "LL_CACHE_HIT_RD_FPRF", "Last level cache demand first hit, read, fetched by preload or prefetch"],
+ ["0x81F8", "L1D_CACHE_HIT_WR_FPRF", "Level 1 data cache demand access first hit, write, fetched by preload or prefetch"],
+ ["0x81F9", "L2D_CACHE_HIT_WR_FPRF", "Level 2 data cache demand access first hit, write, fetched by preload or prefetch"],
+ ["0x81FA", "L3D_CACHE_HIT_WR_FPRF", "Level 3 data cache demand access first hit, write, fetched by preload or prefetch"],
+ ["0x81FB", "LL_CACHE_HIT_WR_FPRF", "Last level cache demand access first hit, write, fetched by preload or prefetch"],
+ ["0x81FC", "L1D_CACHE_HIT_RW_FPRF", "Level 1 data cache demand access first hit, fetched by preload or prefetch"],
+ ["0x81FD", "L2D_CACHE_HIT_RW_FPRF", "Level 2 data cache demand access first hit, fetched by preload or prefetch"],
+ ["0x81FE", "L3D_CACHE_HIT_RW_FPRF", "Level 3 data cache demand access first hit, fetched by preload or prefetch"],
+ ["0x81FF", "LL_CACHE_HIT_RW_FPRF", "Last level cache demand access first hit, fetched by preload or prefetch"],
+ ["0x8200", "L1I_CACHE_HIT", "Level 1 instruction cache hit"],
+ ["0x8201", "L2I_CACHE_HIT", "Level 2 instruction cache hit"],
+ ["0x8204", "L1D_CACHE_HIT", "Level 1 data cache hit"],
+ ["0x8205", "L2D_CACHE_HIT", "Level 2 data cache hit"],
+ ["0x8206", "L3D_CACHE_HIT", "Level 3 data cache hit"],
+ ["0x8207", "LL_CACHE_HIT", "Last level cache hit"],
+ ["0x8208", "L1I_CACHE_HIT_PRFM", "Level 1 instruction cache software preload hit"],
+ ["0x8209", "L2I_CACHE_HIT_PRFM", "Level 2 instruction cache software preload hit"],
+ ["0x820C", "L1D_CACHE_HIT_PRFM", "Level 1 data cache software preload hit"],
+ ["0x820D", "L2D_CACHE_HIT_PRFM", "Level 2 data cache software preload hit"],
+ ["0x820E", "L3D_CACHE_HIT_PRFM", "Level 3 data cache software preload hit"],
+ ["0x820F", "LL_CACHE_HIT_PRFM", "Last level cache software preload hit"],
+ ["0x8214", "L1D_CACHE_HITM_RD", "Level 1 data cache demand hit modified, read"],
+ ["0x8215", "L2D_CACHE_HITM_RD", "Level 2 data cache demand hit modified, read"],
+ ["0x8216", "L3D_CACHE_HITM_RD", "Level 3 data cache demand hit modified, read"],
+ ["0x8217", "LL_CACHE_HITM_RD", "Last level cache demand hit modified, read"],
+ ["0x8218", "L1D_CACHE_HITM_WR", "Level 1 data cache demand access hit modified, write"],
+ ["0x8219", "L2D_CACHE_HITM_WR", "Level 2 data cache demand access hit modified, write"],
+ ["0x821A", "L3D_CACHE_HITM_WR", "Level 3 data cache demand access hit modified, write"],
+ ["0x821B", "LL_CACHE_HITM_WR", "Last level cache demand access hit modified, write"],
+ ["0x821C", "L1D_CACHE_HITM_RW", "Level 1 data cache demand access hit modified"],
+ ["0x821D", "L2D_CACHE_HITM_RW", "Level 2 data cache demand access hit modified"],
+ ["0x821E", "L3D_CACHE_HITM_RW", "Level 3 data cache demand access hit modified"],
+ ["0x821F", "LL_CACHE_HITM_RW", "Last level cache demand access hit modified"],
+ ["0x8224", "DSNP_HITM_RD", "Snoop hit, demand data read, modified"],
+ ["0x8225", "DSNP_HITM_NEAR_RD", "Snoop hit in near cache, demand data read, modified"],
+ ["0x8226", "DSNP_HITM_FAR_RD", "Snoop hit in far cache, demand data read, modified"],
+ ["0x8227", "DSNP_HITM_REMOTE_RD", "Snoop hit in remote cache, demand data read, modified"],
+ ["0x8228", "DSNP_HITM_WR", "Snoop hit, demand data write, modified"],
+ ["0x8229", "DSNP_HITM_NEAR_WR", "Snoop hit in near cache, demand data write, modified"],
+ ["0x822A", "DSNP_HITM_FAR_WR", "Snoop hit in far cache, demand data write, modified"],
+ ["0x822B", "DSNP_HITM_REMOTE_WR", "Snoop hit in remote cache, demand data write, modified"],
+ ["0x822C", "DSNP_HITM_RW", "Snoop hit, demand data access, modified"],
+ ["0x822D", "DSNP_HITM_NEAR_RW", "Snoop hit in near cache, demand data access, modified"],
+ ["0x822E", "DSNP_HITM_FAR_RW", "Snoop hit in far cache, demand data access, modified"],
+ ["0x822F", "DSNP_HITM_REMOTE_RW", "Snoop hit in remote cache, demand data access, modified"],
+ ["0x8230", "LOCAL_MEM", "Access to memory attached to this device"],
+ ["0x8231", "LOCAL_MEM_RD", "Access to memory attached to this device, read"],
+ ["0x8232", "LOCAL_MEM_WR", "Access to memory attached to this device, write"],
+ ["0x8233", "LOCAL_MEM_RW", "Access to memory attached to this device, demand read or write"],
+ ["0x8234", "LOCAL_MEM_PRFM", "Access to memory attached to this device, preload or prefetch"],
+ ["0x8238", "REMOTE_MEM", "Access to memory attached to another socket in a multi-socket system"],
+ ["0x8239", "REMOTE_MEM_RD", "Access to memory attached to another socket in a multi-socket system, read"],
+ ["0x823A", "REMOTE_MEM_WR", "Access to memory attached to another socket in a multi-socket system, write"],
+ ["0x823B", "REMOTE_MEM_RW", "Access to memory attached to another socket in a multi-socket system, demand read or write"],
+ ["0x823C", "REMOTE_MEM_PRFM", "Access to memory attached to another socket in a multi-socket system, preload or prefetch"],
+ ["0x8240", "L1I_LFB_HIT_RD", "Level 1 instruction cache demand fetch line-fill buffer hit"],
+ ["0x8241", "L2I_LFB_HIT_RD", "Level 2 instruction cache demand fetch line-fill buffer hit"],
+ ["0x8244", "L1D_LFB_HIT_RD", "Level 1 data cache demand line-fill buffer hit, read"],
+ ["0x8245", "L2D_LFB_HIT_RD", "Level 2 data cache demand line-fill buffer hit, read"],
+ ["0x8246", "L3D_LFB_HIT_RD", "Level 3 data cache demand line-fill buffer hit, read"],
+ ["0x8247", "LL_LFB_HIT_RD", "Last level cache demand line-fill buffer hit, read"],
+ ["0x8248", "L1D_LFB_HIT_WR", "Level 1 data cache demand access line-fill buffer hit, write"],
+ ["0x8249", "L2D_LFB_HIT_WR", "Level 2 data cache demand access line-fill buffer hit, write"],
+ ["0x824A", "L3D_LFB_HIT_WR", "Level 3 data cache demand access line-fill buffer hit, write"],
+ ["0x824B", "LL_LFB_HIT_WR", "Last level cache demand access line-fill buffer hit, write"],
+ ["0x824C", "L1D_LFB_HIT_RW", "Level 1 data cache demand access line-fill buffer hit"],
+ ["0x824D", "L2D_LFB_HIT_RW", "Level 2 data cache demand access line-fill buffer hit"],
+ ["0x824E", "L3D_LFB_HIT_RW", "Level 3 data cache demand access line-fill buffer hit"],
+ ["0x824F", "LL_LFB_HIT_RW", "Last level cache demand access line-fill buffer hit"],
+ ["0x8250", "L1I_LFB_HIT_RD_FPRFM", "Level 1 instruction cache demand fetch line-fill buffer first hit, recently fetched by software preload"],
+ ["0x8251", "L2I_LFB_HIT_RD_FPRFM", "Level 2 instruction cache demand fetch line-fill buffer first hit, recently fetched by software preload"],
+ ["0x8254", "L1D_LFB_HIT_RD_FPRFM", "Level 1 data cache demand line-fill buffer first hit, read, recently fetched by software preload"],
+ ["0x8255", "L2D_LFB_HIT_RD_FPRFM", "Level 2 data cache demand line-fill buffer first hit, read, recently fetched by software preload"],
+ ["0x8256", "L3D_LFB_HIT_RD_FPRFM", "Level 3 data cache demand line-fill buffer first hit, read, recently fetched by software preload"],
+ ["0x8257", "LL_LFB_HIT_RD_FPRFM", "Last level cache demand line-fill buffer first hit, read, recently fetched by software preload"],
+ ["0x8258", "L1D_LFB_HIT_WR_FPRFM", "Level 1 data cache demand access line-fill buffer first hit, write, recently fetched by software preload"],
+ ["0x8259", "L2D_LFB_HIT_WR_FPRFM", "Level 2 data cache demand access line-fill buffer first hit, write, recently fetched by software preload"],
+ ["0x825A", "L3D_LFB_HIT_WR_FPRFM", "Level 3 data cache demand access line-fill buffer first hit, write, recently fetched by software preload"],
+ ["0x825B", "LL_LFB_HIT_WR_FPRFM", "Last level cache demand access line-fill buffer first hit, write, recently fetched by software preload"],
+ ["0x825C", "L1D_LFB_HIT_RW_FPRFM", "Level 1 data cache demand access line-fill buffer first hit, recently fetched by software preload"],
+ ["0x825D", "L2D_LFB_HIT_RW_FPRFM", "Level 2 data cache demand access line-fill buffer first hit, recently fetched by software preload"],
+ ["0x825E", "L3D_LFB_HIT_RW_FPRFM", "Level 3 data cache demand access line-fill buffer first hit, recently fetched by software preload"],
+ ["0x825F", "LL_LFB_HIT_RW_FPRFM", "Last level cache demand access line-fill buffer first hit, recently fetched by software preload"],
+ ["0x8260", "L1I_LFB_HIT_RD_FHWPRF", "Level 1 instruction cache demand fetch line-fill buffer first hit, recently fetched by hardware prefetcher"],
+ ["0x8261", "L2I_LFB_HIT_RD_FHWPRF", "Level 2 instruction cache demand fetch line-fill buffer first hit, recently fetched by hardware prefetcher"],
+ ["0x8264", "L1D_LFB_HIT_RD_FHWPRF", "Level 1 data cache demand line-fill buffer first hit, read, recently fetched by hardware prefetcher"],
+ ["0x8265", "L2D_LFB_HIT_RD_FHWPRF", "Level 2 data cache demand line-fill buffer first hit, read, recently fetched by hardware prefetcher"],
+ ["0x8266", "L3D_LFB_HIT_RD_FHWPRF", "Level 3 data cache demand line-fill buffer first hit, read, recently fetched by hardware prefetcher"],
+ ["0x8267", "LL_LFB_HIT_RD_FHWPRF", "Last level cache demand line-fill buffer first hit, read, recently fetched by hardware prefetcher"],
+ ["0x8268", "L1D_LFB_HIT_WR_FHWPRF", "Level 1 data cache demand access line-fill buffer first hit, write, recently fetched by hardware prefetcher"],
+ ["0x8269", "L2D_LFB_HIT_WR_FHWPRF", "Level 2 data cache demand access line-fill buffer first hit, write, recently fetched by hardware prefetcher"],
+ ["0x826A", "L3D_LFB_HIT_WR_FHWPRF", "Level 3 data cache demand access line-fill buffer first hit, write, recently fetched by hardware prefetcher"],
+ ["0x826B", "LL_LFB_HIT_WR_FHWPRF", "Last level cache demand access line-fill buffer first hit, write, recently fetched by hardware prefetcher"],
+ ["0x826C", "L1D_LFB_HIT_RW_FHWPRF", "Level 1 data cache demand access line-fill buffer first hit, recently fetched by hardware prefetcher"],
+ ["0x826D", "L2D_LFB_HIT_RW_FHWPRF", "Level 2 data cache demand access line-fill buffer first hit, recently fetched by hardware prefetcher"],
+ ["0x826E", "L3D_LFB_HIT_RW_FHWPRF", "Level 3 data cache demand access line-fill buffer first hit, recently fetched by hardware prefetcher"],
+ ["0x826F", "LL_LFB_HIT_RW_FHWPRF", "Last level cache demand access line-fill buffer first hit, recently fetched by hardware prefetcher"],
+ ["0x8270", "L1I_LFB_HIT_RD_FPRF", "Level 1 instruction cache demand fetch line-fill buffer first hit, recently fetched by preload or prefetch"],
+ ["0x8271", "L2I_LFB_HIT_RD_FPRF", "Level 2 instruction cache demand fetch line-fill buffer first hit, recently fetched by preload or prefetch"],
+ ["0x8274", "L1D_LFB_HIT_RD_FPRF", "Level 1 data cache demand line-fill buffer first hit, read, recently fetched by preload or prefetch"],
+ ["0x8275", "L2D_LFB_HIT_RD_FPRF", "Level 2 data cache demand line-fill buffer first hit, read, recently fetched by preload or prefetch"],
+ ["0x8276", "L3D_LFB_HIT_RD_FPRF", "Level 3 data cache demand line-fill buffer first hit, read, recently fetched by preload or prefetch"],
+ ["0x8277", "LL_LFB_HIT_RD_FPRF", "Last level cache demand line-fill buffer first hit, read, recently fetched by preload or prefetch"],
+ ["0x8278", "L1D_LFB_HIT_WR_FPRF", "Level 1 data cache demand access line-fill buffer first hit, write, recently fetched by preload or prefetch"],
+ ["0x8279", "L2D_LFB_HIT_WR_FPRF", "Level 2 data cache demand access line-fill buffer first hit, write, recently fetched by preload or prefetch"],
+ ["0x827A", "L3D_LFB_HIT_WR_FPRF", "Level 3 data cache demand access line-fill buffer first hit, write, recently fetched by preload or prefetch"],
+ ["0x827B", "LL_LFB_HIT_WR_FPRF", "Last level cache demand access line-fill buffer first hit, write, recently fetched by preload or prefetch"],
+ ["0x827C", "L1D_LFB_HIT_RW_FPRF", "Level 1 data cache demand access line-fill buffer first hit, recently fetched by preload or prefetch"],
+ ["0x827D", "L2D_LFB_HIT_RW_FPRF", "Level 2 data cache demand access line-fill buffer first hit, recently fetched by preload or prefetch"],
+ ["0x827E", "L3D_LFB_HIT_RW_FPRF", "Level 3 data cache demand access line-fill buffer first hit, recently fetched by preload or prefetch"],
+ ["0x827F", "LL_LFB_HIT_RW_FPRF", "Last level cache demand access line-fill buffer first hit, recently fetched by preload or prefetch"],
+ ["0x8280", "L1I_CACHE_PRF", "Level 1 instruction cache, preload or prefetch hit"],
+ ["0x8281", "L2I_CACHE_PRF", "Level 2 instruction cache, preload or prefetch hit"],
+ ["0x8284", "L1D_CACHE_PRF", "Level 1 data cache, preload or prefetch hit"],
+ ["0x8285", "L2D_CACHE_PRF", "Level 2 data cache, preload or prefetch hit"],
+ ["0x8286", "L3D_CACHE_PRF", "Level 3 data cache, preload or prefetch hit"],
+ ["0x8287", "LL_CACHE_PRF", "Last level cache, preload or prefetch hit"],
+ ["0x8288", "L1I_CACHE_REFILL_PRF", "Level 1 instruction cache refill, preload or prefetch hit"],
+ ["0x8289", "L2I_CACHE_REFILL_PRF", "Level 2 instruction cache refill, preload or prefetch hit"],
+ ["0x828C", "L1D_CACHE_REFILL_PRF", "Level 1 data cache refill, preload or prefetch hit"],
+ ["0x828D", "L2D_CACHE_REFILL_PRF", "Level 2 data cache refill, preload or prefetch hit"],
+ ["0x828E", "L3D_CACHE_REFILL_PRF", "Level 3 data cache refill, preload or prefetch hit"],
+ ["0x828F", "LL_CACHE_REFILL_PRF", "Last level cache refill, preload or prefetch hit"],
+ ["0x8290", "ISNP_HIT_PRF", "Snoop hit, any instruction prefetch"],
+ ["0x8291", "ISNP_HIT_NEAR_PRF", "Snoop hit in near cache, instruction preload or prefetch"],
+ ["0x8292", "ISNP_HIT_FAR_PRF", "Snoop hit in far cache, instruction preload or prefetch"],
+ ["0x8293", "ISNP_HIT_REMOTE_PRF", "Snoop hit in remote cache, instruction preload or prefetch"],
+ ["0x8294", "DSNP_HIT_PRF", "Snoop hit, any data prefetch"],
+ ["0x8295", "DSNP_HIT_NEAR_PRF", "Snoop hit in near cache, data preload or prefetch"],
+ ["0x8296", "DSNP_HIT_FAR_PRF", "Snoop hit in far cache, data preload or prefetch"],
+ ["0x8297", "DSNP_HIT_REMOTE_PRF", "Snoop hit in remote cache, data preload or prefetch"],
+ ["0x8298", "LL_CACHE_RW", "Last level cache demand access"],
+ ["0x8299", "LL_CACHE_PRFM", "Last level cache software preload"],
+ ["0x829A", "LL_CACHE_REFILL", "Last level cache refill"],
+ ["0x829B", "LL_CACHE_REFILL_PRFM", "Last level cache refill, software preload"],
+ ["0x829C", "LL_CACHE_WB", "Last level cache write-back"],
+ ["0x829D", "LL_CACHE_WR", "Last level cache access, write"],
+ ["0x829F", "LL_CACHE_REFILL_WR", "Last level cache refill, write"],
+ ["0x82A0", "MEM_ACCESS_RW", "Data memory access, demand access"],
+ ["0x82A1", "INST_FETCH_RD", "Instruction memory access, demand fetch"],
+ ["0x82A2", "MEM_ACCESS_PRFM", "Data memory access, preload"],
+ ["0x82A3", "INST_FETCH_PRFM", "Instruction memory access, preload"]
+ ],
+ "cpus": [
+ {
+ "name": "Cortex-A55",
+ "implementer": "0x41",
+ "partnum": "0xd05",
+ "common_events": [
+ "0x0000", "0x0001", "0x0002", "0x0003", "0x0004", "0x0005", "0x0006", "0x0007", "0x0008", "0x0009", "0x000A", "0x000B", "0x000C", "0x000D", "0x000E", "0x000F",
+ "0x0010", "0x0011", "0x0012", "0x0013", "0x0014", "0x0015", "0x0016", "0x0017", "0x0018", "0x0019", "0x001A", "0x001B", "0x001C", "0x001D", "0x0020", "0x0021",
+ "0x0022", "0x0023", "0x0024", "0x0025", "0x0026", "0x0029", "0x002A", "0x002B", "0x002D", "0x002F", "0x0034", "0x0035", "0x0036", "0x0037", "0x0038", "0x0040",
+ "0x0041", "0x0042", "0x0043", "0x0044", "0x0045", "0x0050", "0x0051", "0x0052", "0x0053", "0x0060", "0x0061", "0x0066", "0x0067", "0x0070", "0x0071", "0x0072",
+ "0x0073", "0x0074", "0x0075", "0x0076", "0x0077", "0x0078", "0x0079", "0x007A", "0x0086", "0x0087", "0x00A0", "0x00A2"
+ ],
+ "implementation_defined_events": [
+ ["0x00C0", "L3D_CACHE_REFILL_PREFETCH", "Level 3 cache refill due to prefetch"],
+ ["0x00C1", "L2D_CACHE_REFILL_PREFETCH", "Level 2 cache refill due to prefetch"],
+ ["0x00C2", "L1D_CACHE_REFILL_PREFETCH", "Level 1 data cache refill due to prefetch"],
+ ["0x00C3", "L2D_WS_MODE", "Level 2 cache write streaming mode"],
+ ["0x00C4", "L1D_WS_MODE_ENTRY", "Level 1 data cache entering write streaming mode"],
+ ["0x00C5", "L1D_WS_MODE", "Level 1 data cache write streaming mode"],
+ ["0x00C6", "PREDECODE_ERROR", "Predecode error"],
+ ["0x00C7", "L3D_WS_MODE", "Level 3 cache write streaming mode"],
+ ["0x00C9", "BR_COND_PRED", "Predicted conditional branch executed"],
+ ["0x00CA", "BR_INDIRECT_MIS_PRED", "Indirect branch mis-predicted"],
+ ["0x00CB", "BR_INDIRECT_ADDR_MIS_PRED", "Indirect branch mis-predicted due to address mis-compare"],
+ ["0x00CC", "BR_COND_MIS_PRED", "Conditional branch mis-predicted"],
+ ["0x00CD", "BR_INDIRECT_ADDR_PRED", "Indirect branch with predicted address executed"],
+ ["0x00CE", "BR_RETURN_ADDR_PRED", "Procedure return with predicted address executed"],
+ ["0x00CF", "BR_RETURN_ADDR_MIS_PRED", "Procedure return mis-predicted due to address mis-compare"],
+ ["0x00D0", "L2D_LLWALK_TLB", "Level 2 TLB last-level walk cache access"],
+ ["0x00D1", "L2D_LLWALK_TLB_REFILL", "Level 2 TLB last-level walk cache refill"],
+ ["0x00D2", "L2D_L2WALK_TLB", "Level 2 TLB level-2 walk cache access"],
+ ["0x00D3", "L2D_L2WALK_TLB_REFILL", "Level 2 TLB level-2 walk cache refill"],
+ ["0x00D4", "L2D_S2_TLB", "Level 2 TLB IPA cache access"],
+ ["0x00D5", "L2D_S2_TLB_REFILL", "Level 2 TLB IPA cache refill"],
+ ["0x00D6", "L2D_CACHE_STASH_DROPPED", "Level 2 cache stash dropped"],
+ ["0x00E1", "STALL_FRONTEND_CACHE", "No operation issued due to the frontend, cache miss"],
+ ["0x00E2", "STALL_FRONTEND_TLB", "No operation issued due to the frontend, TLB miss"],
+ ["0x00E3", "STALL_FRONTEND_PDERR", "No operation issued due to the frontend, pre-decode error"],
+ ["0x00E4", "STALL_BACKEND_ILOCK", "No operation issued due to the backend interlock"],
+ ["0x00E5", "STALL_BACKEND_ILOCK_AGU", "No operation issued due to the backend, interlock, AGU"],
+ ["0x00E6", "STALL_BACKEND_ILOCK_FPU", "No operation issued due to the backend, interlock, FPU"],
+ ["0x00E7", "STALL_BACKEND_LD", "No operation issued due to the backend, load"],
+ ["0x00E8", "STALL_BACKEND_ST", "No operation issued due to the backend, store"],
+ ["0x00E9", "STALL_BACKEND_LD_CACHE", "No operation issued due to the backend, load, cache miss"],
+ ["0x00EA", "STALL_BACKEND_LD_TLB", "No operation issued due to the backend, load, TLB miss"],
+ ["0x00EB", "STALL_BACKEND_ST_STB", "No operation issued due to the backend, store, STB full"],
+ ["0x00EC", "STALL_BACKEND_ST_TLB", "No operation issued due to the backend, store, TLB miss"]
+ ]
+ },
+ {
+ "name": "Cortex-A76",
+ "implementer": "0x41",
+ "partnum": "0xd0b",
+ "common_events": [
+ "0x0000", "0x0001", "0x0002", "0x0003", "0x0004", "0x0005", "0x0008", "0x0009", "0x000A", "0x000B", "0x0010", "0x0011", "0x0012", "0x0013", "0x0014", "0x0015",
+ "0x0016", "0x0017", "0x0018", "0x0019", "0x001A", "0x001B", "0x001C", "0x001D", "0x0020", "0x0021", "0x0022", "0x0023", "0x0024", "0x0025", "0x0026", "0x0029",
+ "0x002A", "0x002B", "0x002D", "0x002F", "0x0031", "0x0034", "0x0035", "0x0036", "0x0037", "0x0040", "0x0041", "0x0042", "0x0043", "0x0044", "0x0045", "0x0046",
+ "0x0047", "0x0048", "0x004C", "0x004D", "0x004E", "0x004F", "0x0050", "0x0051", "0x0052", "0x0053", "0x0056", "0x0057", "0x0058", "0x005C", "0x005D", "0x005E",
+ "0x005F", "0x0060", "0x0061", "0x0066", "0x0067", "0x0068", "0x0069", "0x006C", "0x006D", "0x006E", "0x006F", "0x0070", "0x0071", "0x0073", "0x0074", "0x0075",
+ "0x0076", "0x0077", "0x0078", "0x0079", "0x007A", "0x007C", "0x007D", "0x007E", "0x0081", "0x0082", "0x0083", "0x0084", "0x0086", "0x0087", "0x0088", "0x008A",
+ "0x008B", "0x008C", "0x008D", "0x008E", "0x008F", "0x0090", "0x0091", "0x00A0"
+ ]
+ },
+ {
+ "name": "Cortex-A78",
+ "implementer": "0x41",
+ "partnum": "0xd41",
+ "common_events": [
+ "0x0000", "0x0001", "0x0002", "0x0003", "0x0004", "0x0005", "0x0008", "0x0009", "0x000A", "0x000B", "0x0010", "0x0011", "0x0012", "0x0013", "0x0014", "0x0015",
+ "0x0016", "0x0017", "0x0018", "0x0019", "0x001A", "0x001B", "0x001C", "0x001D", "0x0020", "0x0021", "0x0022", "0x0023", "0x0024", "0x0025", "0x0026", "0x0029",
+ "0x002A", "0x002B", "0x002D", "0x002F", "0x0031", "0x0034", "0x0035", "0x0036", "0x0037", "0x0039", "0x003A", "0x003B", "0x003C", "0x003D", "0x003E", "0x003F",
+ "0x0040", "0x0041", "0x0042", "0x0043", "0x0044", "0x0045", "0x0046", "0x0047", "0x0048", "0x004C", "0x004D", "0x004E", "0x004F", "0x0050", "0x0051", "0x0052",
+ "0x0053", "0x0056", "0x0057", "0x0058", "0x005C", "0x005D", "0x005E", "0x005F", "0x0060", "0x0061", "0x0066", "0x0067", "0x0068", "0x0069", "0x006A", "0x006C",
+ "0x006D", "0x006E", "0x006F", "0x0070", "0x0071", "0x0073", "0x0074", "0x0075", "0x0076", "0x0077", "0x0078", "0x0079", "0x007A", "0x007C", "0x007D", "0x007E",
+ "0x0081", "0x0082", "0x0083", "0x0084", "0x0086", "0x0087", "0x0088", "0x008A", "0x008B", "0x008C", "0x008D", "0x008E", "0x008F", "0x0090", "0x0091", "0x00A0",
+ "0x4004", "0x4005", "0x4006", "0x4009", "0x400B"
+ ]
+ },
+ {
+ "name": "Cortex-A510",
+ "implementer": "0x41",
+ "partnum": "0xd46",
+ "common_events": [
+ "0x0000", "0x0001", "0x0002", "0x0003", "0x0004", "0x0005", "0x0006", "0x0007", "0x0008", "0x0009", "0x000A", "0x000B", "0x000C", "0x000D", "0x000E", "0x0010",
+ "0x0011", "0x0012", "0x0013", "0x0014", "0x0015", "0x0016", "0x0017", "0x0018", "0x0019", "0x001A", "0x001B", "0x001C", "0x001D", "0x0020", "0x0021", "0x0022",
+ "0x0023", "0x0024", "0x0025", "0x0026", "0x002B", "0x002D", "0x002F", "0x0034", "0x0035", "0x0036", "0x0037", "0x0038", "0x0039", "0x003A", "0x003B", "0x003C",
+ "0x003D", "0x003E", "0x003F", "0x0040", "0x0041", "0x0042", "0x0043", "0x0044", "0x0045", "0x0050", "0x0051", "0x0052", "0x0053", "0x0060", "0x0061", "0x0066",
+ "0x0067", "0x0070", "0x0071", "0x0072", "0x0073", "0x0074", "0x0075", "0x0076", "0x0077", "0x0078", "0x0079", "0x007A", "0x0086", "0x0087", "0x00A0", "0x00A2",
+ "0x4005", "0x4006", "0x4009", "0x400B", "0x400C", "0x400E", "0x4020", "0x4021", "0x4022", "0x4024", "0x4025", "0x4026", "0x8002", "0x8006", "0x8014", "0x8018",
+ "0x801C", "0x80E3", "0x80E7", "0x80EB", "0x80EF"
+ ],
+ "implementation_defined_events": [
+ ["0x00C1", "L2D_CACHE_REFILL_PREFETCH", "L2 cache refill due to prefetch"],
+ ["0x00C2", "L1D_CACHE_REFILL_PREFETCH", "L1 data cache refill due to prefetch"],
+ ["0x00C3", "L2D_WS_MODE", "L2 cache write streaming mode"],
+ ["0x00C4", "L1D_WS_MODE_ENTRY", "L1 data cache entering write streaming mode"],
+ ["0x00C5", "L1D_WS_MODE", "L1 data cache write streaming mode"],
+ ["0x00C7", "L3D_WS_MODE", "L3 cache write streaming mode"],
+ ["0x00C8", "LL_WS_MODE", "Last level cache write streaming mode"],
+ ["0x00C9", "BR_COND_PRED", "Predicted conditional branch executed"],
+ ["0x00CA", "BR_INDIRECT_MIS_PRED", "Indirect branch mispredicted"],
+ ["0x00CB", "BR_INDIRECT_ADDR_MIS_PRED", "Indirect branch mispredicted due to address miscompare"],
+ ["0x00CC", "BR_COND_MIS_PRED", "Conditional branch mispredicted"],
+ ["0x00CD", "BR_INDIRECT_ADDR_PRED", "Indirect branch with predicted address executed"],
+ ["0x00CE", "BR_RETURN_ADDR_PRED", "Procedure return with predicted address executed"],
+ ["0x00CF", "BR_RETURN_ADDR_MIS_PRED", "Procedure return mispredicted due to address miscompare"],
+ ["0x00D0", "L2D_WALK_TLB", "L2 TLB walk cache access"],
+ ["0x00D1", "L2D_WALK_TLB_REFILL", "L2 TLB walk cache refill"],
+ ["0x00D4", "L2D_S2_TLB", "L2 TLB IPA cache access"],
+ ["0x00D5", "L2D_S2_TLB_REFILL", "L2 TLB IPA cache refill"],
+ ["0x00D6", "L2D_CACHE_STASH_DROPPED", "L2 cache stash dropped"],
+ ["0x00E1", "STALL_FRONTEND_CACHE", "No operation issued due to the frontend, cache miss"],
+ ["0x00E2", "STALL_FRONTEND_TLB", "No operation issued due to the frontend, TLB miss"],
+ ["0x00E3", "STALL_FRONTEND_PDERR", "No operation issued due to the frontend, pre-decode error"],
+ ["0x00E4", "STALL_BACKEND_ILOCK", "No operation issued due to the backend interlock"],
+ ["0x00E5", "STALL_BACKEND_ILOCK_ADDR", "No operation issued due to the backend, address interlock"],
+ ["0x00E6", "STALL_BACKEND_ILOCK_VPU", "No operation issued due to the backend, interlock, or the Vector Processing Unit (VPU)"],
+ ["0x00E7", "STALL_BACKEND_LD", "No operation issued due to the backend, load"],
+ ["0x00E8", "STALL_BACKEND_ST", "No operation issued due to the backend, store"],
+ ["0x00E9", "STALL_BACKEND_LD_CACHE", "No operation issued due to the backend, load, cache miss"],
+ ["0x00EA", "STALL_BACKEND_LD_TLB", "No operation issued due to the backend, load, TLB miss"],
+ ["0x00EB", "STALL_BACKEND_ST_STB", "No operation issued due to the backend, store, Store Buffer (STB) full"],
+ ["0x00EC", "STALL_BACKEND_ST_TLB", "No operation issued due to the backend, store, TLB miss"],
+ ["0x00ED", "STALL_BACKEND_VPU_HAZARD", "No operation issued due to the backend, VPU hazard"],
+ ["0x00EE", "STALL_SLOT_BACKEND_ILOCK", "Issue slot not issued due to interlock"]
+ ]
+ },
+ {
+ "name": "Cortex-A520",
+ "implementer": "0x41",
+ "partnum": "0xd80",
+ "common_events": [
+ "0x0000", "0x0001", "0x0002", "0x0003", "0x0004", "0x0005", "0x0006", "0x0007", "0x0008", "0x0009", "0x000A", "0x000B", "0x000C", "0x000D", "0x000E", "0x0010",
+ "0x0011", "0x0012", "0x0013", "0x0014", "0x0015", "0x0016", "0x0017", "0x0018", "0x0019", "0x001A", "0x001B", "0x001C", "0x001D", "0x0020", "0x0021", "0x0022",
+ "0x0023", "0x0024", "0x0025", "0x0026", "0x002B", "0x002D", "0x002F", "0x0034", "0x0035", "0x0036", "0x0037", "0x0038", "0x0039", "0x003A", "0x003B", "0x003C",
+ "0x003D", "0x003E", "0x003F", "0x0040", "0x0041", "0x0042", "0x0043", "0x0044", "0x0045", "0x0050", "0x0051", "0x0052", "0x0053", "0x0060", "0x0061", "0x0066",
+ "0x0067", "0x006E", "0x006F", "0x0070", "0x0071", "0x0072", "0x0073", "0x0074", "0x0075", "0x0076", "0x0077", "0x0078", "0x0079", "0x007A", "0x0086", "0x0087",
+ "0x00A0", "0x00A2", "0x4005", "0x4006", "0x4009", "0x400B", "0x400C", "0x400E", "0x4010", "0x4011", "0x4012", "0x4013", "0x4018", "0x4019", "0x401A", "0x401B",
+ "0x4020", "0x4021", "0x4022", "0x4024", "0x4025", "0x4026", "0x8002", "0x8006", "0x8014", "0x8018", "0x801C", "0x80E3", "0x80E7", "0x80EB", "0x80EF", "0x810C",
+ "0x8110", "0x8111", "0x8114", "0x8115", "0x8116", "0x8117", "0x811C", "0x811D", "0x8120", "0x8121", "0x8124", "0x8125", "0x8128", "0x8129", "0x8134", "0x8135",
+ "0x8136", "0x8137", "0x8138", "0x8139", "0x813A", "0x813B", "0x813C", "0x8154", "0x8155", "0x8156", "0x8158", "0x8159", "0x815B", "0x815C", "0x8160", "0x8161",
+ "0x8162", "0x8164", "0x8165", "0x8167", "0x8168", "0x816B", "0x816C", "0x818D", "0x81BC", "0x81BD"
+ ],
+ "implementation_defined_events": [
+ ["0x00C3", "L2D_WS_MODE", "L2 cache write streaming mode"],
+ ["0x00C4", "L1D_WS_MODE_ENTRY", "L1 data cache entering write streaming mode"],
+ ["0x00C5", "L1D_WS_MODE", "L1 data cache write streaming mode"],
+ ["0x00C7", "L3D_WS_MODE", "L3 cache write streaming mode"],
+ ["0x00C8", "LL_WS_MODE", "Last level cache write streaming mode"],
+ ["0x00D0", "L2D_WALK_TLB", "L2 TLB walk cache access"],
+ ["0x00D1", "L2D_WALK_TLB_REFILL", "L2 TLB walk cache refill"],
+ ["0x00D4", "L2D_S2_TLB", "L2 TLB IPA cache access"],
+ ["0x00D5", "L2D_S2_TLB_REFILL", "L2 TLB IPA cache refill"],
+ ["0x00D6", "L2D_CACHE_STASH_DROPPED", "L2 cache stash dropped"],
+ ["0x00D7", "L1D_TLB_REFILL_ETS", "L1D TLB refill due to ETS replay"],
+ ["0x00DA", "L2D_CACHE_REFILL_HWPRF_SPATIAL", "L2 cache refill due to L2 spatial prefetcher"],
+ ["0x00DB", "L2D_CACHE_REFILL_HWPRF_OFFSET", "L2 cache refill due to L2 offset prefetcher"],
+ ["0x00DC", "L2D_CACHE_REFILL_HWPRF_PATTERN", "L2 cache refill due to L2 pattern prefetcher"],
+ ["0x00DD", "L2D_CACHE_REFILL_HWPRF_TLBD", "L2 cache refill due to L2 TLB prefetcher"],
+ ["0x00DE", "L3D_CACHE_HWPRF_STRIDE", "L3 cache access due to L3 stride prefetcher"],
+ ["0x00DF", "L3D_CACHE_HWPRF_OFFSET", "L3 cache access due to L3 offset prefetcher"],
+ ["0x00E5", "STALL_BACKEND_ILOCK_ADDR", "No operation issued due to the backend, input dependency, address"],
+ ["0x00E6", "STALL_BACKEND_ILOCK_VPU", "No operation issued due to the backend, input dependency, Vector Processing Unit"],
+ ["0x00ED", "STALL_BACKEND_BUSY_VPU_HAZARD", "No operation issued due to the backend, VPU hazard"],
+ ["0x00EE", "STALL_SLOT_BACKEND_ILOCK", "No operation sent for execution on a Slot due to the backend, input dependency"],
+ ["0x00F0", "INST_SPEC_LDST_NUKE", "Instruction re-executed, read-after-read hazard"],
+ ["0x82FA", "DTLB_WALK_HWPRF", "Data TLB access, hardware prefetcher"]
+ ]
+ },
+ {
+ "name": "Cortex-A710",
+ "implementer": "0x41",
+ "partnum": "0xd47",
+ "common_events": [
+ "0x0000", "0x0001", "0x0002", "0x0003", "0x0004", "0x0005", "0x0008", "0x0009", "0x000A", "0x000B", "0x0010", "0x0011", "0x0012", "0x0013", "0x0014", "0x0015",
+ "0x0016", "0x0017", "0x0018", "0x0019", "0x001A", "0x001B", "0x001C", "0x001D", "0x0020", "0x0021", "0x0022", "0x0023", "0x0024", "0x0025", "0x0026", "0x0029",
+ "0x002A", "0x002B", "0x002D", "0x002F", "0x0031", "0x0034", "0x0035", "0x0036", "0x0037", "0x0039", "0x003A", "0x003B", "0x003C", "0x003D", "0x003E", "0x003F",
+ "0x0040", "0x0041", "0x0042", "0x0043", "0x0044", "0x0045", "0x0046", "0x0047", "0x0048", "0x004C", "0x004D", "0x004E", "0x004F", "0x0050", "0x0051", "0x0052",
+ "0x0053", "0x0056", "0x0057", "0x0058", "0x005C", "0x005D", "0x005E", "0x005F", "0x0060", "0x0061", "0x0066", "0x0067", "0x0068", "0x0069", "0x006A", "0x006C",
+ "0x006D", "0x006E", "0x006F", "0x0070", "0x0071", "0x0073", "0x0074", "0x0075", "0x0076", "0x0077", "0x0078", "0x0079", "0x007A", "0x007C", "0x007D", "0x007E",
+ "0x0081", "0x0082", "0x0083", "0x0084", "0x0086", "0x0087", "0x0088", "0x008A", "0x008B", "0x008C", "0x008D", "0x008E", "0x008F", "0x0090", "0x0091", "0x00A0",
+ "0x4004", "0x4005", "0x4006", "0x4009", "0x400B", "0x400C", "0x4010", "0x4011", "0x4012", "0x4013", "0x4018", "0x4019", "0x401A", "0x401B", "0x4020", "0x4021",
+ "0x4022", "0x4024", "0x4025", "0x4026", "0x8005", "0x8006", "0x8014", "0x8018", "0x801C", "0x8074", "0x8075", "0x8076", "0x8077", "0x8079", "0x80BC", "0x80BD",
+ "0x80C0", "0x80C1", "0x80E3", "0x80E7", "0x80EB", "0x80EF"
+ ]
+ },
+ {
+ "name": "Cortex-A715",
+ "implementer": "0x41",
+ "partnum": "0xd4d",
+ "common_events": [
+ "0x0000", "0x0001", "0x0002", "0x0003", "0x0004", "0x0005", "0x0008", "0x0009", "0x000A", "0x000B", "0x000C", "0x000D", "0x000E", "0x0010", "0x0011", "0x0012",
+ "0x0013", "0x0014", "0x0015", "0x0016", "0x0017", "0x0018", "0x0019", "0x001B", "0x001C", "0x001D", "0x0020", "0x0021", "0x0022", "0x0023", "0x0024", "0x0025",
+ "0x0026", "0x0029", "0x002A", "0x002B", "0x002D", "0x002F", "0x0031", "0x0034", "0x0035", "0x0036", "0x0037", "0x0039", "0x003A", "0x003B", "0x003C", "0x003D",
+ "0x003E", "0x003F", "0x0040", "0x0041", "0x0044", "0x0045", "0x0048", "0x0050", "0x0051", "0x0052", "0x0053", "0x0056", "0x0057", "0x0058", "0x0060", "0x0061",
+ "0x0066", "0x0067", "0x006E", "0x006F", "0x0070", "0x0071", "0x0073", "0x0074", "0x0075", "0x0076", "0x0077", "0x007C", "0x007D", "0x007E", "0x0081", "0x0082",
+ "0x0083", "0x0084", "0x0086", "0x0087", "0x0088", "0x008A", "0x008B", "0x008C", "0x008D", "0x008E", "0x008F", "0x0090", "0x0091", "0x00A0", "0x4000", "0x4001",
+ "0x4002", "0x4003", "0x4004", "0x4005", "0x4006", "0x4009", "0x400B", "0x400C", "0x4010", "0x4011", "0x4012", "0x4013", "0x4018", "0x4019", "0x401A", "0x401B",
+ "0x4020", "0x4021", "0x4022", "0x4024", "0x4025", "0x4026", "0x8005", "0x8006", "0x8014", "0x8018", "0x801C", "0x8074", "0x8075", "0x8076", "0x8077", "0x8079",
+ "0x80BC", "0x80BD", "0x80C0", "0x80C1", "0x80E3", "0x80E7", "0x80EB", "0x80EF", "0x8108", "0x810C", "0x811D", "0x8128", "0x8129", "0x8136", "0x8137", "0x8162"
+ ]
+ },
+ {
+ "name": "Cortex-A720",
+ "implementer": "0x41",
+ "partnum": "0xd81",
+ "common_events": [
+ "0x0000", "0x0001", "0x0002", "0x0003", "0x0004", "0x0005", "0x0008", "0x0009", "0x000A", "0x000B", "0x000C", "0x000D", "0x000E", "0x0010", "0x0011", "0x0012",
+ "0x0013", "0x0014", "0x0015", "0x0016", "0x0017", "0x0018", "0x0019", "0x001B", "0x001C", "0x001D", "0x0020", "0x0021", "0x0022", "0x0023", "0x0024", "0x0025",
+ "0x0026", "0x0029", "0x002A", "0x002B", "0x002D", "0x002F", "0x0031", "0x0034", "0x0035", "0x0036", "0x0037", "0x0039", "0x003A", "0x003B", "0x003C", "0x003D",
+ "0x003E", "0x003F", "0x0040", "0x0041", "0x0044", "0x0045", "0x0048", "0x0050", "0x0051", "0x0052", "0x0053", "0x0056", "0x0057", "0x0058", "0x0060", "0x0061",
+ "0x0066", "0x0067", "0x006E", "0x006F", "0x0070", "0x0071", "0x0073", "0x0074", "0x0075", "0x0076", "0x0077", "0x007C", "0x007D", "0x007E", "0x0081", "0x0082",
+ "0x0083", "0x0084", "0x0086", "0x0087", "0x0088", "0x008A", "0x008B", "0x008C", "0x008D", "0x008E", "0x008F", "0x0090", "0x0091", "0x00A0", "0x4000", "0x4001",
+ "0x4002", "0x4003", "0x4004", "0x4005", "0x4006", "0x4009", "0x400B", "0x400C", "0x4010", "0x4011", "0x4012", "0x4013", "0x4018", "0x4019", "0x401A", "0x401B",
+ "0x4020", "0x4021", "0x4022", "0x4024", "0x4025", "0x4026", "0x8005", "0x8006", "0x8014", "0x8018", "0x801C", "0x8074", "0x8075", "0x8076", "0x8077", "0x8079",
+ "0x80BC", "0x80BD", "0x80C0", "0x80C1", "0x80E3", "0x80E7", "0x80EB", "0x80EF", "0x8108", "0x810C", "0x8110", "0x8111", "0x8112", "0x8113", "0x8114", "0x8115",
+ "0x8116", "0x8117", "0x811C", "0x811D", "0x8120", "0x8121", "0x8124", "0x8128", "0x8129", "0x812A", "0x812B", "0x812C", "0x812D", "0x812E", "0x812F", "0x8134",
+ "0x8135", "0x8136", "0x8137", "0x8138", "0x8139", "0x813A", "0x813B", "0x8140", "0x8148", "0x8158", "0x8159", "0x815B", "0x815C", "0x8160", "0x8162", "0x8164",
+ "0x8165", "0x8167", "0x8168", "0x816A", "0x816B", "0x816D", "0x8171", "0x8172", "0x8173", "0x8284", "0x8285", "0x828C", "0x828D"
+ ]
+ },
+ {
+ "name": "Cortex-X1",
+ "implementer": "0x41",
+ "partnum": "0xd44",
+ "common_events": [
+ "0x0000", "0x0001", "0x0002", "0x0003", "0x0004", "0x0005", "0x0008", "0x0009", "0x000A", "0x000B", "0x0010", "0x0011", "0x0012", "0x0013", "0x0014", "0x0015",
+ "0x0016", "0x0017", "0x0018", "0x0019", "0x001A", "0x001B", "0x001C", "0x001D", "0x0020", "0x0021", "0x0022", "0x0023", "0x0024", "0x0025", "0x0026", "0x0029",
+ "0x002A", "0x002B", "0x002D", "0x002F", "0x0031", "0x0034", "0x0035", "0x0036", "0x0037", "0x0039", "0x003A", "0x003B", "0x003C", "0x003D", "0x003E", "0x003F",
+ "0x0040", "0x0041", "0x0042", "0x0043", "0x0044", "0x0045", "0x0046", "0x0047", "0x0048", "0x004C", "0x004D", "0x004E", "0x004F", "0x0050", "0x0051", "0x0052",
+ "0x0053", "0x0056", "0x0057", "0x0058", "0x005C", "0x005D", "0x005E", "0x005F", "0x0060", "0x0061", "0x0066", "0x0067", "0x0068", "0x0069", "0x006A", "0x006C",
+ "0x006D", "0x006E", "0x006F", "0x0070", "0x0071", "0x0073", "0x0074", "0x0075", "0x0076", "0x0077", "0x0078", "0x0079", "0x007A", "0x007C", "0x007D", "0x007E",
+ "0x0081", "0x0082", "0x0083", "0x0084", "0x0086", "0x0087", "0x0088", "0x008A", "0x008B", "0x008C", "0x008D", "0x008E", "0x008F", "0x0090", "0x0091", "0x00A0",
+ "0x4004", "0x4005", "0x4006", "0x4009", "0x400B"
+ ]
+ },
+ {
+ "name": "Cortex-X2",
+ "implementer": "0x41",
+ "partnum": "0xd48",
+ "common_events": [
+ "0x0000", "0x0001", "0x0002", "0x0003", "0x0004", "0x0005", "0x0008", "0x0009", "0x000A", "0x000B", "0x0010", "0x0011", "0x0012", "0x0013", "0x0014", "0x0015",
+ "0x0016", "0x0017", "0x0018", "0x0019", "0x001A", "0x001B", "0x001C", "0x001D", "0x0020", "0x0021", "0x0022", "0x0023", "0x0024", "0x0025", "0x0026", "0x0029",
+ "0x002A", "0x002B", "0x002D", "0x002F", "0x0031", "0x0034", "0x0035", "0x0036", "0x0037", "0x0039", "0x003A", "0x003B", "0x003C", "0x003D", "0x003E", "0x003F",
+ "0x0040", "0x0041", "0x0042", "0x0043", "0x0044", "0x0045", "0x0046", "0x0047", "0x0048", "0x004C", "0x004D", "0x004E", "0x004F", "0x0050", "0x0051", "0x0052",
+ "0x0053", "0x0056", "0x0057", "0x0058", "0x005C", "0x005D", "0x005E", "0x005F", "0x0060", "0x0061", "0x0066", "0x0067", "0x0068", "0x0069", "0x006A", "0x006C",
+ "0x006D", "0x006E", "0x006F", "0x0070", "0x0071", "0x0073", "0x0074", "0x0075", "0x0076", "0x0077", "0x0078", "0x0079", "0x007A", "0x007C", "0x007D", "0x007E",
+ "0x0081", "0x0082", "0x0083", "0x0084", "0x0086", "0x0087", "0x0088", "0x008A", "0x008B", "0x008C", "0x008D", "0x008E", "0x008F", "0x0090", "0x0091", "0x00A0",
+ "0x4004", "0x4005", "0x4006", "0x4009", "0x400B", "0x400C", "0x4010", "0x4011", "0x4012", "0x4013", "0x4018", "0x4019", "0x401A", "0x401B", "0x4020", "0x4021",
+ "0x4022", "0x4024", "0x4025", "0x4026", "0x8005", "0x8006", "0x8014", "0x8018", "0x801C", "0x8074", "0x8075", "0x8076", "0x8077", "0x8079", "0x80BC", "0x80BD",
+ "0x80C0", "0x80C1", "0x80E3", "0x80E7", "0x80EB", "0x80EF"
+ ]
+ },
+ {
+ "name": "Cortex-X3",
+ "implementer": "0x41",
+ "partnum": "0xd4e",
+ "common_events": [
+ "0x0000", "0x0001", "0x0002", "0x0003", "0x0004", "0x0005", "0x0008", "0x0009", "0x000A", "0x000B", "0x0010", "0x0011", "0x0012", "0x0013", "0x0014", "0x0015",
+ "0x0016", "0x0017", "0x0018", "0x0019", "0x001A", "0x001B", "0x001C", "0x001D", "0x0020", "0x0021", "0x0022", "0x0023", "0x0024", "0x0025", "0x0026", "0x0029",
+ "0x002A", "0x002B", "0x002D", "0x002F", "0x0031", "0x0034", "0x0035", "0x0036", "0x0037", "0x0039", "0x003A", "0x003B", "0x003C", "0x003D", "0x003E", "0x003F",
+ "0x0040", "0x0041", "0x0042", "0x0043", "0x0044", "0x0045", "0x0046", "0x0047", "0x0048", "0x004C", "0x004D", "0x004E", "0x004F", "0x0050", "0x0051", "0x0052",
+ "0x0053", "0x0056", "0x0057", "0x0058", "0x005C", "0x005D", "0x005E", "0x005F", "0x0060", "0x0061", "0x0066", "0x0067", "0x0068", "0x0069", "0x006A", "0x006C",
+ "0x006D", "0x006E", "0x006F", "0x0070", "0x0071", "0x0073", "0x0074", "0x0075", "0x0076", "0x0077", "0x0078", "0x0079", "0x007A", "0x007C", "0x007D", "0x007E",
+ "0x0081", "0x0082", "0x0083", "0x0084", "0x0086", "0x0087", "0x0088", "0x008A", "0x008B", "0x008C", "0x008D", "0x008E", "0x008F", "0x0090", "0x0091", "0x00A0",
+ "0x4004", "0x4005", "0x4006", "0x4009", "0x400B", "0x400C", "0x4010", "0x4011", "0x4012", "0x4013", "0x4018", "0x4019", "0x401A", "0x401B", "0x4020", "0x4021",
+ "0x4022", "0x4024", "0x4025", "0x4026", "0x8005", "0x8006", "0x8014", "0x8018", "0x801C", "0x8074", "0x8075", "0x8076", "0x8077", "0x8079", "0x80BC", "0x80BD",
+ "0x80C0", "0x80C1", "0x80E3", "0x80E7", "0x80EB", "0x80EF"
+ ]
+ },
+ {
+ "name": "Cortex-X4",
+ "implementer": "0x41",
+ "partnum": "0xd82",
+ "common_events": [
+ "0x0000", "0x0001", "0x0002", "0x0003", "0x0004", "0x0005", "0x0008", "0x0009", "0x000A", "0x000B", "0x000C", "0x000D", "0x000E", "0x0010", "0x0011", "0x0012",
+ "0x0013", "0x0014", "0x0015", "0x0016", "0x0017", "0x0018", "0x0019", "0x001A", "0x001B", "0x001C", "0x001D", "0x0020", "0x0021", "0x0022", "0x0023", "0x0024",
+ "0x0025", "0x0026", "0x0029", "0x002A", "0x002B", "0x002D", "0x002F", "0x0031", "0x0034", "0x0035", "0x0036", "0x0037", "0x0039", "0x003A", "0x003B", "0x003C",
+ "0x003D", "0x003E", "0x003F", "0x0040", "0x0041", "0x0044", "0x0045", "0x0046", "0x0047", "0x0048", "0x004C", "0x004D", "0x004E", "0x004F", "0x0050", "0x0051",
+ "0x0052", "0x0053", "0x0056", "0x0057", "0x0058", "0x005C", "0x005D", "0x005E", "0x005F", "0x0060", "0x0061", "0x0066", "0x0067", "0x0068", "0x0069", "0x006A",
+ "0x006C", "0x006D", "0x006E", "0x006F", "0x0070", "0x0071", "0x0072", "0x0073", "0x0074", "0x0075", "0x0076", "0x0077", "0x0078", "0x0079", "0x007A", "0x007C",
+ "0x007D", "0x007E", "0x007F", "0x0081", "0x0082", "0x0083", "0x0084", "0x0086", "0x0087", "0x0088", "0x008A", "0x008B", "0x008C", "0x008D", "0x008E", "0x008F",
+ "0x0090", "0x0091", "0x00A0", "0x4000", "0x4001", "0x4002", "0x4003", "0x4004", "0x4005", "0x4006", "0x4009", "0x400B", "0x400C", "0x4010", "0x4011", "0x4012",
+ "0x4013", "0x4018", "0x4019", "0x401A", "0x401B", "0x4020", "0x4021", "0x4022", "0x4024", "0x4025", "0x4026", "0x8004", "0x8005", "0x8006", "0x8014", "0x8018",
+ "0x801C", "0x8040", "0x8074", "0x8075", "0x8076", "0x8077", "0x8079", "0x8087", "0x80BC", "0x80BD", "0x80C0", "0x80C1", "0x80E3", "0x80E7", "0x80EB", "0x80EF",
+ "0x8108", "0x810C", "0x8110", "0x8111", "0x8112", "0x8113", "0x8114", "0x8115", "0x8116", "0x8117", "0x8118", "0x8119", "0x811A", "0x811B", "0x811C", "0x811D",
+ "0x8120", "0x8121", "0x8124", "0x8128", "0x8129", "0x812A", "0x812B", "0x812C", "0x812D", "0x812E", "0x812F", "0x8130", "0x8131", "0x8132", "0x8133", "0x8134",
+ "0x8135", "0x8136", "0x8137", "0x8138", "0x8139", "0x813A", "0x813B", "0x813C", "0x813D", "0x813E", "0x813F", "0x8140", "0x8141", "0x8142", "0x8143", "0x8144",
+ "0x8145", "0x8146", "0x8147", "0x8148", "0x814A", "0x814C", "0x814E", "0x8150", "0x8151", "0x8152", "0x8153", "0x8154", "0x8155", "0x8156", "0x8158", "0x8159",
+ "0x815B", "0x815C", "0x8160", "0x8161", "0x8162", "0x8164", "0x8165", "0x8167", "0x8168", "0x816A", "0x816B", "0x816D", "0x81C0", "0x81D0", "0x81E0", "0x8200",
+ "0x8208", "0x8240", "0x8250", "0x8260"
+ ]
+ }
+ ]
+ }
+}
diff --git a/simpleperf/event_table_generator.py b/simpleperf/event_table_generator.py
new file mode 100755
index 00000000..d7a3e1b5
--- /dev/null
+++ b/simpleperf/event_table_generator.py
@@ -0,0 +1,253 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2015 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.
+#
+
+import dataclasses
+from dataclasses import dataclass
+import json
+import sys
+
+
+def gen_event_type_entry_str(event_type_name, event_type, event_config, description='',
+ limited_arch=''):
+ return '{"%s", %s, %s, "%s", "%s"},\n' % (
+ event_type_name, event_type, event_config, description, limited_arch)
+
+
+def gen_hardware_events():
+ hardware_configs = ["cpu-cycles",
+ "instructions",
+ "cache-references",
+ "cache-misses",
+ "branch-instructions",
+ "branch-misses",
+ "bus-cycles",
+ "stalled-cycles-frontend",
+ "stalled-cycles-backend",
+ ]
+ generated_str = ""
+ for config in hardware_configs:
+ event_type_name = config
+ event_config = "PERF_COUNT_HW_" + config.replace('-', '_').upper()
+
+ generated_str += gen_event_type_entry_str(
+ event_type_name, "PERF_TYPE_HARDWARE", event_config)
+
+ return generated_str
+
+
+def gen_software_events():
+ software_configs = ["cpu-clock",
+ "task-clock",
+ "page-faults",
+ "context-switches",
+ "cpu-migrations",
+ ["minor-faults", "PERF_COUNT_SW_PAGE_FAULTS_MIN"],
+ ["major-faults", "PERF_COUNT_SW_PAGE_FAULTS_MAJ"],
+ "alignment-faults",
+ "emulation-faults",
+ ]
+ generated_str = ""
+ for config in software_configs:
+ if isinstance(config, list):
+ event_type_name = config[0]
+ event_config = config[1]
+ else:
+ event_type_name = config
+ event_config = "PERF_COUNT_SW_" + config.replace('-', '_').upper()
+
+ generated_str += gen_event_type_entry_str(
+ event_type_name, "PERF_TYPE_SOFTWARE", event_config)
+
+ return generated_str
+
+
+def gen_hw_cache_events():
+ hw_cache_types = [["L1-dcache", "PERF_COUNT_HW_CACHE_L1D"],
+ ["L1-icache", "PERF_COUNT_HW_CACHE_L1I"],
+ ["LLC", "PERF_COUNT_HW_CACHE_LL"],
+ ["dTLB", "PERF_COUNT_HW_CACHE_DTLB"],
+ ["iTLB", "PERF_COUNT_HW_CACHE_ITLB"],
+ ["branch", "PERF_COUNT_HW_CACHE_BPU"],
+ ["node", "PERF_COUNT_HW_CACHE_NODE"],
+ ]
+ hw_cache_ops = [["loads", "load", "PERF_COUNT_HW_CACHE_OP_READ"],
+ ["stores", "store", "PERF_COUNT_HW_CACHE_OP_WRITE"],
+ ["prefetches", "prefetch",
+ "PERF_COUNT_HW_CACHE_OP_PREFETCH"],
+ ]
+ hw_cache_op_results = [["accesses", "PERF_COUNT_HW_CACHE_RESULT_ACCESS"],
+ ["misses", "PERF_COUNT_HW_CACHE_RESULT_MISS"],
+ ]
+ generated_str = ""
+ for (type_name, type_config) in hw_cache_types:
+ for (op_name_access, op_name_miss, op_config) in hw_cache_ops:
+ for (result_name, result_config) in hw_cache_op_results:
+ if result_name == "accesses":
+ event_type_name = type_name + '-' + op_name_access
+ else:
+ event_type_name = type_name + '-' + \
+ op_name_miss + '-' + result_name
+ event_config = "((%s) | (%s << 8) | (%s << 16))" % (
+ type_config, op_config, result_config)
+ generated_str += gen_event_type_entry_str(
+ event_type_name, "PERF_TYPE_HW_CACHE", event_config)
+
+ return generated_str
+
+
+@dataclass
+class RawEvent:
+ number: int
+ name: str
+ desc: str
+ limited_arch: str
+
+
+@dataclass
+class CpuModel:
+ name: str
+ implementer: int
+ partnum: int
+ supported_raw_events: list[int] = dataclasses.field(default_factory=list)
+
+
+class ArchData:
+ def __init__(self, arch: str):
+ self.arch = arch
+ self.events: List[RawEvent] = []
+ self.cpus: List[CpuModel] = []
+
+ def load_from_json_data(self, data) -> None:
+ # Load common events
+ for event in data['events']:
+ number = int(event[0], 16)
+ name = 'raw-' + event[1].lower().replace('_', '-')
+ desc = event[2]
+ self.events.append(RawEvent(number, name, desc, self.arch))
+ for cpu in data['cpus']:
+ cpu_name = cpu['name'].lower().replace('_', '-')
+ cpu_model = CpuModel(cpu['name'], int(cpu['implementer'], 16),
+ int(cpu['partnum'], 16), [])
+ cpu_index = len(self.cpus)
+ self.cpus.append(cpu_model)
+ # Load common events supported in this cpu model.
+ for number in cpu['common_events']:
+ number = int(number, 16)
+ event = self.get_event(number)
+ cpu_model.supported_raw_events.append(number)
+
+ # Load cpu specific events supported in this cpu model.
+ if 'implementation_defined_events' in cpu:
+ for event in cpu['implementation_defined_events']:
+ number = int(event[0], 16)
+ name = ('raw-' + cpu_name + '-' + event[1]).lower().replace('_', '-')
+ desc = event[2]
+ limited_arch = self.arch + ':' + cpu['name']
+ self.events.append(RawEvent(number, name, desc, limited_arch))
+ cpu_model.supported_raw_events.append(number)
+
+ def get_event(self, event_number: int) -> RawEvent:
+ for event in self.events:
+ if event.number == event_number:
+ return event
+ raise Exception(f'no event for event number {event_number}')
+
+
+class RawEventGenerator:
+ def __init__(self, event_table_file: str):
+ with open(event_table_file, 'r') as fh:
+ event_table = json.load(fh)
+ self.arm64_data = ArchData('arm64')
+ self.arm64_data.load_from_json_data(event_table['arm64'])
+
+ def generate_raw_events(self) -> str:
+ lines = []
+ for event in self.arm64_data.events:
+ lines.append(gen_event_type_entry_str(event.name, 'PERF_TYPE_RAW', '0x%x' %
+ event.number, event.desc, event.limited_arch))
+ return self.add_arm_guard(''.join(lines))
+
+ def generate_cpu_support_events(self) -> str:
+ text = """
+ // Map from cpu model to raw events supported on that cpu.',
+ std::unordered_map<std::string, std::unordered_set<int>> cpu_supported_raw_events = {
+ """
+
+ lines = []
+ for cpu in self.arm64_data.cpus:
+ event_list = ', '.join('0x%x' % number for number in cpu.supported_raw_events)
+ lines.append('{"%s", {%s}},' % (cpu.name, event_list))
+ text += self.add_arm_guard('\n'.join(lines))
+ text += '};\n'
+ return text
+
+ def generate_cpu_models(self) -> str:
+ text = """
+ std::unordered_map<uint64_t, std::string> arm64_cpuid_to_name = {
+ """
+ lines = []
+ for cpu in self.arm64_data.cpus:
+ cpu_id = (cpu.implementer << 32) | cpu.partnum
+ lines.append('{0x%xull, "%s"},' % (cpu_id, cpu.name))
+ text += '\n'.join(lines)
+ text += '};\n'
+ return self.add_arm_guard(text)
+
+ def add_arm_guard(self, data: str) -> str:
+ return f'#if defined(__aarch64__) || defined(__arm__)\n{data}\n#endif\n'
+
+
+def gen_events(event_table_file: str):
+ generated_str = """
+ #include <unordered_map>
+ #include <unordered_set>
+
+ #include "event_type.h"
+
+ namespace simpleperf {
+
+ std::set<EventType> builtin_event_types = {
+ """
+ generated_str += gen_hardware_events() + '\n'
+ generated_str += gen_software_events() + '\n'
+ generated_str += gen_hw_cache_events() + '\n'
+ raw_event_generator = RawEventGenerator(event_table_file)
+ generated_str += raw_event_generator.generate_raw_events() + '\n'
+ generated_str += """
+ };
+
+
+ """
+ generated_str += raw_event_generator.generate_cpu_support_events()
+ generated_str += raw_event_generator.generate_cpu_models()
+
+ generated_str += """
+ } // namespace simpleperf
+ """
+ return generated_str
+
+
+def main():
+ event_table_file = sys.argv[1]
+ output_file = sys.argv[2]
+ generated_str = gen_events(event_table_file)
+ with open(output_file, 'w') as fh:
+ fh.write(generated_str)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/simpleperf/event_type.cpp b/simpleperf/event_type.cpp
index 135e69da..d8379141 100644
--- a/simpleperf/event_type.cpp
+++ b/simpleperf/event_type.cpp
@@ -44,12 +44,7 @@ struct EventFormat {
int shift;
};
-#define EVENT_TYPE_TABLE_ENTRY(name, type, config, description, limited_arch) \
- {name, type, config, description, limited_arch},
-
-static const std::set<EventType> builtin_event_types = {
-#include "event_type_table.h"
-};
+extern std::set<EventType> builtin_event_types;
enum class EventFinderType {
BUILTIN,
@@ -109,7 +104,11 @@ class TracepointStringFinder : public EventTypeFinder {
protected:
void LoadTypes() override {
for (const auto& line : android::base::Split(s_, "\n")) {
- std::vector<std::string> items = android::base::Split(line, " ");
+ std::string str = android::base::Trim(line);
+ if (str.empty()) {
+ continue;
+ }
+ std::vector<std::string> items = android::base::Split(str, " ");
CHECK_EQ(items.size(), 2u);
std::string event_name = items[0];
uint64_t id;
diff --git a/simpleperf/event_type.h b/simpleperf/event_type.h
index 14863caa..d85f71d1 100644
--- a/simpleperf/event_type.h
+++ b/simpleperf/event_type.h
@@ -55,6 +55,7 @@ struct EventType {
bool IsHardwareEvent() const {
return type == PERF_TYPE_HARDWARE || type == PERF_TYPE_HW_CACHE || type == PERF_TYPE_RAW;
}
+ bool IsTracepointEvent() const { return type == PERF_TYPE_TRACEPOINT; }
std::vector<int> GetPmuCpumask();
diff --git a/simpleperf/event_type_table.h b/simpleperf/event_type_table.h
deleted file mode 100644
index d209f0b7..00000000
--- a/simpleperf/event_type_table.h
+++ /dev/null
@@ -1,496 +0,0 @@
-// This file is auto-generated by generate-event_table.py.
-
-EVENT_TYPE_TABLE_ENTRY("cpu-cycles", PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES, "", "")
-EVENT_TYPE_TABLE_ENTRY("instructions", PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS, "", "")
-EVENT_TYPE_TABLE_ENTRY("cache-references", PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_REFERENCES, "",
- "")
-EVENT_TYPE_TABLE_ENTRY("cache-misses", PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_MISSES, "", "")
-EVENT_TYPE_TABLE_ENTRY("branch-instructions", PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_INSTRUCTIONS,
- "", "")
-EVENT_TYPE_TABLE_ENTRY("branch-misses", PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_MISSES, "", "")
-EVENT_TYPE_TABLE_ENTRY("bus-cycles", PERF_TYPE_HARDWARE, PERF_COUNT_HW_BUS_CYCLES, "", "")
-EVENT_TYPE_TABLE_ENTRY("stalled-cycles-frontend", PERF_TYPE_HARDWARE,
- PERF_COUNT_HW_STALLED_CYCLES_FRONTEND, "", "")
-EVENT_TYPE_TABLE_ENTRY("stalled-cycles-backend", PERF_TYPE_HARDWARE,
- PERF_COUNT_HW_STALLED_CYCLES_BACKEND, "", "")
-
-EVENT_TYPE_TABLE_ENTRY("cpu-clock", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_CLOCK, "", "")
-EVENT_TYPE_TABLE_ENTRY("task-clock", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_TASK_CLOCK, "", "")
-EVENT_TYPE_TABLE_ENTRY("page-faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS, "", "")
-EVENT_TYPE_TABLE_ENTRY("context-switches", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CONTEXT_SWITCHES, "",
- "")
-EVENT_TYPE_TABLE_ENTRY("cpu-migrations", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_MIGRATIONS, "", "")
-EVENT_TYPE_TABLE_ENTRY("minor-faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS_MIN, "", "")
-EVENT_TYPE_TABLE_ENTRY("major-faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS_MAJ, "", "")
-EVENT_TYPE_TABLE_ENTRY("alignment-faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_ALIGNMENT_FAULTS, "",
- "")
-EVENT_TYPE_TABLE_ENTRY("emulation-faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_EMULATION_FAULTS, "",
- "")
-
-EVENT_TYPE_TABLE_ENTRY("L1-dcache-loads", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_L1D) | (PERF_COUNT_HW_CACHE_OP_READ << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("L1-dcache-load-misses", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_L1D) | (PERF_COUNT_HW_CACHE_OP_READ << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("L1-dcache-stores", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_L1D) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("L1-dcache-store-misses", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_L1D) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("L1-dcache-prefetches", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_L1D) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("L1-dcache-prefetch-misses", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_L1D) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("L1-icache-loads", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_L1I) | (PERF_COUNT_HW_CACHE_OP_READ << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("L1-icache-load-misses", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_L1I) | (PERF_COUNT_HW_CACHE_OP_READ << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("L1-icache-stores", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_L1I) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("L1-icache-store-misses", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_L1I) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("L1-icache-prefetches", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_L1I) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("L1-icache-prefetch-misses", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_L1I) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("LLC-loads", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_LL) | (PERF_COUNT_HW_CACHE_OP_READ << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("LLC-load-misses", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_LL) | (PERF_COUNT_HW_CACHE_OP_READ << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("LLC-stores", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_LL) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("LLC-store-misses", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_LL) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("LLC-prefetches", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_LL) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("LLC-prefetch-misses", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_LL) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("dTLB-loads", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_DTLB) | (PERF_COUNT_HW_CACHE_OP_READ << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("dTLB-load-misses", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_DTLB) | (PERF_COUNT_HW_CACHE_OP_READ << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("dTLB-stores", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_DTLB) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("dTLB-store-misses", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_DTLB) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("dTLB-prefetches", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_DTLB) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("dTLB-prefetch-misses", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_DTLB) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("iTLB-loads", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_ITLB) | (PERF_COUNT_HW_CACHE_OP_READ << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("iTLB-load-misses", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_ITLB) | (PERF_COUNT_HW_CACHE_OP_READ << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("iTLB-stores", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_ITLB) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("iTLB-store-misses", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_ITLB) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("iTLB-prefetches", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_ITLB) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("iTLB-prefetch-misses", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_ITLB) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("branch-loads", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_BPU) | (PERF_COUNT_HW_CACHE_OP_READ << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("branch-load-misses", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_BPU) | (PERF_COUNT_HW_CACHE_OP_READ << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("branch-stores", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_BPU) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("branch-store-misses", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_BPU) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("branch-prefetches", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_BPU) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("branch-prefetch-misses", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_BPU) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("node-loads", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_NODE) | (PERF_COUNT_HW_CACHE_OP_READ << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("node-load-misses", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_NODE) | (PERF_COUNT_HW_CACHE_OP_READ << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("node-stores", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_NODE) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("node-store-misses", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_NODE) | (PERF_COUNT_HW_CACHE_OP_WRITE << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("node-prefetches", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_NODE) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)),
- "", "")
-EVENT_TYPE_TABLE_ENTRY("node-prefetch-misses", PERF_TYPE_HW_CACHE,
- ((PERF_COUNT_HW_CACHE_NODE) | (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) |
- (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)),
- "", "")
-
-EVENT_TYPE_TABLE_ENTRY(
- "raw-sw-incr", PERF_TYPE_RAW, 0x0,
- "Instruction architecturally executed, Condition code check pass, software increment", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1i-cache-refill", PERF_TYPE_RAW, 0x1,
- "Level 1 instruction cache refill", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1i-tlb-refill", PERF_TYPE_RAW, 0x2,
- "Attributable Level 1 instruction TLB refill", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-refill", PERF_TYPE_RAW, 0x3, "Level 1 data cache refill",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache", PERF_TYPE_RAW, 0x4, "Level 1 data cache access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-tlb-refill", PERF_TYPE_RAW, 0x5,
- "Attributable Level 1 data TLB refill", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-ld-retired", PERF_TYPE_RAW, 0x6,
- "Instruction architecturally executed, Condition code check pass, load",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-st-retired", PERF_TYPE_RAW, 0x7,
- "Instruction architecturally executed, Condition code check pass, store",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-inst-retired", PERF_TYPE_RAW, 0x8,
- "Instruction architecturally executed", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-taken", PERF_TYPE_RAW, 0x9, "Exception taken", "arm")
-EVENT_TYPE_TABLE_ENTRY(
- "raw-exc-return", PERF_TYPE_RAW, 0xa,
- "Instruction architecturally executed, Condition code check pass, exception return", "arm")
-EVENT_TYPE_TABLE_ENTRY(
- "raw-cid-write-retired", PERF_TYPE_RAW, 0xb,
- "Instruction architecturally executed, Condition code check pass, write to CONTEXTIDR", "arm")
-EVENT_TYPE_TABLE_ENTRY(
- "raw-pc-write-retired", PERF_TYPE_RAW, 0xc,
- "Instruction architecturally executed, Condition code check pass, software change of the PC",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-br-immed-retired", PERF_TYPE_RAW, 0xd,
- "Instruction architecturally executed, immediate branch", "arm")
-EVENT_TYPE_TABLE_ENTRY(
- "raw-br-return-retired", PERF_TYPE_RAW, 0xe,
- "Instruction architecturally executed, Condition code check pass, procedure return", "arm")
-EVENT_TYPE_TABLE_ENTRY(
- "raw-unaligned-ldst-retired", PERF_TYPE_RAW, 0xf,
- "Instruction architecturally executed, Condition code check pass, unaligned load or store",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-br-mis-pred", PERF_TYPE_RAW, 0x10,
- "Mispredicted or not predicted branch Speculatively executed", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-cpu-cycles", PERF_TYPE_RAW, 0x11, "Cycle", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-br-pred", PERF_TYPE_RAW, 0x12,
- "Predictable branch Speculatively executed", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-mem-access", PERF_TYPE_RAW, 0x13, "Data memory access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1i-cache", PERF_TYPE_RAW, 0x14,
- "Attributable Level 1 instruction cache access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-wb", PERF_TYPE_RAW, 0x15,
- "Attributable Level 1 data cache write-back", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache", PERF_TYPE_RAW, 0x16, "Level 2 data cache access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-refill", PERF_TYPE_RAW, 0x17, "Level 2 data cache refill",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-wb", PERF_TYPE_RAW, 0x18,
- "Attributable Level 2 data cache write-back", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-bus-access", PERF_TYPE_RAW, 0x19, "Bus access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-memory-error", PERF_TYPE_RAW, 0x1a, "Local memory error", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-inst-spec", PERF_TYPE_RAW, 0x1b, "Operation Speculatively executed",
- "arm")
-EVENT_TYPE_TABLE_ENTRY(
- "raw-ttbr-write-retired", PERF_TYPE_RAW, 0x1c,
- "Instruction architecturally executed, Condition code check pass, write to TTBR", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-bus-cycles", PERF_TYPE_RAW, 0x1d, "Bus cycle", "arm")
-EVENT_TYPE_TABLE_ENTRY(
- "raw-chain", PERF_TYPE_RAW, 0x1e,
- "For odd-numbered counters, increments the count by one for each overflow of the preceding "
- "even-numbered counter. For even-numbered counters, there is no increment.",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-allocate", PERF_TYPE_RAW, 0x1f,
- "Attributable Level 1 data cache allocation without refill", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-allocate", PERF_TYPE_RAW, 0x20,
- "Attributable Level 2 data cache allocation without refill", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-br-retired", PERF_TYPE_RAW, 0x21,
- "Instruction architecturally executed, branch", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-br-mis-pred-retired", PERF_TYPE_RAW, 0x22,
- "Instruction architecturally executed, mispredicted branch", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-stall-frontend", PERF_TYPE_RAW, 0x23,
- "No operation issued due to the frontend", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-stall-backend", PERF_TYPE_RAW, 0x24,
- "No operation issued due to backend", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-tlb", PERF_TYPE_RAW, 0x25,
- "Attributable Level 1 data or unified TLB access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1i-tlb", PERF_TYPE_RAW, 0x26,
- "Attributable Level 1 instruction TLB access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2i-cache", PERF_TYPE_RAW, 0x27,
- "Attributable Level 2 instruction cache access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2i-cache-refill", PERF_TYPE_RAW, 0x28,
- "Attributable Level 2 instruction cache refill", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-allocate", PERF_TYPE_RAW, 0x29,
- "Attributable Level 3 data or unified cache allocation without refill",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-refill", PERF_TYPE_RAW, 0x2a,
- "Attributable Level 3 data cache refill", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache", PERF_TYPE_RAW, 0x2b,
- "Attributable Level 3 data cache access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-wb", PERF_TYPE_RAW, 0x2c,
- "Attributable Level 3 data or unified cache write-back", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-tlb-refill", PERF_TYPE_RAW, 0x2d,
- "Attributable Level 2 data or unified TLB refill", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2i-tlb-refill", PERF_TYPE_RAW, 0x2e,
- "Attributable Level 2 instruction TLB refill", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-tlb", PERF_TYPE_RAW, 0x2f,
- "Attributable Level 2 data or unified TLB access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2i-tlb", PERF_TYPE_RAW, 0x30,
- "Attributable Level 2 instruction TLB access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-remote-access", PERF_TYPE_RAW, 0x31,
- "Attributable access to another socket in a multi-socket system", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-ll-cache", PERF_TYPE_RAW, 0x32,
- "Attributable Last Level data cache access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-ll-cache-miss", PERF_TYPE_RAW, 0x33,
- "Attributable Last level data or unified cache miss", "arm")
-EVENT_TYPE_TABLE_ENTRY(
- "raw-dtlb-walk", PERF_TYPE_RAW, 0x34,
- "Attributable data or unified TLB access with at least one translation table walk", "arm")
-EVENT_TYPE_TABLE_ENTRY(
- "raw-itlb-walk", PERF_TYPE_RAW, 0x35,
- "Attributable instruction TLB access with at least one translation table walk", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-ll-cache-rd", PERF_TYPE_RAW, 0x36,
- "Attributable Last Level cache memory read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-ll-cache-miss-rd", PERF_TYPE_RAW, 0x37,
- "Attributable Last Level cache memory read miss", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-remote-access-rd", PERF_TYPE_RAW, 0x38,
- "Attributable memory read access to another socket in a multi-socket system",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-lmiss-rd", PERF_TYPE_RAW, 0x39,
- "Level 1 data cache long-latency read miss", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-op-retired", PERF_TYPE_RAW, 0x3a,
- "Micro-operation architecturally executed", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-op-spec", PERF_TYPE_RAW, 0x3b, "Micro-operation Speculatively executed",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-stall", PERF_TYPE_RAW, 0x3c, "No operation sent for execution", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-stall-slot-backend", PERF_TYPE_RAW, 0x3d,
- "No operation sent for execution on a Slot due to the backend", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-stall-slot-frontend", PERF_TYPE_RAW, 0x3e,
- "No operation send for execution on a Slot due to the frontend", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-stall-slot", PERF_TYPE_RAW, 0x3f,
- "No operation sent for execution on a Slot", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-rd", PERF_TYPE_RAW, 0x40, "Level 1 data cache read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-sample-pop", PERF_TYPE_RAW, 0x4000, "Sample Population", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-sample-feed", PERF_TYPE_RAW, 0x4001, "Sample Taken", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-sample-filtrate", PERF_TYPE_RAW, 0x4002,
- "Sample Taken and not removed by filtering", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-sample-collision", PERF_TYPE_RAW, 0x4003,
- "Sample collided with previous sample", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-cnt-cycles", PERF_TYPE_RAW, 0x4004, "Constant frequency cycles", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-stall-backend-mem", PERF_TYPE_RAW, 0x4005, "Memory stall cycles", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1i-cache-lmiss", PERF_TYPE_RAW, 0x4006,
- "Level 1 instruction cache long-latency miss", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-lmiss-rd", PERF_TYPE_RAW, 0x4009,
- "Level 2 data cache long-latency read miss", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2i-cache-lmiss", PERF_TYPE_RAW, 0x400a,
- "Level 2 instruction cache long-latency miss", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-lmiss-rd", PERF_TYPE_RAW, 0x400b,
- "Level 3 data cache long-latency read miss", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-sve-inst-retired", PERF_TYPE_RAW, 0x8002,
- "SVE Instructions architecturally executed", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-sve-inst-spec", PERF_TYPE_RAW, 0x8006,
- "SVE Instructions speculatively executed", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-wr", PERF_TYPE_RAW, 0x41,
- "Attributable Level 1 data cache access, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-refill-rd", PERF_TYPE_RAW, 0x42,
- "Attributable Level 1 data cache refill, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-refill-wr", PERF_TYPE_RAW, 0x43,
- "Attributable Level 1 data cache refill, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-refill-inner", PERF_TYPE_RAW, 0x44,
- "Attributable Level 1 data cache refill, inner", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-refill-outer", PERF_TYPE_RAW, 0x45,
- "Attributable Level 1 data cache refill, outer", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-wb-victim", PERF_TYPE_RAW, 0x46,
- "Attributable Level 1 data cache Write-Back, victim", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-wb-clean", PERF_TYPE_RAW, 0x47,
- "Level 1 data cache Write-Back, cleaning and coherency", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-inval", PERF_TYPE_RAW, 0x48,
- "Attributable Level 1 data cache invalidate", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-tlb-refill-rd", PERF_TYPE_RAW, 0x4c,
- "Attributable Level 1 data TLB refill, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-tlb-refill-wr", PERF_TYPE_RAW, 0x4d,
- "Attributable Level 1 data TLB refill, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-tlb-rd", PERF_TYPE_RAW, 0x4e,
- "Attributable Level 1 data or unified TLB access, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-tlb-wr", PERF_TYPE_RAW, 0x4f,
- "Attributable Level 1 data or unified TLB access, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-rd", PERF_TYPE_RAW, 0x50,
- "Attributable Level 2 data cache access, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-wr", PERF_TYPE_RAW, 0x51,
- "Attributable Level 2 data cache access, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-refill-rd", PERF_TYPE_RAW, 0x52,
- "Attributable Level 2 data cache refill, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-refill-wr", PERF_TYPE_RAW, 0x53,
- "Attributable Level 2 data cache refill, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-wb-victim", PERF_TYPE_RAW, 0x56,
- "Attributable Level 2 data cache Write-Back, victim", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-wb-clean", PERF_TYPE_RAW, 0x57,
- "Level 2 data cache Write-Back, cleaning and coherency", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-inval", PERF_TYPE_RAW, 0x58,
- "Attributable Level 2 data cache invalidate", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-tlb-refill-rd", PERF_TYPE_RAW, 0x5c,
- "Attributable Level 2 data or unified TLB refill, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-tlb-refill-wr", PERF_TYPE_RAW, 0x5d,
- "Attributable Level 2 data or unified TLB refill, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-tlb-rd", PERF_TYPE_RAW, 0x5e,
- "Attributable Level 2 data or unified TLB access, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-tlb-wr", PERF_TYPE_RAW, 0x5f,
- "Attributable Level 2 data or unified TLB access, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-bus-access-rd", PERF_TYPE_RAW, 0x60, "Bus access, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-bus-access-wr", PERF_TYPE_RAW, 0x61, "Bus access, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-bus-access-shared", PERF_TYPE_RAW, 0x62,
- "Bus access, Normal, Cacheable, Shareable", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-bus-access-not-shared", PERF_TYPE_RAW, 0x63,
- "Bus access, not Normal, Cacheable, Shareable", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-bus-access-normal", PERF_TYPE_RAW, 0x64, "Bus access, normal", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-bus-access-periph", PERF_TYPE_RAW, 0x65, "Bus access, peripheral",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-mem-access-rd", PERF_TYPE_RAW, 0x66, "Data memory access, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-mem-access-wr", PERF_TYPE_RAW, 0x67, "Data memory access, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-unaligned-ld-spec", PERF_TYPE_RAW, 0x68, "Unaligned access, read",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-unaligned-st-spec", PERF_TYPE_RAW, 0x69, "Unaligned access, write",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-unaligned-ldst-spec", PERF_TYPE_RAW, 0x6a, "Unaligned access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-ldrex-spec", PERF_TYPE_RAW, 0x6c,
- "Exclusive operation speculatively executed, LDREX or LDX", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-strex-pass-spec", PERF_TYPE_RAW, 0x6d,
- "Exclusive operation speculatively executed, STREX or STX pass", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-strex-fail-spec", PERF_TYPE_RAW, 0x6e,
- "Exclusive operation speculatively executed, STREX or STX fail", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-strex-spec", PERF_TYPE_RAW, 0x6f,
- "Exclusive operation speculatively executed, STREX or STX", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-ld-spec", PERF_TYPE_RAW, 0x70, "Operation speculatively executed, load",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-st-spec", PERF_TYPE_RAW, 0x71,
- "Operation speculatively executed, store", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-ldst-spec", PERF_TYPE_RAW, 0x72,
- "Operation speculatively executed, load or store", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-dp-spec", PERF_TYPE_RAW, 0x73,
- "Operation speculatively executed, integer data processing", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-ase-spec", PERF_TYPE_RAW, 0x74,
- "Operation speculatively executed, Advanced SIMD instruction", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-vfp-spec", PERF_TYPE_RAW, 0x75,
- "Operation speculatively executed, floating-point instruction", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-pc-write-spec", PERF_TYPE_RAW, 0x76,
- "Operation speculatively executed, software change of the PC", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-crypto-spec", PERF_TYPE_RAW, 0x77,
- "Operation speculatively executed, Cryptographic instruction", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-br-immed-spec", PERF_TYPE_RAW, 0x78,
- "Branch speculatively executed, immediate branch", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-br-return-spec", PERF_TYPE_RAW, 0x79,
- "Branch speculatively executed, procedure return", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-br-indirect-spec", PERF_TYPE_RAW, 0x7a,
- "Branch speculatively executed, indirect branch", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-isb-spec", PERF_TYPE_RAW, 0x7c, "Barrier speculatively executed, ISB",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-dsb-spec", PERF_TYPE_RAW, 0x7d, "Barrier speculatively executed, DSB",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-dmb-spec", PERF_TYPE_RAW, 0x7e, "Barrier speculatively executed, DMB",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-undef", PERF_TYPE_RAW, 0x81, "Exception taken, Other synchronous",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-svc", PERF_TYPE_RAW, 0x82, "Exception taken, Supervisor Call",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-pabort", PERF_TYPE_RAW, 0x83, "Exception taken, Instruction Abort",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-dabort", PERF_TYPE_RAW, 0x84,
- "Exception taken, Data Abort and SError", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-irq", PERF_TYPE_RAW, 0x86, "Exception taken, IRQ", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-fiq", PERF_TYPE_RAW, 0x87, "Exception taken, FIQ", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-smc", PERF_TYPE_RAW, 0x88, "Exception taken, Secure Monitor Call",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-hvc", PERF_TYPE_RAW, 0x8a, "Exception taken, Hypervisor Call",
- "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-trap-pabort", PERF_TYPE_RAW, 0x8b,
- "Exception taken, Instruction Abort not Taken locallyb", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-trap-dabort", PERF_TYPE_RAW, 0x8c,
- "Exception taken, Data Abort or SError not Taken locallyb", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-trap-other", PERF_TYPE_RAW, 0x8d,
- "Exception taken, Other traps not Taken locallyb", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-trap-irq", PERF_TYPE_RAW, 0x8e,
- "Exception taken, IRQ not Taken locallyb", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-trap-fiq", PERF_TYPE_RAW, 0x8f,
- "Exception taken, FIQ not Taken locallyb", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-rc-ld-spec", PERF_TYPE_RAW, 0x90,
- "Release consistency operation speculatively executed, Load-Acquire", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-rc-st-spec", PERF_TYPE_RAW, 0x91,
- "Release consistency operation speculatively executed, Store-Release", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-rd", PERF_TYPE_RAW, 0xa0,
- "Attributable Level 3 data or unified cache access, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-wr", PERF_TYPE_RAW, 0xa1,
- "Attributable Level 3 data or unified cache access, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-refill-rd", PERF_TYPE_RAW, 0xa2,
- "Attributable Level 3 data or unified cache refill, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-refill-wr", PERF_TYPE_RAW, 0xa3,
- "Attributable Level 3 data or unified cache refill, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-wb-victim", PERF_TYPE_RAW, 0xa6,
- "Attributable Level 3 data or unified cache Write-Back, victim", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-wb-clean", PERF_TYPE_RAW, 0xa7,
- "Attributable Level 3 data or unified cache Write-Back, cache clean", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-inval", PERF_TYPE_RAW, 0xa8,
- "Attributable Level 3 data or unified cache access, invalidate", "arm")
diff --git a/simpleperf/generate_event_type_table.py b/simpleperf/generate_event_type_table.py
deleted file mode 100755
index 8588e317..00000000
--- a/simpleperf/generate_event_type_table.py
+++ /dev/null
@@ -1,305 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright (C) 2015 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.
-#
-
-
-def gen_event_type_entry_str(event_type_name, event_type, event_config, description='',
- limited_arch=''):
- """
- return string as below:
- EVENT_TYPE_TABLE_ENTRY(event_type_name, event_type, event_config, description, limited_arch)
- """
- return 'EVENT_TYPE_TABLE_ENTRY("%s", %s, %s, "%s", "%s")\n' % (
- event_type_name, event_type, event_config, description, limited_arch)
-
-def gen_arm_event_type_entry_str(event_type_name, event_type, event_config, description):
- return gen_event_type_entry_str(event_type_name, event_type, event_config, description,
- "arm")
-
-
-def gen_hardware_events():
- hardware_configs = ["cpu-cycles",
- "instructions",
- "cache-references",
- "cache-misses",
- "branch-instructions",
- "branch-misses",
- "bus-cycles",
- "stalled-cycles-frontend",
- "stalled-cycles-backend",
- ]
- generated_str = ""
- for config in hardware_configs:
- event_type_name = config
- event_config = "PERF_COUNT_HW_" + config.replace('-', '_').upper()
-
- generated_str += gen_event_type_entry_str(
- event_type_name, "PERF_TYPE_HARDWARE", event_config)
-
- return generated_str
-
-
-def gen_software_events():
- software_configs = ["cpu-clock",
- "task-clock",
- "page-faults",
- "context-switches",
- "cpu-migrations",
- ["minor-faults", "PERF_COUNT_SW_PAGE_FAULTS_MIN"],
- ["major-faults", "PERF_COUNT_SW_PAGE_FAULTS_MAJ"],
- "alignment-faults",
- "emulation-faults",
- ]
- generated_str = ""
- for config in software_configs:
- if isinstance(config, list):
- event_type_name = config[0]
- event_config = config[1]
- else:
- event_type_name = config
- event_config = "PERF_COUNT_SW_" + config.replace('-', '_').upper()
-
- generated_str += gen_event_type_entry_str(
- event_type_name, "PERF_TYPE_SOFTWARE", event_config)
-
- return generated_str
-
-
-def gen_hw_cache_events():
- hw_cache_types = [["L1-dcache", "PERF_COUNT_HW_CACHE_L1D"],
- ["L1-icache", "PERF_COUNT_HW_CACHE_L1I"],
- ["LLC", "PERF_COUNT_HW_CACHE_LL"],
- ["dTLB", "PERF_COUNT_HW_CACHE_DTLB"],
- ["iTLB", "PERF_COUNT_HW_CACHE_ITLB"],
- ["branch", "PERF_COUNT_HW_CACHE_BPU"],
- ["node", "PERF_COUNT_HW_CACHE_NODE"],
- ]
- hw_cache_ops = [["loads", "load", "PERF_COUNT_HW_CACHE_OP_READ"],
- ["stores", "store", "PERF_COUNT_HW_CACHE_OP_WRITE"],
- ["prefetches", "prefetch",
- "PERF_COUNT_HW_CACHE_OP_PREFETCH"],
- ]
- hw_cache_op_results = [["accesses", "PERF_COUNT_HW_CACHE_RESULT_ACCESS"],
- ["misses", "PERF_COUNT_HW_CACHE_RESULT_MISS"],
- ]
- generated_str = ""
- for (type_name, type_config) in hw_cache_types:
- for (op_name_access, op_name_miss, op_config) in hw_cache_ops:
- for (result_name, result_config) in hw_cache_op_results:
- if result_name == "accesses":
- event_type_name = type_name + '-' + op_name_access
- else:
- event_type_name = type_name + '-' + \
- op_name_miss + '-' + result_name
- event_config = "((%s) | (%s << 8) | (%s << 16))" % (
- type_config, op_config, result_config)
- generated_str += gen_event_type_entry_str(
- event_type_name, "PERF_TYPE_HW_CACHE", event_config)
-
- return generated_str
-
-
-def gen_arm_raw_events():
- raw_types = [
- # Refer to "Table D6-7 PMU common architectural and microarchitectural event numbers" in ARMv8 specification.
- [0x0000, "sw-incr", "Instruction architecturally executed, Condition code check pass, software increment"],
- [0x0001, "l1i-cache-refill", "Level 1 instruction cache refill"],
- [0x0002, "l1i-tlb-refill", "Attributable Level 1 instruction TLB refill"],
- [0x0003, "l1d-cache-refill", "Level 1 data cache refill"],
- [0x0004, "l1d-cache", "Level 1 data cache access"],
- [0x0005, "l1d-tlb-refill", "Attributable Level 1 data TLB refill"],
- [0x0006, "ld-retired", "Instruction architecturally executed, Condition code check pass, load"],
- [0x0007, "st-retired", "Instruction architecturally executed, Condition code check pass, store"],
- [0x0008, "inst-retired", "Instruction architecturally executed"],
- [0x0009, "exc-taken", "Exception taken"],
- [0x000A, "exc-return", "Instruction architecturally executed, Condition code check pass, exception return"],
- [0x000B, "cid-write-retired", "Instruction architecturally executed, Condition code check pass, write to CONTEXTIDR"],
- [0x000C, "pc-write-retired", "Instruction architecturally executed, Condition code check pass, software change of the PC"],
- [0x000D, "br-immed-retired", "Instruction architecturally executed, immediate branch"],
- [0x000E, "br-return-retired", "Instruction architecturally executed, Condition code check pass, procedure return"],
- [0x000F, "unaligned-ldst-retired", "Instruction architecturally executed, Condition code check pass, unaligned load or store"],
- [0x0010, "br-mis-pred", "Mispredicted or not predicted branch Speculatively executed"],
- [0x0011, "cpu-cycles", "Cycle"],
- [0x0012, "br-pred", "Predictable branch Speculatively executed"],
- [0x0013, "mem-access", "Data memory access"],
- [0x0014, "l1i-cache", "Attributable Level 1 instruction cache access"],
- [0x0015, "l1d-cache-wb", "Attributable Level 1 data cache write-back"],
- [0x0016, "l2d-cache", "Level 2 data cache access"],
- [0x0017, "l2d-cache-refill", "Level 2 data cache refill"],
- [0x0018, "l2d-cache-wb", "Attributable Level 2 data cache write-back"],
- [0x0019, "bus-access", "Bus access"],
- [0x001A, "memory-error", "Local memory error"],
- [0x001B, "inst-spec", "Operation Speculatively executed"],
- [0x001C, "ttbr-write-retired", "Instruction architecturally executed, Condition code check pass, write to TTBR"],
- [0x001D, "bus-cycles", "Bus cycle"],
- [0x001E, "chain", "For odd-numbered counters, increments the count by one for each overflow of the preceding even-numbered counter. For even-numbered counters, there is no increment."],
- [0x001F, "l1d-cache-allocate", "Attributable Level 1 data cache allocation without refill"],
- [0x0020, "l2d-cache-allocate", "Attributable Level 2 data cache allocation without refill"],
- [0x0021, "br-retired", "Instruction architecturally executed, branch"],
- [0x0022, "br-mis-pred-retired", "Instruction architecturally executed, mispredicted branch"],
- [0x0023, "stall-frontend", "No operation issued due to the frontend"],
- [0x0024, "stall-backend", "No operation issued due to backend"],
- [0x0025, "l1d-tlb", "Attributable Level 1 data or unified TLB access"],
- [0x0026, "l1i-tlb", "Attributable Level 1 instruction TLB access"],
- [0x0027, "l2i-cache", "Attributable Level 2 instruction cache access"],
- [0x0028, "l2i-cache-refill", "Attributable Level 2 instruction cache refill"],
- [0x0029, "l3d-cache-allocate", "Attributable Level 3 data or unified cache allocation without refill"],
- [0x002A, "l3d-cache-refill", "Attributable Level 3 data cache refill"],
- [0x002B, "l3d-cache", "Attributable Level 3 data cache access"],
- [0x002C, "l3d-cache-wb", "Attributable Level 3 data or unified cache write-back"],
- [0x002D, "l2d-tlb-refill", "Attributable Level 2 data or unified TLB refill"],
- [0x002E, "l2i-tlb-refill", "Attributable Level 2 instruction TLB refill"],
- [0x002F, "l2d-tlb", "Attributable Level 2 data or unified TLB access"],
- [0x0030, "l2i-tlb", "Attributable Level 2 instruction TLB access"],
- [0x0031, "remote-access", "Attributable access to another socket in a multi-socket system"],
- [0x0032, "ll-cache", "Attributable Last Level data cache access"],
- [0x0033, "ll-cache-miss", "Attributable Last level data or unified cache miss"],
- [0x0034, "dtlb-walk", "Attributable data or unified TLB access with at least one translation table walk"],
- [0x0035, "itlb-walk", "Attributable instruction TLB access with at least one translation table walk"],
- [0x0036, "ll-cache-rd", "Attributable Last Level cache memory read"],
- [0x0037, "ll-cache-miss-rd", "Attributable Last Level cache memory read miss"],
- [0x0038, "remote-access-rd", "Attributable memory read access to another socket in a multi-socket system"],
- [0x0039, "l1d-cache-lmiss-rd", "Level 1 data cache long-latency read miss"],
- [0x003A, "op-retired", "Micro-operation architecturally executed"],
- [0x003B, "op-spec", "Micro-operation Speculatively executed"],
- [0x003C, "stall", "No operation sent for execution"],
- [0x003D, "stall-slot-backend", "No operation sent for execution on a Slot due to the backend"],
- [0x003E, "stall-slot-frontend", "No operation send for execution on a Slot due to the frontend"],
- [0x003F, "stall-slot", "No operation sent for execution on a Slot"],
- [0x0040, "l1d-cache-rd", "Level 1 data cache read"],
- [0x4000, "sample-pop", "Sample Population"],
- [0x4001, "sample-feed", "Sample Taken"],
- [0x4002, "sample-filtrate", "Sample Taken and not removed by filtering"],
- [0x4003, "sample-collision", "Sample collided with previous sample"],
- [0x4004, "cnt-cycles", "Constant frequency cycles"],
- [0x4005, "stall-backend-mem", "Memory stall cycles"],
- [0x4006, "l1i-cache-lmiss", "Level 1 instruction cache long-latency miss"],
- [0x4009, "l2d-cache-lmiss-rd", "Level 2 data cache long-latency read miss"],
- [0x400A, "l2i-cache-lmiss", "Level 2 instruction cache long-latency miss"],
- [0x400B, "l3d-cache-lmiss-rd", "Level 3 data cache long-latency read miss"],
- [0x8002, "sve-inst-retired", "SVE Instructions architecturally executed"],
- [0x8006, "sve-inst-spec", "SVE Instructions speculatively executed"],
-
- # Refer to "Table K3.1 ARM recommendations for IMPLEMENTATION DEFINED event numbers" in ARMv8 specification.
- #[0x0040, "l1d-cache-rd", "Attributable Level 1 data cache access, read"],
- [0x0041, "l1d-cache-wr", "Attributable Level 1 data cache access, write"],
- [0x0042, "l1d-cache-refill-rd", "Attributable Level 1 data cache refill, read"],
- [0x0043, "l1d-cache-refill-wr", "Attributable Level 1 data cache refill, write"],
- [0x0044, "l1d-cache-refill-inner", "Attributable Level 1 data cache refill, inner"],
- [0x0045, "l1d-cache-refill-outer", "Attributable Level 1 data cache refill, outer"],
- [0x0046, "l1d-cache-wb-victim", "Attributable Level 1 data cache Write-Back, victim"],
- [0x0047, "l1d-cache-wb-clean", "Level 1 data cache Write-Back, cleaning and coherency"],
- [0x0048, "l1d-cache-inval", "Attributable Level 1 data cache invalidate"],
- # 0x0049-0x004B - Reserved
- [0x004C, "l1d-tlb-refill-rd", "Attributable Level 1 data TLB refill, read"],
- [0x004D, "l1d-tlb-refill-wr", "Attributable Level 1 data TLB refill, write"],
- [0x004E, "l1d-tlb-rd", "Attributable Level 1 data or unified TLB access, read"],
- [0x004F, "l1d-tlb-wr", "Attributable Level 1 data or unified TLB access, write"],
- [0x0050, "l2d-cache-rd", "Attributable Level 2 data cache access, read"],
- [0x0051, "l2d-cache-wr", "Attributable Level 2 data cache access, write"],
- [0x0052, "l2d-cache-refill-rd", "Attributable Level 2 data cache refill, read"],
- [0x0053, "l2d-cache-refill-wr", "Attributable Level 2 data cache refill, write"],
- # 0x0054-0x0055 - Reserved
- [0x0056, "l2d-cache-wb-victim", "Attributable Level 2 data cache Write-Back, victim"],
- [0x0057, "l2d-cache-wb-clean", "Level 2 data cache Write-Back, cleaning and coherency"],
- [0x0058, "l2d-cache-inval", "Attributable Level 2 data cache invalidate"],
- # 0x0059-0x005B - Reserved
- [0x005C, "l2d-tlb-refill-rd", "Attributable Level 2 data or unified TLB refill, read"],
- [0x005D, "l2d-tlb-refill-wr", "Attributable Level 2 data or unified TLB refill, write"],
- [0x005E, "l2d-tlb-rd", "Attributable Level 2 data or unified TLB access, read"],
- [0x005F, "l2d-tlb-wr", "Attributable Level 2 data or unified TLB access, write"],
- [0x0060, "bus-access-rd", "Bus access, read"],
- [0x0061, "bus-access-wr", "Bus access, write"],
- [0x0062, "bus-access-shared", "Bus access, Normal, Cacheable, Shareable"],
- [0x0063, "bus-access-not-shared", "Bus access, not Normal, Cacheable, Shareable"],
- [0x0064, "bus-access-normal", "Bus access, normal"],
- [0x0065, "bus-access-periph", "Bus access, peripheral"],
- [0x0066, "mem-access-rd", "Data memory access, read"],
- [0x0067, "mem-access-wr", "Data memory access, write"],
- [0x0068, "unaligned-ld-spec", "Unaligned access, read"],
- [0x0069, "unaligned-st-spec", "Unaligned access, write"],
- [0x006A, "unaligned-ldst-spec", "Unaligned access"],
- # 0x006B - Reserved
- [0x006C, "ldrex-spec", "Exclusive operation speculatively executed, LDREX or LDX"],
- [0x006D, "strex-pass-spec", "Exclusive operation speculatively executed, STREX or STX pass"],
- [0x006E, "strex-fail-spec", "Exclusive operation speculatively executed, STREX or STX fail"],
- [0x006F, "strex-spec", "Exclusive operation speculatively executed, STREX or STX"],
- [0x0070, "ld-spec", "Operation speculatively executed, load"],
- [0x0071, "st-spec", "Operation speculatively executed, store"],
- [0x0072, "ldst-spec", "Operation speculatively executed, load or store"],
- [0x0073, "dp-spec", "Operation speculatively executed, integer data processing"],
- [0x0074, "ase-spec", "Operation speculatively executed, Advanced SIMD instruction"],
- [0x0075, "vfp-spec", "Operation speculatively executed, floating-point instruction"],
- [0x0076, "pc-write-spec", "Operation speculatively executed, software change of the PC"],
- [0x0077, "crypto-spec", "Operation speculatively executed, Cryptographic instruction"],
- [0x0078, "br-immed-spec", "Branch speculatively executed, immediate branch"],
- [0x0079, "br-return-spec", "Branch speculatively executed, procedure return"],
- [0x007A, "br-indirect-spec", "Branch speculatively executed, indirect branch"],
- # 0x007B - Reserved
- [0x007C, "isb-spec", "Barrier speculatively executed, ISB"],
- [0x007D, "dsb-spec", "Barrier speculatively executed, DSB"],
- [0x007E, "dmb-spec", "Barrier speculatively executed, DMB"],
- # 0x007F-0x0080 - Reserved
- [0x0081, "exc-undef", "Exception taken, Other synchronous"],
- [0x0082, "exc-svc", "Exception taken, Supervisor Call"],
- [0x0083, "exc-pabort", "Exception taken, Instruction Abort"],
- [0x0084, "exc-dabort", "Exception taken, Data Abort and SError"],
- # 0x0085 - Reserved
- [0x0086, "exc-irq", "Exception taken, IRQ"],
- [0x0087, "exc-fiq", "Exception taken, FIQ"],
- [0x0088, "exc-smc", "Exception taken, Secure Monitor Call"],
- # 0x0089 - Reserved
- [0x008A, "exc-hvc", "Exception taken, Hypervisor Call"],
- [0x008B, "exc-trap-pabort", "Exception taken, Instruction Abort not Taken locallyb"],
- [0x008C, "exc-trap-dabort", "Exception taken, Data Abort or SError not Taken locallyb"],
- [0x008D, "exc-trap-other", "Exception taken, Other traps not Taken locallyb"],
- [0x008E, "exc-trap-irq", "Exception taken, IRQ not Taken locallyb"],
- [0x008F, "exc-trap-fiq", "Exception taken, FIQ not Taken locallyb"],
- [0x0090, "rc-ld-spec", "Release consistency operation speculatively executed, Load-Acquire"],
- [0x0091, "rc-st-spec", "Release consistency operation speculatively executed, Store-Release"],
- # 0x0092-0x009F - Reserved
- [0x00A0, "l3d-cache-rd", "Attributable Level 3 data or unified cache access, read"],
- [0x00A1, "l3d-cache-wr", "Attributable Level 3 data or unified cache access, write"],
- [0x00A2, "l3d-cache-refill-rd", "Attributable Level 3 data or unified cache refill, read"],
- [0x00A3, "l3d-cache-refill-wr", "Attributable Level 3 data or unified cache refill, write"],
- # 0x00A4-0x00A5 - Reserved
- [0x00A6, "l3d-cache-wb-victim", "Attributable Level 3 data or unified cache Write-Back, victim"],
- [0x00A7, "l3d-cache-wb-clean", "Attributable Level 3 data or unified cache Write-Back, cache clean"],
- [0x00A8, "l3d-cache-inval", "Attributable Level 3 data or unified cache access, invalidate"],
- ]
- generated_str = ""
- for item in raw_types:
- event_type = 'PERF_TYPE_RAW'
- event_type_name = "raw-" + item[1]
- event_config = '0x%x' % item[0]
- description = item[2]
- generated_str += gen_arm_event_type_entry_str(event_type_name, event_type, event_config,
- description)
- return generated_str
-
-
-def gen_events():
- generated_str = "// This file is auto-generated by generate-event_table.py.\n\n"
- generated_str += gen_hardware_events() + '\n'
- generated_str += gen_software_events() + '\n'
- generated_str += gen_hw_cache_events() + '\n'
- generated_str += gen_arm_raw_events() + '\n'
- return generated_str
-
-generated_str = gen_events()
-fh = open('event_type_table.h', 'w')
-fh.write(generated_str)
-fh.close()
diff --git a/simpleperf/get_test_data.h b/simpleperf/get_test_data.h
index a8acb059..e246366d 100644
--- a/simpleperf/get_test_data.h
+++ b/simpleperf/get_test_data.h
@@ -97,8 +97,6 @@ static const std::string PERF_DATA_FOR_BUILD_ID_CHECK = "perf_for_build_id_check
static const std::string CORRECT_SYMFS_FOR_BUILD_ID_CHECK = "data/correct_symfs_for_build_id_check";
static const std::string WRONG_SYMFS_FOR_BUILD_ID_CHECK = "data/wrong_symfs_for_build_id_check";
-static const std::string SYMFS_FOR_NO_SYMBOL_TABLE_WARNING =
- "data/symfs_for_no_symbol_table_warning";
static const std::string SYMFS_FOR_READ_ELF_FILE_WARNING = "data/symfs_for_read_elf_file_warning";
static BuildId CHECK_ELF_FILE_BUILD_ID("91b1c10fdd9fe2221dfec525497637f2229bfdbb");
diff --git a/simpleperf/gtest_main.cpp b/simpleperf/gtest_main.cpp
index 3fce9857..e3224652 100644
--- a/simpleperf/gtest_main.cpp
+++ b/simpleperf/gtest_main.cpp
@@ -70,12 +70,14 @@ class ScopedEnablingPerf {
#endif // defined(__ANDROID__)
int main(int argc, char** argv) {
+ RegisterAllCommands();
// To test profiling apps, simpleperf_unit_test needs to copy itself to the app's directory,
// and run the binary as simpleperf executable.
if (android::base::Basename(argv[0]) == "simpleperf") {
return RunSimpleperfCmd(argc, argv) ? 0 : 1;
}
+ testing::InitGoogleTest(&argc, argv);
android::base::InitLogging(argv, android::base::StderrLogger);
android::base::LogSeverity log_severity = android::base::WARNING;
testdata_dir = std::string(dirname(argv[0])) + "/testdata";
@@ -104,7 +106,6 @@ int main(int argc, char** argv) {
ScopedEnablingPerf scoped_enabling_perf;
#endif
- testing::InitGoogleTest(&argc, argv);
if (!::testing::GTEST_FLAG(list_tests)) {
if (!IsDir(testdata_dir)) {
LOG(ERROR) << "testdata wasn't found. Use \"" << argv[0] << " -t <testdata_dir>\"";
diff --git a/simpleperf/include/simpleperf_profcollect.hpp b/simpleperf/include/simpleperf_profcollect.hpp
index 67123e58..f86eefc0 100644
--- a/simpleperf/include/simpleperf_profcollect.hpp
+++ b/simpleperf/include/simpleperf_profcollect.hpp
@@ -16,8 +16,11 @@
extern "C" {
-bool HasDriverSupport();
-bool HasDeviceSupport();
-bool Record(const char* event_name, const char* output, float duration);
-bool Inject(const char* traceInput, const char* profileOutput, const char* binary_filter);
+bool IsETMDriverAvailable();
+bool IsETMDeviceAvailable();
+bool IsLBRAvailable();
+bool RunRecordCmd(const char** args, int arg_count);
+bool RunInjectCmd(const char** args, int arg_count);
+void SetLogFile(const char* filename);
+void ResetLogFile();
}
diff --git a/simpleperf/kallsyms_test.cpp b/simpleperf/kallsyms_test.cpp
index cacd1634..d2bc8583 100644
--- a/simpleperf/kallsyms_test.cpp
+++ b/simpleperf/kallsyms_test.cpp
@@ -39,6 +39,7 @@ static bool KernelSymbolsMatch(const KernelSymbol& sym1, const KernelSymbol& sym
ModulesMatch(sym1.module, sym2.module);
}
+// @CddTest = 6.1/C-0-2
TEST(kallsyms, ProcessKernelSymbols) {
std::string data =
"ffffffffa005c4e4 d __warned.41698 [libsas]\n"
@@ -64,6 +65,7 @@ TEST(kallsyms, ProcessKernelSymbols) {
data, std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol)));
}
+// @CddTest = 6.1/C-0-2
TEST(kallsyms, ProcessKernelSymbols_ignore_arm_mapping_symbols) {
std::string data =
"aaaaaaaaaaaaaaaa t $x.9 [coresight_etm4x]\n"
@@ -84,17 +86,20 @@ TEST(kallsyms, ProcessKernelSymbols_ignore_arm_mapping_symbols) {
}
#if defined(__ANDROID__)
+// @CddTest = 6.1/C-0-2
TEST(kallsyms, GetKernelStartAddress) {
TEST_REQUIRE_ROOT();
ASSERT_NE(GetKernelStartAddress(), 0u);
}
+// @CddTest = 6.1/C-0-2
TEST(kallsyms, LoadKernelSymbols) {
TEST_REQUIRE_ROOT();
std::string kallsyms;
ASSERT_TRUE(LoadKernelSymbols(&kallsyms));
}
+// @CddTest = 6.1/C-0-2
TEST(kallsyms, print_warning) {
TEST_REQUIRE_NON_ROOT();
const std::string warning_msg = "Access to kernel symbol addresses is restricted.";
diff --git a/simpleperf/libsimpleperf_report_fuzzer.cpp b/simpleperf/libsimpleperf_report_fuzzer.cpp
index 8c6d8788..f448af87 100644
--- a/simpleperf/libsimpleperf_report_fuzzer.cpp
+++ b/simpleperf/libsimpleperf_report_fuzzer.cpp
@@ -9,6 +9,13 @@ using namespace simpleperf;
namespace {
+class CommandRegister {
+ public:
+ CommandRegister() { RegisterDumpRecordCommand(); }
+};
+
+CommandRegister command_register;
+
void TestReportLib(const char* record_file) {
ReportLib* report_lib = CreateReportLib();
SetRecordFile(report_lib, record_file);
diff --git a/simpleperf/main.cpp b/simpleperf/main.cpp
index 62fcd6f7..221ecd07 100644
--- a/simpleperf/main.cpp
+++ b/simpleperf/main.cpp
@@ -76,5 +76,6 @@ int main(int argc, char** argv) {
return 1;
}
#endif
+ RegisterAllCommands();
return RunSimpleperfCmd(argc, argv) ? 0 : 1;
}
diff --git a/simpleperf/nonlinux_support/nonlinux_support.cpp b/simpleperf/nonlinux_support/nonlinux_support.cpp
index 23471f6a..e7084fef 100644
--- a/simpleperf/nonlinux_support/nonlinux_support.cpp
+++ b/simpleperf/nonlinux_support/nonlinux_support.cpp
@@ -23,10 +23,22 @@
namespace simpleperf {
+bool GetThreadMmapsInProcess(pid_t, std::vector<ThreadMmap>*) {
+ return false;
+}
+
bool GetKernelBuildId(BuildId*) {
return false;
}
+bool GetModuleBuildId(const std::string&, BuildId*, const std::string&) {
+ return false;
+}
+
+bool ReadThreadNameAndPid(pid_t, std::string*, pid_t*) {
+ return false;
+}
+
bool CanRecordRawData() {
return false;
}
diff --git a/simpleperf/perf_regs_test.cpp b/simpleperf/perf_regs_test.cpp
index 0af97478..bc33ca9d 100644
--- a/simpleperf/perf_regs_test.cpp
+++ b/simpleperf/perf_regs_test.cpp
@@ -20,6 +20,7 @@
using namespace simpleperf;
+// @CddTest = 6.1/C-0-2
TEST(RegSet, arch) {
ArchType arch_pairs[3][2] = {
{ARCH_X86_32, ARCH_X86_64},
diff --git a/simpleperf/profcollect.cpp b/simpleperf/profcollect.cpp
index 724111e8..cc6fb5c0 100644
--- a/simpleperf/profcollect.cpp
+++ b/simpleperf/profcollect.cpp
@@ -14,6 +14,13 @@
* limitations under the License.
*/
+#include <time.h>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
#include <wakelock/wakelock.h>
#include <include/simpleperf_profcollect.hpp>
@@ -21,53 +28,122 @@
#include "command.h"
#include "event_attr.h"
#include "event_fd.h"
+#include "event_selection_set.h"
#include "event_type.h"
using namespace simpleperf;
-bool HasDriverSupport() {
- return ETMRecorder::GetInstance().IsETMDriverAvailable();
+namespace {
+
+class CommandRegister {
+ public:
+ CommandRegister() {
+ RegisterRecordCommand();
+ RegisterInjectCommand();
+ }
+};
+
+CommandRegister command_register;
+
+} // namespace
+
+bool IsETMDriverAvailable() {
+ bool result = ETMRecorder::GetInstance().IsETMDriverAvailable();
+ LOG(INFO) << "HasDriverSupport result " << result;
+ return result;
}
-bool HasDeviceSupport() {
+bool IsETMDeviceAvailable() {
auto result = ETMRecorder::GetInstance().CheckEtmSupport();
if (!result.ok()) {
- LOG(DEBUG) << result.error();
+ if (result.error().find("can't find etr device") != std::string::npos) {
+ // Trigger a manual probe of etr. It may take effect the next time running
+ // IsETMDeviceAvailable().
+ std::string prop_name = "profcollectd.etr.probe";
+ bool res = android::base::SetProperty(prop_name, "1");
+ if (!res) {
+ LOG(ERROR) << "fails to setprop" << prop_name;
+ }
+ }
+ LOG(INFO) << "HasDeviceSupport check failed: " << result.error();
return false;
}
const EventType* type = FindEventTypeByName("cs-etm", false);
if (type == nullptr) {
+ LOG(INFO) << "HasDeviceSupport check failed: no etm event";
return false;
}
- return IsEventAttrSupported(CreateDefaultPerfEventAttr(*type), type->name);
+ bool ret = IsEventAttrSupported(CreateDefaultPerfEventAttr(*type), type->name);
+ LOG(INFO) << "HasDeviceSupport result " << ret;
+ return ret;
+}
+
+bool IsLBRAvailable() {
+ return IsBranchSamplingSupported();
+}
+
+static std::vector<std::string> ConvertArgs(const char** args, int arg_count) {
+ std::vector<std::string> cmd_args(arg_count);
+ for (int i = 0; i < arg_count; ++i) {
+ cmd_args[i] = args[i];
+ }
+ return cmd_args;
}
-bool Record(const char* event_name, const char* output, float duration) {
+bool RunRecordCmd(const char** args, int arg_count) {
+ std::vector<std::string> cmd_args = ConvertArgs(args, arg_count);
+ LOG(INFO) << "Record " << android::base::Join(cmd_args, " ");
// The kernel may panic when trying to hibernate or hotplug CPUs while collecting
// ETM data. So get wakelock to keep the CPUs on.
auto wakelock = android::wakelock::WakeLock::tryGet("profcollectd");
if (!wakelock) {
- LOG(ERROR) << "Failed to request wakelock.";
+ LOG(ERROR) << "Record failed: Failed to request wakelock.";
return false;
}
- auto recordCmd = CreateCommandInstance("record");
- std::vector<std::string> args;
- args.push_back("-a");
- args.insert(args.end(), {"-e", event_name});
- args.insert(args.end(), {"--duration", std::to_string(duration)});
- args.insert(args.end(), {"-o", output});
- return recordCmd->Run(args);
+ bool result = CreateCommandInstance("record")->Run(cmd_args);
+ LOG(INFO) << "Record result " << result;
+ return result;
+}
+
+bool RunInjectCmd(const char** args, int arg_count) {
+ std::vector<std::string> cmd_args = ConvertArgs(args, arg_count);
+ LOG(INFO) << "Inject " << android::base::Join(cmd_args, " ");
+ bool result = CreateCommandInstance("inject")->Run(cmd_args);
+ LOG(INFO) << "Inject result " << result;
+ return result;
+}
+
+static android::base::unique_fd log_fd;
+static android::base::LogFunction saved_log_func;
+
+static void FileLogger(android::base::LogId id, android::base::LogSeverity severity,
+ const char* tag, const char* file, unsigned int line, const char* message) {
+ if (log_fd.ok()) {
+ static const char log_characters[] = "VDIWEFF";
+ char severity_char = log_characters[severity];
+ struct tm now;
+ time_t t = time(nullptr);
+ localtime_r(&t, &now);
+ char timestamp[32];
+ strftime(timestamp, sizeof(timestamp), "%m-%d %H:%M:%S", &now);
+ std::string s = android::base::StringPrintf("%s %c %s %s:%u] %s\n", tag, severity_char,
+ timestamp, file, line, message);
+ WriteStringToFd(s, log_fd);
+ }
+ saved_log_func(id, severity, tag, file, line, message);
}
-bool Inject(const char* traceInput, const char* profileOutput, const char* binary_filter) {
- auto injectCmd = CreateCommandInstance("inject");
- std::vector<std::string> args;
- args.insert(args.end(), {"-i", traceInput});
- args.insert(args.end(), {"-o", profileOutput});
- if (binary_filter) {
- args.insert(args.end(), {"--binary", binary_filter});
+void SetLogFile(const char* filename) {
+ int fd = TEMP_FAILURE_RETRY(open(filename, O_APPEND | O_CREAT | O_WRONLY | O_CLOEXEC, 0600));
+ if (fd == -1) {
+ PLOG(ERROR) << "failed to open " << filename;
+ return;
}
- args.insert(args.end(), {"--output", "branch-list"});
- args.emplace_back("--exclude-perf");
- return injectCmd->Run(args);
+ log_fd.reset(fd);
+ saved_log_func = SetLogger(FileLogger);
+}
+
+void ResetLogFile() {
+ log_fd.reset();
+ SetLogger(std::move(saved_log_func));
}
diff --git a/simpleperf/read_apk_test.cpp b/simpleperf/read_apk_test.cpp
index e4dd71f5..c0be177c 100644
--- a/simpleperf/read_apk_test.cpp
+++ b/simpleperf/read_apk_test.cpp
@@ -22,6 +22,7 @@
using namespace simpleperf;
+// @CddTest = 6.1/C-0-2
TEST(read_apk, FindElfInApkByOffset) {
ApkInspector inspector;
ASSERT_TRUE(inspector.FindElfInApkByOffset("/dev/null", 0) == nullptr);
@@ -36,6 +37,7 @@ TEST(read_apk, FindElfInApkByOffset) {
ASSERT_EQ(NATIVELIB_SIZE_IN_APK, ee->entry_size());
}
+// @CddTest = 6.1/C-0-2
TEST(read_apk, FindElfInApkByName) {
ASSERT_TRUE(ApkInspector::FindElfInApkByName("/dev/null", "") == nullptr);
ASSERT_TRUE(ApkInspector::FindElfInApkByName(GetTestData(APK_FILE), "") == nullptr);
@@ -45,6 +47,7 @@ TEST(read_apk, FindElfInApkByName) {
ASSERT_EQ(NATIVELIB_SIZE_IN_APK, ee->entry_size());
}
+// @CddTest = 6.1/C-0-2
TEST(read_apk, ParseExtractedInMemoryPath) {
std::string zip_path;
std::string entry_name;
diff --git a/simpleperf/read_dex_file.cpp b/simpleperf/read_dex_file.cpp
index afab576b..09584ec9 100644
--- a/simpleperf/read_dex_file.cpp
+++ b/simpleperf/read_dex_file.cpp
@@ -19,6 +19,7 @@
#include <fcntl.h>
#include <algorithm>
+#include <functional>
#include <iterator>
#include <string>
#include <utility>
diff --git a/simpleperf/read_dex_file.h b/simpleperf/read_dex_file.h
index 7700a06a..900b94dd 100644
--- a/simpleperf/read_dex_file.h
+++ b/simpleperf/read_dex_file.h
@@ -19,6 +19,7 @@
#include <inttypes.h>
+#include <functional>
#include <string>
#include <vector>
diff --git a/simpleperf/read_dex_file_test.cpp b/simpleperf/read_dex_file_test.cpp
index 843a964c..a9860f83 100644
--- a/simpleperf/read_dex_file_test.cpp
+++ b/simpleperf/read_dex_file_test.cpp
@@ -27,6 +27,7 @@
using namespace simpleperf;
+// @CddTest = 6.1/C-0-2
TEST(read_dex_file, smoke) {
std::vector<Symbol> symbols;
auto symbol_callback = [&](DexFileSymbol* symbol) {
diff --git a/simpleperf/read_elf.cpp b/simpleperf/read_elf.cpp
index db3d242a..59396a6a 100644
--- a/simpleperf/read_elf.cpp
+++ b/simpleperf/read_elf.cpp
@@ -24,6 +24,7 @@
#include <algorithm>
#include <limits>
+#include <version>
#include <android-base/file.h>
#include <android-base/logging.h>
@@ -32,7 +33,6 @@
#pragma clang diagnostic ignored "-Wunused-parameter"
#include <llvm/ADT/StringRef.h>
-#include <llvm/Object/Binary.h>
#include <llvm/Object/ELFObjectFile.h>
#include <llvm/Object/ObjectFile.h>
@@ -142,8 +142,7 @@ namespace {
struct BinaryWrapper {
std::unique_ptr<llvm::MemoryBuffer> buffer;
- std::unique_ptr<llvm::object::Binary> binary;
- llvm::object::ObjectFile* obj = nullptr;
+ std::unique_ptr<llvm::object::ObjectFile> obj;
};
static ElfStatus OpenObjectFile(const std::string& filename, uint64_t file_offset,
@@ -165,38 +164,72 @@ static ElfStatus OpenObjectFile(const std::string& filename, uint64_t file_offse
if (status != ElfStatus::NO_ERROR) {
return status;
}
- auto buffer_or_err = llvm::MemoryBuffer::getOpenFileSlice(fd, filename, file_size, file_offset);
+ auto buffer_or_err = llvm::MemoryBuffer::getFileSlice(filename, file_size, file_offset);
if (!buffer_or_err) {
return ElfStatus::READ_FAILED;
}
- auto binary_or_err = llvm::object::createBinary(buffer_or_err.get()->getMemBufferRef());
- if (!binary_or_err) {
+ auto obj_or_err =
+ llvm::object::ObjectFile::createObjectFile(buffer_or_err.get()->getMemBufferRef());
+ if (!obj_or_err) {
return ElfStatus::READ_FAILED;
}
wrapper->buffer = std::move(buffer_or_err.get());
- wrapper->binary = std::move(binary_or_err.get());
- wrapper->obj = llvm::dyn_cast<llvm::object::ObjectFile>(wrapper->binary.get());
- if (wrapper->obj == nullptr) {
- return ElfStatus::FILE_MALFORMED;
- }
+ wrapper->obj = std::move(obj_or_err.get());
return ElfStatus::NO_ERROR;
}
static ElfStatus OpenObjectFileInMemory(const char* data, size_t size, BinaryWrapper* wrapper) {
auto buffer = llvm::MemoryBuffer::getMemBuffer(llvm::StringRef(data, size));
- auto binary_or_err = llvm::object::createBinary(buffer->getMemBufferRef());
- if (!binary_or_err) {
+ auto obj_or_err = llvm::object::ObjectFile::createObjectFile(buffer->getMemBufferRef());
+ if (!obj_or_err) {
return ElfStatus::FILE_MALFORMED;
}
wrapper->buffer = std::move(buffer);
- wrapper->binary = std::move(binary_or_err.get());
- wrapper->obj = llvm::dyn_cast<llvm::object::ObjectFile>(wrapper->binary.get());
- if (wrapper->obj == nullptr) {
- return ElfStatus::FILE_MALFORMED;
- }
+ wrapper->obj = std::move(obj_or_err.get());
return ElfStatus::NO_ERROR;
}
+static inline llvm::Expected<uint32_t> GetSymbolFlags(const llvm::object::ELFSymbolRef& symbol) {
+ return symbol.getFlags();
+}
+
+static inline llvm::Expected<uint64_t> GetSymbolValue(const llvm::object::ELFSymbolRef& symbol) {
+ return symbol.getValue();
+}
+
+static inline llvm::Expected<llvm::StringRef> GetSectionName(
+ const llvm::object::SectionRef& section) {
+ return section.getName();
+}
+
+static inline llvm::Expected<llvm::StringRef> GetSectionContents(
+ const llvm::object::SectionRef& section) {
+ return section.getContents();
+}
+
+template <typename ELFT>
+static inline const llvm::object::ELFFile<ELFT>* GetELFFile(
+ const llvm::object::ELFObjectFile<ELFT>* obj) {
+ return &obj->getELFFile();
+}
+
+template <typename ELFT>
+static inline const typename ELFT::Ehdr& GetELFHeader(const llvm::object::ELFFile<ELFT>* elf) {
+ return elf->getHeader();
+}
+
+template <typename ELFT>
+static inline llvm::Expected<typename ELFT::PhdrRange> GetELFProgramHeaders(
+ const llvm::object::ELFFile<ELFT>* elf) {
+ return elf->program_headers();
+}
+
+template <typename ELFT>
+static inline llvm::Expected<llvm::StringRef> GetELFSectionName(
+ const llvm::object::ELFFile<ELFT>* elf, const typename ELFT::Shdr& section_header) {
+ return elf->getSectionName(section_header);
+}
+
void ReadSymbolTable(llvm::object::symbol_iterator sym_begin, llvm::object::symbol_iterator sym_end,
const std::function<void(const ElfFileSymbol&)>& callback, bool is_arm,
const llvm::object::section_iterator& section_end) {
@@ -204,7 +237,8 @@ void ReadSymbolTable(llvm::object::symbol_iterator sym_begin, llvm::object::symb
ElfFileSymbol symbol;
auto symbol_ref = static_cast<const llvm::object::ELFSymbolRef*>(&*sym_begin);
// Exclude undefined symbols, otherwise we may wrongly use them as labels in functions.
- if (symbol_ref->getFlags() & symbol_ref->SF_Undefined) {
+ if (auto flags = GetSymbolFlags(*symbol_ref);
+ !flags || (flags.get() & symbol_ref->SF_Undefined)) {
continue;
}
llvm::Expected<llvm::object::section_iterator> section_it_or_err = symbol_ref->getSection();
@@ -213,11 +247,11 @@ void ReadSymbolTable(llvm::object::symbol_iterator sym_begin, llvm::object::symb
}
// Symbols in .dynsym section don't have associated section.
if (section_it_or_err.get() != section_end) {
- llvm::StringRef section_name;
- if (section_it_or_err.get()->getName(section_name) || section_name.empty()) {
+ llvm::Expected<llvm::StringRef> section_name = GetSectionName(*section_it_or_err.get());
+ if (!section_name || section_name.get().empty()) {
continue;
}
- if (section_name == ".text") {
+ if (section_name.get() == ".text") {
symbol.is_in_text_section = true;
}
}
@@ -228,7 +262,11 @@ void ReadSymbolTable(llvm::object::symbol_iterator sym_begin, llvm::object::symb
}
symbol.name = symbol_name_or_err.get();
- symbol.vaddr = symbol_ref->getValue();
+ llvm::Expected<uint64_t> symbol_value = GetSymbolValue(*symbol_ref);
+ if (!symbol_value) {
+ continue;
+ }
+ symbol.vaddr = symbol_value.get();
if ((symbol.vaddr & 1) != 0 && is_arm) {
// Arm sets bit 0 to mark it as thumb code, remove the flag.
symbol.vaddr &= ~1;
@@ -267,9 +305,8 @@ void AddSymbolForPltSection(const llvm::object::ELFObjectFile<ELFT>* elf,
// just use a symbol @plt to represent instructions in .plt section.
for (auto it = elf->section_begin(); it != elf->section_end(); ++it) {
const llvm::object::ELFSectionRef& section_ref = *it;
- llvm::StringRef section_name;
- std::error_code err = section_ref.getName(section_name);
- if (err || section_name != ".plt") {
+ llvm::Expected<llvm::StringRef> section_name = GetSectionName(section_ref);
+ if (!section_name || section_name.get() != ".plt") {
continue;
}
const auto* shdr = elf->getSection(section_ref.getRawDataRefImpl());
@@ -295,14 +332,13 @@ void CheckSymbolSections(const llvm::object::ELFObjectFile<ELFT>* elf, bool* has
*has_dynsym = false;
for (auto it = elf->section_begin(); it != elf->section_end(); ++it) {
const llvm::object::ELFSectionRef& section_ref = *it;
- llvm::StringRef section_name;
- std::error_code err = section_ref.getName(section_name);
- if (err) {
+ llvm::Expected<llvm::StringRef> section_name = GetSectionName(section_ref);
+ if (!section_name) {
continue;
}
- if (section_name == ".dynsym") {
+ if (section_name.get() == ".dynsym") {
*has_dynsym = true;
- } else if (section_name == ".symtab") {
+ } else if (section_name.get() == ".symtab") {
*has_symtab = true;
}
}
@@ -315,17 +351,20 @@ template <typename ELFT>
class ElfFileImpl<llvm::object::ELFObjectFile<ELFT>> : public ElfFile {
public:
ElfFileImpl(BinaryWrapper&& wrapper, const llvm::object::ELFObjectFile<ELFT>* elf_obj)
- : wrapper_(std::move(wrapper)), elf_obj_(elf_obj), elf_(elf_obj->getELFFile()) {}
+ : wrapper_(std::move(wrapper)), elf_obj_(elf_obj), elf_(GetELFFile(elf_obj_)) {}
- bool Is64Bit() override { return elf_->getHeader()->getFileClass() == llvm::ELF::ELFCLASS64; }
+ bool Is64Bit() override { return GetELFHeader(elf_).getFileClass() == llvm::ELF::ELFCLASS64; }
llvm::MemoryBuffer* GetMemoryBuffer() override { return wrapper_.buffer.get(); }
std::vector<ElfSegment> GetProgramHeader() override {
- auto program_headers = elf_->program_headers();
- std::vector<ElfSegment> segments(program_headers.size());
- for (size_t i = 0; i < program_headers.size(); i++) {
- const auto& phdr = program_headers[i];
+ auto program_headers = GetELFProgramHeaders(elf_);
+ if (!program_headers) {
+ return {};
+ }
+ std::vector<ElfSegment> segments(program_headers.get().size());
+ for (size_t i = 0; i < program_headers.get().size(); i++) {
+ const auto& phdr = program_headers.get()[i];
segments[i].vaddr = phdr.p_vaddr;
segments[i].file_offset = phdr.p_offset;
segments[i].file_size = phdr.p_filesz;
@@ -345,7 +384,7 @@ class ElfFileImpl<llvm::object::ELFObjectFile<ELFT>> : public ElfFile {
std::vector<ElfSection> sections(section_headers.size());
for (size_t i = 0; i < section_headers.size(); i++) {
const auto& shdr = section_headers[i];
- if (auto name = elf_->getSectionName(&shdr); name) {
+ if (auto name = GetELFSectionName(elf_, shdr); name) {
sections[i].name = name.get();
}
sections[i].vaddr = shdr.sh_addr;
@@ -362,9 +401,11 @@ class ElfFileImpl<llvm::object::ELFObjectFile<ELFT>> : public ElfFile {
for (auto it = elf_obj_->section_begin(); it != elf_obj_->section_end(); ++it) {
const llvm::object::ELFSectionRef& section_ref = *it;
if (section_ref.getType() == llvm::ELF::SHT_NOTE) {
- if (it->getContents(data)) {
- return ElfStatus::READ_FAILED;
+ llvm::Expected<llvm::StringRef> content = GetSectionContents(section_ref);
+ if (!content) {
+ return ElfStatus::NO_BUILD_ID;
}
+ const llvm::StringRef& data = content.get();
if (data.data() < binary_start || data.data() + data.size() > binary_end) {
return ElfStatus::NO_BUILD_ID;
}
@@ -377,7 +418,7 @@ class ElfFileImpl<llvm::object::ELFObjectFile<ELFT>> : public ElfFile {
}
ElfStatus ParseSymbols(const ParseSymbolCallback& callback) override {
- auto machine = elf_->getHeader()->e_machine;
+ auto machine = GetELFHeader(elf_).e_machine;
bool is_arm = (machine == llvm::ELF::EM_ARM || machine == llvm::ELF::EM_AARCH64);
AddSymbolForPltSection(elf_obj_, callback);
// Some applications deliberately ship elf files with broken section tables.
@@ -412,7 +453,7 @@ class ElfFileImpl<llvm::object::ELFObjectFile<ELFT>> : public ElfFile {
}
void ParseDynamicSymbols(const ParseSymbolCallback& callback) override {
- auto machine = elf_->getHeader()->e_machine;
+ auto machine = GetELFHeader(elf_).e_machine;
bool is_arm = (machine == llvm::ELF::EM_ARM || machine == llvm::ELF::EM_AARCH64);
ReadSymbolTable(elf_obj_->dynamic_symbol_begin(), elf_obj_->dynamic_symbol_end(), callback,
is_arm, elf_obj_->section_end());
@@ -421,16 +462,15 @@ class ElfFileImpl<llvm::object::ELFObjectFile<ELFT>> : public ElfFile {
ElfStatus ReadSection(const std::string& section_name, std::string* content) override {
for (llvm::object::section_iterator it = elf_obj_->section_begin();
it != elf_obj_->section_end(); ++it) {
- llvm::StringRef name;
- if (it->getName(name) || name != section_name) {
+ llvm::Expected<llvm::StringRef> name = GetSectionName(*it);
+ if (!name || name.get() != section_name) {
continue;
}
- llvm::StringRef data;
- std::error_code err = it->getContents(data);
- if (err) {
+ llvm::Expected<llvm::StringRef> data = GetSectionContents(*it);
+ if (!data) {
return ElfStatus::READ_FAILED;
}
- *content = data;
+ *content = data.get();
return ElfStatus::NO_ERROR;
}
return ElfStatus::SECTION_NOT_FOUND;
@@ -439,11 +479,13 @@ class ElfFileImpl<llvm::object::ELFObjectFile<ELFT>> : public ElfFile {
uint64_t ReadMinExecutableVaddr(uint64_t* file_offset) {
bool has_vaddr = false;
uint64_t min_addr = std::numeric_limits<uint64_t>::max();
- for (auto it = elf_->program_header_begin(); it != elf_->program_header_end(); ++it) {
- if ((it->p_type == llvm::ELF::PT_LOAD) && (it->p_flags & llvm::ELF::PF_X)) {
- if (it->p_vaddr < min_addr) {
- min_addr = it->p_vaddr;
- *file_offset = it->p_offset;
+ auto program_headers = GetELFProgramHeaders(elf_);
+ if (program_headers) {
+ for (const auto& ph : program_headers.get()) {
+ if ((ph.p_type == llvm::ELF::PT_LOAD) && (ph.p_flags & llvm::ELF::PF_X) &&
+ (ph.p_vaddr < min_addr)) {
+ min_addr = ph.p_vaddr;
+ *file_offset = ph.p_offset;
has_vaddr = true;
}
}
@@ -457,10 +499,14 @@ class ElfFileImpl<llvm::object::ELFObjectFile<ELFT>> : public ElfFile {
}
bool VaddrToOff(uint64_t vaddr, uint64_t* file_offset) override {
- for (auto ph = elf_->program_header_begin(); ph != elf_->program_header_end(); ++ph) {
- if (ph->p_type == llvm::ELF::PT_LOAD && vaddr >= ph->p_vaddr &&
- vaddr < ph->p_vaddr + ph->p_filesz) {
- *file_offset = vaddr - ph->p_vaddr + ph->p_offset;
+ auto program_headers = GetELFProgramHeaders(elf_);
+ if (!program_headers) {
+ return false;
+ }
+ for (const auto& ph : program_headers.get()) {
+ if (ph.p_type == llvm::ELF::PT_LOAD && vaddr >= ph.p_vaddr &&
+ vaddr < ph.p_vaddr + ph.p_filesz) {
+ *file_offset = vaddr - ph.p_vaddr + ph.p_offset;
return true;
}
}
@@ -474,11 +520,11 @@ class ElfFileImpl<llvm::object::ELFObjectFile<ELFT>> : public ElfFile {
};
std::unique_ptr<ElfFile> CreateElfFileImpl(BinaryWrapper&& wrapper, ElfStatus* status) {
- if (auto obj = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(wrapper.obj)) {
+ if (auto obj = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(wrapper.obj.get())) {
return std::unique_ptr<ElfFile>(
new ElfFileImpl<llvm::object::ELF32LEObjectFile>(std::move(wrapper), obj));
}
- if (auto obj = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(wrapper.obj)) {
+ if (auto obj = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(wrapper.obj.get())) {
return std::unique_ptr<ElfFile>(
new ElfFileImpl<llvm::object::ELF64LEObjectFile>(std::move(wrapper), obj));
}
@@ -569,3 +615,17 @@ __attribute__((weak)) extern "C" int del_curterm(struct term*) {
__attribute__((weak)) extern "C" int tigetnum(char*) {
return -1;
}
+
+// libsimpleperf_readelf.a may depend on __libcpp_verbose_abort(), which isn't available
+// in the old libc++ used in platform. So define a custom version here, as instructed in
+// https://github.com/llvm/llvm-project/blob/main/libcxx/docs/UsingLibcxx.rst.
+_LIBCPP_BEGIN_NAMESPACE_STD
+void __libcpp_verbose_abort(char const* format, ...) {
+ va_list list;
+ va_start(list, format);
+ vfprintf(stderr, format, list);
+ va_end(list);
+
+ abort();
+}
+_LIBCPP_END_NAMESPACE_STD
diff --git a/simpleperf/read_elf_test.cpp b/simpleperf/read_elf_test.cpp
index e2a2cd88..709205a8 100644
--- a/simpleperf/read_elf_test.cpp
+++ b/simpleperf/read_elf_test.cpp
@@ -32,6 +32,7 @@
using namespace simpleperf;
+// @CddTest = 6.1/C-0-2
TEST(read_elf, GetBuildIdFromNoteSection) {
BuildId build_id;
std::vector<char> data;
@@ -62,6 +63,7 @@ TEST(read_elf, GetBuildIdFromNoteSection) {
}
}
+// @CddTest = 6.1/C-0-2
TEST(read_elf, GetBuildIdFromElfFile) {
BuildId build_id;
ElfStatus status;
@@ -71,6 +73,7 @@ TEST(read_elf, GetBuildIdFromElfFile) {
ASSERT_EQ(build_id, BuildId(elf_file_build_id));
}
+// @CddTest = 6.1/C-0-2
TEST(read_elf, GetBuildIdFromEmbeddedElfFile) {
BuildId build_id;
ElfStatus status;
@@ -103,6 +106,7 @@ void CheckElfFileSymbols(const std::map<std::string, ElfFileSymbol>& symbols) {
CheckFunctionSymbols(symbols);
}
+// @CddTest = 6.1/C-0-2
TEST(read_elf, parse_symbols_from_elf_file_with_correct_build_id) {
std::map<std::string, ElfFileSymbol> symbols;
ElfStatus status;
@@ -113,6 +117,7 @@ TEST(read_elf, parse_symbols_from_elf_file_with_correct_build_id) {
CheckElfFileSymbols(symbols);
}
+// @CddTest = 6.1/C-0-2
TEST(read_elf, parse_symbols_from_elf_file_without_build_id) {
std::map<std::string, ElfFileSymbol> symbols;
ElfStatus status;
@@ -133,6 +138,7 @@ TEST(read_elf, parse_symbols_from_elf_file_without_build_id) {
CheckElfFileSymbols(symbols);
}
+// @CddTest = 6.1/C-0-2
TEST(read_elf, parse_symbols_from_elf_file_with_wrong_build_id) {
BuildId build_id("01010101010101010101");
std::map<std::string, ElfFileSymbol> symbols;
@@ -141,6 +147,7 @@ TEST(read_elf, parse_symbols_from_elf_file_with_wrong_build_id) {
ASSERT_EQ(ElfStatus::BUILD_ID_MISMATCH, status);
}
+// @CddTest = 6.1/C-0-2
TEST(read_elf, ParseSymbolsFromEmbeddedElfFile) {
std::map<std::string, ElfFileSymbol> symbols;
ElfStatus status;
@@ -152,6 +159,7 @@ TEST(read_elf, ParseSymbolsFromEmbeddedElfFile) {
CheckElfFileSymbols(symbols);
}
+// @CddTest = 6.1/C-0-2
TEST(read_elf, ParseSymbolFromMiniDebugInfoElfFile) {
std::map<std::string, ElfFileSymbol> symbols;
ElfStatus status;
@@ -162,6 +170,7 @@ TEST(read_elf, ParseSymbolFromMiniDebugInfoElfFile) {
CheckFunctionSymbols(symbols);
}
+// @CddTest = 6.1/C-0-2
TEST(read_elf, arm_mapping_symbol) {
ASSERT_TRUE(IsArmMappingSymbol("$a"));
ASSERT_FALSE(IsArmMappingSymbol("$b"));
@@ -169,6 +178,7 @@ TEST(read_elf, arm_mapping_symbol) {
ASSERT_FALSE(IsArmMappingSymbol("$a_no_dot"));
}
+// @CddTest = 6.1/C-0-2
TEST(read_elf, ElfFile_Open) {
auto IsValidElfPath = [](const std::string& path) {
ElfStatus status;
@@ -183,6 +193,7 @@ TEST(read_elf, ElfFile_Open) {
ASSERT_EQ(ElfStatus::NO_ERROR, IsValidElfPath(GetTestData(ELF_FILE)));
}
+// @CddTest = 6.1/C-0-2
TEST(read_elf, check_symbol_for_plt_section) {
std::map<std::string, ElfFileSymbol> symbols;
ElfStatus status;
@@ -193,6 +204,7 @@ TEST(read_elf, check_symbol_for_plt_section) {
ASSERT_NE(symbols.find("@plt"), symbols.end());
}
+// @CddTest = 6.1/C-0-2
TEST(read_elf, read_elf_with_broken_section_table) {
std::string elf_path = GetTestData("libsgmainso-6.4.36.so");
std::map<std::string, ElfFileSymbol> symbols;
@@ -211,6 +223,7 @@ TEST(read_elf, read_elf_with_broken_section_table) {
ASSERT_EQ(file_offset_of_min_vaddr, 0u);
}
+// @CddTest = 6.1/C-0-2
TEST(read_elf, ReadMinExecutableVaddr) {
ElfStatus status;
auto elf = ElfFile::Open(GetTestData("libc.so"), &status);
@@ -221,6 +234,7 @@ TEST(read_elf, ReadMinExecutableVaddr) {
ASSERT_EQ(file_offset_of_min_vaddr, 0x29000u);
}
+// @CddTest = 6.1/C-0-2
TEST(read_elf, NoUndefinedSymbol) {
// Check if we read undefined symbols (like dlerror) from libc.so.
bool has_dlerror = false;
@@ -237,6 +251,7 @@ TEST(read_elf, NoUndefinedSymbol) {
ASSERT_FALSE(has_dlerror);
}
+// @CddTest = 6.1/C-0-2
TEST(read_elf, VaddrToOff) {
auto elf = ElfFile::Open(GetTestData(ELF_FILE));
ASSERT_TRUE(elf != nullptr);
@@ -247,6 +262,7 @@ TEST(read_elf, VaddrToOff) {
ASSERT_FALSE(elf->VaddrToOff(0x420000, &off));
}
+// @CddTest = 6.1/C-0-2
TEST(read_elf, GetSectionHeader) {
auto elf = ElfFile::Open(GetTestData(ELF_FILE));
ASSERT_TRUE(elf != nullptr);
diff --git a/simpleperf/read_symbol_map.cpp b/simpleperf/read_symbol_map.cpp
index 15d59e06..c00922d0 100644
--- a/simpleperf/read_symbol_map.cpp
+++ b/simpleperf/read_symbol_map.cpp
@@ -74,16 +74,17 @@ void ReadSymbol(std::string_view content, std::vector<Symbol>* symbols) {
return;
}
- auto name = ConsumeWord(content);
- if (!name) {
+ // Interpret the rest of the line as the symbol name, after stripping leading
+ // and trailing spaces.
+ size_t symbol_begin = content.find_first_not_of(" \t");
+ if (symbol_begin == content.npos) {
return;
}
+ size_t symbol_end = content.find_last_not_of(" \t") + 1;
- if (ConsumeWord(content)) {
- return;
- }
+ auto name = content.substr(symbol_begin, symbol_end - symbol_begin);
- symbols->emplace_back(name.value(), addr.value(), size.value());
+ symbols->emplace_back(name, addr.value(), size.value());
}
} // namespace
diff --git a/simpleperf/read_symbol_map_test.cpp b/simpleperf/read_symbol_map_test.cpp
index 234bea8c..4365fc9e 100644
--- a/simpleperf/read_symbol_map_test.cpp
+++ b/simpleperf/read_symbol_map_test.cpp
@@ -25,6 +25,7 @@
using namespace simpleperf;
+// @CddTest = 6.1/C-0-2
TEST(read_symbol_map, smoke) {
std::string content(
"\n" // skip
@@ -32,15 +33,15 @@ TEST(read_symbol_map, smoke) {
"0x4000\n" // skip
" 0x40 four\n" // skip
"0x1000 0x10 one\n"
- " \n" // skip
- "0x5000 0x50five\n" // skip
- " skip this line\n" // skip
- "0x6000 0x60 six six\n" // skip
+ " \n" // skip
+ "0x5000 0x50five\n" // skip
+ " skip this line\n" // skip
+ "0x6000 0x60 six six\n"
"0x3000 48 three \n");
auto symbols = ReadSymbolMapFromString(content);
- ASSERT_EQ(3u, symbols.size());
+ ASSERT_EQ(4u, symbols.size());
ASSERT_EQ(0x1000, symbols[0].addr);
ASSERT_EQ(0x10, symbols[0].len);
@@ -53,4 +54,67 @@ TEST(read_symbol_map, smoke) {
ASSERT_EQ(0x3000, symbols[2].addr);
ASSERT_EQ(0x30, symbols[2].len);
ASSERT_STREQ("three", symbols[2].Name());
+
+ // Specifically allow spaces in symbols, JIT runtimes such as V8 may generate
+ // them, as "my_function (./path/to/file.js:42:0)" or "get property_name" for
+ // example.
+ ASSERT_EQ(0x6000, symbols[3].addr);
+ ASSERT_EQ(0x60, symbols[3].len);
+ ASSERT_STREQ("six six", symbols[3].Name());
+}
+
+// @CddTest = 6.1/C-0-2
+TEST(read_symbol_map, v8_basic_perf_prof) {
+ // Interesting sample of jitted function names generated by V8 running the
+ // JetStream2 benchmark.
+ std::string content(
+ "0x300019b02e 0x77 Script:~ cli.js:1:1\n"
+ "0x58f00097e0 0x4c8 JS:~initialize ./JetStreamDriver.js:373:21\n"
+ "0x58f003c120 0x4c8 JS:~ (d8):246158:16\n"
+ "0x58f065ad40 0x4c8 JS:~pp$1.parseEmptyStatement (d8):67313:37\n"
+ "0x58f06e7aa0 0x638 RegExp:([a-z0-9$_]+)(=|@|>|%)([a-z0-9$_]+)\n"
+ "0x5df00e05a0 0x324 JS:*runRichards (d8):1101:21\n"
+ "0x5df017cc20 0xb18 JS:^stringToUTF8Array (d8):386:27\n"
+ "0x7a4ddd2780 0x1c0 JS:wasm-to-js:iii:i-19-turbofan\n"
+ "0x7a4ddedae0 0x600 JS:wasm-function[74]-74-liftoff\n");
+
+ auto symbols = ReadSymbolMapFromString(content);
+
+ ASSERT_EQ(9u, symbols.size());
+
+ ASSERT_EQ(0x300019b02e, symbols[0].addr);
+ ASSERT_EQ(0x77, symbols[0].len);
+ ASSERT_STREQ("Script:~ cli.js:1:1", symbols[0].Name());
+
+ ASSERT_EQ(0x58f00097e0, symbols[1].addr);
+ ASSERT_EQ(0x4c8, symbols[1].len);
+ ASSERT_STREQ("JS:~initialize ./JetStreamDriver.js:373:21", symbols[1].Name());
+
+ ASSERT_EQ(0x58f003c120, symbols[2].addr);
+ ASSERT_EQ(0x4c8, symbols[2].len);
+ ASSERT_STREQ("JS:~ (d8):246158:16", symbols[2].Name());
+
+ ASSERT_EQ(0x58f065ad40, symbols[3].addr);
+ ASSERT_EQ(0x4c8, symbols[3].len);
+ ASSERT_STREQ("JS:~pp$1.parseEmptyStatement (d8):67313:37", symbols[3].Name());
+
+ ASSERT_EQ(0x58f06e7aa0, symbols[4].addr);
+ ASSERT_EQ(0x638, symbols[4].len);
+ ASSERT_STREQ("RegExp:([a-z0-9$_]+)(=|@|>|%)([a-z0-9$_]+)", symbols[4].Name());
+
+ ASSERT_EQ(0x5df00e05a0, symbols[5].addr);
+ ASSERT_EQ(0x324, symbols[5].len);
+ ASSERT_STREQ("JS:*runRichards (d8):1101:21", symbols[5].Name());
+
+ ASSERT_EQ(0x5df017cc20, symbols[6].addr);
+ ASSERT_EQ(0xb18, symbols[6].len);
+ ASSERT_STREQ("JS:^stringToUTF8Array (d8):386:27", symbols[6].Name());
+
+ ASSERT_EQ(0x7a4ddd2780, symbols[7].addr);
+ ASSERT_EQ(0x1c0, symbols[7].len);
+ ASSERT_STREQ("JS:wasm-to-js:iii:i-19-turbofan", symbols[7].Name());
+
+ ASSERT_EQ(0x7a4ddedae0, symbols[8].addr);
+ ASSERT_EQ(0x600, symbols[8].len);
+ ASSERT_STREQ("JS:wasm-function[74]-74-liftoff", symbols[8].Name());
}
diff --git a/simpleperf/record.cpp b/simpleperf/record.cpp
index 40d3287a..b9f03829 100644
--- a/simpleperf/record.cpp
+++ b/simpleperf/record.cpp
@@ -72,6 +72,7 @@ static std::string RecordTypeToString(int record_type) {
{SIMPLE_PERF_RECORD_CALLCHAIN, "callchain"},
{SIMPLE_PERF_RECORD_UNWINDING_RESULT, "unwinding_result"},
{SIMPLE_PERF_RECORD_TRACING_DATA, "tracing_data"},
+ {SIMPLE_PERF_RECORD_DEBUG, "debug"},
};
auto it = record_type_names.find(record_type);
@@ -557,6 +558,8 @@ bool SampleRecord::Parse(const perf_event_attr& attr, char* p, char* end) {
MoveFromBinaryFormat(regs_user_data.abi, p);
if (regs_user_data.abi == 0) {
regs_user_data.reg_mask = 0;
+ regs_user_data.reg_nr = 0;
+ regs_user_data.regs = nullptr;
} else {
regs_user_data.reg_mask = attr.sample_regs_user;
size_t bit_nr = __builtin_popcountll(regs_user_data.reg_mask);
@@ -580,7 +583,7 @@ bool SampleRecord::Parse(const perf_event_attr& attr, char* p, char* end) {
}
// TODO: Add parsing of other PERF_SAMPLE_*.
if (UNLIKELY(p < end)) {
- LOG(DEBUG) << "Record has " << end - p << " bytes left\n";
+ LOG(DEBUG) << "Sample (" << time_data.time << ") has " << end - p << " bytes left";
}
return true;
}
@@ -711,10 +714,16 @@ SampleRecord::SampleRecord(const perf_event_attr& attr, uint64_t id, uint64_t ip
}
void SampleRecord::ReplaceRegAndStackWithCallChain(const std::vector<uint64_t>& ips) {
- uint32_t size_added_in_callchain = sizeof(uint64_t) * (ips.size() + 1);
- uint32_t size_reduced_in_reg_stack =
- regs_user_data.reg_nr * sizeof(uint64_t) + stack_user_data.size + sizeof(uint64_t);
- uint32_t new_size = size() + size_added_in_callchain - size_reduced_in_reg_stack;
+ uint32_t add_size_in_callchain = ips.empty() ? 0 : sizeof(uint64_t) * (ips.size() + 1);
+ uint32_t reduce_size_in_reg = (regs_user_data.reg_nr + 1) * sizeof(uint64_t);
+ uint32_t reduce_size_in_stack =
+ stack_user_data.size == 0 ? sizeof(uint64_t) : (stack_user_data.size + 2 * sizeof(uint64_t));
+ uint32_t reduce_size = reduce_size_in_reg + reduce_size_in_stack;
+
+ uint32_t new_size = size() + add_size_in_callchain;
+ CHECK_GT(new_size, reduce_size);
+ new_size -= reduce_size;
+ sample_type &= ~(PERF_SAMPLE_STACK_USER | PERF_SAMPLE_REGS_USER);
BuildBinaryWithNewCallChain(new_size, ips);
}
@@ -816,16 +825,21 @@ void SampleRecord::BuildBinaryWithNewCallChain(uint32_t new_size,
memcpy(p, &raw_data.size, sizeof(uint32_t));
}
uint64_t* p64 = reinterpret_cast<uint64_t*>(p);
- p64 -= ips.size();
- memcpy(p64, ips.data(), ips.size() * sizeof(uint64_t));
- *--p64 = PERF_CONTEXT_USER;
- if (callchain_data.ip_nr > 0) {
- p64 -= callchain_data.ip_nr;
+ if (!ips.empty()) {
+ p64 -= ips.size();
+ memcpy(p64, ips.data(), ips.size() * sizeof(uint64_t));
+ *--p64 = PERF_CONTEXT_USER;
+ }
+ p64 -= callchain_data.ip_nr;
+ if (p64 != callchain_data.ips) {
memcpy(p64, callchain_data.ips, callchain_data.ip_nr * sizeof(uint64_t));
+ callchain_data.ips = p64;
+ }
+ p64--;
+ if (!ips.empty()) {
+ callchain_data.ip_nr += 1 + ips.size();
+ *p64 = callchain_data.ip_nr;
}
- callchain_data.ips = p64;
- callchain_data.ip_nr += 1 + ips.size();
- *--p64 = callchain_data.ip_nr;
CHECK_EQ(callchain_pos, static_cast<size_t>(reinterpret_cast<char*>(p64) - new_binary))
<< "record time " << time_data.time;
if (new_binary != binary_) {
@@ -1590,6 +1604,38 @@ void UnwindingResultRecord::DumpData(size_t indent) const {
}
}
+DebugRecord::DebugRecord(uint64_t time, const std::string& s) {
+ SetTypeAndMisc(SIMPLE_PERF_RECORD_DEBUG, 0);
+ uint32_t size = header_size() + sizeof(uint64_t) + Align(strlen(s.c_str()) + 1, sizeof(uint64_t));
+ SetSize(size);
+ char* new_binary = new char[size];
+ char* p = new_binary;
+ MoveToBinaryFormat(header, p);
+ MoveToBinaryFormat(time, p);
+ this->time = time;
+ this->s = p;
+ MoveToBinaryFormat(s.c_str(), strlen(s.c_str()) + 1, p);
+ CHECK_LE(p, new_binary + size);
+ UpdateBinary(new_binary);
+}
+
+bool DebugRecord::Parse(const perf_event_attr&, char* p, char* end) {
+ if (!ParseHeader(p, end)) {
+ return false;
+ }
+ CHECK_SIZE_U64(p, end, 1);
+ MoveFromBinaryFormat(time, p);
+ if (memchr(p, '\0', end - p) == nullptr) {
+ return false;
+ }
+ s = p;
+ return true;
+}
+
+void DebugRecord::DumpData(size_t indent) const {
+ PrintIndented(indent, "s %s\n", s);
+}
+
bool UnknownRecord::Parse(const perf_event_attr&, char* p, char* end) {
if (!ParseHeader(p, end)) {
return false;
@@ -1664,6 +1710,9 @@ std::unique_ptr<Record> ReadRecordFromBuffer(const perf_event_attr& attr, uint32
case SIMPLE_PERF_RECORD_TRACING_DATA:
r.reset(new TracingDataRecord);
break;
+ case SIMPLE_PERF_RECORD_DEBUG:
+ r.reset(new DebugRecord);
+ break;
default:
r.reset(new UnknownRecord);
break;
diff --git a/simpleperf/record.h b/simpleperf/record.h
index 54ce89df..92cc88c4 100644
--- a/simpleperf/record.h
+++ b/simpleperf/record.h
@@ -56,6 +56,7 @@ enum user_record_type {
SIMPLE_PERF_RECORD_CALLCHAIN,
SIMPLE_PERF_RECORD_UNWINDING_RESULT,
SIMPLE_PERF_RECORD_TRACING_DATA,
+ SIMPLE_PERF_RECORD_DEBUG,
};
// perf_event_header uses u16 to store record size. However, that is not
@@ -550,6 +551,7 @@ struct AuxTraceRecord : public Record {
AuxTraceRecord(uint64_t aux_size, uint64_t offset, uint32_t idx, uint32_t tid, uint32_t cpu);
bool Parse(const perf_event_attr& attr, char* p, char* end) override;
+ uint32_t Cpu() const override { return data->cpu; }
static size_t Size() { return sizeof(perf_event_header) + sizeof(DataType); }
protected:
@@ -670,6 +672,22 @@ struct UnwindingResultRecord : public Record {
void DumpData(size_t indent) const override;
};
+// Add a debug string in the recording file.
+struct DebugRecord : public Record {
+ uint64_t time = 0;
+ char* s = nullptr;
+
+ DebugRecord() {}
+
+ DebugRecord(uint64_t time, const std::string& s);
+
+ bool Parse(const perf_event_attr& attr, char* p, char* end) override;
+ uint64_t Timestamp() const override { return time; }
+
+ protected:
+ void DumpData(size_t indent) const override;
+};
+
// UnknownRecord is used for unknown record types, it makes sure all unknown
// records are not changed when modifying perf.data.
struct UnknownRecord : public Record {
diff --git a/simpleperf/record_equal_test.h b/simpleperf/record_equal_test.h
index bd66415a..fce1a840 100644
--- a/simpleperf/record_equal_test.h
+++ b/simpleperf/record_equal_test.h
@@ -16,23 +16,28 @@
namespace simpleperf {
-static void CheckMmapRecordDataEqual(const MmapRecord& r1, const MmapRecord& r2) {
+static void CheckRecordDataEqual(const MmapRecord& r1, const MmapRecord& r2) {
ASSERT_EQ(0, memcmp(r1.data, r2.data, sizeof(*r1.data)));
ASSERT_STREQ(r1.filename, r2.filename);
}
-static void CheckCommRecordDataEqual(const CommRecord& r1, const CommRecord& r2) {
+static void CheckRecordDataEqual(const CommRecord& r1, const CommRecord& r2) {
ASSERT_EQ(0, memcmp(r1.data, r2.data, sizeof(*r1.data)));
ASSERT_STREQ(r1.comm, r2.comm);
}
-static void CheckBuildIdRecordDataEqual(const BuildIdRecord& r1, const BuildIdRecord& r2) {
+static void CheckRecordDataEqual(const BuildIdRecord& r1, const BuildIdRecord& r2) {
ASSERT_EQ(r1.pid, r2.pid);
ASSERT_EQ(r1.build_id, r2.build_id);
ASSERT_STREQ(r1.filename, r2.filename);
}
-static void CheckSampleRecordDataEqual(const SampleRecord& r1, const SampleRecord& r2) {
+static void CheckRecordDataEqual(const DebugRecord& r1, const DebugRecord& r2) {
+ ASSERT_EQ(r1.time, r2.time);
+ ASSERT_STREQ(r1.s, r2.s);
+}
+
+static void CheckRecordDataEqual(const SampleRecord& r1, const SampleRecord& r2) {
ASSERT_EQ(r1.sample_type, r2.sample_type);
ASSERT_EQ(r1.read_format, r2.read_format);
if (r1.sample_type & PERF_SAMPLE_IP) {
@@ -95,20 +100,20 @@ static void CheckRecordEqual(const Record& r1, const Record& r2) {
ASSERT_EQ(r1.misc(), r2.misc());
ASSERT_EQ(r1.size(), r2.size());
if (r1.type() == PERF_RECORD_SAMPLE) {
- CheckSampleRecordDataEqual(static_cast<const SampleRecord&>(r1),
- static_cast<const SampleRecord&>(r2));
+ CheckRecordDataEqual(static_cast<const SampleRecord&>(r1),
+ static_cast<const SampleRecord&>(r2));
return;
}
ASSERT_EQ(0, memcmp(&r1.sample_id, &r2.sample_id, sizeof(r1.sample_id)));
if (r1.type() == PERF_RECORD_MMAP) {
- CheckMmapRecordDataEqual(static_cast<const MmapRecord&>(r1),
- static_cast<const MmapRecord&>(r2));
+ CheckRecordDataEqual(static_cast<const MmapRecord&>(r1), static_cast<const MmapRecord&>(r2));
} else if (r1.type() == PERF_RECORD_COMM) {
- CheckCommRecordDataEqual(static_cast<const CommRecord&>(r1),
- static_cast<const CommRecord&>(r2));
+ CheckRecordDataEqual(static_cast<const CommRecord&>(r1), static_cast<const CommRecord&>(r2));
} else if (r1.type() == PERF_RECORD_BUILD_ID) {
- CheckBuildIdRecordDataEqual(static_cast<const BuildIdRecord&>(r1),
- static_cast<const BuildIdRecord&>(r2));
+ CheckRecordDataEqual(static_cast<const BuildIdRecord&>(r1),
+ static_cast<const BuildIdRecord&>(r2));
+ } else if (r1.type() == SIMPLE_PERF_RECORD_DEBUG) {
+ CheckRecordDataEqual(static_cast<const DebugRecord&>(r1), static_cast<const DebugRecord&>(r2));
}
}
diff --git a/simpleperf/record_file.h b/simpleperf/record_file.h
index 6daae288..76d982d3 100644
--- a/simpleperf/record_file.h
+++ b/simpleperf/record_file.h
@@ -81,7 +81,7 @@ class RecordFileWriter {
RecordFileWriter(const std::string& filename, FILE* fp, bool own_fp);
~RecordFileWriter();
- bool WriteAttrSection(const std::vector<EventAttrWithId>& attr_ids);
+ bool WriteAttrSection(const EventAttrIds& attr_ids);
bool WriteRecord(const Record& record);
bool WriteData(const void* buf, size_t len);
@@ -139,16 +139,10 @@ class RecordFileReader {
~RecordFileReader();
+ const std::string FileName() const { return filename_; }
const PerfFileFormat::FileHeader& FileHeader() const { return header_; }
- std::vector<EventAttrWithId> AttrSection() const {
- std::vector<EventAttrWithId> result(file_attrs_.size());
- for (size_t i = 0; i < file_attrs_.size(); ++i) {
- result[i].attr = &file_attrs_[i].attr;
- result[i].ids = event_ids_for_file_attrs_[i];
- }
- return result;
- }
+ const EventAttrIds& AttrSection() const { return event_attrs_; }
const std::unordered_map<uint64_t, size_t>& EventIdMap() const { return event_id_to_attr_map_; }
@@ -195,7 +189,12 @@ class RecordFileReader {
bool LoadBuildIdAndFileFeatures(ThreadTree& thread_tree);
- bool ReadAuxData(uint32_t cpu, uint64_t aux_offset, size_t size, std::vector<uint8_t>* buf);
+ // Read aux data into buf.
+ // When read successfully, return true and set error to false.
+ // When the data isn't available, return false and set error to false.
+ // When having error, return false and set error to true.
+ bool ReadAuxData(uint32_t cpu, uint64_t aux_offset, size_t size, std::vector<uint8_t>& buf,
+ bool& error);
bool Close();
@@ -208,7 +207,7 @@ class RecordFileReader {
bool CheckSectionDesc(const PerfFileFormat::SectionDesc& desc, uint64_t min_offset,
uint64_t alignment = 1);
bool ReadAttrSection();
- bool ReadIdsForAttr(const PerfFileFormat::FileAttr& attr, std::vector<uint64_t>* ids);
+ bool ReadIdSection(const PerfFileFormat::SectionDesc& section, std::vector<uint64_t>* ids);
bool ReadFeatureSectionDescriptors();
bool ReadFileV1Feature(uint64_t& read_pos, uint64_t max_size, FileFeature& file);
bool ReadFileV2Feature(uint64_t& read_pos, uint64_t max_size, FileFeature& file);
@@ -224,8 +223,7 @@ class RecordFileReader {
uint64_t file_size_;
PerfFileFormat::FileHeader header_;
- std::vector<PerfFileFormat::FileAttr> file_attrs_;
- std::vector<std::vector<uint64_t>> event_ids_for_file_attrs_;
+ EventAttrIds event_attrs_;
std::unordered_map<uint64_t, size_t> event_id_to_attr_map_;
std::map<int, PerfFileFormat::SectionDesc> feature_section_descriptors_;
diff --git a/simpleperf/record_file_format.h b/simpleperf/record_file_format.h
index 53c6719b..0b36f351 100644
--- a/simpleperf/record_file_format.h
+++ b/simpleperf/record_file_format.h
@@ -82,6 +82,9 @@ file2 feature section (used to replace file feature section):
uint32_t file_msg2_size;
FileFeature file_msg2;
...
+
+etm_branch_list feature section:
+ ETMBranchList etm_branch_list; // from etm_branch_list.proto
*/
namespace simpleperf {
@@ -116,6 +119,7 @@ enum {
FEAT_DEBUG_UNWIND,
FEAT_DEBUG_UNWIND_FILE,
FEAT_FILE2,
+ FEAT_ETM_BRANCH_LIST,
FEAT_MAX_NUM = 256,
};
diff --git a/simpleperf/record_file_reader.cpp b/simpleperf/record_file_reader.cpp
index 166917c8..deeab564 100644
--- a/simpleperf/record_file_reader.cpp
+++ b/simpleperf/record_file_reader.cpp
@@ -60,6 +60,7 @@ static const std::map<int, std::string> feature_name_map = {
{FEAT_DEBUG_UNWIND, "debug_unwind"},
{FEAT_DEBUG_UNWIND_FILE, "debug_unwind_file"},
{FEAT_FILE2, "file2"},
+ {FEAT_ETM_BRANCH_LIST, "etm_branch_list"},
};
std::string GetFeatureName(int feature_id) {
@@ -166,42 +167,42 @@ bool RecordFileReader::ReadAttrSection() {
PLOG(ERROR) << "fseek() failed";
return false;
}
+ event_attrs_.resize(attr_count);
+ std::vector<SectionDesc> id_sections(attr_count);
+ size_t attr_size_in_file = header_.attr_size - sizeof(SectionDesc);
for (size_t i = 0; i < attr_count; ++i) {
std::vector<char> buf(header_.attr_size);
if (!Read(buf.data(), buf.size())) {
return false;
}
- // The size of perf_event_attr is changing between different linux kernel versions.
- // Make sure we copy correct data to memory.
- FileAttr attr;
- memset(&attr, 0, sizeof(attr));
- size_t section_desc_size = sizeof(attr.ids);
- size_t perf_event_attr_size = header_.attr_size - section_desc_size;
- memcpy(&attr.attr, &buf[0], std::min(sizeof(attr.attr), perf_event_attr_size));
- memcpy(&attr.ids, &buf[perf_event_attr_size], section_desc_size);
- if (!CheckSectionDesc(attr.ids, 0, sizeof(uint64_t))) {
+ // The struct perf_event_attr is defined in a Linux header file. It can be extended in newer
+ // kernel versions with more fields and a bigger size. To disable these extensions, set their
+ // values to zero. So to copy perf_event_attr from file to memory safely, ensure the copy
+ // doesn't overflow the file or memory, and set the values of any extra fields in memory to
+ // zero.
+ if (attr_size_in_file >= sizeof(perf_event_attr)) {
+ memcpy(&event_attrs_[i].attr, &buf[0], sizeof(perf_event_attr));
+ } else {
+ memset(&event_attrs_[i].attr, 0, sizeof(perf_event_attr));
+ memcpy(&event_attrs_[i].attr, &buf[0], attr_size_in_file);
+ }
+ memcpy(&id_sections[i], &buf[attr_size_in_file], sizeof(SectionDesc));
+ if (!CheckSectionDesc(id_sections[i], 0, sizeof(uint64_t))) {
LOG(ERROR) << "invalid attr section in " << filename_;
return false;
}
- file_attrs_.push_back(attr);
}
- if (file_attrs_.size() > 1) {
- std::vector<perf_event_attr> attrs;
- for (const auto& file_attr : file_attrs_) {
- attrs.push_back(file_attr.attr);
- }
- if (!GetCommonEventIdPositionsForAttrs(attrs, &event_id_pos_in_sample_records_,
+ if (event_attrs_.size() > 1) {
+ if (!GetCommonEventIdPositionsForAttrs(event_attrs_, &event_id_pos_in_sample_records_,
&event_id_reverse_pos_in_non_sample_records_)) {
return false;
}
}
- for (size_t i = 0; i < file_attrs_.size(); ++i) {
- std::vector<uint64_t> ids;
- if (!ReadIdsForAttr(file_attrs_[i], &ids)) {
+ for (size_t i = 0; i < attr_count; ++i) {
+ if (!ReadIdSection(id_sections[i], &event_attrs_[i].ids)) {
return false;
}
- event_ids_for_file_attrs_.push_back(ids);
- for (auto id : ids) {
+ for (auto id : event_attrs_[i].ids) {
event_id_to_attr_map_[id] = i;
}
}
@@ -237,14 +238,14 @@ bool RecordFileReader::ReadFeatureSectionDescriptors() {
return true;
}
-bool RecordFileReader::ReadIdsForAttr(const FileAttr& attr, std::vector<uint64_t>* ids) {
- size_t id_count = attr.ids.size / sizeof(uint64_t);
- if (fseek(record_fp_, attr.ids.offset, SEEK_SET) != 0) {
+bool RecordFileReader::ReadIdSection(const SectionDesc& section, std::vector<uint64_t>* ids) {
+ size_t id_count = section.size / sizeof(uint64_t);
+ if (fseek(record_fp_, section.offset, SEEK_SET) != 0) {
PLOG(ERROR) << "fseek() failed";
return false;
}
ids->resize(id_count);
- if (!Read(ids->data(), attr.ids.size)) {
+ if (!Read(ids->data(), section.size)) {
return false;
}
return true;
@@ -342,8 +343,8 @@ std::unique_ptr<Record> RecordFileReader::ReadRecord() {
read_record_size_ += header.size;
}
- const perf_event_attr* attr = &file_attrs_[0].attr;
- if (file_attrs_.size() > 1 && header.type < PERF_RECORD_USER_DEFINED_TYPE_START) {
+ const perf_event_attr* attr = &event_attrs_[0].attr;
+ if (event_attrs_.size() > 1 && header.type < PERF_RECORD_USER_DEFINED_TYPE_START) {
bool has_event_id = false;
uint64_t event_id;
if (header.type == PERF_RECORD_SAMPLE) {
@@ -361,7 +362,7 @@ std::unique_ptr<Record> RecordFileReader::ReadRecord() {
if (has_event_id) {
auto it = event_id_to_attr_map_.find(event_id);
if (it != event_id_to_attr_map_.end()) {
- attr = &file_attrs_[it->second].attr;
+ attr = &event_attrs_[it->second].attr;
}
}
}
@@ -401,8 +402,9 @@ bool RecordFileReader::ReadAtOffset(uint64_t offset, void* buf, size_t len) {
void RecordFileReader::ProcessEventIdRecord(const EventIdRecord& r) {
for (size_t i = 0; i < r.count; ++i) {
- event_ids_for_file_attrs_[r.data[i].attr_id].push_back(r.data[i].event_id);
- event_id_to_attr_map_[r.data[i].event_id] = r.data[i].attr_id;
+ const auto& data = r.data[i];
+ event_attrs_[data.attr_id].ids.push_back(data.event_id);
+ event_id_to_attr_map_[data.event_id] = data.attr_id;
}
}
@@ -489,7 +491,7 @@ std::vector<BuildIdRecord> RecordFileReader::ReadBuildIdFeature() {
memcpy(binary.get(), p, header->size);
p += header->size;
BuildIdRecord record;
- if (!record.Parse(file_attrs_[0].attr, binary.get(), binary.get() + header->size)) {
+ if (!record.Parse(event_attrs_[0].attr, binary.get(), binary.get() + header->size)) {
return {};
}
binary.release();
@@ -753,18 +755,22 @@ bool RecordFileReader::LoadBuildIdAndFileFeatures(ThreadTree& thread_tree) {
}
bool RecordFileReader::ReadAuxData(uint32_t cpu, uint64_t aux_offset, size_t size,
- std::vector<uint8_t>* buf) {
+ std::vector<uint8_t>& buf, bool& error) {
+ error = false;
long saved_pos = ftell(record_fp_);
if (saved_pos == -1) {
PLOG(ERROR) << "ftell() failed";
+ error = true;
return false;
}
OverflowResult aux_end = SafeAdd(aux_offset, size);
if (aux_end.overflow) {
LOG(ERROR) << "aux_end overflow";
+ error = true;
return false;
}
if (aux_data_location_.empty() && !BuildAuxDataLocation()) {
+ error = true;
return false;
}
AuxDataLocation* location = nullptr;
@@ -782,18 +788,21 @@ bool RecordFileReader::ReadAuxData(uint32_t cpu, uint64_t aux_offset, size_t siz
}
}
if (location == nullptr) {
- LOG(ERROR) << "failed to find file offset of aux data: cpu " << cpu << ", aux_offset "
- << aux_offset << ", size " << size;
+ // ETM data can be dropped when recording if the userspace buffer is full. This isn't an error.
+ LOG(INFO) << "aux data is missing: cpu " << cpu << ", aux_offset " << aux_offset << ", size "
+ << size << ". Probably the data is lost when recording.";
return false;
}
- if (buf->size() < size) {
- buf->resize(size);
+ if (buf.size() < size) {
+ buf.resize(size);
}
- if (!ReadAtOffset(aux_offset - location->aux_offset + location->file_offset, buf->data(), size)) {
+ if (!ReadAtOffset(aux_offset - location->aux_offset + location->file_offset, buf.data(), size)) {
+ error = true;
return false;
}
if (fseek(record_fp_, saved_pos, SEEK_SET) != 0) {
PLOG(ERROR) << "fseek() failed";
+ error = true;
return false;
}
return true;
@@ -801,17 +810,13 @@ bool RecordFileReader::ReadAuxData(uint32_t cpu, uint64_t aux_offset, size_t siz
bool RecordFileReader::BuildAuxDataLocation() {
std::vector<uint64_t> auxtrace_offset = ReadAuxTraceFeature();
- if (auxtrace_offset.empty()) {
- LOG(ERROR) << "failed to read auxtrace feature section";
- return false;
- }
std::unique_ptr<char[]> buf(new char[AuxTraceRecord::Size()]);
for (auto offset : auxtrace_offset) {
if (!ReadAtOffset(offset, buf.get(), AuxTraceRecord::Size())) {
return false;
}
AuxTraceRecord auxtrace;
- if (!auxtrace.Parse(file_attrs_[0].attr, buf.get(), buf.get() + AuxTraceRecord::Size())) {
+ if (!auxtrace.Parse(event_attrs_[0].attr, buf.get(), buf.get() + AuxTraceRecord::Size())) {
return false;
}
AuxDataLocation location(auxtrace.data->offset, auxtrace.data->aux_size,
@@ -825,9 +830,8 @@ bool RecordFileReader::BuildAuxDataLocation() {
auto location_it = aux_data_location_.find(auxtrace.data->cpu);
if (location_it != aux_data_location_.end()) {
const AuxDataLocation& prev_location = location_it->second.back();
- uint64_t prev_aux_end = prev_location.aux_offset + prev_location.aux_size;
// The AuxTraceRecords should be sorted by aux_offset for each cpu.
- if (prev_aux_end > location.aux_offset) {
+ if (prev_location.aux_offset > location.aux_offset) {
LOG(ERROR) << "invalid auxtrace feature section";
return false;
}
diff --git a/simpleperf/record_file_test.cpp b/simpleperf/record_file_test.cpp
index 395b6603..7967362e 100644
--- a/simpleperf/record_file_test.cpp
+++ b/simpleperf/record_file_test.cpp
@@ -35,27 +35,27 @@
using namespace simpleperf;
using namespace simpleperf::PerfFileFormat;
+// @CddTest = 6.1/C-0-2
class RecordFileTest : public ::testing::Test {
protected:
void SetUp() override { close(tmpfile_.release()); }
void AddEventType(const std::string& event_type_str) {
+ uint64_t fake_id = attr_ids_.size();
+ attr_ids_.resize(attr_ids_.size() + 1);
+ EventAttrWithId& attr_id = attr_ids_.back();
std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType(event_type_str);
ASSERT_TRUE(event_type_modifier != nullptr);
- perf_event_attr attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
- attr.sample_id_all = 1;
- attrs_.push_back(std::unique_ptr<perf_event_attr>(new perf_event_attr(attr)));
- EventAttrWithId attr_id;
- attr_id.attr = attrs_.back().get();
- attr_id.ids.push_back(attrs_.size()); // Fake id.
- attr_ids_.push_back(attr_id);
+ attr_id.attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
+ attr_id.attr.sample_id_all = 1;
+ attr_id.ids.push_back(fake_id);
}
TemporaryFile tmpfile_;
- std::vector<std::unique_ptr<perf_event_attr>> attrs_;
- std::vector<EventAttrWithId> attr_ids_;
+ EventAttrIds attr_ids_;
};
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFileTest, smoke) {
// Write to a record file.
std::unique_ptr<RecordFileWriter> writer = RecordFileWriter::CreateInstance(tmpfile_.path);
@@ -66,7 +66,7 @@ TEST_F(RecordFileTest, smoke) {
ASSERT_TRUE(writer->WriteAttrSection(attr_ids_));
// Write data section.
- MmapRecord mmap_record(*(attr_ids_[0].attr), true, 1, 1, 0x1000, 0x2000, 0x3000,
+ MmapRecord mmap_record(attr_ids_[0].attr, true, 1, 1, 0x1000, 0x2000, 0x3000,
"mmap_record_example", attr_ids_[0].ids[0]);
ASSERT_TRUE(writer->WriteRecord(mmap_record));
@@ -86,9 +86,9 @@ TEST_F(RecordFileTest, smoke) {
// Read from a record file.
std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile_.path);
ASSERT_TRUE(reader != nullptr);
- std::vector<EventAttrWithId> attrs = reader->AttrSection();
+ const EventAttrIds& attrs = reader->AttrSection();
ASSERT_EQ(1u, attrs.size());
- ASSERT_EQ(0, memcmp(attrs[0].attr, attr_ids_[0].attr, sizeof(perf_event_attr)));
+ ASSERT_EQ(0, memcmp(&attrs[0].attr, &attr_ids_[0].attr, sizeof(perf_event_attr)));
ASSERT_EQ(attrs[0].ids, attr_ids_[0].ids);
// Read and check data section.
@@ -104,6 +104,7 @@ TEST_F(RecordFileTest, smoke) {
ASSERT_TRUE(reader->Close());
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFileTest, record_more_than_one_attr) {
// Write to a record file.
std::unique_ptr<RecordFileWriter> writer = RecordFileWriter::CreateInstance(tmpfile_.path);
@@ -120,14 +121,15 @@ TEST_F(RecordFileTest, record_more_than_one_attr) {
// Read from a record file.
std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile_.path);
ASSERT_TRUE(reader != nullptr);
- std::vector<EventAttrWithId> attrs = reader->AttrSection();
+ const EventAttrIds& attrs = reader->AttrSection();
ASSERT_EQ(3u, attrs.size());
for (size_t i = 0; i < attrs.size(); ++i) {
- ASSERT_EQ(0, memcmp(attrs[i].attr, attr_ids_[i].attr, sizeof(perf_event_attr)));
+ ASSERT_EQ(0, memcmp(&attrs[i].attr, &attr_ids_[i].attr, sizeof(perf_event_attr)));
ASSERT_EQ(attrs[i].ids, attr_ids_[i].ids);
}
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFileTest, write_meta_info_feature_section) {
// Write to a record file.
std::unique_ptr<RecordFileWriter> writer = RecordFileWriter::CreateInstance(tmpfile_.path);
@@ -152,6 +154,7 @@ TEST_F(RecordFileTest, write_meta_info_feature_section) {
ASSERT_EQ(reader->GetMetaInfoFeature(), info_map);
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFileTest, write_debug_unwind_feature_section) {
// Write to a record file.
std::unique_ptr<RecordFileWriter> writer = RecordFileWriter::CreateInstance(tmpfile_.path);
@@ -182,6 +185,7 @@ TEST_F(RecordFileTest, write_debug_unwind_feature_section) {
}
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordFileTest, write_file2_feature_section) {
// Write to a record file.
std::unique_ptr<RecordFileWriter> writer = RecordFileWriter::CreateInstance(tmpfile_.path);
@@ -256,4 +260,4 @@ TEST_F(RecordFileTest, write_file2_feature_section) {
}
ASSERT_FALSE(error);
ASSERT_EQ(file_id, files.size());
-} \ No newline at end of file
+}
diff --git a/simpleperf/record_file_writer.cpp b/simpleperf/record_file_writer.cpp
index bcc26a33..ed94e398 100644
--- a/simpleperf/record_file_writer.cpp
+++ b/simpleperf/record_file_writer.cpp
@@ -74,7 +74,7 @@ RecordFileWriter::~RecordFileWriter() {
}
}
-bool RecordFileWriter::WriteAttrSection(const std::vector<EventAttrWithId>& attr_ids) {
+bool RecordFileWriter::WriteAttrSection(const EventAttrIds& attr_ids) {
if (attr_ids.empty()) {
return false;
}
@@ -102,7 +102,7 @@ bool RecordFileWriter::WriteAttrSection(const std::vector<EventAttrWithId>& attr
}
for (auto& attr_id : attr_ids) {
FileAttr file_attr;
- file_attr.attr = *attr_id.attr;
+ file_attr.attr = attr_id.attr;
file_attr.ids.offset = id_section_offset;
file_attr.ids.size = attr_id.ids.size() * sizeof(uint64_t);
id_section_offset += file_attr.ids.size;
@@ -121,7 +121,7 @@ bool RecordFileWriter::WriteAttrSection(const std::vector<EventAttrWithId>& attr
data_section_offset_ = data_section_offset;
// Save event_attr for use when reading records.
- event_attr_ = *attr_ids[0].attr;
+ event_attr_ = attr_ids[0].attr;
return true;
}
diff --git a/simpleperf/record_lib_interface.cpp b/simpleperf/record_lib_interface.cpp
index b6b892e0..2c9e9595 100644
--- a/simpleperf/record_lib_interface.cpp
+++ b/simpleperf/record_lib_interface.cpp
@@ -151,7 +151,7 @@ bool PerfEventSetForCounting::CreateEventSelectionSet() {
}
set->AddMonitoredThreads(threads_);
}
- if (!set->OpenEventFiles({-1})) {
+ if (!set->OpenEventFiles()) {
return false;
}
event_selection_set_ = std::move(set);
diff --git a/simpleperf/record_lib_test.cpp b/simpleperf/record_lib_test.cpp
index 4c1b7e87..15aa9267 100644
--- a/simpleperf/record_lib_test.cpp
+++ b/simpleperf/record_lib_test.cpp
@@ -22,6 +22,7 @@
using namespace simpleperf;
+// @CddTest = 6.1/C-0-2
TEST(get_all_events, smoke) {
std::vector<std::string> events = GetAllEvents();
ASSERT_GT(events.size(), 0u);
@@ -36,6 +37,7 @@ static void DoSomeWork() {
}
}
+// @CddTest = 6.1/C-0-2
TEST(counter, add_event) {
std::unique_ptr<PerfEventSet> perf(
PerfEventSet::CreateInstance(PerfEventSet::Type::kPerfForCounting));
@@ -61,6 +63,7 @@ TEST(counter, add_event) {
}
}
+// @CddTest = 6.1/C-0-2
TEST(counter, different_targets) {
auto test_function = [](std::function<void(PerfEventSet*)> set_target_func) {
std::unique_ptr<PerfEventSet> perf(
@@ -87,6 +90,7 @@ TEST(counter, different_targets) {
[](PerfEventSet* perf) { ASSERT_TRUE(perf->MonitorThreadsInCurrentProcess({getpid()})); });
}
+// @CddTest = 6.1/C-0-2
TEST(counter, start_stop_multiple_times) {
const size_t TEST_COUNT = 10;
std::unique_ptr<PerfEventSet> perf(
@@ -116,6 +120,7 @@ TEST(counter, start_stop_multiple_times) {
}
}
+// @CddTest = 6.1/C-0-2
TEST(counter, no_change_after_stop) {
std::unique_ptr<PerfEventSet> perf(
PerfEventSet::CreateInstance(PerfEventSet::Type::kPerfForCounting));
diff --git a/simpleperf/record_test.cpp b/simpleperf/record_test.cpp
index ddecc9bd..ed437501 100644
--- a/simpleperf/record_test.cpp
+++ b/simpleperf/record_test.cpp
@@ -23,6 +23,7 @@
using namespace simpleperf;
+// @CddTest = 6.1/C-0-2
class RecordTest : public ::testing::Test {
protected:
virtual void SetUp() {
@@ -42,16 +43,19 @@ class RecordTest : public ::testing::Test {
perf_event_attr event_attr;
};
+// @CddTest = 6.1/C-0-2
TEST_F(RecordTest, MmapRecordMatchBinary) {
MmapRecord record(event_attr, true, 1, 2, 0x1000, 0x2000, 0x3000, "MmapRecord", 0);
CheckRecordMatchBinary(record);
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordTest, CommRecordMatchBinary) {
CommRecord record(event_attr, 1, 2, "CommRecord", 0, 7);
CheckRecordMatchBinary(record);
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordTest, SampleRecordMatchBinary) {
event_attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME | PERF_SAMPLE_ID |
PERF_SAMPLE_CPU | PERF_SAMPLE_PERIOD | PERF_SAMPLE_CALLCHAIN;
@@ -59,6 +63,7 @@ TEST_F(RecordTest, SampleRecordMatchBinary) {
CheckRecordMatchBinary(record);
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordTest, SampleRecord_exclude_kernel_callchain) {
SampleRecord r(event_attr, 0, 1, 0, 0, 0, 0, 0, {}, {}, {}, 0);
ASSERT_TRUE(r.ExcludeKernelCallChain());
@@ -103,18 +108,35 @@ TEST_F(RecordTest, SampleRecord_exclude_kernel_callchain) {
{}, 0));
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordTest, SampleRecord_ReplaceRegAndStackWithCallChain) {
- event_attr.sample_type |= PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
- SampleRecord expected(event_attr, 0, 1, 2, 3, 4, 5, 6, {}, {1, PERF_CONTEXT_USER, 2, 3, 4, 5}, {},
- 0);
- for (size_t stack_size : {8, 1024}) {
- SampleRecord r(event_attr, 0, 1, 2, 3, 4, 5, 6, {}, {1}, std::vector<char>(stack_size), 10);
- r.ReplaceRegAndStackWithCallChain({2, 3, 4, 5});
- CheckRecordMatchBinary(r);
- CheckRecordEqual(r, expected);
+ event_attr.sample_type |= PERF_SAMPLE_CALLCHAIN;
+ std::vector<std::vector<uint64_t>> user_ip_tests = {
+ {}, // no userspace ips, just remove stack and reg fields
+ {2}, // add one userspace ip, no need to allocate new binary
+ {2, 3, 4, 5, 6, 7, 8}, // add more userspace ips, may need to allocate new binary
+ };
+ std::vector<uint64_t> stack_size_tests = {0, 8, 1024};
+
+ for (const auto& user_ips : user_ip_tests) {
+ std::vector<uint64_t> ips = {1};
+ if (!user_ips.empty()) {
+ ips.push_back(PERF_CONTEXT_USER);
+ ips.insert(ips.end(), user_ips.begin(), user_ips.end());
+ }
+ SampleRecord expected(event_attr, 0, 1, 2, 3, 4, 5, 6, {}, ips, {}, 0);
+ for (size_t stack_size : stack_size_tests) {
+ event_attr.sample_type |= PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
+ SampleRecord r(event_attr, 0, 1, 2, 3, 4, 5, 6, {}, {1}, std::vector<char>(stack_size), 10);
+ event_attr.sample_type &= ~(PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER);
+ r.ReplaceRegAndStackWithCallChain(user_ips);
+ CheckRecordMatchBinary(r);
+ CheckRecordEqual(r, expected);
+ }
}
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordTest, SampleRecord_UpdateUserCallChain) {
event_attr.sample_type |= PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
SampleRecord r(event_attr, 0, 1, 2, 3, 4, 5, 6, {}, {1, PERF_CONTEXT_USER, 2}, {}, 0);
@@ -125,6 +147,7 @@ TEST_F(RecordTest, SampleRecord_UpdateUserCallChain) {
CheckRecordEqual(r, expected);
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordTest, SampleRecord_AdjustCallChainGeneratedByKernel) {
event_attr.sample_type |= PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
SampleRecord r(event_attr, 0, 1, 2, 3, 4, 5, 6, {}, {1, 5, 0, PERF_CONTEXT_USER, 6, 0}, {}, 0);
@@ -139,6 +162,7 @@ TEST_F(RecordTest, SampleRecord_AdjustCallChainGeneratedByKernel) {
CheckRecordEqual(r, expected);
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordTest, SampleRecord_PerfSampleReadData) {
event_attr.sample_type |= PERF_SAMPLE_READ;
event_attr.read_format =
@@ -165,6 +189,7 @@ TEST_F(RecordTest, SampleRecord_PerfSampleReadData) {
CheckRecordMatchBinary(r2);
}
+// @CddTest = 6.1/C-0-2
TEST_F(RecordTest, CommRecord) {
CommRecord r(event_attr, 1, 2, "init_name", 3, 4);
size_t record_size = r.size();
@@ -178,3 +203,12 @@ TEST_F(RecordTest, CommRecord) {
ASSERT_EQ(r.sample_id.time_data.time, 4u);
CheckRecordMatchBinary(r);
}
+
+// @CddTest = 6.1/C-0-2
+TEST_F(RecordTest, DebugRecord) {
+ DebugRecord r(1234, "hello");
+ ASSERT_EQ(r.size() % sizeof(uint64_t), 0);
+ ASSERT_EQ(r.Timestamp(), 1234);
+ ASSERT_STREQ(r.s, "hello");
+ CheckRecordMatchBinary(r);
+}
diff --git a/simpleperf/report_lib_interface.cpp b/simpleperf/report_lib_interface.cpp
index d94d6a76..e59dd3a3 100644
--- a/simpleperf/report_lib_interface.cpp
+++ b/simpleperf/report_lib_interface.cpp
@@ -193,6 +193,9 @@ class ReportLib {
bool remove_art_frame = !show;
callchain_report_builder_.SetRemoveArtFrame(remove_art_frame);
}
+ bool RemoveMethod(const char* method_name_regex) {
+ return callchain_report_builder_.RemoveMethod(method_name_regex);
+ }
void MergeJavaMethods(bool merge) { callchain_report_builder_.SetConvertJITFrame(merge); }
bool AddProguardMappingFile(const char* mapping_file) {
return callchain_report_builder_.AddProguardMappingFile(mapping_file);
@@ -212,11 +215,12 @@ class ReportLib {
FeatureSection* GetFeatureSection(const char* feature_name);
private:
+ std::unique_ptr<SampleRecord> GetNextSampleRecord();
void ProcessSampleRecord(std::unique_ptr<Record> r);
void ProcessSwitchRecord(std::unique_ptr<Record> r);
void AddSampleRecordToQueue(SampleRecord* r);
- void SetCurrentSample(const SampleRecord& r);
- const EventInfo* FindEventOfCurrentSample();
+ bool SetCurrentSample(std::unique_ptr<SampleRecord> sample_record);
+ const EventInfo& FindEvent(const SampleRecord& r);
void CreateEvents();
bool OpenRecordFileIfNecessary();
@@ -332,7 +336,7 @@ bool ReportLib::OpenRecordFileIfNecessary() {
auto& meta_info = record_file_reader_->GetMetaInfoFeature();
if (auto it = meta_info.find("trace_offcpu"); it != meta_info.end() && it->second == "true") {
// If recorded with --trace-offcpu, default is to report on-off-cpu samples.
- std::string event_name = GetEventNameByAttr(*record_file_reader_->AttrSection()[0].attr);
+ std::string event_name = GetEventNameByAttr(record_file_reader_->AttrSection()[0].attr);
if (!android::base::StartsWith(event_name, "cpu-clock") &&
!android::base::StartsWith(event_name, "task-clock")) {
LOG(ERROR) << "Recording file " << record_filename_ << " is no longer supported. "
@@ -357,9 +361,20 @@ Sample* ReportLib::GetNextSample() {
if (!OpenRecordFileIfNecessary()) {
return nullptr;
}
- if (!sample_record_queue_.empty()) {
- sample_record_queue_.pop();
+
+ while (true) {
+ std::unique_ptr<SampleRecord> r = GetNextSampleRecord();
+ if (!r) {
+ break;
+ }
+ if (SetCurrentSample(std::move(r))) {
+ return &current_sample_;
+ }
}
+ return nullptr;
+}
+
+std::unique_ptr<SampleRecord> ReportLib::GetNextSampleRecord() {
while (sample_record_queue_.empty()) {
std::unique_ptr<Record> record;
if (!record_file_reader_->ReadRecord(record) || record == nullptr) {
@@ -380,8 +395,9 @@ Sample* ReportLib::GetNextSample() {
}
}
}
- SetCurrentSample(*sample_record_queue_.front());
- return &current_sample_;
+ std::unique_ptr<SampleRecord> result = std::move(sample_record_queue_.front());
+ sample_record_queue_.pop();
+ return result;
}
void ReportLib::ProcessSampleRecord(std::unique_ptr<Record> r) {
@@ -448,12 +464,13 @@ void ReportLib::ProcessSwitchRecord(std::unique_ptr<Record> r) {
}
void ReportLib::AddSampleRecordToQueue(SampleRecord* r) {
- if (record_filter_.Check(r)) {
+ if (record_filter_.Check(*r)) {
sample_record_queue_.emplace(r);
}
}
-void ReportLib::SetCurrentSample(const SampleRecord& r) {
+bool ReportLib::SetCurrentSample(std::unique_ptr<SampleRecord> sample_record) {
+ const SampleRecord& r = *sample_record;
current_mappings_.clear();
callchain_entries_.clear();
current_sample_.ip = r.ip_data.ip;
@@ -471,6 +488,10 @@ void ReportLib::SetCurrentSample(const SampleRecord& r) {
std::vector<uint64_t> ips = r.GetCallChain(&kernel_ip_count);
std::vector<CallChainReportEntry> report_entries =
callchain_report_builder_.Build(current_thread_, ips, kernel_ip_count);
+ if (report_entries.empty()) {
+ // Skip samples with callchain fully removed by RemoveMethod().
+ return false;
+ }
for (const auto& report_entry : report_entries) {
callchain_entries_.resize(callchain_entries_.size() + 1);
@@ -491,40 +512,49 @@ void ReportLib::SetCurrentSample(const SampleRecord& r) {
current_symbol_ = &(callchain_entries_[0].symbol);
current_callchain_.nr = callchain_entries_.size() - 1;
current_callchain_.entries = &callchain_entries_[1];
- const EventInfo* event = FindEventOfCurrentSample();
- current_event_.name = event->name.c_str();
- current_event_.tracing_data_format = event->tracing_info.data_format;
+ const EventInfo& event = FindEvent(r);
+ current_event_.name = event.name.c_str();
+ current_event_.tracing_data_format = event.tracing_info.data_format;
if (current_event_.tracing_data_format.size > 0u && (r.sample_type & PERF_SAMPLE_RAW)) {
CHECK_GE(r.raw_data.size, current_event_.tracing_data_format.size);
current_tracing_data_ = r.raw_data.data;
} else {
current_tracing_data_ = nullptr;
}
+ return true;
}
-const EventInfo* ReportLib::FindEventOfCurrentSample() {
+const EventInfo& ReportLib::FindEvent(const SampleRecord& r) {
if (events_.empty()) {
CreateEvents();
}
if (trace_offcpu_.mode == TraceOffCpuMode::MIXED_ON_OFF_CPU) {
// To mix on-cpu and off-cpu samples, pretend they are from the same event type.
// Otherwise, some report scripts may split them.
- return &events_[0];
+ return events_[0];
}
- SampleRecord* r = sample_record_queue_.front().get();
- size_t attr_index = record_file_reader_->GetAttrIndexOfRecord(r);
- return &events_[attr_index];
+ size_t attr_index = record_file_reader_->GetAttrIndexOfRecord(&r);
+ return events_[attr_index];
}
void ReportLib::CreateEvents() {
- std::vector<EventAttrWithId> attrs = record_file_reader_->AttrSection();
+ const EventAttrIds& attrs = record_file_reader_->AttrSection();
events_.resize(attrs.size());
for (size_t i = 0; i < attrs.size(); ++i) {
- events_[i].attr = *attrs[i].attr;
+ events_[i].attr = attrs[i].attr;
events_[i].name = GetEventNameByAttr(events_[i].attr);
EventInfo::TracingInfo& tracing_info = events_[i].tracing_info;
+ tracing_info.data_format.size = 0;
+ tracing_info.data_format.field_count = 0;
+ tracing_info.data_format.fields = nullptr;
+
if (events_[i].attr.type == PERF_TYPE_TRACEPOINT && tracing_) {
- TracingFormat format = tracing_->GetTracingFormatHavingId(events_[i].attr.config);
+ std::optional<TracingFormat> opt_format =
+ tracing_->GetTracingFormatHavingId(events_[i].attr.config);
+ if (!opt_format.has_value() || opt_format.value().fields.empty()) {
+ continue;
+ }
+ const TracingFormat& format = opt_format.value();
tracing_info.field_names.resize(format.fields.size());
tracing_info.fields.resize(format.fields.size());
for (size_t i = 0; i < format.fields.size(); ++i) {
@@ -537,18 +567,10 @@ void ReportLib::CreateEvents() {
field.is_signed = format.fields[i].is_signed;
field.is_dynamic = format.fields[i].is_dynamic;
}
- if (tracing_info.fields.empty()) {
- tracing_info.data_format.size = 0;
- } else {
- TracingFieldFormat& field = tracing_info.fields.back();
- tracing_info.data_format.size = field.offset + field.elem_size * field.elem_count;
- }
+ TracingFieldFormat& field = tracing_info.fields.back();
+ tracing_info.data_format.size = field.offset + field.elem_size * field.elem_count;
tracing_info.data_format.field_count = tracing_info.fields.size();
tracing_info.data_format.fields = &tracing_info.fields[0];
- } else {
- tracing_info.data_format.size = 0;
- tracing_info.data_format.field_count = 0;
- tracing_info.data_format.fields = nullptr;
}
}
}
@@ -610,6 +632,7 @@ bool SetRecordFile(ReportLib* report_lib, const char* record_file) EXPORT;
bool SetKallsymsFile(ReportLib* report_lib, const char* kallsyms_file) EXPORT;
void ShowIpForUnknownSymbol(ReportLib* report_lib) EXPORT;
void ShowArtFrames(ReportLib* report_lib, bool show) EXPORT;
+bool RemoveMethod(ReportLib* report_lib, const char* method_name_regex) EXPORT;
void MergeJavaMethods(ReportLib* report_lib, bool merge) EXPORT;
bool AddProguardMappingFile(ReportLib* report_lib, const char* mapping_file) EXPORT;
const char* GetSupportedTraceOffCpuModes(ReportLib* report_lib) EXPORT;
@@ -657,6 +680,10 @@ void ShowArtFrames(ReportLib* report_lib, bool show) {
return report_lib->ShowArtFrames(show);
}
+bool RemoveMethod(ReportLib* report_lib, const char* method_name_regex) {
+ return report_lib->RemoveMethod(method_name_regex);
+}
+
void MergeJavaMethods(ReportLib* report_lib, bool merge) {
return report_lib->MergeJavaMethods(merge);
}
diff --git a/simpleperf/report_utils.cpp b/simpleperf/report_utils.cpp
index ecbc6aa8..5c3ce6f9 100644
--- a/simpleperf/report_utils.cpp
+++ b/simpleperf/report_utils.cpp
@@ -23,6 +23,7 @@
#include <android-base/strings.h>
#include "JITDebugReader.h"
+#include "RegEx.h"
#include "utils.h"
namespace simpleperf {
@@ -189,6 +190,160 @@ static bool IsArtEntry(const CallChainReportEntry& entry, bool* is_jni_trampolin
return false;
};
+CallChainReportModifier::~CallChainReportModifier() {}
+
+// Remove art frames.
+class ArtFrameRemover : public CallChainReportModifier {
+ public:
+ void Modify(std::vector<CallChainReportEntry>& callchain) override {
+ auto it =
+ std::remove_if(callchain.begin(), callchain.end(), [](const CallChainReportEntry& entry) {
+ return entry.execution_type == CallChainExecutionType::ART_METHOD;
+ });
+ callchain.erase(it, callchain.end());
+ }
+};
+
+// Convert JIT methods to their corresponding interpreted Java methods.
+class JITFrameConverter : public CallChainReportModifier {
+ public:
+ JITFrameConverter(const ThreadTree& thread_tree) : thread_tree_(thread_tree) {}
+
+ void Modify(std::vector<CallChainReportEntry>& callchain) override {
+ CollectJavaMethods();
+ for (size_t i = 0; i < callchain.size();) {
+ auto& entry = callchain[i];
+ if (entry.execution_type == CallChainExecutionType::JIT_JVM_METHOD) {
+ // This is a JIT java method, merge it with the interpreted java method having the same
+ // name if possible. Otherwise, merge it with other JIT java methods having the same name
+ // by assigning a common dso_name.
+ if (auto it = java_method_map_.find(std::string(entry.symbol->FunctionName()));
+ it != java_method_map_.end()) {
+ entry.dso = it->second.dso;
+ entry.symbol = it->second.symbol;
+ // Not enough info to map an offset in a JIT method to an offset in a dex file. So just
+ // use the symbol_addr.
+ entry.vaddr_in_file = entry.symbol->addr;
+
+ // ART may call from an interpreted Java method into its corresponding JIT method. To
+ // avoid showing the method calling itself, remove the JIT frame.
+ if (i + 1 < callchain.size() && callchain[i + 1].dso == entry.dso &&
+ callchain[i + 1].symbol == entry.symbol) {
+ callchain.erase(callchain.begin() + i);
+ continue;
+ }
+
+ } else if (!JITDebugReader::IsPathInJITSymFile(entry.dso->Path())) {
+ // Old JITSymFiles use names like "TemporaryFile-XXXXXX". So give them a better name.
+ entry.dso_name = "[JIT cache]";
+ }
+ }
+ i++;
+ }
+ }
+
+ private:
+ struct JavaMethod {
+ Dso* dso;
+ const Symbol* symbol;
+ JavaMethod(Dso* dso, const Symbol* symbol) : dso(dso), symbol(symbol) {}
+ };
+
+ void CollectJavaMethods() {
+ if (!java_method_initialized_) {
+ java_method_initialized_ = true;
+ for (Dso* dso : thread_tree_.GetAllDsos()) {
+ if (dso->type() == DSO_DEX_FILE) {
+ dso->LoadSymbols();
+ for (auto& symbol : dso->GetSymbols()) {
+ java_method_map_.emplace(symbol.Name(), JavaMethod(dso, &symbol));
+ }
+ }
+ }
+ }
+ }
+
+ const ThreadTree& thread_tree_;
+ bool java_method_initialized_ = false;
+ std::unordered_map<std::string, JavaMethod> java_method_map_;
+};
+
+// Use proguard mapping.txt to de-obfuscate minified symbols.
+class JavaMethodDeobfuscater : public CallChainReportModifier {
+ public:
+ JavaMethodDeobfuscater(bool remove_r8_synthesized_frame)
+ : remove_r8_synthesized_frame_(remove_r8_synthesized_frame) {}
+
+ bool AddProguardMappingFile(std::string_view mapping_file) {
+ return retrace_.AddProguardMappingFile(mapping_file);
+ }
+
+ void Modify(std::vector<CallChainReportEntry>& callchain) override {
+ for (size_t i = 0; i < callchain.size();) {
+ auto& entry = callchain[i];
+ if (!IsJavaEntry(entry)) {
+ i++;
+ continue;
+ }
+ std::string_view name = entry.symbol->FunctionName();
+ std::string original_name;
+ bool synthesized;
+ if (retrace_.DeObfuscateJavaMethods(name, &original_name, &synthesized)) {
+ if (synthesized && remove_r8_synthesized_frame_) {
+ callchain.erase(callchain.begin() + i);
+ continue;
+ }
+ entry.symbol->SetDemangledName(original_name);
+ }
+ i++;
+ }
+ }
+
+ private:
+ bool IsJavaEntry(const CallChainReportEntry& entry) {
+ static const char* COMPILED_JAVA_FILE_SUFFIXES[] = {".odex", ".oat", ".dex"};
+ if (entry.execution_type == CallChainExecutionType::JIT_JVM_METHOD ||
+ entry.execution_type == CallChainExecutionType::INTERPRETED_JVM_METHOD) {
+ return true;
+ }
+ if (entry.execution_type == CallChainExecutionType::NATIVE_METHOD) {
+ const std::string& path = entry.dso->Path();
+ for (const char* suffix : COMPILED_JAVA_FILE_SUFFIXES) {
+ if (android::base::EndsWith(path, suffix)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ const bool remove_r8_synthesized_frame_;
+ ProguardMappingRetrace retrace_;
+};
+
+// Use regex to filter method names.
+class MethodNameFilter : public CallChainReportModifier {
+ public:
+ bool RemoveMethod(std::string_view method_name_regex) {
+ if (auto regex = RegEx::Create(method_name_regex); regex != nullptr) {
+ exclude_names_.emplace_back(std::move(regex));
+ return true;
+ }
+ return false;
+ }
+
+ void Modify(std::vector<CallChainReportEntry>& callchain) override {
+ auto it = std::remove_if(callchain.begin(), callchain.end(),
+ [this](const CallChainReportEntry& entry) {
+ return SearchInRegs(entry.symbol->DemangledName(), exclude_names_);
+ });
+ callchain.erase(it, callchain.end());
+ }
+
+ private:
+ std::vector<std::unique_ptr<RegEx>> exclude_names_;
+};
+
CallChainReportBuilder::CallChainReportBuilder(ThreadTree& thread_tree)
: thread_tree_(thread_tree) {
const char* env_name = "REMOVE_R8_SYNTHESIZED_FRAME";
@@ -202,13 +357,39 @@ CallChainReportBuilder::CallChainReportBuilder(ThreadTree& thread_tree)
remove_r8_synthesized_frame_ = true;
}
}
+ SetRemoveArtFrame(true);
+ SetConvertJITFrame(true);
+}
+
+void CallChainReportBuilder::SetRemoveArtFrame(bool enable) {
+ if (enable) {
+ art_frame_remover_.reset(new ArtFrameRemover);
+ } else {
+ art_frame_remover_.reset(nullptr);
+ }
+}
+
+void CallChainReportBuilder::SetConvertJITFrame(bool enable) {
+ if (enable) {
+ jit_frame_converter_.reset(new JITFrameConverter(thread_tree_));
+ } else {
+ jit_frame_converter_.reset(nullptr);
+ }
}
bool CallChainReportBuilder::AddProguardMappingFile(std::string_view mapping_file) {
- if (!retrace_) {
- retrace_.reset(new ProguardMappingRetrace);
+ if (!java_method_deobfuscater_) {
+ java_method_deobfuscater_.reset(new JavaMethodDeobfuscater(remove_r8_synthesized_frame_));
+ }
+ return static_cast<JavaMethodDeobfuscater&>(*java_method_deobfuscater_)
+ .AddProguardMappingFile(mapping_file);
+}
+
+bool CallChainReportBuilder::RemoveMethod(std::string_view method_name_regex) {
+ if (!method_name_filter_) {
+ method_name_filter_.reset(new MethodNameFilter);
}
- return retrace_->AddProguardMappingFile(mapping_file);
+ return static_cast<MethodNameFilter&>(*method_name_filter_).RemoveMethod(method_name_regex);
}
std::vector<CallChainReportEntry> CallChainReportBuilder::Build(const ThreadEntry* thread,
@@ -239,17 +420,17 @@ std::vector<CallChainReportEntry> CallChainReportBuilder::Build(const ThreadEntr
entry.execution_type = execution_type;
}
MarkArtFrame(result);
- if (remove_art_frame_) {
- auto it = std::remove_if(result.begin(), result.end(), [](const CallChainReportEntry& entry) {
- return entry.execution_type == CallChainExecutionType::ART_METHOD;
- });
- result.erase(it, result.end());
+ if (art_frame_remover_) {
+ art_frame_remover_->Modify(result);
+ }
+ if (jit_frame_converter_) {
+ jit_frame_converter_->Modify(result);
}
- if (convert_jit_frame_) {
- ConvertJITFrame(result);
+ if (java_method_deobfuscater_) {
+ java_method_deobfuscater_->Modify(result);
}
- if (retrace_) {
- DeObfuscateJavaMethods(result);
+ if (method_name_filter_) {
+ method_name_filter_->Modify(result);
}
return result;
}
@@ -292,91 +473,6 @@ void CallChainReportBuilder::MarkArtFrame(std::vector<CallChainReportEntry>& cal
}
}
-void CallChainReportBuilder::ConvertJITFrame(std::vector<CallChainReportEntry>& callchain) {
- CollectJavaMethods();
- for (size_t i = 0; i < callchain.size();) {
- auto& entry = callchain[i];
- if (entry.execution_type == CallChainExecutionType::JIT_JVM_METHOD) {
- // This is a JIT java method, merge it with the interpreted java method having the same
- // name if possible. Otherwise, merge it with other JIT java methods having the same name
- // by assigning a common dso_name.
- if (auto it = java_method_map_.find(std::string(entry.symbol->FunctionName()));
- it != java_method_map_.end()) {
- entry.dso = it->second.dso;
- entry.symbol = it->second.symbol;
- // Not enough info to map an offset in a JIT method to an offset in a dex file. So just
- // use the symbol_addr.
- entry.vaddr_in_file = entry.symbol->addr;
-
- // ART may call from an interpreted Java method into its corresponding JIT method. To
- // avoid showing the method calling itself, remove the JIT frame.
- if (i + 1 < callchain.size() && callchain[i + 1].dso == entry.dso &&
- callchain[i + 1].symbol == entry.symbol) {
- callchain.erase(callchain.begin() + i);
- continue;
- }
-
- } else if (!JITDebugReader::IsPathInJITSymFile(entry.dso->Path())) {
- // Old JITSymFiles use names like "TemporaryFile-XXXXXX". So give them a better name.
- entry.dso_name = "[JIT cache]";
- }
- }
- i++;
- }
-}
-
-void CallChainReportBuilder::CollectJavaMethods() {
- if (!java_method_initialized_) {
- java_method_initialized_ = true;
- for (Dso* dso : thread_tree_.GetAllDsos()) {
- if (dso->type() == DSO_DEX_FILE) {
- dso->LoadSymbols();
- for (auto& symbol : dso->GetSymbols()) {
- java_method_map_.emplace(symbol.Name(), JavaMethod(dso, &symbol));
- }
- }
- }
- }
-}
-
-static bool IsJavaEntry(const CallChainReportEntry& entry) {
- static const char* COMPILED_JAVA_FILE_SUFFIXES[] = {".odex", ".oat", ".dex"};
- if (entry.execution_type == CallChainExecutionType::JIT_JVM_METHOD ||
- entry.execution_type == CallChainExecutionType::INTERPRETED_JVM_METHOD) {
- return true;
- }
- if (entry.execution_type == CallChainExecutionType::NATIVE_METHOD) {
- const std::string& path = entry.dso->Path();
- for (const char* suffix : COMPILED_JAVA_FILE_SUFFIXES) {
- if (android::base::EndsWith(path, suffix)) {
- return true;
- }
- }
- }
- return false;
-}
-
-void CallChainReportBuilder::DeObfuscateJavaMethods(std::vector<CallChainReportEntry>& callchain) {
- for (size_t i = 0; i < callchain.size();) {
- auto& entry = callchain[i];
- if (!IsJavaEntry(entry)) {
- i++;
- continue;
- }
- std::string_view name = entry.symbol->FunctionName();
- std::string original_name;
- bool synthesized;
- if (retrace_->DeObfuscateJavaMethods(name, &original_name, &synthesized)) {
- if (synthesized && remove_r8_synthesized_frame_) {
- callchain.erase(callchain.begin() + i);
- continue;
- }
- entry.symbol->SetDemangledName(original_name);
- }
- i++;
- }
-}
-
bool ThreadReportBuilder::AggregateThreads(const std::vector<std::string>& thread_name_regex) {
size_t i = thread_regs_.size();
thread_regs_.resize(i + thread_name_regex.size());
diff --git a/simpleperf/report_utils.h b/simpleperf/report_utils.h
index fa42bba6..631e5fad 100644
--- a/simpleperf/report_utils.h
+++ b/simpleperf/report_utils.h
@@ -91,39 +91,39 @@ struct CallChainReportEntry {
CallChainExecutionType execution_type = CallChainExecutionType::NATIVE_METHOD;
};
+// a base class for modifying callchain reports
+class CallChainReportModifier {
+ public:
+ virtual ~CallChainReportModifier();
+
+ virtual void Modify(std::vector<CallChainReportEntry>& callchain) = 0;
+};
+
class CallChainReportBuilder {
public:
CallChainReportBuilder(ThreadTree& thread_tree);
// If true, remove interpreter frames both before and after a Java frame.
// Default is true.
- void SetRemoveArtFrame(bool enable) { remove_art_frame_ = enable; }
+ void SetRemoveArtFrame(bool enable);
// If true, convert a JIT method into its corresponding interpreted Java method. So they can be
// merged in reports like flamegraph. Default is true.
- void SetConvertJITFrame(bool enable) { convert_jit_frame_ = enable; }
+ void SetConvertJITFrame(bool enable);
// Add proguard mapping.txt to de-obfuscate minified symbols.
bool AddProguardMappingFile(std::string_view mapping_file);
+ // Remove methods with name containing the given regular expression.
+ bool RemoveMethod(std::string_view method_name_regex);
std::vector<CallChainReportEntry> Build(const ThreadEntry* thread,
const std::vector<uint64_t>& ips, size_t kernel_ip_count);
private:
- struct JavaMethod {
- Dso* dso;
- const Symbol* symbol;
- JavaMethod(Dso* dso, const Symbol* symbol) : dso(dso), symbol(symbol) {}
- };
-
void MarkArtFrame(std::vector<CallChainReportEntry>& callchain);
- void ConvertJITFrame(std::vector<CallChainReportEntry>& callchain);
- void CollectJavaMethods();
- void DeObfuscateJavaMethods(std::vector<CallChainReportEntry>& callchain);
ThreadTree& thread_tree_;
- bool remove_art_frame_ = true;
bool remove_r8_synthesized_frame_ = false;
- bool convert_jit_frame_ = true;
- bool java_method_initialized_ = false;
- std::unordered_map<std::string, JavaMethod> java_method_map_;
- std::unique_ptr<ProguardMappingRetrace> retrace_;
+ std::unique_ptr<CallChainReportModifier> art_frame_remover_;
+ std::unique_ptr<CallChainReportModifier> jit_frame_converter_;
+ std::unique_ptr<CallChainReportModifier> java_method_deobfuscater_;
+ std::unique_ptr<CallChainReportModifier> method_name_filter_;
};
struct ThreadReport {
diff --git a/simpleperf/report_utils_test.cpp b/simpleperf/report_utils_test.cpp
index 55771218..0d96d7d1 100644
--- a/simpleperf/report_utils_test.cpp
+++ b/simpleperf/report_utils_test.cpp
@@ -27,6 +27,7 @@
using namespace simpleperf;
+// @CddTest = 6.1/C-0-2
TEST(ProguardMappingRetrace, smoke) {
TemporaryFile tmpfile;
close(tmpfile.release());
@@ -151,6 +152,7 @@ class CallChainReportBuilderTest : public testing::Test {
};
};
+// @CddTest = 6.1/C-0-2
TEST_F(CallChainReportBuilderTest, default_option) {
// Test default option: remove_art_frame = true, convert_jit_frame = true.
// The callchain shouldn't include interpreter frames. And the JIT frame is
@@ -170,6 +172,7 @@ TEST_F(CallChainReportBuilderTest, default_option) {
ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
}
+// @CddTest = 6.1/C-0-2
TEST_F(CallChainReportBuilderTest, not_convert_jit_frame) {
// Test option: remove_art_frame = true, convert_jit_frame = false.
// The callchain shouldn't include interpreter frames. And the JIT frame isn't
@@ -190,6 +193,7 @@ TEST_F(CallChainReportBuilderTest, not_convert_jit_frame) {
ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
}
+// @CddTest = 6.1/C-0-2
TEST_F(CallChainReportBuilderTest, not_remove_art_frame) {
// Test option: remove_art_frame = false, convert_jit_frame = true.
// The callchain should include interpreter frames. And the JIT frame is
@@ -222,6 +226,7 @@ TEST_F(CallChainReportBuilderTest, not_remove_art_frame) {
ASSERT_EQ(entries[5].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
}
+// @CddTest = 6.1/C-0-2
TEST_F(CallChainReportBuilderTest, remove_jit_frame_called_by_dex_frame) {
// Test option: remove_art_frame = true, convert_jit_frame = true.
// The callchain should remove the JIT frame called by a dex frame having the same symbol name.
@@ -242,6 +247,7 @@ TEST_F(CallChainReportBuilderTest, remove_jit_frame_called_by_dex_frame) {
ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
}
+// @CddTest = 6.1/C-0-2
TEST_F(CallChainReportBuilderTest, remove_art_frame_only_near_jvm_method) {
// Test option: remove_art_frame = true, convert_jit_frame = true.
// The callchain should not remove ART symbols not near a JVM method.
@@ -277,6 +283,7 @@ TEST_F(CallChainReportBuilderTest, remove_art_frame_only_near_jvm_method) {
ASSERT_EQ(entries[2].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
}
+// @CddTest = 6.1/C-0-2
TEST_F(CallChainReportBuilderTest, keep_art_jni_method) {
// Test option: remove_art_frame = true.
// The callchain should remove art_jni_trampoline, but keep jni methods.
@@ -305,6 +312,7 @@ TEST_F(CallChainReportBuilderTest, keep_art_jni_method) {
ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
}
+// @CddTest = 6.1/C-0-2
TEST_F(CallChainReportBuilderTest, add_proguard_mapping_file) {
std::vector<uint64_t> fake_ips = {
0x2200, // 2200, // obfuscated_class.obfuscated_java_method
@@ -364,6 +372,7 @@ TEST_F(CallChainReportBuilderTest, add_proguard_mapping_file) {
ASSERT_EQ(entries[2].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
}
+// @CddTest = 6.1/C-0-2
TEST_F(CallChainReportBuilderTest, not_remove_synthesized_frame_by_default) {
std::vector<uint64_t> fake_ips = {
0x2200, // 2200, // obfuscated_class.obfuscated_java_method
@@ -400,6 +409,7 @@ TEST_F(CallChainReportBuilderTest, not_remove_synthesized_frame_by_default) {
ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
}
+// @CddTest = 6.1/C-0-2
TEST_F(CallChainReportBuilderTest, remove_synthesized_frame_with_env_variable) {
// Windows doesn't support setenv and unsetenv. So don't test on it.
#if !defined(__WIN32)
@@ -435,6 +445,7 @@ TEST_F(CallChainReportBuilderTest, remove_synthesized_frame_with_env_variable) {
#endif // !defined(__WIN32)
}
+// @CddTest = 6.1/C-0-2
TEST_F(CallChainReportBuilderTest, add_proguard_mapping_file_for_jit_method_with_signature) {
std::vector<uint64_t> fake_ips = {
0x3200, // 3200, // void ctep.v(cteo, ctgc, ctbn)
@@ -460,6 +471,7 @@ TEST_F(CallChainReportBuilderTest, add_proguard_mapping_file_for_jit_method_with
ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
}
+// @CddTest = 6.1/C-0-2
TEST_F(CallChainReportBuilderTest,
add_proguard_mapping_file_for_compiled_java_method_with_signature) {
TemporaryFile tmpfile;
@@ -492,6 +504,7 @@ TEST_F(CallChainReportBuilderTest,
}
}
+// @CddTest = 6.1/C-0-2
TEST_F(CallChainReportBuilderTest, convert_jit_frame_for_jit_method_with_signature) {
std::vector<uint64_t> fake_ips = {
0x2200, // 2200, // ctep.v
@@ -540,6 +553,39 @@ TEST_F(CallChainReportBuilderTest, convert_jit_frame_for_jit_method_with_signatu
ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
}
+// @CddTest = 6.1/C-0-2
+TEST_F(CallChainReportBuilderTest, remove_method_name) {
+ // Test excluding method names.
+ CallChainReportBuilder builder(thread_tree);
+ builder.SetRemoveArtFrame(false);
+ builder.RemoveMethod("art_");
+ std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
+ ASSERT_EQ(entries.size(), 2);
+ ASSERT_EQ(entries[0].ip, 0x2000);
+ ASSERT_STREQ(entries[0].symbol->Name(), "java_method1");
+ ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
+ ASSERT_EQ(entries[0].vaddr_in_file, 0);
+ ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
+ ASSERT_EQ(entries[1].ip, 0x3000);
+ ASSERT_STREQ(entries[1].symbol->Name(), "java_method2");
+ ASSERT_EQ(entries[1].dso->Path(), fake_dex_file_path);
+ ASSERT_EQ(entries[1].vaddr_in_file, 0x100);
+ ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
+
+ builder.RemoveMethod("java_method2");
+ entries = builder.Build(thread, fake_ips, 0);
+ ASSERT_EQ(entries.size(), 1);
+ ASSERT_EQ(entries[0].ip, 0x2000);
+ ASSERT_STREQ(entries[0].symbol->Name(), "java_method1");
+ ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
+ ASSERT_EQ(entries[0].vaddr_in_file, 0);
+ ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
+
+ builder.RemoveMethod("java_method1");
+ entries = builder.Build(thread, fake_ips, 0);
+ ASSERT_EQ(entries.size(), 0);
+}
+
class ThreadReportBuilderTest : public testing::Test {
protected:
virtual void SetUp() {
@@ -556,6 +602,7 @@ class ThreadReportBuilderTest : public testing::Test {
ThreadTree thread_tree;
};
+// @CddTest = 6.1/C-0-2
TEST_F(ThreadReportBuilderTest, no_setting) {
ThreadReportBuilder builder;
ThreadEntry* thread = thread_tree.FindThread(1);
@@ -563,6 +610,7 @@ TEST_F(ThreadReportBuilderTest, no_setting) {
ASSERT_TRUE(IsReportEqual(report, ThreadReport(1, 1, "thread1")));
}
+// @CddTest = 6.1/C-0-2
TEST_F(ThreadReportBuilderTest, aggregate_threads) {
ThreadReportBuilder builder;
ASSERT_TRUE(builder.AggregateThreads({"thread-pool.*"}));
@@ -577,6 +625,7 @@ TEST_F(ThreadReportBuilderTest, aggregate_threads) {
ASSERT_TRUE(IsReportEqual(report, ThreadReport(1, 2, "thread-pool.*")));
}
+// @CddTest = 6.1/C-0-2
TEST_F(ThreadReportBuilderTest, aggregate_threads_bad_regex) {
ThreadReportBuilder builder;
ASSERT_FALSE(builder.AggregateThreads({"?thread-pool*"}));
diff --git a/simpleperf/runtest/Android.bp b/simpleperf/runtest/Android.bp
index 53fdfefe..62a0aeeb 100644
--- a/simpleperf/runtest/Android.bp
+++ b/simpleperf/runtest/Android.bp
@@ -103,3 +103,9 @@ cc_binary {
srcs: ["etm_test_loop.cpp"],
// afdo: true,
}
+
+cc_binary {
+ name: "etm_test_loop_small",
+ srcs: ["etm_test_loop.cpp"],
+ ldflags: ["-Wl,-z,noseparate-code"],
+}
diff --git a/simpleperf/rust/lib.rs b/simpleperf/rust/lib.rs
index 7d39273e..c5fe5dac 100644
--- a/simpleperf/rust/lib.rs
+++ b/simpleperf/rust/lib.rs
@@ -14,12 +14,11 @@
// limitations under the License.
//
-//! This module implements safe wrappers for simpleperf etm operations required
+//! This module implements safe wrappers for simpleperf operations required
//! by profcollect.
-use std::ffi::CString;
+use std::ffi::{c_char, CString};
use std::path::Path;
-use std::time::Duration;
fn path_to_cstr(path: &Path) -> CString {
CString::new(path.to_str().unwrap()).unwrap()
@@ -27,52 +26,55 @@ fn path_to_cstr(path: &Path) -> CString {
/// Returns whether the system has etm driver. ETM driver should be available immediately
/// after boot.
-pub fn has_driver_support() -> bool {
- unsafe { simpleperf_profcollect_bindgen::HasDriverSupport() }
+pub fn is_etm_driver_available() -> bool {
+ // SAFETY: This is always safe to call.
+ unsafe { simpleperf_profcollect_bindgen::IsETMDriverAvailable() }
}
/// Returns whether the system has etm device. ETM device may not be available immediately
/// after boot.
-pub fn has_device_support() -> bool {
- unsafe { simpleperf_profcollect_bindgen::HasDeviceSupport() }
+pub fn is_etm_device_available() -> bool {
+ // SAFETY: This is always safe to call.
+ unsafe { simpleperf_profcollect_bindgen::IsETMDeviceAvailable() }
}
-/// ETM recording scope
-pub enum RecordScope {
- /// Record etm data only for userspace.
- USERSPACE,
- /// Record etm data only for kernel.
- KERNEL,
- /// Record etm data for both userspace and kernel.
- BOTH,
+/// Returns whether the system support LBR recording.
+pub fn is_lbr_available() -> bool {
+ // SAFETY: This is always safe to call.
+ unsafe { simpleperf_profcollect_bindgen::IsLBRAvailable() }
}
-/// Trigger an ETM trace event.
-pub fn record(trace_file: &Path, duration: &Duration, scope: RecordScope) {
- let event_name: CString = match scope {
- RecordScope::USERSPACE => CString::new("cs-etm:u").unwrap(),
- RecordScope::KERNEL => CString::new("cs-etm:k").unwrap(),
- RecordScope::BOTH => CString::new("cs-etm").unwrap(),
- };
- let trace_file = path_to_cstr(trace_file);
- let duration = duration.as_secs_f32();
+/// Run the record command to record ETM/LBR data.
+pub fn run_record_cmd(args: &[&str]) -> bool {
+ let c_args: Vec<CString> = args.iter().map(|s| CString::new(s.as_bytes()).unwrap()).collect();
+ let mut pointer_args: Vec<*const c_char> = c_args.iter().map(|s| s.as_ptr()).collect();
+ let arg_count: i32 = pointer_args.len().try_into().unwrap();
+ // SAFETY: pointer_args is an array of valid C strings. Its length is defined by arg_count.
+ unsafe { simpleperf_profcollect_bindgen::RunRecordCmd(pointer_args.as_mut_ptr(), arg_count) }
+}
+
+/// Run the inject command to process ETM/LBR data.
+pub fn run_inject_cmd(args: &[&str]) -> bool {
+ let c_args: Vec<CString> = args.iter().map(|s| CString::new(s.as_bytes()).unwrap()).collect();
+ let mut pointer_args: Vec<*const c_char> = c_args.iter().map(|s| s.as_ptr()).collect();
+ let arg_count: i32 = pointer_args.len().try_into().unwrap();
+ // SAFETY: pointer_args is an array of valid C strings. Its length is defined by arg_count.
+ unsafe { simpleperf_profcollect_bindgen::RunInjectCmd(pointer_args.as_mut_ptr(), arg_count) }
+}
+/// Save logs in file.
+pub fn set_log_file(filename: &Path) {
+ let log_file = path_to_cstr(filename);
+ // SAFETY: The pointer is a valid C string, and isn't retained after the function call returns.
unsafe {
- simpleperf_profcollect_bindgen::Record(event_name.as_ptr(), trace_file.as_ptr(), duration);
+ simpleperf_profcollect_bindgen::SetLogFile(log_file.as_ptr());
}
}
-/// Translate ETM trace to profile.
-pub fn process(trace_path: &Path, profile_path: &Path, binary_filter: &str) {
- let trace_path = path_to_cstr(trace_path);
- let profile_path = path_to_cstr(profile_path);
- let binary_filter = CString::new(binary_filter).unwrap();
-
+/// Stop using log file.
+pub fn reset_log_file() {
+ // SAFETY: This is always safe to call.
unsafe {
- simpleperf_profcollect_bindgen::Inject(
- trace_path.as_ptr(),
- profile_path.as_ptr(),
- binary_filter.as_ptr(),
- );
+ simpleperf_profcollect_bindgen::ResetLogFile();
}
}
diff --git a/simpleperf/sample_tree_test.cpp b/simpleperf/sample_tree_test.cpp
index bee187d7..e2df1cec 100644
--- a/simpleperf/sample_tree_test.cpp
+++ b/simpleperf/sample_tree_test.cpp
@@ -132,6 +132,7 @@ class SampleTreeTest : public testing::Test {
std::unique_ptr<TestSampleTreeBuilder> sample_tree_builder;
};
+// @CddTest = 6.1/C-0-2
TEST_F(SampleTreeTest, ip_in_map) {
sample_tree_builder->AddSample(1, 1, 1, false);
sample_tree_builder->AddSample(1, 1, 2, false);
@@ -142,6 +143,7 @@ TEST_F(SampleTreeTest, ip_in_map) {
CheckSamples(expected_samples);
}
+// @CddTest = 6.1/C-0-2
TEST_F(SampleTreeTest, different_pid) {
sample_tree_builder->AddSample(1, 1, 1, false);
sample_tree_builder->AddSample(2, 2, 1, false);
@@ -152,6 +154,7 @@ TEST_F(SampleTreeTest, different_pid) {
CheckSamples(expected_samples);
}
+// @CddTest = 6.1/C-0-2
TEST_F(SampleTreeTest, different_tid) {
sample_tree_builder->AddSample(1, 1, 1, false);
sample_tree_builder->AddSample(1, 11, 1, false);
@@ -162,6 +165,7 @@ TEST_F(SampleTreeTest, different_tid) {
CheckSamples(expected_samples);
}
+// @CddTest = 6.1/C-0-2
TEST_F(SampleTreeTest, different_comm) {
sample_tree_builder->AddSample(1, 1, 1, false);
thread_tree.SetThreadName(1, 1, "p1t1_comm2");
@@ -173,6 +177,7 @@ TEST_F(SampleTreeTest, different_comm) {
CheckSamples(expected_samples);
}
+// @CddTest = 6.1/C-0-2
TEST_F(SampleTreeTest, different_map) {
sample_tree_builder->AddSample(1, 1, 1, false);
sample_tree_builder->AddSample(1, 1, 6, false);
@@ -183,6 +188,7 @@ TEST_F(SampleTreeTest, different_map) {
CheckSamples(expected_samples);
}
+// @CddTest = 6.1/C-0-2
TEST_F(SampleTreeTest, unmapped_sample) {
sample_tree_builder->AddSample(1, 1, 0, false);
sample_tree_builder->AddSample(1, 1, 31, false);
@@ -194,6 +200,7 @@ TEST_F(SampleTreeTest, unmapped_sample) {
CheckSamples(expected_samples);
}
+// @CddTest = 6.1/C-0-2
TEST_F(SampleTreeTest, map_kernel) {
sample_tree_builder->AddSample(1, 1, 10, true);
sample_tree_builder->AddSample(1, 1, 10, false);
@@ -204,6 +211,7 @@ TEST_F(SampleTreeTest, map_kernel) {
CheckSamples(expected_samples);
}
+// @CddTest = 6.1/C-0-2
TEST(sample_tree, overlapped_map) {
ThreadTree thread_tree;
TestSampleTreeBuilder sample_tree_builder(&thread_tree);
@@ -226,6 +234,7 @@ TEST(sample_tree, overlapped_map) {
CheckSamples(sample_tree_builder.GetSamples(), expected_samples);
}
+// @CddTest = 6.1/C-0-2
TEST(thread_tree, symbol_ULLONG_MAX) {
ThreadTree thread_tree;
thread_tree.ShowIpForUnknownSymbol();
diff --git a/simpleperf/scripts/Android.bp b/simpleperf/scripts/Android.bp
index abbbe249..df94b8e0 100644
--- a/simpleperf/scripts/Android.bp
+++ b/simpleperf/scripts/Android.bp
@@ -26,6 +26,7 @@ package {
python_library_host {
name: "simpleperf_report_lib",
srcs: [
+ "report_sample_pb2.py",
"simpleperf_report_lib.py",
"simpleperf_utils.py",
],
diff --git a/simpleperf/scripts/annotate.py b/simpleperf/scripts/annotate.py
index 7bf8fe92..b349fce4 100755
--- a/simpleperf/scripts/annotate.py
+++ b/simpleperf/scripts/annotate.py
@@ -25,7 +25,7 @@ import shutil
from texttable import Texttable
from typing import Dict, Union
-from simpleperf_report_lib import ReportLib
+from simpleperf_report_lib import GetReportLib
from simpleperf_utils import (
Addr2Nearestline, BaseArgumentParser, BinaryFinder, extant_dir, flatten_arg_list, is_windows,
log_exit, ReadElf, SourceFileSearcher)
@@ -186,8 +186,7 @@ class SourceFileAnnotator(object):
source file:line.
"""
for perf_data in self.config['perf_data_list']:
- lib = ReportLib()
- lib.SetRecordFile(perf_data)
+ lib = GetReportLib(perf_data)
if self.symfs_dir:
lib.SetSymfs(self.symfs_dir)
if self.kallsyms:
@@ -224,8 +223,7 @@ class SourceFileAnnotator(object):
binaries, source files, functions, lines.
"""
for perf_data in self.config['perf_data_list']:
- lib = ReportLib()
- lib.SetRecordFile(perf_data)
+ lib = GetReportLib(perf_data)
if self.symfs_dir:
lib.SetSymfs(self.symfs_dir)
if self.kallsyms:
diff --git a/simpleperf/scripts/app_profiler.py b/simpleperf/scripts/app_profiler.py
index 808e3e9c..7b42de22 100755
--- a/simpleperf/scripts/app_profiler.py
+++ b/simpleperf/scripts/app_profiler.py
@@ -28,6 +28,7 @@ import re
import subprocess
import sys
import time
+from typing import Optional
from simpleperf_utils import (
AdbHelper, BaseArgumentParser, bytes_to_str, extant_dir, get_script_dir, get_target_binary_path,
@@ -194,6 +195,8 @@ class ProfilerBase(object):
def __init__(self, args):
self.args = args
self.adb = AdbHelper(enable_switch_to_root=not args.disable_adb_root)
+ if not self.adb.is_device_available():
+ log_exit('No Android device is connected via ADB.')
self.is_root_device = self.adb.switch_to_root()
self.android_version = self.adb.get_android_version()
if self.android_version < 7:
@@ -233,7 +236,7 @@ class ProfilerBase(object):
raise NotImplementedError
def start_profiling(self, target_args):
- """Start simpleperf reocrd process on device."""
+ """Start simpleperf record process on device."""
args = ['/data/local/tmp/simpleperf', 'record', '-o', '/data/local/tmp/perf.data',
self.args.record_options]
if self.adb.run(['shell', 'ls', NATIVE_LIBS_DIR_ON_DEVICE]):
@@ -293,9 +296,23 @@ class AppProfiler(ProfilerBase):
def prepare(self):
super(AppProfiler, self).prepare()
+ self.app_versioncode = self.get_app_versioncode()
if self.args.compile_java_code:
self.compile_java_code()
+ def get_app_versioncode(self) -> Optional[str]:
+ result, output = self.adb.run_and_return_output(
+ ['shell', 'pm', 'list', 'packages', '--show-versioncode'])
+ if not result:
+ return None
+ prefix = f'package:{self.args.app} '
+ for line in output.splitlines():
+ if line.startswith(prefix):
+ pos = line.find('versionCode:')
+ if pos != -1:
+ return line[pos + len('versionCode:'):].strip()
+ return None
+
def compile_java_code(self):
self.kill_app_process()
# Fully compile Java code on Android >= N.
@@ -335,10 +352,10 @@ class AppProfiler(ProfilerBase):
result, ps_output = self.adb.run_and_return_output(
['shell', 'ps', '-p', pid, '-o', 'USER'])
if not result:
- return None
+ return None
uid = SHELL_PS_UID_PATTERN.search(ps_output).group(1)
if uid == current_user.strip():
- return int(pid)
+ return int(pid)
return None
def run_in_app_dir(self, args):
@@ -349,15 +366,26 @@ class AppProfiler(ProfilerBase):
return self.adb.run_and_return_output(adb_args)
def start(self):
- if self.args.activity or self.args.test:
+ if self.args.launch or self.args.activity or self.args.test:
self.kill_app_process()
- self.start_profiling(['--app', self.args.app])
+ args = ['--app', self.args.app]
+ if self.app_versioncode:
+ args += ['--add-meta-info', f'app_versioncode={self.app_versioncode}']
+ self.start_profiling(args)
+ if self.args.launch:
+ self.start_app()
if self.args.activity:
self.start_activity()
elif self.args.test:
self.start_test()
# else: no need to start an activity or test.
+ def start_app(self):
+ result = self.adb.run(['shell', 'monkey', '-p', self.args.app, '1'])
+ if not result:
+ self.record_subproc.terminate()
+ log_exit(f"Can't start {self.args.app}")
+
def start_activity(self):
activity = self.args.app + '/' + self.args.activity
result = self.adb.run(['shell', 'am', 'start', '-n', activity])
@@ -447,6 +475,9 @@ def main():
wrap.sh in the apk to use the native instructions.""")
app_start_group = app_target_group.add_mutually_exclusive_group()
+ app_start_group.add_argument('--launch', action='store_true', help="""Used with -p. Profile the
+ launch time of an Android app. The app will be started or
+ restarted.""")
app_start_group.add_argument('-a', '--activity', help="""Used with -p. Profile the launch time
of an activity in an Android app. The app will be started or
restarted to run the activity. Like `-a .MainActivity`.""")
diff --git a/simpleperf/scripts/bin/android/arm/simpleperf b/simpleperf/scripts/bin/android/arm/simpleperf
index 9b35b2a2..5a0446eb 100755
--- a/simpleperf/scripts/bin/android/arm/simpleperf
+++ b/simpleperf/scripts/bin/android/arm/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/android/arm64/simpleperf b/simpleperf/scripts/bin/android/arm64/simpleperf
index b3ff0b4d..503e96c0 100755
--- a/simpleperf/scripts/bin/android/arm64/simpleperf
+++ b/simpleperf/scripts/bin/android/arm64/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/android/riscv64/simpleperf b/simpleperf/scripts/bin/android/riscv64/simpleperf
new file mode 100755
index 00000000..d7bedd66
--- /dev/null
+++ b/simpleperf/scripts/bin/android/riscv64/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/android/x86/simpleperf b/simpleperf/scripts/bin/android/x86/simpleperf
index de9efc16..fe29203d 100755
--- a/simpleperf/scripts/bin/android/x86/simpleperf
+++ b/simpleperf/scripts/bin/android/x86/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/android/x86_64/simpleperf b/simpleperf/scripts/bin/android/x86_64/simpleperf
index 16416c0f..00966d57 100755
--- a/simpleperf/scripts/bin/android/x86_64/simpleperf
+++ b/simpleperf/scripts/bin/android/x86_64/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylib b/simpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylib
index da84ab3f..5af23eb1 100755
--- a/simpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylib
+++ b/simpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylib
Binary files differ
diff --git a/simpleperf/scripts/bin/darwin/x86_64/simpleperf b/simpleperf/scripts/bin/darwin/x86_64/simpleperf
index fffdec9f..2650b7cf 100755
--- a/simpleperf/scripts/bin/darwin/x86_64/simpleperf
+++ b/simpleperf/scripts/bin/darwin/x86_64/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.so b/simpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.so
index ab4066b3..b0c15fa5 100755
--- a/simpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.so
+++ b/simpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.so
Binary files differ
diff --git a/simpleperf/scripts/bin/linux/x86_64/simpleperf b/simpleperf/scripts/bin/linux/x86_64/simpleperf
index 587d1e87..0083999b 100755
--- a/simpleperf/scripts/bin/linux/x86_64/simpleperf
+++ b/simpleperf/scripts/bin/linux/x86_64/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dll b/simpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dll
deleted file mode 100755
index 099ddfb2..00000000
--- a/simpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dll
+++ /dev/null
Binary files differ
diff --git a/simpleperf/scripts/bin/windows/x86_64/libwinpthread-1.dll b/simpleperf/scripts/bin/windows/x86_64/libwinpthread-1.dll
deleted file mode 100755
index 221fac1a..00000000
--- a/simpleperf/scripts/bin/windows/x86_64/libwinpthread-1.dll
+++ /dev/null
Binary files differ
diff --git a/simpleperf/scripts/bin/windows/x86_64/simpleperf.exe b/simpleperf/scripts/bin/windows/x86_64/simpleperf.exe
deleted file mode 100755
index 3756745e..00000000
--- a/simpleperf/scripts/bin/windows/x86_64/simpleperf.exe
+++ /dev/null
Binary files differ
diff --git a/simpleperf/scripts/binary_cache_builder.py b/simpleperf/scripts/binary_cache_builder.py
index 1b09e7ad..31f03e67 100755
--- a/simpleperf/scripts/binary_cache_builder.py
+++ b/simpleperf/scripts/binary_cache_builder.py
@@ -48,8 +48,7 @@ class BinaryCache:
if build_id:
filename = device_path.split('/')[-1]
# Add build id to make the filename unique.
- unique_filename = build_id[2:] + '-' + filename
- return self.binary_dir / unique_filename
+ return self.binary_dir / build_id[2:] / filename
# For elf file without build id, we can only follow its path on device. Otherwise,
# simpleperf can't find it. However, we don't prefer this way. Because:
@@ -110,13 +109,18 @@ class BinarySourceFromDevice(BinarySource):
else:
logging.info('pull file to binary_cache: %s to %s', path, binary_cache_file)
target_dir = binary_cache_file.parent
- if not target_dir.is_dir():
- os.makedirs(target_dir)
- if binary_cache_file.is_file():
- binary_cache_file.unlink()
- self.pull_file_from_device(path, binary_cache_file)
-
- def pull_file_from_device(self, device_path: str, host_path: Path):
+ try:
+ os.makedirs(target_dir, exist_ok=True)
+ if binary_cache_file.is_file():
+ binary_cache_file.unlink()
+ success = self.pull_file_from_device(path, binary_cache_file)
+ except FileNotFoundError:
+ # It happens on windows when the filename or extension is too long.
+ success = False
+ if not success:
+ logging.warning('failed to pull %s from device', path)
+
+ def pull_file_from_device(self, device_path: str, host_path: Path) -> bool:
if self.adb.run(['pull', device_path, str(host_path)]):
return True
# On non-root devices, we can't pull /data/app/XXX/base.odex directly.
@@ -126,7 +130,6 @@ class BinarySourceFromDevice(BinarySource):
self.adb.run(['pull', '/data/local/tmp/' + filename, host_path])):
self.adb.run(['shell', 'rm', '/data/local/tmp/' + filename])
return True
- logging.warning('failed to pull %s from device', device_path)
return False
def pull_kernel_symbols(self, file_path: Path):
@@ -255,6 +258,7 @@ class BinaryCacheBuilder:
self.binaries = {}
def build_binary_cache(self, perf_data_path: str, symfs_dirs: List[Union[Path, str]]) -> bool:
+ self.binary_cache_dir.mkdir(exist_ok=True)
self.collect_used_binaries(perf_data_path)
if not self.copy_binaries_from_symfs_dirs(symfs_dirs):
return False
diff --git a/simpleperf/scripts/gecko_profile_generator.py b/simpleperf/scripts/gecko_profile_generator.py
index 9fe9ad3d..980012c0 100755
--- a/simpleperf/scripts/gecko_profile_generator.py
+++ b/simpleperf/scripts/gecko_profile_generator.py
@@ -32,7 +32,7 @@ import logging
import sys
from typing import List, Dict, Optional, NamedTuple, Tuple
-from simpleperf_report_lib import ReportLib
+from simpleperf_report_lib import GetReportLib
from simpleperf_utils import BaseArgumentParser, ReportLibOptions
@@ -114,6 +114,13 @@ CATEGORIES = [
"color": 'green',
"subcategories": ['Other']
},
+ {
+ "name": 'Off-CPU',
+ # Follow Brendan Gregg's Flamegraph convention: blue for off-CPU
+ # https://github.com/brendangregg/FlameGraph/blob/810687f180f3c4929b5d965f54817a5218c9d89b/flamegraph.pl#L470
+ "color": 'blue',
+ "subcategories": ['Other']
+ },
# Not used by this exporter yet, but some Firefox Profiler code assumes
# there is an 'Other' category by searching for a category with
# color=grey, so include this.
@@ -122,6 +129,13 @@ CATEGORIES = [
"color": 'grey',
"subcategories": ['Other']
},
+ {
+ "name": 'JIT',
+ # Follow Brendan Gregg's Flamegraph convention: green for Java/JIT
+ # https://github.com/brendangregg/FlameGraph/blob/810687f180f3c4929b5d965f54817a5218c9d89b/flamegraph.pl#L411
+ "color": 'green',
+ "subcategories": ['Other']
+ },
]
@@ -197,12 +211,23 @@ class Thread:
# Heuristic: kernel code contains "kallsyms" as the library name.
if "kallsyms" in frame_str or ".ko" in frame_str:
category = 1
+ # Heuristic: empirically, off-CPU profiles mostly measure off-CPU
+ # time accounted to the linux kernel __schedule function, which
+ # handles blocking. This only works if we have kernel symbol
+ # (kallsyms) access though. __schedule defined here:
+ # https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/kernel/sched/core.c;l=6593;drc=0c99414a07ddaa18d8eb4be90b551d2687cbde2f
+ if frame_str.startswith("__schedule "):
+ category = 5
elif ".so" in frame_str:
category = 2
elif ".vdex" in frame_str:
category = 3
elif ".oat" in frame_str:
category = 4
+ # "[JIT app cache]" is returned for JIT code here:
+ # https://cs.android.com/android/platform/superproject/+/master:system/extras/simpleperf/dso.cpp;l=551;drc=4d8137f55782cc1e8cc93e4694ba3a7159d9a2bc
+ elif "[JIT app cache]" in frame_str:
+ category = 7
self.frameTable.append(Frame(
string_id=string_id,
@@ -365,16 +390,20 @@ def _gecko_profile(
symfs_dir: Optional[str],
kallsyms_file: Optional[str],
report_lib_options: ReportLibOptions,
- max_remove_gap_length: int) -> GeckoProfile:
+ max_remove_gap_length: int,
+ percpu_samples: bool) -> GeckoProfile:
"""convert a simpleperf profile to gecko format"""
- lib = ReportLib()
+ lib = GetReportLib(record_file)
lib.ShowIpForUnknownSymbol()
if symfs_dir is not None:
lib.SetSymfs(symfs_dir)
- lib.SetRecordFile(record_file)
if kallsyms_file is not None:
lib.SetKallsymsFile(kallsyms_file)
+ if percpu_samples:
+ # Grouping samples by cpus doesn't support off cpu samples.
+ if lib.GetSupportedTraceOffCpuModes():
+ report_lib_options.trace_offcpu = 'on-cpu'
lib.SetReportOptions(report_lib_options)
arch = lib.GetArch()
@@ -382,7 +411,9 @@ def _gecko_profile(
record_cmd = lib.GetRecordCmd()
# Map from tid to Thread
- threadMap: Dict[int, Thread] = {}
+ thread_map: Dict[int, Thread] = {}
+ # Map from pid to process name
+ process_names: Dict[int, str] = {}
while True:
sample = lib.GetNextSample()
@@ -400,28 +431,44 @@ def _gecko_profile(
# We want root first, leaf last.
stack.reverse()
- # add thread sample
- thread = threadMap.get(sample.tid)
- if thread is None:
- thread = Thread(comm=sample.thread_comm, pid=sample.pid, tid=sample.tid)
- threadMap[sample.tid] = thread
- thread.add_sample(
- comm=sample.thread_comm,
- stack=stack,
- # We are being a bit fast and loose here with time here. simpleperf
- # uses CLOCK_MONOTONIC by default, which doesn't use the normal unix
- # epoch, but rather some arbitrary time. In practice, this doesn't
- # matter, the Firefox Profiler normalises all the timestamps to begin at
- # the minimum time. Consider fixing this in future, if needed, by
- # setting `simpleperf record --clockid realtime`.
- time_ms=sample_time_ms)
-
- for thread in threadMap.values():
+ if percpu_samples:
+ if sample.tid == sample.pid:
+ process_names[sample.pid] = sample.thread_comm
+ process_name = process_names.get(sample.pid)
+ stack = [
+ '%s tid %d (in %s pid %d)' %
+ (sample.thread_comm, sample.tid, process_name, sample.pid)] + stack
+ thread = thread_map.get(sample.cpu)
+ if thread is None:
+ thread = Thread(comm=f'Cpu {sample.cpu}', pid=sample.cpu, tid=sample.cpu)
+ thread_map[sample.cpu] = thread
+ thread.add_sample(
+ comm=f'Cpu {sample.cpu}',
+ stack=stack,
+ time_ms=sample_time_ms)
+ else:
+ # add thread sample
+ thread = thread_map.get(sample.tid)
+ if thread is None:
+ thread = Thread(comm=sample.thread_comm, pid=sample.pid, tid=sample.tid)
+ thread_map[sample.tid] = thread
+ thread.add_sample(
+ comm=sample.thread_comm,
+ stack=stack,
+ # We are being a bit fast and loose here with time here. simpleperf
+ # uses CLOCK_MONOTONIC by default, which doesn't use the normal unix
+ # epoch, but rather some arbitrary time. In practice, this doesn't
+ # matter, the Firefox Profiler normalises all the timestamps to begin at
+ # the minimum time. Consider fixing this in future, if needed, by
+ # setting `simpleperf record --clockid realtime`.
+ time_ms=sample_time_ms)
+
+ for thread in thread_map.values():
thread.sort_samples()
- remove_stack_gaps(max_remove_gap_length, threadMap)
+ remove_stack_gaps(max_remove_gap_length, thread_map)
- threads = [thread.to_json_dict() for thread in threadMap.values()]
+ threads = [thread.to_json_dict() for thread in thread_map.values()]
profile_timestamp = meta_info.get('timestamp')
end_time_ms = (int(profile_timestamp) * 1000) if profile_timestamp else 0
@@ -450,6 +497,7 @@ def _gecko_profile(
"markerSchema": [],
"abi": arch,
"oscpu": meta_info.get("android_build_fingerprint"),
+ "appBuildID": meta_info.get("app_versioncode"),
}
# Schema:
@@ -479,6 +527,9 @@ def main() -> None:
broken-stack samples we want to remove.
"""
)
+ parser.add_argument(
+ '--percpu-samples', action='store_true',
+ help='show samples based on cpus instead of threads')
parser.add_report_lib_options()
args = parser.parse_args()
profile = _gecko_profile(
@@ -486,7 +537,9 @@ def main() -> None:
symfs_dir=args.symfs,
kallsyms_file=args.kallsyms,
report_lib_options=args.report_lib_options,
- max_remove_gap_length=args.max_remove_gap_length)
+ max_remove_gap_length=args.max_remove_gap_length,
+ percpu_samples=args.percpu_samples,
+ )
json.dump(profile, sys.stdout, sort_keys=True)
diff --git a/simpleperf/scripts/ipc.py b/simpleperf/scripts/ipc.py
new file mode 100755
index 00000000..9871875e
--- /dev/null
+++ b/simpleperf/scripts/ipc.py
@@ -0,0 +1,137 @@
+#!/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 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.
+#
+
+"""ipc.py: Capture the Instructions per Cycle (IPC) of the system during a
+ specified duration.
+
+ Example:
+ ./ipc.py
+ ./ipc.py 2 20 # Set interval to 2 secs and total duration to 20 secs
+ ./ipc.py -p 284 -C 4 # Only profile the PID 284 while running on core 4
+ ./ipc.py -c 'sleep 5' # Only profile the command to run
+
+ Result looks like:
+ K_CYCLES K_INSTR IPC
+ 36840 14138 0.38
+ 70701 27743 0.39
+ 104562 41350 0.40
+ 138264 54916 0.40
+"""
+
+import io
+import logging
+import subprocess
+import sys
+import time
+
+from simpleperf_utils import (
+ AdbHelper, BaseArgumentParser, get_target_binary_path, log_exit)
+
+def start_profiling(adb, args, target_args):
+ """Start simpleperf process on device."""
+ shell_args = ['simpleperf', 'stat', '-e', 'cpu-cycles',
+ '-e', 'instructions', '--interval', str(args.interval * 1000),
+ '--duration', str(args.duration)]
+ shell_args += target_args
+ adb_args = [adb.adb_path, 'shell'] + shell_args
+ logging.info('run adb cmd: %s' % adb_args)
+ return subprocess.Popen(adb_args, stdout=subprocess.PIPE)
+
+def capture_stats(adb, args, stat_subproc):
+ """Capture IPC profiling stats or stop profiling when user presses Ctrl-C."""
+ try:
+ print("%-10s %-10s %5s" % ("K_CYCLES", "K_INSTR", "IPC"))
+ cpu_cycles = 0
+ for line in io.TextIOWrapper(stat_subproc.stdout, encoding="utf-8"):
+ if 'cpu-cycles' in line:
+ if args.cpu == None:
+ cpu_cycles = int(line.split()[0].replace(",", ""))
+ continue
+ columns = line.split()
+ if args.cpu == int(columns[0]):
+ cpu_cycles = int(columns[1].replace(",", ""))
+ elif 'instructions' in line:
+ if cpu_cycles == 0: cpu_cycles = 1 # PMCs are broken, or no events
+ ins = -1
+ columns = line.split()
+ if args.cpu == None:
+ ins = int(columns[0].replace(",", ""))
+ elif args.cpu == int(columns[0]):
+ ins = int(columns[1].replace(",", ""))
+ if ins >= 0:
+ print("%-10d %-10d %5.2f" %
+ (cpu_cycles / 1000, ins / 1000, ins / cpu_cycles))
+
+ except KeyboardInterrupt:
+ stop_profiling(adb)
+ stat_subproc = None
+
+def stop_profiling(adb):
+ """Stop profiling by sending SIGINT to simpleperf and wait until it exits."""
+ has_killed = False
+ while True:
+ (result, _) = adb.run_and_return_output(['shell', 'pidof', 'simpleperf'])
+ if not result:
+ break
+ if not has_killed:
+ has_killed = True
+ adb.run_and_return_output(['shell', 'pkill', '-l', '2', 'simpleperf'])
+ time.sleep(1)
+
+def capture_ipc(args):
+ # Initialize adb and verify device
+ adb = AdbHelper(enable_switch_to_root=True)
+ if not adb.is_device_available():
+ log_exit('No Android device is connected via ADB.')
+ is_root_device = adb.switch_to_root()
+ device_arch = adb.get_device_arch()
+
+ if args.pid:
+ (result, _) = adb.run_and_return_output(['shell', 'ls', '/proc/%s' % args.pid])
+ if not result:
+ log_exit("Pid '%s' does not exist" % args.pid)
+
+ target_args = []
+ if args.cpu is not None:
+ target_args += ['--per-core']
+ if args.pid:
+ target_args += ['-p', args.pid]
+ elif args.command:
+ target_args += [args.command]
+ else:
+ target_args += ['-a']
+
+ stat_subproc = start_profiling(adb, args, target_args)
+ capture_stats(adb, args, stat_subproc)
+
+def main():
+ parser = BaseArgumentParser(description=__doc__)
+ parser.add_argument('-C', '--cpu', type=int, help='Capture IPC only for this CPU core')
+ process_group = parser.add_mutually_exclusive_group()
+ process_group.add_argument('-p', '--pid', help='Capture IPC only for this PID')
+ process_group.add_argument('-c', '--command', help='Capture IPC only for this command')
+ parser.add_argument('interval', nargs='?', default=1, type=int, help='sampling interval in seconds')
+ parser.add_argument('duration', nargs='?', default=10, type=int, help='sampling duration in seconds')
+
+ args = parser.parse_args()
+ if args.interval > args.duration:
+ log_exit("interval cannot be greater than duration")
+
+ capture_ipc(args)
+
+if __name__ == '__main__':
+ main()
diff --git a/simpleperf/scripts/pprof_proto_generator.py b/simpleperf/scripts/pprof_proto_generator.py
index a22e424f..70f60844 100755
--- a/simpleperf/scripts/pprof_proto_generator.py
+++ b/simpleperf/scripts/pprof_proto_generator.py
@@ -30,7 +30,7 @@ import os.path
import re
import sys
-from simpleperf_report_lib import ReportLib
+from simpleperf_report_lib import GetReportLib
from simpleperf_utils import (Addr2Nearestline, BaseArgumentParser, BinaryFinder, extant_dir,
flatten_arg_list, log_exit, ReadElf, ToolFinder)
try:
@@ -292,8 +292,7 @@ class PprofProfileGenerator(object):
self.binary_finder = BinaryFinder(config['binary_cache_dir'], self.read_elf)
def load_record_file(self, record_file):
- self.lib = ReportLib()
- self.lib.SetRecordFile(record_file)
+ self.lib = GetReportLib(record_file)
if self.config['binary_cache_dir']:
self.lib.SetSymfs(self.config['binary_cache_dir'])
@@ -310,8 +309,13 @@ class PprofProfileGenerator(object):
"Converted to pprof with:\n" + " ".join(sys.argv),
"Architecture:\n" + self.lib.GetArch(),
]
+ meta_info = self.lib.MetaInfo()
+ if "app_versioncode" in meta_info:
+ comments.append("App Version Code:\n" + meta_info["app_versioncode"])
for comment in comments:
self.profile.comment.append(self.get_string_id(comment))
+ if "timestamp" in meta_info:
+ self.profile.time_nanos = int(meta_info["timestamp"]) * 1000 * 1000 * 1000
numbers_re = re.compile(r"\d+")
@@ -648,6 +652,7 @@ def main():
profile = generator.gen(args.jobs)
store_pprof_profile(config['output_file'], profile)
logging.info("Report is generated at '%s' successfully." % config['output_file'])
+ logging.info('Before uploading to the continuous PProf UI, use gzip to compress the file.')
if __name__ == '__main__':
diff --git a/simpleperf/scripts/report_html.js b/simpleperf/scripts/report_html.js
index b0827f40..a3f899dd 100644
--- a/simpleperf/scripts/report_html.js
+++ b/simpleperf/scripts/report_html.js
@@ -379,10 +379,10 @@ class ChartView {
};
if (isClockEvent(this.eventInfo)) {
this.getSampleWeight = function (eventCount) {
- return (eventCount / 1000000.0).toFixed(3) + ' ms';
+ return (eventCount / 1000000.0).toFixed(3).toLocaleString() + ' ms';
};
} else {
- this.getSampleWeight = (eventCount) => '' + eventCount;
+ this.getSampleWeight = (eventCount) => eventCount.toLocaleString();
}
}
@@ -622,10 +622,10 @@ class SampleTableWeightSelectorView {
return (eventCount) => (eventCount * 100.0 / this.eventCount).toFixed(2) + '%';
}
if (this.curOption == 'event_count') {
- return (eventCount) => '' + eventCount;
+ return (eventCount) => eventCount.toLocaleString();
}
if (this.curOption == 'event_count_in_ms') {
- return (eventCount) => (eventCount / 1000000.0).toFixed(3);
+ return (eventCount) => (eventCount / 1000000.0).toFixed(3).toLocaleString();
}
}
@@ -701,9 +701,13 @@ class SampleTableView {
let table = this.tableDiv.find('table');
let dataTable = table.DataTable({
lengthMenu: [10, 20, 50, 100, -1],
- order: [0, 'desc'],
+ pageLength: 100,
+ order: [[0, 'desc'], [1, 'desc'], [2, 'desc']],
data: data,
responsive: true,
+ columnDefs: [
+ { orderSequence: [ 'desc' ], className: 'textRight', targets: [0, 1, 2] },
+ ],
});
dataTable.column(7).visible(false);
@@ -1078,13 +1082,13 @@ class SampleWeightSelectorView {
}
if (this.curOption == 'event_count') {
return function(eventCount, _) {
- return '' + eventCount;
+ return eventCount.toLocaleString();
};
}
if (this.curOption == 'event_count_in_ms') {
return function(eventCount, _) {
let timeInMs = eventCount / 1000000.0;
- return timeInMs.toFixed(3) + ' ms';
+ return timeInMs.toFixed(3).toLocaleString() + ' ms';
};
}
}
@@ -1215,18 +1219,21 @@ class FlameGraphView {
let map = new Map();
for (let node of nodes) {
for (let child of node.c) {
- let subNodes = map.get(child.f);
+ let funcName = getFuncName(child.f);
+ let subNodes = map.get(funcName);
if (subNodes) {
subNodes.push(child);
} else {
- map.set(child.f, [child]);
+ map.set(funcName, [child]);
}
}
}
+ const funcNames = [...map.keys()].sort();
let res = [];
- for (let subNodes of map.values()) {
+ funcNames.forEach(function (funcName) {
+ const subNodes = map.get(funcName);
res.push(subNodes.length == 1 ? subNodes[0] : subNodes);
- }
+ });
return res;
}
@@ -1583,12 +1590,9 @@ class SourceCodeView {
data.addColumn('string', 'Self');
data.addColumn('string', 'Code');
data.addRows(rows);
- for (let i = 0; i < rows.length; ++i) {
- data.setProperty(i, 0, 'className', 'colForLine');
- for (let j = 1; j <= 2; ++j) {
- data.setProperty(i, j, 'className', 'colForCount');
- }
- }
+ data.setColumnProperty(0, 'className', 'colForLine');
+ data.setColumnProperty(1, 'className', 'colForCount');
+ data.setColumnProperty(2, 'className', 'colForCount');
this.div.append(getHtml('pre', {text: sourceFile.path}));
let wrapperDiv = $('<div>');
wrapperDiv.appendTo(this.div);
@@ -1680,11 +1684,8 @@ class DisassemblyView {
data.addColumn('string', 'Self');
data.addColumn('string', 'Code');
data.addRows(rows);
- for (let i = 0; i < rows.length; ++i) {
- for (let j = 0; j < 2; ++j) {
- data.setProperty(i, j, 'className', 'colForCount');
- }
- }
+ data.setColumnProperty(0, 'className', 'colForCount');
+ data.setColumnProperty(1, 'className', 'colForCount');
let wrapperDiv = $('<div>');
wrapperDiv.appendTo(this.div);
let table = new google.visualization.Table(wrapperDiv.get(0));
diff --git a/simpleperf/scripts/report_html.py b/simpleperf/scripts/report_html.py
index 56f8dae5..314a33fe 100755
--- a/simpleperf/scripts/report_html.py
+++ b/simpleperf/scripts/report_html.py
@@ -28,10 +28,10 @@ from pathlib import Path
import sys
from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Tuple, Union
-from simpleperf_report_lib import ReportLib, SymbolStruct
+from simpleperf_report_lib import GetReportLib, SymbolStruct
from simpleperf_utils import (
- Addr2Nearestline, BaseArgumentParser, BinaryFinder, get_script_dir, log_exit, Objdump,
- open_report_in_browser, ReadElf, ReportLibOptions, SourceFileSearcher)
+ Addr2Nearestline, AddrRange, BaseArgumentParser, BinaryFinder, Disassembly, get_script_dir,
+ log_exit, Objdump, open_report_in_browser, ReadElf, ReportLibOptions, SourceFileSearcher)
MAX_CALLSTACK_LENGTH = 750
@@ -246,6 +246,10 @@ class ThreadScope(object):
self.call_graph.merge(thread.call_graph)
self.reverse_call_graph.merge(thread.reverse_call_graph)
+ def sort_call_graph_by_function_name(self, get_func_name: Callable[[int], str]) -> None:
+ self.call_graph.sort_by_function_name(get_func_name)
+ self.reverse_call_graph.sort_by_function_name(get_func_name)
+
class LibScope(object):
@@ -408,6 +412,17 @@ class CallNode(object):
else:
cur_child.merge(child)
+ def sort_by_function_name(self, get_func_name: Callable[[int], str]) -> None:
+ if self.children:
+ child_func_ids = list(self.children.keys())
+ child_func_ids.sort(key=get_func_name)
+ new_children = collections.OrderedDict()
+ for func_id in child_func_ids:
+ new_children[func_id] = self.children[func_id]
+ self.children = new_children
+ for child in self.children.values():
+ child.sort_by_function_name(get_func_name)
+
@dataclass
class LibInfo:
@@ -467,6 +482,9 @@ class FunctionSet(object):
self.id_to_func[func_id] = function
return function.func_id
+ def get_func_name(self, func_id: int) -> str:
+ return self.id_to_func[func_id].func_name
+
def trim_functions(self, left_func_ids: Set[int]):
""" Remove functions excepts those in left_func_ids. """
for function in self.name_to_func.values():
@@ -621,8 +639,7 @@ class RecordData(object):
self.binary_finder = BinaryFinder(binary_cache_path, ReadElf(ndk_path))
def load_record_file(self, record_file: str, report_lib_options: ReportLibOptions):
- lib = ReportLib()
- lib.SetRecordFile(record_file)
+ lib = GetReportLib(record_file)
# If not showing ip for unknown symbols, the percent of the unknown symbol may be
# accumulated to very big, and ranks first in the sample table.
lib.ShowIpForUnknownSymbol()
@@ -704,6 +721,12 @@ class RecordData(object):
del event.processes[process]
self.functions.trim_functions(hit_func_ids)
+ def sort_call_graph_by_function_name(self) -> None:
+ for event in self.events.values():
+ for process in event.processes.values():
+ for thread in process.threads.values():
+ thread.sort_call_graph_by_function_name(self.functions.get_func_name)
+
def _get_event(self, event_name: str) -> EventScope:
if event_name not in self.events:
self.events[event_name] = EventScope(event_name)
@@ -778,7 +801,8 @@ class RecordData(object):
# Collect needed source code in SourceFileSet.
self.source_files.load_source_code(source_dirs)
- def add_disassembly(self, filter_lib: Callable[[str], bool], jobs: int):
+ def add_disassembly(self, filter_lib: Callable[[str], bool],
+ jobs: int, disassemble_job_size: int):
""" Collect disassembly information:
1. Use objdump to collect disassembly for each function in FunctionSet.
2. Set flag to dump addr_hit_map when generating record info.
@@ -792,6 +816,8 @@ class RecordData(object):
lib_functions[function.lib_id].append(function)
with ThreadPoolExecutor(jobs) as executor:
+ futures: List[Future] = []
+ all_tasks = []
for lib_id, functions in lib_functions.items():
lib = self.libs.get_lib(lib_id)
if not filter_lib(lib.name):
@@ -799,17 +825,46 @@ class RecordData(object):
dso_info = objdump.get_dso_info(lib.name, lib.build_id)
if not dso_info:
continue
- logging.info('Disassemble %s' % dso_info[0])
- futures: List[Future] = []
- for function in functions:
- futures.append(
- executor.submit(objdump.disassemble_code, dso_info,
- function.start_addr, function.addr_len))
- for i in range(len(functions)):
- # Call future.result() to report exceptions raised in the executor.
- functions[i].disassembly = futures[i].result()
+
+ tasks = self.split_disassembly_jobs(functions, disassemble_job_size)
+ logging.debug('create %d jobs to disassemble %d functions in %s',
+ len(tasks), len(functions), lib.name)
+ for task in tasks:
+ futures.append(executor.submit(
+ self._disassemble_functions, objdump, dso_info, task))
+ all_tasks.append(task)
+
+ for task, future in zip(all_tasks, futures):
+ result = future.result()
+ if result and len(result) == len(task):
+ for function, disassembly in zip(task, result):
+ function.disassembly = disassembly.lines
+
+ logging.debug('finished all disassemble jobs')
self.gen_addr_hit_map_in_record_info = True
+ def split_disassembly_jobs(self, functions: List[Function],
+ disassemble_job_size: int) -> List[List[Function]]:
+ """ Decide how to split the task of dissassembly functions in one library. """
+ if not functions:
+ return []
+ functions.sort(key=lambda f: f.start_addr)
+ result = []
+ job_start_addr = None
+ for function in functions:
+ if (job_start_addr is None or
+ function.start_addr - job_start_addr > disassemble_job_size):
+ job_start_addr = function.start_addr
+ result.append([function])
+ else:
+ result[-1].append(function)
+ return result
+
+ def _disassemble_functions(self, objdump: Objdump, dso_info,
+ functions: List[Function]) -> Optional[List[Disassembly]]:
+ addr_ranges = [AddrRange(f.start_addr, f.addr_len) for f in functions]
+ return objdump.disassemble_functions(dso_info, addr_ranges)
+
def gen_record_info(self) -> Dict[str, Any]:
""" Return json data which will be used by report_html.js. """
record_info = {}
@@ -927,10 +982,11 @@ class ReportGenerator(object):
self.hw.open_tag('script').add(
"google.charts.load('current', {'packages': ['corechart', 'table']});").close_tag()
self.hw.open_tag('style', type='text/css').add("""
- .colForLine { width: 50px; }
- .colForCount { width: 100px; }
+ .colForLine { width: 50px; text-align: right; }
+ .colForCount { width: 100px; text-align: right; }
.tableCell { font-size: 17px; }
.boldTableCell { font-weight: bold; font-size: 17px; }
+ .textRight { text-align: right; }
""").close_tag()
self.hw.close_tag('head')
self.hw.open_tag('body')
@@ -969,8 +1025,13 @@ def get_args() -> argparse.Namespace:
parser.add_argument('--add_source_code', action='store_true', help='Add source code.')
parser.add_argument('--source_dirs', nargs='+', help='Source code directories.')
parser.add_argument('--add_disassembly', action='store_true', help='Add disassembled code.')
+ parser.add_argument('--disassemble-job-size', type=int, default=1024*1024,
+ help='address range for one disassemble job')
parser.add_argument('--binary_filter', nargs='+', help="""Annotate source code and disassembly
- only for selected binaries.""")
+ only for selected binaries, whose recorded paths contains [BINARY_FILTER] as
+ a substring. Example: to select binaries belonging to an app with package
+ name 'com.example.myapp', use `--binary_filter com.example.myapp`.
+ """)
parser.add_argument(
'-j', '--jobs', type=int, default=os.cpu_count(),
help='Use multithreading to speed up disassembly and source code annotation.')
@@ -1011,6 +1072,7 @@ def main():
if args.aggregate_by_thread_name:
record_data.aggregate_by_thread_name()
record_data.limit_percents(args.min_func_percent, args.min_callchain_percent)
+ record_data.sort_call_graph_by_function_name()
def filter_lib(lib_name: str) -> bool:
if not args.binary_filter:
@@ -1022,7 +1084,7 @@ def main():
if args.add_source_code:
record_data.add_source_code(args.source_dirs, filter_lib, args.jobs)
if args.add_disassembly:
- record_data.add_disassembly(filter_lib, args.jobs)
+ record_data.add_disassembly(filter_lib, args.jobs, args.disassemble_job_size)
# 3. Generate report html.
report_generator = ReportGenerator(args.report_path)
diff --git a/simpleperf/scripts/report_sample.py b/simpleperf/scripts/report_sample.py
index 9b6947f7..c5fc8606 100755
--- a/simpleperf/scripts/report_sample.py
+++ b/simpleperf/scripts/report_sample.py
@@ -19,7 +19,7 @@
"""
import sys
-from simpleperf_report_lib import ReportLib
+from simpleperf_report_lib import GetReportLib
from simpleperf_utils import BaseArgumentParser, flatten_arg_list, ReportLibOptions
from typing import List, Set, Optional
@@ -32,13 +32,11 @@ def report_sample(
header: bool,
report_lib_options: ReportLibOptions):
""" read record_file, and print each sample"""
- lib = ReportLib()
+ lib = GetReportLib(record_file)
lib.ShowIpForUnknownSymbol()
if symfs_dir is not None:
lib.SetSymfs(symfs_dir)
- if record_file is not None:
- lib.SetRecordFile(record_file)
if kallsyms_file is not None:
lib.SetKallsymsFile(kallsyms_file)
lib.SetReportOptions(report_lib_options)
diff --git a/simpleperf/scripts/report_sample_pb2.py b/simpleperf/scripts/report_sample_pb2.py
new file mode 100644
index 00000000..5e9d6dfd
--- /dev/null
+++ b/simpleperf/scripts/report_sample_pb2.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: cmd_report_sample.proto
+"""Generated protocol buffer code."""
+from google.protobuf.internal import builder as _builder
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17\x63md_report_sample.proto\x12\x17simpleperf_report_proto\"\xed\x06\n\x06Sample\x12\x0c\n\x04time\x18\x01 \x01(\x04\x12\x11\n\tthread_id\x18\x02 \x01(\x05\x12\x41\n\tcallchain\x18\x03 \x03(\x0b\x32..simpleperf_report_proto.Sample.CallChainEntry\x12\x13\n\x0b\x65vent_count\x18\x04 \x01(\x04\x12\x15\n\revent_type_id\x18\x05 \x01(\r\x12I\n\x10unwinding_result\x18\x06 \x01(\x0b\x32/.simpleperf_report_proto.Sample.UnwindingResult\x1a\x94\x02\n\x0e\x43\x61llChainEntry\x12\x15\n\rvaddr_in_file\x18\x01 \x01(\x04\x12\x0f\n\x07\x66ile_id\x18\x02 \x01(\r\x12\x11\n\tsymbol_id\x18\x03 \x01(\x05\x12\x63\n\x0e\x65xecution_type\x18\x04 \x01(\x0e\x32<.simpleperf_report_proto.Sample.CallChainEntry.ExecutionType:\rNATIVE_METHOD\"b\n\rExecutionType\x12\x11\n\rNATIVE_METHOD\x10\x00\x12\x1a\n\x16INTERPRETED_JVM_METHOD\x10\x01\x12\x12\n\x0eJIT_JVM_METHOD\x10\x02\x12\x0e\n\nART_METHOD\x10\x03\x1a\xf0\x02\n\x0fUnwindingResult\x12\x16\n\x0eraw_error_code\x18\x01 \x01(\r\x12\x12\n\nerror_addr\x18\x02 \x01(\x04\x12M\n\nerror_code\x18\x03 \x01(\x0e\x32\x39.simpleperf_report_proto.Sample.UnwindingResult.ErrorCode\"\xe1\x01\n\tErrorCode\x12\x0e\n\nERROR_NONE\x10\x00\x12\x11\n\rERROR_UNKNOWN\x10\x01\x12\x1a\n\x16\x45RROR_NOT_ENOUGH_STACK\x10\x02\x12\x18\n\x14\x45RROR_MEMORY_INVALID\x10\x03\x12\x15\n\x11\x45RROR_UNWIND_INFO\x10\x04\x12\x15\n\x11\x45RROR_INVALID_MAP\x10\x05\x12\x1c\n\x18\x45RROR_MAX_FRAME_EXCEEDED\x10\x06\x12\x18\n\x14\x45RROR_REPEATED_FRAME\x10\x07\x12\x15\n\x11\x45RROR_INVALID_ELF\x10\x08\"9\n\rLostSituation\x12\x14\n\x0csample_count\x18\x01 \x01(\x04\x12\x12\n\nlost_count\x18\x02 \x01(\x04\"H\n\x04\x46ile\x12\n\n\x02id\x18\x01 \x01(\r\x12\x0c\n\x04path\x18\x02 \x01(\t\x12\x0e\n\x06symbol\x18\x03 \x03(\t\x12\x16\n\x0emangled_symbol\x18\x04 \x03(\t\"D\n\x06Thread\x12\x11\n\tthread_id\x18\x01 \x01(\r\x12\x12\n\nprocess_id\x18\x02 \x01(\r\x12\x13\n\x0bthread_name\x18\x03 \x01(\t\"\x99\x01\n\x08MetaInfo\x12\x12\n\nevent_type\x18\x01 \x03(\t\x12\x18\n\x10\x61pp_package_name\x18\x02 \x01(\t\x12\x10\n\x08\x61pp_type\x18\x03 \x01(\t\x12\x1b\n\x13\x61ndroid_sdk_version\x18\x04 \x01(\t\x12\x1a\n\x12\x61ndroid_build_type\x18\x05 \x01(\t\x12\x14\n\x0ctrace_offcpu\x18\x06 \x01(\x08\"C\n\rContextSwitch\x12\x11\n\tswitch_on\x18\x01 \x01(\x08\x12\x0c\n\x04time\x18\x02 \x01(\x04\x12\x11\n\tthread_id\x18\x03 \x01(\r\"\xde\x02\n\x06Record\x12\x31\n\x06sample\x18\x01 \x01(\x0b\x32\x1f.simpleperf_report_proto.SampleH\x00\x12\x36\n\x04lost\x18\x02 \x01(\x0b\x32&.simpleperf_report_proto.LostSituationH\x00\x12-\n\x04\x66ile\x18\x03 \x01(\x0b\x32\x1d.simpleperf_report_proto.FileH\x00\x12\x31\n\x06thread\x18\x04 \x01(\x0b\x32\x1f.simpleperf_report_proto.ThreadH\x00\x12\x36\n\tmeta_info\x18\x05 \x01(\x0b\x32!.simpleperf_report_proto.MetaInfoH\x00\x12@\n\x0e\x63ontext_switch\x18\x06 \x01(\x0b\x32&.simpleperf_report_proto.ContextSwitchH\x00\x42\r\n\x0brecord_dataB6\n com.android.tools.profiler.protoB\x10SimpleperfReportH\x03')
+
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cmd_report_sample_pb2', globals())
+if _descriptor._USE_C_DESCRIPTORS == False:
+
+ DESCRIPTOR._options = None
+ DESCRIPTOR._serialized_options = b'\n com.android.tools.profiler.protoB\020SimpleperfReportH\003'
+ _SAMPLE._serialized_start=53
+ _SAMPLE._serialized_end=930
+ _SAMPLE_CALLCHAINENTRY._serialized_start=283
+ _SAMPLE_CALLCHAINENTRY._serialized_end=559
+ _SAMPLE_CALLCHAINENTRY_EXECUTIONTYPE._serialized_start=461
+ _SAMPLE_CALLCHAINENTRY_EXECUTIONTYPE._serialized_end=559
+ _SAMPLE_UNWINDINGRESULT._serialized_start=562
+ _SAMPLE_UNWINDINGRESULT._serialized_end=930
+ _SAMPLE_UNWINDINGRESULT_ERRORCODE._serialized_start=705
+ _SAMPLE_UNWINDINGRESULT_ERRORCODE._serialized_end=930
+ _LOSTSITUATION._serialized_start=932
+ _LOSTSITUATION._serialized_end=989
+ _FILE._serialized_start=991
+ _FILE._serialized_end=1063
+ _THREAD._serialized_start=1065
+ _THREAD._serialized_end=1133
+ _METAINFO._serialized_start=1136
+ _METAINFO._serialized_end=1289
+ _CONTEXTSWITCH._serialized_start=1291
+ _CONTEXTSWITCH._serialized_end=1358
+ _RECORD._serialized_start=1361
+ _RECORD._serialized_end=1711
+# @@protoc_insertion_point(module_scope)
diff --git a/simpleperf/scripts/sample_filter.py b/simpleperf/scripts/sample_filter.py
new file mode 100755
index 00000000..c3617629
--- /dev/null
+++ b/simpleperf/scripts/sample_filter.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 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.
+#
+
+"""sample_filter.py: generate sample filter files, which can be passed in the
+ --filter-file option when reporting.
+
+Example:
+ ./sample_filter.py -i perf.data --split-time-range 2 -o sample_filter
+ ./gecko_profile_generator.py -i perf.data --filter-file sample_filter_part1 \
+ | gzip >profile-part1.json.gz
+ ./gecko_profile_generator.py -i perf.data --filter-file sample_filter_part2 \
+ | gzip >profile-part2.json.gz
+"""
+
+import logging
+from simpleperf_report_lib import ReportLib
+from simpleperf_utils import BaseArgumentParser
+from typing import Tuple
+
+
+class RecordFileReader:
+ def __init__(self, record_file: str):
+ self.record_file = record_file
+
+ def get_time_range(self) -> Tuple[int, int]:
+ """ Return a tuple of (min_timestamp, max_timestamp). """
+ min_timestamp = 0
+ max_timestamp = 0
+ lib = ReportLib()
+ lib.SetRecordFile(self.record_file)
+ while True:
+ sample = lib.GetNextSample()
+ if not sample:
+ break
+ if not min_timestamp or sample.time < min_timestamp:
+ min_timestamp = sample.time
+ if not max_timestamp or sample.time > max_timestamp:
+ max_timestamp = sample.time
+ lib.Close()
+ return (min_timestamp, max_timestamp)
+
+
+def show_time_range(record_file: str) -> None:
+ reader = RecordFileReader(record_file)
+ time_range = reader.get_time_range()
+ print('time range of samples is %.3f s' % ((time_range[1] - time_range[0]) / 1e9))
+
+
+def filter_samples(
+ record_file: str, split_time_range: int, exclude_first_seconds: int,
+ exclude_last_seconds: int, output_file_prefix: str) -> None:
+ reader = RecordFileReader(record_file)
+ min_timestamp, max_timestamp = reader.get_time_range()
+ comment = 'total time range: %d seconds' % ((max_timestamp - min_timestamp) // 1e9)
+ if exclude_first_seconds:
+ min_timestamp += int(exclude_first_seconds * 1e9)
+ comment += ', exclude first %d seconds' % exclude_first_seconds
+ if exclude_last_seconds:
+ max_timestamp -= int(exclude_last_seconds * 1e9)
+ comment += ', exclude last %d seconds' % exclude_last_seconds
+ if min_timestamp > max_timestamp:
+ logging.error('All samples are filtered out')
+ return
+ if not split_time_range:
+ output_file = output_file_prefix
+ with open(output_file, 'w') as fh:
+ fh.write('// %s\n' % comment)
+ fh.write('GLOBAL_BEGIN %d\n' % min_timestamp)
+ fh.write('GLOBAL_END %d\n' % max_timestamp)
+ print('Generate sample filter file: %s' % output_file)
+ else:
+ step = (max_timestamp - min_timestamp) // split_time_range
+ cur_timestamp = min_timestamp
+ for i in range(split_time_range):
+ output_file = output_file_prefix + '_part%s' % (i + 1)
+ with open(output_file, 'w') as fh:
+ time_range_comment = 'current range: %d to %d seconds' % (
+ (cur_timestamp - min_timestamp) // 1e9,
+ (cur_timestamp + step - min_timestamp) // 1e9)
+ fh.write('// %s, %s\n' % (comment, time_range_comment))
+ fh.write('GLOBAL_BEGIN %d\n' % cur_timestamp)
+ if i == split_time_range - 1:
+ cur_timestamp = max_timestamp
+ else:
+ cur_timestamp += step
+ fh.write('GLOBAL_END %d\n' % (cur_timestamp + 1))
+ cur_timestamp += 1
+ print('Generate sample filter file: %s' % output_file)
+
+
+def main():
+ parser = BaseArgumentParser(description=__doc__)
+ parser.add_argument('-i', '--record-file', nargs='?', default='perf.data',
+ help='Default is perf.data.')
+ parser.add_argument('--show-time-range', action='store_true', help='show time range of samples')
+ parser.add_argument('--split-time-range', type=int,
+ help='split time ranges of samples into several parts')
+ parser.add_argument('--exclude-first-seconds', type=int,
+ help='exclude samples recorded in the first seconds')
+ parser.add_argument('--exclude-last-seconds', type=int,
+ help='exclude samples recorded in the last seconds')
+ parser.add_argument(
+ '-o', '--output-file-prefix', default='sample_filter',
+ help='prefix for the generated sample filter files')
+ args = parser.parse_args()
+
+ if args.show_time_range:
+ show_time_range(args.record_file)
+
+ if args.split_time_range or args.exclude_first_seconds or args.exclude_last_seconds:
+ filter_samples(args.record_file, args.split_time_range, args.exclude_first_seconds,
+ args.exclude_last_seconds, args.output_file_prefix)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/simpleperf/scripts/simpleperf_report_lib.py b/simpleperf/scripts/simpleperf_report_lib.py
index 5f990ac8..0f4be8c8 100644
--- a/simpleperf/scripts/simpleperf_report_lib.py
+++ b/simpleperf/scripts/simpleperf_report_lib.py
@@ -21,13 +21,14 @@
"""
import collections
+from collections import namedtuple
import ctypes as ct
from pathlib import Path
import struct
from typing import Any, Dict, List, Optional, Union
-from simpleperf_utils import (bytes_to_str, get_host_binary_path, is_windows, str_to_bytes,
- ReportLibOptions)
+from simpleperf_utils import (bytes_to_str, get_host_binary_path, is_windows, log_exit,
+ str_to_bytes, ReportLibOptions)
def _is_null(p: Optional[ct._Pointer]) -> bool:
@@ -237,8 +238,26 @@ class ReportLibStructure(ct.Structure):
_fields_ = []
+def SetReportOptionsForReportLib(report_lib, options: ReportLibOptions):
+ if options.proguard_mapping_files:
+ for file_path in options.proguard_mapping_files:
+ report_lib.AddProguardMappingFile(file_path)
+ if options.show_art_frames:
+ report_lib.ShowArtFrames(True)
+ if options.remove_method:
+ for name in options.remove_method:
+ report_lib.RemoveMethod(name)
+ if options.trace_offcpu:
+ report_lib.SetTraceOffCpuMode(options.trace_offcpu)
+ if options.sample_filters:
+ report_lib.SetSampleFilter(options.sample_filters)
+ if options.aggregate_threads:
+ report_lib.AggregateThreads(options.aggregate_threads)
+
+
# pylint: disable=invalid-name
class ReportLib(object):
+ """ Read contents from perf.data. """
def __init__(self, native_lib_path: Optional[str] = None):
if native_lib_path is None:
@@ -255,6 +274,8 @@ class ReportLib(object):
self._SetKallsymsFileFunc = self._lib.SetKallsymsFile
self._ShowIpForUnknownSymbolFunc = self._lib.ShowIpForUnknownSymbol
self._ShowArtFramesFunc = self._lib.ShowArtFrames
+ self._RemoveMethodFunc = self._lib.RemoveMethod
+ self._RemoveMethodFunc.restype = ct.c_bool
self._MergeJavaMethodsFunc = self._lib.MergeJavaMethods
self._AddProguardMappingFileFunc = self._lib.AddProguardMappingFile
self._AddProguardMappingFileFunc.restype = ct.c_bool
@@ -302,17 +323,7 @@ class ReportLib(object):
def SetReportOptions(self, options: ReportLibOptions):
""" Set report options in one call. """
- if options.proguard_mapping_files:
- for file_path in options.proguard_mapping_files:
- self.AddProguardMappingFile(file_path)
- if options.show_art_frames:
- self.ShowArtFrames(True)
- if options.trace_offcpu:
- self.SetTraceOffCpuMode(options.trace_offcpu)
- if options.sample_filters:
- self.SetSampleFilter(options.sample_filters)
- if options.aggregate_threads:
- self.AggregateThreads(options.aggregate_threads)
+ SetReportOptionsForReportLib(self, options)
def SetLogSeverity(self, log_level: str = 'info'):
""" Set log severity of native lib, can be verbose,debug,info,error,fatal."""
@@ -336,6 +347,11 @@ class ReportLib(object):
""" Show frames of internal methods of the Java interpreter. """
self._ShowArtFramesFunc(self.getInstance(), show)
+ def RemoveMethod(self, method_name_regex: str):
+ """ Remove methods with name containing method_name_regex. """
+ res = self._RemoveMethodFunc(self.getInstance(), _char_pt(method_name_regex))
+ _check(res, f'failed to call RemoveMethod({method_name_regex})')
+
def MergeJavaMethods(self, merge: bool = True):
""" This option merges jitted java methods with the same name but in different jit
symfiles. If possible, it also merges jitted methods with interpreted methods,
@@ -529,3 +545,261 @@ class ReportLib(object):
if self._instance is None:
raise Exception('Instance is Closed')
return self._instance
+
+
+ProtoSample = namedtuple('ProtoSample', ['ip', 'pid', 'tid',
+ 'thread_comm', 'time', 'in_kernel', 'cpu', 'period'])
+ProtoEvent = namedtuple('ProtoEvent', ['name', 'tracing_data_format'])
+ProtoSymbol = namedtuple(
+ 'ProtoSymbol',
+ ['dso_name', 'vaddr_in_file', 'symbol_name', 'symbol_addr', 'symbol_len', 'mapping'])
+ProtoMapping = namedtuple('ProtoMapping', ['start', 'end', 'pgoff'])
+ProtoCallChain = namedtuple('ProtoCallChain', ['nr', 'entries'])
+ProtoCallChainEntry = namedtuple('ProtoCallChainEntry', ['ip', 'symbol'])
+
+
+class ProtoFileReportLib:
+ """ Read contents from profile in cmd_report_sample.proto format.
+ It is generated by `simpleperf report-sample`.
+ """
+
+ @staticmethod
+ def is_supported_format(record_file: str):
+ with open(record_file, 'rb') as fh:
+ if fh.read(10) == b'SIMPLEPERF':
+ return True
+
+ @staticmethod
+ def get_report_sample_pb2():
+ try:
+ import report_sample_pb2
+ return report_sample_pb2
+ except ImportError as e:
+ log_exit(f'{e}\nprotobuf package is missing or too old. Please install it like ' +
+ '`pip install protobuf==4.21`.')
+
+ def __init__(self):
+ self.record_file = None
+ self.report_sample_pb2 = ProtoFileReportLib.get_report_sample_pb2()
+ self.records: List[self.report_sample_pb2.Record] = []
+ self.record_index = -1
+ self.files: List[self.report_sample_pb2.File] = []
+ self.thread_map: Dict[int, self.report_sample_pb2.Thread] = {}
+ self.meta_info: Optional[self.report_sample_pb2.MetaInfo] = None
+ self.fake_mapping_starts = []
+ self.sample_queue: List[self.report_sample_pb2.Sample] = collections.deque()
+ self.trace_offcpu_mode = None
+ # mapping from thread id to the last off-cpu sample in the thread
+ self.offcpu_samples = {}
+
+ def Close(self):
+ pass
+
+ def SetReportOptions(self, options: ReportLibOptions):
+ """ Set report options in one call. """
+ SetReportOptionsForReportLib(self, options)
+
+ def SetLogSeverity(self, log_level: str = 'info'):
+ pass
+
+ def SetSymfs(self, symfs_dir: str):
+ pass
+
+ def SetRecordFile(self, record_file: str):
+ self.record_file = record_file
+ with open(record_file, 'rb') as fh:
+ data = fh.read()
+ _check(data[:10] == b'SIMPLEPERF', f'magic number mismatch: {data[:10]}')
+ version = struct.unpack('<H', data[10:12])[0]
+ _check(version == 1, f'version mismatch: {version}')
+ i = 12
+ while i < len(data):
+ _check(i + 4 <= len(data), 'data format error')
+ size = struct.unpack('<I', data[i:i + 4])[0]
+ if size == 0:
+ break
+ i += 4
+ _check(i + size <= len(data), 'data format error')
+ record = self.report_sample_pb2.Record()
+ record.ParseFromString(data[i: i + size])
+ i += size
+ if record.HasField('sample') or record.HasField('context_switch'):
+ self.records.append(record)
+ elif record.HasField('file'):
+ self.files.append(record.file)
+ elif record.HasField('thread'):
+ self.thread_map[record.thread.thread_id] = record.thread
+ elif record.HasField('meta_info'):
+ self.meta_info = record.meta_info
+ if self.meta_info.trace_offcpu:
+ self.trace_offcpu_mode = 'mixed-on-off-cpu'
+ fake_mapping_start = 0
+ for file in self.files:
+ self.fake_mapping_starts.append(fake_mapping_start)
+ fake_mapping_start += len(file.symbol) + 1
+
+ def AddProguardMappingFile(self, mapping_file: Union[str, Path]):
+ """ Add proguard mapping.txt to de-obfuscate method names. """
+ raise NotImplementedError(
+ 'Adding proguard mapping files are not implemented for report_sample profiles')
+
+ def ShowIpForUnknownSymbol(self):
+ pass
+
+ def ShowArtFrames(self, show: bool = True):
+ raise NotImplementedError(
+ 'Showing art frames are not implemented for report_sample profiles')
+
+ def RemoveMethod(self, method_name_regex: str):
+ """ Remove methods with name containing method_name_regex. """
+ raise NotImplementedError("Removing method isn't implemented for report_sample profiles")
+
+ def SetSampleFilter(self, filters: List[str]):
+ raise NotImplementedError('sample filters are not implemented for report_sample profiles')
+
+ def GetSupportedTraceOffCpuModes(self) -> List[str]:
+ """ Get trace-offcpu modes supported by the recording file. It should be called after
+ SetRecordFile(). The modes are only available for profiles recorded with --trace-offcpu
+ option. All possible modes are:
+ on-cpu: report on-cpu samples with period representing time spent on cpu
+ off-cpu: report off-cpu samples with period representing time spent off cpu
+ on-off-cpu: report both on-cpu samples and off-cpu samples, which can be split
+ by event name.
+ mixed-on-off-cpu: report on-cpu and off-cpu samples under the same event name.
+ """
+ _check(self.meta_info,
+ 'GetSupportedTraceOffCpuModes() should be called after SetRecordFile()')
+ if self.meta_info.trace_offcpu:
+ return ['on-cpu', 'off-cpu', 'on-off-cpu', 'mixed-on-off-cpu']
+ return []
+
+ def SetTraceOffCpuMode(self, mode: str):
+ """ Set trace-offcpu mode. It should be called after SetRecordFile().
+ """
+ _check(mode in ['on-cpu', 'off-cpu', 'on-off-cpu', 'mixed-on-off-cpu'], 'invalide mode')
+ # Don't check if mode is in self.GetSupportedTraceOffCpuModes(). Because the profile may
+ # be generated by an old simpleperf.
+ self.trace_offcpu_mode = mode
+
+ def AggregateThreads(self, thread_name_regex_list: List[str]):
+ """ Given a list of thread name regex, threads with names matching the same regex are merged
+ into one thread. As a result, samples from different threads (like a thread pool) can be
+ shown in one flamegraph.
+ """
+ raise NotImplementedError(
+ 'Aggregating threads are not implemented for report_sample profiles')
+
+ def GetNextSample(self) -> Optional[ProtoSample]:
+ if self.sample_queue:
+ self.sample_queue.popleft()
+ while not self.sample_queue:
+ self.record_index += 1
+ if self.record_index >= len(self.records):
+ break
+ record = self.records[self.record_index]
+ if record.HasField('sample'):
+ self._process_sample_record(record.sample)
+ elif record.HasField('context_switch'):
+ self._process_context_switch(record.context_switch)
+ return self.GetCurrentSample()
+
+ def _process_sample_record(self, sample) -> None:
+ if not self.trace_offcpu_mode:
+ self._add_to_sample_queue(sample)
+ return
+ event_name = self._get_event_name(sample.event_type_id)
+ is_offcpu = 'sched_switch' in event_name
+
+ if self.trace_offcpu_mode == 'on-cpu':
+ if not is_offcpu:
+ self._add_to_sample_queue(sample)
+ return
+
+ if prev_offcpu_sample := self.offcpu_samples.get(sample.thread_id):
+ # If there is a previous off-cpu sample, update its period.
+ prev_offcpu_sample.event_count = max(sample.time - prev_offcpu_sample.time, 1)
+ self._add_to_sample_queue(prev_offcpu_sample)
+
+ if is_offcpu:
+ self.offcpu_samples[sample.thread_id] = sample
+ else:
+ self.offcpu_samples[sample.thread_id] = None
+ if self.trace_offcpu_mode in ('on-off-cpu', 'mixed-on-off-cpu'):
+ self._add_to_sample_queue(sample)
+
+ def _process_context_switch(self, context_switch) -> None:
+ if not context_switch.switch_on:
+ return
+ if prev_offcpu_sample := self.offcpu_samples.get(context_switch.thread_id):
+ prev_offcpu_sample.event_count = max(context_switch.time - prev_offcpu_sample.time, 1)
+ self.offcpu_samples[context_switch.thread_id] = None
+ self._add_to_sample_queue(prev_offcpu_sample)
+
+ def _add_to_sample_queue(self, sample) -> None:
+ self.sample_queue.append(sample)
+
+ def GetCurrentSample(self) -> Optional[ProtoSample]:
+ if not self.sample_queue:
+ return None
+ sample = self.sample_queue[0]
+ thread = self.thread_map[sample.thread_id]
+ return ProtoSample(
+ ip=0, pid=thread.process_id, tid=thread.thread_id, thread_comm=thread.thread_name,
+ time=sample.time, in_kernel=False, cpu=0, period=sample.event_count)
+
+ def GetEventOfCurrentSample(self) -> ProtoEvent:
+ sample = self.sample_queue[0]
+ event_type_id = 0 if self.trace_offcpu_mode == 'mixed-on-off-cpu' else sample.event_type_id
+ event_name = self._get_event_name(event_type_id)
+ return ProtoEvent(name=event_name, tracing_data_format=None)
+
+ def _get_event_name(self, event_type_id: int) -> str:
+ return self.meta_info.event_type[event_type_id]
+
+ def GetSymbolOfCurrentSample(self) -> ProtoSymbol:
+ sample = self.sample_queue[0]
+ node = sample.callchain[0]
+ return self._build_symbol(node)
+
+ def GetCallChainOfCurrentSample(self) -> ProtoCallChain:
+ entries = []
+ sample = self.sample_queue[0]
+ for node in sample.callchain[1:]:
+ symbol = self._build_symbol(node)
+ entries.append(ProtoCallChainEntry(ip=0, symbol=symbol))
+ return ProtoCallChain(nr=len(entries), entries=entries)
+
+ def _build_symbol(self, node) -> ProtoSymbol:
+ file = self.files[node.file_id]
+ if node.symbol_id == -1:
+ symbol_name = 'unknown'
+ fake_symbol_addr = self.fake_mapping_starts[node.file_id] + len(file.symbol)
+ fake_symbol_pgoff = 0
+ else:
+ symbol_name = file.symbol[node.symbol_id]
+ fake_symbol_addr = self.fake_mapping_starts[node.file_id] = node.symbol_id + 1
+ fake_symbol_pgoff = node.symbol_id + 1
+ mapping = ProtoMapping(fake_symbol_addr, 1, fake_symbol_pgoff)
+ return ProtoSymbol(dso_name=file.path, vaddr_in_file=node.vaddr_in_file,
+ symbol_name=symbol_name, symbol_addr=0, symbol_len=1, mapping=[mapping])
+
+ def GetBuildIdForPath(self, path: str) -> str:
+ return ''
+
+ def GetRecordCmd(self) -> str:
+ return ''
+
+ def GetArch(self) -> str:
+ return ''
+
+ def MetaInfo(self) -> Dict[str, str]:
+ return {}
+
+
+def GetReportLib(record_file: str) -> Union[ReportLib, ProtoFileReportLib]:
+ if ProtoFileReportLib.is_supported_format(record_file):
+ lib = ProtoFileReportLib()
+ else:
+ lib = ReportLib()
+ lib.SetRecordFile(record_file)
+ return lib
diff --git a/simpleperf/scripts/simpleperf_utils.py b/simpleperf/scripts/simpleperf_utils.py
index f8d50dca..e536b1b5 100644
--- a/simpleperf/scripts/simpleperf_utils.py
+++ b/simpleperf/scripts/simpleperf_utils.py
@@ -31,7 +31,7 @@ import shutil
import subprocess
import sys
import time
-from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union
+from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union, TextIO
NDK_ERROR_MESSAGE = "Please install the Android NDK (https://developer.android.com/studio/projects/install-ndk), then set NDK path with --ndk_path option."
@@ -360,6 +360,8 @@ class AdbHelper(object):
return 'x86_64'
if '86' in output:
return 'x86'
+ if 'riscv64' in output:
+ return 'riscv64'
log_fatal('unsupported architecture: %s' % output.strip())
return ''
@@ -787,6 +789,24 @@ class SourceFileSearcher(object):
return os.path.join(best_matched_rparent[::-1], file_name)
+class AddrRange:
+ def __init__(self, start: int, len: int):
+ self.start = start
+ self.len = len
+
+ @property
+ def end(self) -> int:
+ return self.start + self.len
+
+ def is_in_range(self, addr: int) -> bool:
+ return addr >= self.start and addr < self.end
+
+
+class Disassembly:
+ def __init__(self):
+ self.lines: List[Tuple[str, int]] = []
+
+
class Objdump(object):
""" A wrapper of objdump to disassemble code. """
@@ -806,9 +826,8 @@ class Objdump(object):
return None
return (str(real_path), arch)
- def disassemble_code(self, dso_info, start_addr, addr_len) -> List[Tuple[str, int]]:
- """ Disassemble [start_addr, start_addr + addr_len] of dso_path.
- Return a list of pair (disassemble_code_line, addr).
+ def disassemble_function(self, dso_info, addr_range: AddrRange) -> Optional[Disassembly]:
+ """ Disassemble code for an addr range in a binary.
"""
real_path, arch = dso_info
objdump_path = self.objdump_paths.get(arch)
@@ -818,15 +837,16 @@ class Objdump(object):
log_exit("Can't find llvm-objdump." + NDK_ERROR_MESSAGE)
self.objdump_paths[arch] = objdump_path
- # 3. Run objdump.
+ # Run objdump.
args = [objdump_path, '-dlC', '--no-show-raw-insn',
- '--start-address=0x%x' % start_addr,
- '--stop-address=0x%x' % (start_addr + addr_len),
+ '--start-address=0x%x' % addr_range.start,
+ '--stop-address=0x%x' % (addr_range.end),
real_path]
if arch == 'arm' and 'llvm-objdump' in objdump_path:
args += ['--print-imm-hex']
+ logging.debug('disassembling: %s', ' '.join(args))
try:
- subproc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+ subproc = subprocess.Popen(args, stdout=subprocess.PIPE)
(stdoutdata, _) = subproc.communicate()
stdoutdata = bytes_to_str(stdoutdata)
except OSError:
@@ -834,7 +854,7 @@ class Objdump(object):
if not stdoutdata:
return None
- result = []
+ result = Disassembly()
for line in stdoutdata.split('\n'):
line = line.rstrip() # Remove '\r' on Windows.
items = line.split(':', 1)
@@ -842,9 +862,81 @@ class Objdump(object):
addr = int(items[0], 16)
except ValueError:
addr = 0
- result.append((line, addr))
+ result.lines.append((line, addr))
return result
+ def disassemble_functions(self, dso_info, sorted_addr_ranges: List[AddrRange]
+ ) -> Optional[List[Disassembly]]:
+ """ Disassemble code for multiple addr ranges in a binary. sorted_addr_ranges should be
+ sorted by addr_range.start.
+ """
+ if not sorted_addr_ranges:
+ return []
+ real_path, arch = dso_info
+ objdump_path = self.objdump_paths.get(arch)
+ if not objdump_path:
+ objdump_path = ToolFinder.find_tool_path('llvm-objdump', self.ndk_path, arch)
+ if not objdump_path:
+ log_exit("Can't find llvm-objdump." + NDK_ERROR_MESSAGE)
+ self.objdump_paths[arch] = objdump_path
+
+ # Run objdump.
+ start_addr = sorted_addr_ranges[0].start
+ stop_addr = max(addr_range.end for addr_range in sorted_addr_ranges)
+ args = [objdump_path, '-dlC', '--no-show-raw-insn',
+ '--start-address=0x%x' % start_addr,
+ '--stop-address=0x%x' % stop_addr,
+ real_path]
+ if arch == 'arm' and 'llvm-objdump' in objdump_path:
+ args += ['--print-imm-hex']
+ try:
+ proc = subprocess.Popen(args, stdout=subprocess.PIPE, text=True)
+ result = self._parse_disassembly_for_functions(proc.stdout, sorted_addr_ranges)
+ proc.wait()
+ except OSError:
+ return None
+ return result
+
+ def _parse_disassembly_for_functions(self, fh: TextIO, sorted_addr_ranges: List[AddrRange]) -> Optional[List[Disassembly]]:
+ current_id = 0
+ in_range = False
+ result = [Disassembly() for _ in sorted_addr_ranges]
+ while True:
+ line = fh.readline()
+ if not line:
+ break
+ line = line.rstrip() # Remove '\r\n'.
+ addr = self._get_addr_from_disassembly_line(line)
+ if current_id >= len(sorted_addr_ranges):
+ continue
+ if addr:
+ if in_range and not sorted_addr_ranges[current_id].is_in_range(addr):
+ in_range = False
+ if not in_range:
+ # Skip addr ranges before the current address.
+ while current_id < len(sorted_addr_ranges) and sorted_addr_ranges[current_id].end <= addr:
+ current_id += 1
+ if current_id < len(sorted_addr_ranges) and sorted_addr_ranges[current_id].is_in_range(addr):
+ in_range = True
+ if in_range:
+ result[current_id].lines.append((line, addr))
+ return result
+
+ def _get_addr_from_disassembly_line(self, line: str) -> int:
+ # line may be an instruction, like: " 24a469c: stp x29, x30, [sp, #-0x60]!" or
+ # "ffffffc0085d9664: paciasp".
+ # line may be a function start point, like "00000000024a4698 <DoWork()>:".
+ items = line.strip().split()
+ if not items:
+ return 0
+ s = items[0]
+ if s.endswith(':'):
+ s = s[:-1]
+ try:
+ return int(s, 16)
+ except ValueError:
+ return 0
+
class ReadElf(object):
""" A wrapper of readelf. """
@@ -875,6 +967,8 @@ class ReadElf(object):
return 'x86_64'
if output.find('80386') != -1:
return 'x86'
+ if output.find('RISC-V') != -1:
+ return 'riscv64'
except subprocess.CalledProcessError:
pass
return 'unknown'
@@ -1003,6 +1097,7 @@ class ArgParseFormatter(
@dataclass
class ReportLibOptions:
show_art_frames: bool
+ remove_method: List[str]
trace_offcpu: str
proguard_mapping_files: List[str]
sample_filters: List[str]
@@ -1028,6 +1123,8 @@ class BaseArgumentParser(argparse.ArgumentParser):
parser.add_argument('--show-art-frames', '--show_art_frames',
action=argparse.BooleanOptionalAction, default=default_show_art_frames,
help='Show frames of internal methods in the ART Java interpreter.')
+ parser.add_argument('--remove-method', nargs='+', metavar='method_name_regex',
+ help='remove methods with name containing the regular expression')
parser.add_argument(
'--trace-offcpu', choices=['on-cpu', 'off-cpu', 'on-off-cpu', 'mixed-on-off-cpu'],
help="""Set report mode for profiles recorded with --trace-offcpu option. All possible
@@ -1047,6 +1144,8 @@ class BaseArgumentParser(argparse.ArgumentParser):
self, group: Optional[Any] = None, with_pid_shortcut: bool = True):
if not group:
group = self.add_argument_group('Sample filter options')
+ group.add_argument('--cpu', nargs='+', help="""only include samples for the selected cpus.
+ cpu can be a number like 1, or a range like 0-3""")
group.add_argument('--exclude-pid', metavar='pid', nargs='+', type=int,
help='exclude samples for selected processes')
group.add_argument('--exclude-tid', metavar='tid', nargs='+', type=int,
@@ -1084,6 +1183,8 @@ class BaseArgumentParser(argparse.ArgumentParser):
def _build_sample_filter(self, args: argparse.Namespace) -> List[str]:
""" Build sample filters, which can be passed to ReportLib.SetSampleFilter(). """
filters = []
+ if args.cpu:
+ filters.extend(['--cpu', ','.join(args.cpu)])
if args.exclude_pid:
filters.extend(['--exclude-pid', ','.join(str(pid) for pid in args.exclude_pid)])
if args.exclude_tid:
@@ -1123,8 +1224,8 @@ class BaseArgumentParser(argparse.ArgumentParser):
if self.has_report_lib_options:
sample_filters = self._build_sample_filter(namespace)
report_lib_options = ReportLibOptions(
- namespace.show_art_frames, namespace.trace_offcpu, namespace.proguard_mapping_file,
- sample_filters, namespace.aggregate_threads)
+ namespace.show_art_frames, namespace.remove_method, namespace.trace_offcpu,
+ namespace.proguard_mapping_file, sample_filters, namespace.aggregate_threads)
setattr(namespace, 'report_lib_options', report_lib_options)
if not Log.initialized:
diff --git a/simpleperf/scripts/stackcollapse.py b/simpleperf/scripts/stackcollapse.py
index e0e1d86f..94ffaec0 100755
--- a/simpleperf/scripts/stackcollapse.py
+++ b/simpleperf/scripts/stackcollapse.py
@@ -25,7 +25,7 @@
"""
from collections import defaultdict
-from simpleperf_report_lib import ReportLib
+from simpleperf_report_lib import GetReportLib
from simpleperf_utils import BaseArgumentParser, flatten_arg_list, ReportLibOptions
from typing import DefaultDict, List, Optional, Set
@@ -45,14 +45,12 @@ def collapse_stacks(
include_addrs: bool,
report_lib_options: ReportLibOptions):
"""read record_file, aggregate per-stack and print totals per-stack"""
- lib = ReportLib()
+ lib = GetReportLib(record_file)
if include_addrs:
lib.ShowIpForUnknownSymbol()
if symfs_dir is not None:
lib.SetSymfs(symfs_dir)
- if record_file is not None:
- lib.SetRecordFile(record_file)
if kallsyms_file is not None:
lib.SetKallsymsFile(kallsyms_file)
lib.SetReportOptions(report_lib_options)
diff --git a/simpleperf/scripts/test/app_profiler_test.py b/simpleperf/scripts/test/app_profiler_test.py
index 9fc4bcc5..b6b39ba8 100644
--- a/simpleperf/scripts/test/app_profiler_test.py
+++ b/simpleperf/scripts/test/app_profiler_test.py
@@ -16,6 +16,8 @@
from app_profiler import NativeLibDownloader
import shutil
+import subprocess
+import sys
from simpleperf_utils import str_to_bytes, bytes_to_str, remove
from . test_utils import TestBase, TestHelper, INFERNO_SCRIPT
@@ -53,6 +55,13 @@ class TestNativeProfiling(TestBase):
return
self.run_cmd(['app_profiler.py', '--system_wide', '-r', '--duration 1'])
+ def test_device_not_connected(self):
+ args = [sys.executable, TestHelper.script_path('app_profiler.py'), '-cmd', 'ls']
+ proc = subprocess.run(
+ args, env={'ANDROID_SERIAL': 'not_exist_device'},
+ stderr=subprocess.PIPE, text=True)
+ self.assertIn('No Android device is connected via ADB.', proc.stderr)
+
class TestNativeLibDownloader(TestBase):
def setUp(self):
diff --git a/simpleperf/scripts/test/app_test.py b/simpleperf/scripts/test/app_test.py
index 4b31e4d4..2c6835d3 100644
--- a/simpleperf/scripts/test/app_test.py
+++ b/simpleperf/scripts/test/app_test.py
@@ -23,6 +23,7 @@ import subprocess
import time
from typing import List, Tuple
+from simpleperf_report_lib import ReportLib
from simpleperf_utils import remove
from . test_utils import TestBase, TestHelper, AdbHelper, INFERNO_SCRIPT
@@ -62,6 +63,9 @@ class TestExampleBase(TestBase):
def setUp(self):
super(TestExampleBase, self).setUp()
+ if TestHelper.android_version == 8 and (
+ 'ExampleJava' in self.id() or 'ExampleKotlin' in self.id()):
+ self.skipTest('Profiling java code needs wrap.sh on Android O (8.x)')
if 'TraceOffCpu' in self.id() and not TestHelper.is_trace_offcpu_supported():
self.skipTest('trace-offcpu is not supported on device')
# Use testcase_dir to share a common perf.data for reporting. So we don't need to
@@ -265,7 +269,13 @@ class TestRecordingRealApps(TestBase):
def test_recording_endless_tunnel(self):
self.install_apk(TestHelper.testdata_path(
'EndlessTunnel.apk'), 'com.google.sample.tunnel')
- self.start_app('shell am start -n com.google.sample.tunnel/android.app.NativeActivity -a ' +
- 'android.intent.action.MAIN -c android.intent.category.LAUNCHER')
- self.record_data('com.google.sample.tunnel', '-e cpu-clock -g --duration 10')
+ # Test using --launch to start the app.
+ self.run_cmd(['app_profiler.py', '--app', 'com.google.sample.tunnel',
+ '--launch', '-r', '-e cpu-clock -g --duration 10'])
self.check_symbol_in_record_file('PlayScene::DoFrame')
+
+ # Check app versioncode.
+ report = ReportLib()
+ meta_info = report.MetaInfo()
+ self.assertEqual(meta_info.get('app_versioncode'), '1')
+ report.Close()
diff --git a/simpleperf/scripts/test/do_test.py b/simpleperf/scripts/test/do_test.py
index 012dc629..d341c20b 100755
--- a/simpleperf/scripts/test/do_test.py
+++ b/simpleperf/scripts/test/do_test.py
@@ -59,6 +59,7 @@ from . report_html_test import *
from . report_lib_test import *
from . report_sample_test import *
from . run_simpleperf_on_device_test import *
+from . sample_filter_test import *
from . stackcollapse_test import *
from . tools_test import *
from . test_utils import TestHelper
@@ -130,10 +131,12 @@ def get_test_type(test: str) -> Optional[str]:
'TestDebugUnwindReporter',
'TestInferno',
'TestPprofProtoGenerator',
+ 'TestProtoFileReportLib',
'TestPurgatorio',
'TestReportHtml',
'TestReportLib',
'TestReportSample',
+ 'TestSampleFilter',
'TestStackCollapse',
'TestTools',
'TestGeckoProfileGenerator'):
diff --git a/simpleperf/scripts/test/gecko_profile_generator_test.py b/simpleperf/scripts/test/gecko_profile_generator_test.py
index 97cedd6f..1c744f94 100644
--- a/simpleperf/scripts/test/gecko_profile_generator_test.py
+++ b/simpleperf/scripts/test/gecko_profile_generator_test.py
@@ -41,6 +41,38 @@ class TestGeckoProfileGenerator(TestBase):
golden_path = TestHelper.testdata_path('perf_with_interpreter_frames.gecko.json')
with open(golden_path) as f:
want = json.load(f)
+ # Golden data is formatted with `jq` tool (https://stedolan.github.io/jq/).
+ # Regenerate golden data by running:
+ # $ apt install jq
+ # $ ./gecko_profile_generator.py --remove-gaps 0 -i ../testdata/perf_with_interpreter_frames.data | jq > test/script_testdata/perf_with_interpreter_frames.gecko.json
+ self.assertEqual(
+ json.dumps(got, sort_keys=True, indent=2),
+ json.dumps(want, sort_keys=True, indent=2))
+
+ def test_golden_offcpu(self):
+ output = self.run_generator('perf_with_tracepoint_event.data', ['--remove-gaps', '0'])
+ got = json.loads(output)
+ golden_path = TestHelper.testdata_path('perf_with_tracepoint_event.gecko.json')
+ with open(golden_path) as f:
+ want = json.load(f)
+ # Golden data is formatted with `jq` tool (https://stedolan.github.io/jq/).
+ # Regenerate golden data by running:
+ # $ apt install jq
+ # $ ./gecko_profile_generator.py --remove-gaps 0 -i ../testdata/perf_with_tracepoint_event.data | jq > test/script_testdata/perf_with_tracepoint_event.gecko.json
+ self.assertEqual(
+ json.dumps(got, sort_keys=True, indent=2),
+ json.dumps(want, sort_keys=True, indent=2))
+
+ def test_golden_jit(self):
+ output = self.run_generator('perf_with_jit_symbol.data', ['--remove-gaps', '0'])
+ got = json.loads(output)
+ golden_path = TestHelper.testdata_path('perf_with_jit_symbol.gecko.json')
+ with open(golden_path) as f:
+ want = json.load(f)
+ # Golden data is formatted with `jq` tool (https://stedolan.github.io/jq/).
+ # Regenerate golden data by running:
+ # $ apt install jq
+ # $ ./gecko_profile_generator.py --remove-gaps 0 -i ../testdata/perf_with_jit_symbol.data | jq > test/script_testdata/perf_with_jit_symbol.gecko.json
self.assertEqual(
json.dumps(got, sort_keys=True, indent=2),
json.dumps(want, sort_keys=True, indent=2))
diff --git a/simpleperf/scripts/test/java_app_test.py b/simpleperf/scripts/test/java_app_test.py
index efd17461..5f74c643 100644
--- a/simpleperf/scripts/test/java_app_test.py
+++ b/simpleperf/scripts/test/java_app_test.py
@@ -227,7 +227,7 @@ class TestExampleJavaTraceOffCpu(TestExampleBase):
("RunFunction", 20, 20),
("SleepFunction", 20, 0),
("line 24", 1, 0),
- ("line 32", 20, 0)])
+ ("line 31", 20, 0)])
self.run_cmd([INFERNO_SCRIPT, "-sc"])
self.check_inferno_report_html(
[('simpleperf.example.java.SleepActivity$1.run', 80),
diff --git a/simpleperf/scripts/test/kotlin_app_test.py b/simpleperf/scripts/test/kotlin_app_test.py
index 08d207ab..74f50229 100644
--- a/simpleperf/scripts/test/kotlin_app_test.py
+++ b/simpleperf/scripts/test/kotlin_app_test.py
@@ -140,8 +140,8 @@ class TestExampleKotlinTraceOffCpu(TestExampleBase):
("run", 80, 0),
("RunFunction", 20, 20),
("SleepFunction", 20, 0),
- ("line 24", 20, 0),
- ("line 32", 20, 0)])
+ ("line 23", 20, 0),
+ ("line 31", 20, 0)])
self.run_cmd([INFERNO_SCRIPT, "-sc"])
self.check_inferno_report_html([
diff --git a/simpleperf/scripts/test/pprof_proto_generator_test.py b/simpleperf/scripts/test/pprof_proto_generator_test.py
index 297cf149..1abd61b8 100644
--- a/simpleperf/scripts/test/pprof_proto_generator_test.py
+++ b/simpleperf/scripts/test/pprof_proto_generator_test.py
@@ -96,6 +96,10 @@ class TestPprofProtoGenerator(TestBase):
""" Test the build ids generated are not padded with zeros. """
self.assertIn('build_id: e3e938cc9e40de2cfe1a5ac7595897de(', self.run_generator())
+ def test_time_nanos(self):
+ """ Test the timestamp is adjusted to be nanoseconds. """
+ self.assertIn('time_nanos: 1516268753000000000\n', self.run_generator())
+
def test_build_id_with_binary_cache(self):
""" Test the build ids for elf files in binary_cache are not padded with zero. """
# Test with binary_cache.
@@ -218,7 +222,7 @@ class TestPprofProtoGenerator(TestBase):
# Read recording file.
config = {'ndk_path': TestHelper.ndk_path, 'max_chain_length': 1000000,
- 'report_lib_options': ReportLibOptions(False, '', None, None, None)}
+ 'report_lib_options': ReportLibOptions(False, None, '', None, None, None)}
generator = PprofProfileGenerator(config)
generator.load_record_file(testdata_file)
@@ -291,3 +295,6 @@ class TestPprofProtoGenerator(TestBase):
self.assertIn(31881, threads)
self.assertNotIn(31850, threads)
os.unlink(filter_file.name)
+
+ def test_report_sample_proto_file(self):
+ self.run_generator('', testdata_file='display_bitmaps.proto_data')
diff --git a/simpleperf/scripts/test/report_html_test.py b/simpleperf/scripts/test/report_html_test.py
index 7241a5e1..32e58219 100644
--- a/simpleperf/scripts/test/report_html_test.py
+++ b/simpleperf/scripts/test/report_html_test.py
@@ -276,3 +276,18 @@ class TestReportHtml(TestBase):
self.assertEqual(thread_names['AsyncTask.*'], 19)
self.assertNotIn('AsyncTask #3', thread_names)
self.assertNotIn('AsyncTask #4', thread_names)
+
+ def test_sort_call_graph_by_function_name(self):
+ record_data = self.get_record_data(
+ ['-i', TestHelper.testdata_path('perf_display_bitmaps.data'),
+ '--aggregate-threads', '.*'])
+
+ def get_func_name(func_id: int) -> str:
+ return record_data['functionMap'][str(func_id)]['f']
+
+ # Test if the top functions are sorted by function names.
+ thread = record_data['sampleInfo'][0]['processes'][0]['threads'][0]
+ top_functions = [get_func_name(c['f']) for c in thread['g']['c']]
+ self.assertIn('__libc_init', top_functions)
+ self.assertIn('__start_thread', top_functions)
+ self.assertEqual(top_functions, sorted(top_functions))
diff --git a/simpleperf/scripts/test/report_lib_test.py b/simpleperf/scripts/test/report_lib_test.py
index 8e212a27..e633b6ab 100644
--- a/simpleperf/scripts/test/report_lib_test.py
+++ b/simpleperf/scripts/test/report_lib_test.py
@@ -15,10 +15,14 @@
# limitations under the License.
import os
+from pathlib import Path
+import shutil
+import subprocess
import tempfile
from typing import Dict, List, Optional, Set
-from simpleperf_report_lib import ReportLib
+from simpleperf_report_lib import ReportLib, ProtoFileReportLib
+from simpleperf_utils import get_host_binary_path, ReadElf
from . test_utils import TestBase, TestHelper
@@ -133,6 +137,28 @@ class TestReportLib(TestBase):
report_lib.ShowArtFrames(True)
self.assertTrue(has_art_frame(report_lib))
+ def test_remove_method(self):
+ def get_methods(report_lib) -> Set[str]:
+ methods = set()
+ report_lib.SetRecordFile(TestHelper.testdata_path('perf_display_bitmaps.data'))
+ while True:
+ sample = report_lib.GetNextSample()
+ if not sample:
+ break
+ methods.add(report_lib.GetSymbolOfCurrentSample().symbol_name)
+ callchain = report_lib.GetCallChainOfCurrentSample()
+ for i in range(callchain.nr):
+ methods.add(callchain.entries[i].symbol.symbol_name)
+ report_lib.Close()
+ return methods
+
+ report_lib = ReportLib()
+ report_lib.RemoveMethod('android.view')
+ methods = get_methods(report_lib)
+ self.assertFalse(any('android.view' in method for method in methods))
+ self.assertTrue(any('android.widget' in method for method in methods))
+
+
def test_merge_java_methods(self):
def parse_dso_names(report_lib):
dso_names = set()
@@ -312,6 +338,25 @@ class TestReportLib(TestBase):
self.assertNotIn(31850, threads)
os.unlink(filter_file.name)
+ def test_set_sample_filter_for_cpu(self):
+ """ Test --cpu in ReportLib.SetSampleFilter(). """
+ def get_cpus_for_filter(filters: List[str]) -> Set[int]:
+ self.report_lib.Close()
+ self.report_lib = ReportLib()
+ self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_display_bitmaps.data'))
+ self.report_lib.SetSampleFilter(filters)
+ cpus = set()
+ while self.report_lib.GetNextSample():
+ sample = self.report_lib.GetCurrentSample()
+ cpus.add(sample.cpu)
+ return cpus
+
+ cpus = get_cpus_for_filter(['--cpu', '0,1-2'])
+ self.assertIn(0, cpus)
+ self.assertIn(1, cpus)
+ self.assertIn(2, cpus)
+ self.assertNotIn(3, cpus)
+
def test_aggregate_threads(self):
""" Test using ReportLib.AggregateThreads(). """
def get_thread_names(aggregate_regex_list: Optional[List[str]]) -> Dict[str, int]:
@@ -332,3 +377,113 @@ class TestReportLib(TestBase):
self.assertEqual(thread_names['AsyncTask.*'], 19)
self.assertNotIn('AsyncTask #3', thread_names)
self.assertNotIn('AsyncTask #4', thread_names)
+
+ def test_use_vmlinux(self):
+ """ Test if we can use vmlinux in symfs_dir. """
+ record_file = TestHelper.testdata_path('perf_test_vmlinux.data')
+ # Create a symfs_dir.
+ symfs_dir = Path('symfs_dir')
+ symfs_dir.mkdir()
+ shutil.copy(TestHelper.testdata_path('vmlinux'), symfs_dir)
+ kernel_build_id = ReadElf(TestHelper.ndk_path).get_build_id(symfs_dir / 'vmlinux')
+ (symfs_dir / 'build_id_list').write_text('%s=vmlinux' % kernel_build_id)
+
+ # Check if vmlinux in symfs_dir is used, when we set record file before setting symfs_dir.
+ self.report_lib.SetRecordFile(record_file)
+ self.report_lib.SetSymfs(str(symfs_dir))
+ sample = self.report_lib.GetNextSample()
+ self.assertIsNotNone(sample)
+ symbol = self.report_lib.GetSymbolOfCurrentSample()
+ self.assertEqual(symbol.dso_name, "[kernel.kallsyms]")
+ # vaddr_in_file and symbol_addr are adjusted after using vmlinux.
+ self.assertEqual(symbol.vaddr_in_file, 0xffffffc008fb3e28)
+ self.assertEqual(symbol.symbol_name, "_raw_spin_unlock_irq")
+ self.assertEqual(symbol.symbol_addr, 0xffffffc008fb3e0c)
+ self.assertEqual(symbol.symbol_len, 0x4c)
+
+
+class TestProtoFileReportLib(TestBase):
+ def test_smoke(self):
+ report_lib = ProtoFileReportLib()
+ report_lib.SetRecordFile(TestHelper.testdata_path('display_bitmaps.proto_data'))
+ sample_count = 0
+ while True:
+ sample = report_lib.GetNextSample()
+ if sample is None:
+ report_lib.Close()
+ break
+ sample_count += 1
+ event = report_lib.GetEventOfCurrentSample()
+ self.assertEqual(event.name, 'cpu-clock')
+ report_lib.GetSymbolOfCurrentSample()
+ report_lib.GetCallChainOfCurrentSample()
+ self.assertEqual(sample_count, 525)
+
+ def convert_perf_data_to_proto_file(self, perf_data_path: str) -> str:
+ simpleperf_path = get_host_binary_path('simpleperf')
+ proto_file_path = 'perf.trace'
+ subprocess.check_call([simpleperf_path, 'report-sample', '--show-callchain', '--protobuf',
+ '--remove-gaps', '0', '-i', perf_data_path, '-o', proto_file_path])
+ return proto_file_path
+
+ def test_set_trace_offcpu_mode(self):
+ report_lib = ProtoFileReportLib()
+ # GetSupportedTraceOffCpuModes() before SetRecordFile() triggers RuntimeError.
+ with self.assertRaises(RuntimeError):
+ report_lib.GetSupportedTraceOffCpuModes()
+
+ mode_dict = {
+ 'on-cpu': {
+ 'cpu-clock:u': (208, 52000000),
+ 'sched:sched_switch': (0, 0),
+ },
+ 'off-cpu': {
+ 'cpu-clock:u': (0, 0),
+ 'sched:sched_switch': (91, 344124304),
+ },
+ 'on-off-cpu': {
+ 'cpu-clock:u': (208, 52000000),
+ 'sched:sched_switch': (91, 344124304),
+ },
+ 'mixed-on-off-cpu': {
+ 'cpu-clock:u': (299, 396124304),
+ 'sched:sched_switch': (0, 0),
+ },
+ }
+
+ proto_file_path = self.convert_perf_data_to_proto_file(
+ TestHelper.testdata_path('perf_with_trace_offcpu_v2.data'))
+ report_lib.SetRecordFile(proto_file_path)
+ self.assertEqual(set(report_lib.GetSupportedTraceOffCpuModes()), set(mode_dict.keys()))
+ for mode, expected_values in mode_dict.items():
+ report_lib.Close()
+ report_lib = ProtoFileReportLib()
+ report_lib.SetRecordFile(proto_file_path)
+ report_lib.SetTraceOffCpuMode(mode)
+
+ cpu_clock_period = 0
+ cpu_clock_samples = 0
+ sched_switch_period = 0
+ sched_switch_samples = 0
+ while report_lib.GetNextSample():
+ sample = report_lib.GetCurrentSample()
+ event = report_lib.GetEventOfCurrentSample()
+ if event.name == 'cpu-clock:u':
+ cpu_clock_period += sample.period
+ cpu_clock_samples += 1
+ else:
+ self.assertEqual(event.name, 'sched:sched_switch')
+ sched_switch_period += sample.period
+ sched_switch_samples += 1
+ self.assertEqual(cpu_clock_samples, expected_values['cpu-clock:u'][0])
+ self.assertEqual(cpu_clock_period, expected_values['cpu-clock:u'][1])
+ self.assertEqual(sched_switch_samples, expected_values['sched:sched_switch'][0])
+ self.assertEqual(sched_switch_period, expected_values['sched:sched_switch'][1])
+
+ # Check trace-offcpu modes on a profile not recorded with --trace-offcpu.
+ report_lib.Close()
+ report_lib = ProtoFileReportLib()
+ proto_file_path = self.convert_perf_data_to_proto_file(
+ TestHelper.testdata_path('perf.data'))
+ report_lib.SetRecordFile(proto_file_path)
+ self.assertEqual(report_lib.GetSupportedTraceOffCpuModes(), [])
diff --git a/simpleperf/scripts/test/sample_filter_test.py b/simpleperf/scripts/test/sample_filter_test.py
new file mode 100644
index 00000000..46d51d08
--- /dev/null
+++ b/simpleperf/scripts/test/sample_filter_test.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 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.
+
+import json
+import os
+from pathlib import Path
+import re
+import tempfile
+from typing import List, Optional, Set
+
+from . test_utils import TestBase, TestHelper
+
+
+class TestSampleFilter(TestBase):
+ def test_show_time_range(self):
+ testdata_file = TestHelper.testdata_path('perf_display_bitmaps.data')
+ output = self.run_cmd(['sample_filter.py', '-i', testdata_file,
+ '--show-time-range'], return_output=True)
+ self.assertIn('0.134 s', output)
+
+ def test_split_time_range(self):
+ testdata_file = TestHelper.testdata_path('perf_display_bitmaps.data')
+ self.run_cmd(['sample_filter.py', '-i', testdata_file, '--split-time-range', '2'])
+ part1_data = Path('sample_filter_part1').read_text()
+ self.assertIn('GLOBAL_BEGIN 684943449406175', part1_data)
+ self.assertIn('GLOBAL_END 684943516618526', part1_data)
+ part2_data = Path('sample_filter_part2').read_text()
+ self.assertIn('GLOBAL_BEGIN 684943516618526', part2_data)
+ self.assertIn('GLOBAL_END 684943583830876', part2_data)
diff --git a/simpleperf/scripts/test/script_testdata/display_bitmaps.proto_data b/simpleperf/scripts/test/script_testdata/display_bitmaps.proto_data
new file mode 100644
index 00000000..6b8b2690
--- /dev/null
+++ b/simpleperf/scripts/test/script_testdata/display_bitmaps.proto_data
Binary files differ
diff --git a/simpleperf/scripts/test/script_testdata/perf_test_vmlinux.data b/simpleperf/scripts/test/script_testdata/perf_test_vmlinux.data
new file mode 100644
index 00000000..08de1993
--- /dev/null
+++ b/simpleperf/scripts/test/script_testdata/perf_test_vmlinux.data
Binary files differ
diff --git a/simpleperf/scripts/test/script_testdata/perf_with_interpreter_frames.gecko.json b/simpleperf/scripts/test/script_testdata/perf_with_interpreter_frames.gecko.json
index 1903451a..264dc44c 100644
--- a/simpleperf/scripts/test/script_testdata/perf_with_interpreter_frames.gecko.json
+++ b/simpleperf/scripts/test/script_testdata/perf_with_interpreter_frames.gecko.json
@@ -2,6 +2,7 @@
"libs": [],
"meta": {
"abi": "aarch64",
+ "appBuildID": null,
"asyncstack": 1,
"categories": [
{
@@ -40,11 +41,25 @@
]
},
{
+ "color": "blue",
+ "name": "Off-CPU",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
"color": "grey",
"name": "Other",
"subcategories": [
"Other"
]
+ },
+ {
+ "color": "green",
+ "name": "JIT",
+ "subcategories": [
+ "Other"
+ ]
}
],
"debug": 0,
diff --git a/simpleperf/scripts/test/script_testdata/perf_with_jit_symbol.gecko.json b/simpleperf/scripts/test/script_testdata/perf_with_jit_symbol.gecko.json
new file mode 100644
index 00000000..3988fefb
--- /dev/null
+++ b/simpleperf/scripts/test/script_testdata/perf_with_jit_symbol.gecko.json
@@ -0,0 +1,1487 @@
+{
+ "libs": [],
+ "meta": {
+ "abi": "aarch64",
+ "appBuildID": null,
+ "asyncstack": 1,
+ "categories": [
+ {
+ "color": "yellow",
+ "name": "User",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
+ "color": "orange",
+ "name": "Kernel",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
+ "color": "yellow",
+ "name": "Native",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
+ "color": "green",
+ "name": "DEX",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
+ "color": "green",
+ "name": "OAT",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
+ "color": "blue",
+ "name": "Off-CPU",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
+ "color": "grey",
+ "name": "Other",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
+ "color": "green",
+ "name": "JIT",
+ "subcategories": [
+ "Other"
+ ]
+ }
+ ],
+ "debug": 0,
+ "device": "Google:Pixel 4:flame",
+ "gcpoison": 0,
+ "interval": 1,
+ "markerSchema": [],
+ "oscpu": null,
+ "platform": null,
+ "presymbolicated": true,
+ "processType": 0,
+ "product": "/data/user/0/com.android.simpleperf.debuggable/simpleperf record --app com.android.simpleperf.debuggable --in-app --tracepoint-events /data/local/tmp/tracepoint_events --out-fd 3 --stop-signal-fd 4 -g --duration 0.1",
+ "shutdownTime": null,
+ "stackwalk": 1,
+ "startTime": 1596654389000,
+ "version": 24
+ },
+ "pausedRanges": [],
+ "processes": [],
+ "threads": [
+ {
+ "frameTable": {
+ "data": [
+ [
+ 0,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 2,
+ 0
+ ],
+ [
+ 1,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 2,
+ 0
+ ],
+ [
+ 2,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 0,
+ 0
+ ],
+ [
+ 3,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 3,
+ 0
+ ],
+ [
+ 4,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 7,
+ 0
+ ],
+ [
+ 5,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 2,
+ 0
+ ],
+ [
+ 6,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 2,
+ 0
+ ],
+ [
+ 7,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 8,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 9,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 10,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 11,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 12,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 13,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 14,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 15,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 16,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 17,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 18,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 19,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 20,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 21,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 22,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 2,
+ 0
+ ],
+ [
+ 23,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 2,
+ 0
+ ],
+ [
+ 24,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 25,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 26,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 27,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 28,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 29,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 30,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 31,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ]
+ ],
+ "schema": {
+ "category": 7,
+ "column": 6,
+ "implementation": 3,
+ "innerWindowID": 2,
+ "line": 5,
+ "location": 0,
+ "optimizations": 4,
+ "relevantForJS": 1,
+ "subcategory": 8
+ }
+ },
+ "markers": {
+ "data": [],
+ "schema": {
+ "category": 4,
+ "data": 5,
+ "endTime": 2,
+ "name": 0,
+ "phase": 3,
+ "startTime": 1
+ }
+ },
+ "name": "BusyThread",
+ "pid": 7601,
+ "processType": "default",
+ "registerTime": 0,
+ "samples": {
+ "data": [
+ [
+ 14,
+ 83638048.442366,
+ 0
+ ],
+ [
+ 14,
+ 83638048.483251,
+ 0
+ ],
+ [
+ 14,
+ 83638048.502626,
+ 0
+ ],
+ [
+ 14,
+ 83638048.522262,
+ 0
+ ],
+ [
+ 14,
+ 83638048.539241,
+ 0
+ ],
+ [
+ 14,
+ 83638048.558251,
+ 0
+ ],
+ [
+ 14,
+ 83638048.57523,
+ 0
+ ],
+ [
+ 14,
+ 83638048.593512,
+ 0
+ ],
+ [
+ 14,
+ 83638048.610178,
+ 0
+ ],
+ [
+ 14,
+ 83638048.62872,
+ 0
+ ],
+ [
+ 14,
+ 83638048.645491,
+ 0
+ ],
+ [
+ 14,
+ 83638048.66372,
+ 0
+ ],
+ [
+ 14,
+ 83638048.68023,
+ 0
+ ],
+ [
+ 6,
+ 83638048.735439,
+ 0
+ ],
+ [
+ 3,
+ 83638048.888199,
+ 0
+ ],
+ [
+ 3,
+ 83638049.11023,
+ 0
+ ],
+ [
+ 3,
+ 83638049.350439,
+ 0
+ ],
+ [
+ 3,
+ 83638049.594658,
+ 0
+ ],
+ [
+ 3,
+ 83638049.839814,
+ 0
+ ],
+ [
+ 3,
+ 83638050.085908,
+ 0
+ ],
+ [
+ 3,
+ 83638050.33247,
+ 0
+ ],
+ [
+ 3,
+ 83638050.581741,
+ 0
+ ],
+ [
+ 3,
+ 83638050.82721,
+ 0
+ ],
+ [
+ 3,
+ 83638051.074814,
+ 0
+ ],
+ [
+ 3,
+ 83638051.323043,
+ 0
+ ],
+ [
+ 3,
+ 83638051.571481,
+ 0
+ ],
+ [
+ 3,
+ 83638051.82747,
+ 0
+ ],
+ [
+ 3,
+ 83638052.074658,
+ 0
+ ],
+ [
+ 3,
+ 83638052.321637,
+ 0
+ ],
+ [
+ 3,
+ 83638052.569085,
+ 0
+ ],
+ [
+ 6,
+ 83638052.822054,
+ 0
+ ],
+ [
+ 6,
+ 83638053.209345,
+ 0
+ ],
+ [
+ 3,
+ 83638056.655752,
+ 0
+ ],
+ [
+ 3,
+ 83638056.861742,
+ 0
+ ],
+ [
+ 3,
+ 83638057.071846,
+ 0
+ ],
+ [
+ 3,
+ 83638057.300179,
+ 0
+ ],
+ [
+ 3,
+ 83638057.615544,
+ 0
+ ],
+ [
+ 3,
+ 83638057.799919,
+ 0
+ ],
+ [
+ 3,
+ 83638058.105752,
+ 0
+ ],
+ [
+ 19,
+ 83638058.418773,
+ 0
+ ],
+ [
+ 3,
+ 83638058.646794,
+ 0
+ ],
+ [
+ 3,
+ 83638058.866742,
+ 0
+ ],
+ [
+ 3,
+ 83638059.140388,
+ 0
+ ],
+ [
+ 3,
+ 83638059.315961,
+ 0
+ ],
+ [
+ 3,
+ 83638059.540127,
+ 0
+ ],
+ [
+ 3,
+ 83638059.777002,
+ 0
+ ],
+ [
+ 3,
+ 83638060.123617,
+ 0
+ ],
+ [
+ 3,
+ 83638060.264086,
+ 0
+ ],
+ [
+ 3,
+ 83638060.521586,
+ 0
+ ],
+ [
+ 3,
+ 83638060.750023,
+ 0
+ ],
+ [
+ 3,
+ 83638061.001742,
+ 0
+ ],
+ [
+ 3,
+ 83638061.298565,
+ 0
+ ],
+ [
+ 3,
+ 83638061.51044,
+ 0
+ ],
+ [
+ 3,
+ 83638061.766742,
+ 0
+ ],
+ [
+ 3,
+ 83638062.019346,
+ 0
+ ],
+ [
+ 3,
+ 83638062.271846,
+ 0
+ ],
+ [
+ 3,
+ 83638062.523148,
+ 0
+ ],
+ [
+ 3,
+ 83638062.785023,
+ 0
+ ],
+ [
+ 3,
+ 83638063.034034,
+ 0
+ ],
+ [
+ 3,
+ 83638063.278357,
+ 0
+ ],
+ [
+ 3,
+ 83638063.52893,
+ 0
+ ],
+ [
+ 22,
+ 83638063.948201,
+ 0
+ ],
+ [
+ 24,
+ 83638071.922368,
+ 0
+ ],
+ [
+ 3,
+ 83638072.241587,
+ 0
+ ],
+ [
+ 3,
+ 83638072.398774,
+ 0
+ ],
+ [
+ 3,
+ 83638072.633149,
+ 0
+ ],
+ [
+ 3,
+ 83638072.883879,
+ 0
+ ],
+ [
+ 3,
+ 83638073.136066,
+ 0
+ ],
+ [
+ 3,
+ 83638073.388618,
+ 0
+ ],
+ [
+ 3,
+ 83638073.642837,
+ 0
+ ],
+ [
+ 3,
+ 83638073.893254,
+ 0
+ ],
+ [
+ 3,
+ 83638074.14492,
+ 0
+ ],
+ [
+ 3,
+ 83638074.396483,
+ 0
+ ],
+ [
+ 3,
+ 83638074.647889,
+ 0
+ ],
+ [
+ 3,
+ 83638074.899035,
+ 0
+ ],
+ [
+ 3,
+ 83638075.155285,
+ 0
+ ],
+ [
+ 3,
+ 83638075.425389,
+ 0
+ ],
+ [
+ 3,
+ 83638075.694764,
+ 0
+ ],
+ [
+ 3,
+ 83638075.961744,
+ 0
+ ],
+ [
+ 3,
+ 83638076.226379,
+ 0
+ ],
+ [
+ 3,
+ 83638098.803933,
+ 0
+ ],
+ [
+ 3,
+ 83638099.057215,
+ 0
+ ],
+ [
+ 3,
+ 83638099.316433,
+ 0
+ ],
+ [
+ 3,
+ 83638099.57685,
+ 0
+ ],
+ [
+ 3,
+ 83638099.83534,
+ 0
+ ],
+ [
+ 3,
+ 83638100.092996,
+ 0
+ ],
+ [
+ 3,
+ 83638100.349454,
+ 0
+ ],
+ [
+ 3,
+ 83638100.605236,
+ 0
+ ],
+ [
+ 3,
+ 83638100.859923,
+ 0
+ ],
+ [
+ 3,
+ 83638101.11409,
+ 0
+ ],
+ [
+ 3,
+ 83638101.367579,
+ 0
+ ],
+ [
+ 3,
+ 83638101.620548,
+ 0
+ ],
+ [
+ 3,
+ 83638101.879298,
+ 0
+ ],
+ [
+ 3,
+ 83638102.139767,
+ 0
+ ],
+ [
+ 3,
+ 83638102.400184,
+ 0
+ ],
+ [
+ 3,
+ 83638102.659194,
+ 0
+ ],
+ [
+ 31,
+ 83638108.426903,
+ 0
+ ],
+ [
+ 3,
+ 83638108.624195,
+ 0
+ ],
+ [
+ 3,
+ 83638108.830236,
+ 0
+ ],
+ [
+ 3,
+ 83638109.044299,
+ 0
+ ],
+ [
+ 3,
+ 83638109.264351,
+ 0
+ ],
+ [
+ 3,
+ 83638109.488986,
+ 0
+ ],
+ [
+ 3,
+ 83638109.717372,
+ 0
+ ],
+ [
+ 3,
+ 83638109.949299,
+ 0
+ ],
+ [
+ 3,
+ 83638110.184299,
+ 0
+ ],
+ [
+ 3,
+ 83638110.420601,
+ 0
+ ],
+ [
+ 3,
+ 83638110.659299,
+ 0
+ ],
+ [
+ 3,
+ 83638110.899612,
+ 0
+ ],
+ [
+ 3,
+ 83638111.141539,
+ 0
+ ],
+ [
+ 3,
+ 83638111.38456,
+ 0
+ ],
+ [
+ 3,
+ 83638111.62857,
+ 0
+ ],
+ [
+ 3,
+ 83638111.880028,
+ 0
+ ],
+ [
+ 3,
+ 83638112.125966,
+ 0
+ ],
+ [
+ 3,
+ 83638112.371695,
+ 0
+ ],
+ [
+ 33,
+ 83638112.619508,
+ 0
+ ],
+ [
+ 3,
+ 83638118.801956,
+ 0
+ ],
+ [
+ 3,
+ 83638119.044873,
+ 0
+ ],
+ [
+ 3,
+ 83638119.261904,
+ 0
+ ],
+ [
+ 3,
+ 83638119.480133,
+ 0
+ ],
+ [
+ 3,
+ 83638119.702529,
+ 0
+ ],
+ [
+ 3,
+ 83638119.928987,
+ 0
+ ],
+ [
+ 3,
+ 83638120.158935,
+ 0
+ ],
+ [
+ 3,
+ 83638120.391956,
+ 0
+ ],
+ [
+ 3,
+ 83638120.627581,
+ 0
+ ],
+ [
+ 3,
+ 83638120.865238,
+ 0
+ ],
+ [
+ 3,
+ 83638121.104717,
+ 0
+ ],
+ [
+ 3,
+ 83638121.345758,
+ 0
+ ],
+ [
+ 3,
+ 83638121.588102,
+ 0
+ ],
+ [
+ 3,
+ 83638121.838675,
+ 0
+ ],
+ [
+ 3,
+ 83638122.091488,
+ 0
+ ],
+ [
+ 3,
+ 83638122.3418,
+ 0
+ ],
+ [
+ 3,
+ 83638122.592998,
+ 0
+ ],
+ [
+ 35,
+ 83638122.846019,
+ 0
+ ],
+ [
+ 41,
+ 83638158.461804,
+ 0
+ ],
+ [
+ 3,
+ 83638158.665137,
+ 0
+ ],
+ [
+ 3,
+ 83638158.896231,
+ 0
+ ],
+ [
+ 3,
+ 83638159.133418,
+ 0
+ ],
+ [
+ 3,
+ 83638159.373887,
+ 0
+ ],
+ [
+ 3,
+ 83638159.616283,
+ 0
+ ],
+ [
+ 3,
+ 83638159.859825,
+ 0
+ ],
+ [
+ 3,
+ 83638160.105189,
+ 0
+ ],
+ [
+ 3,
+ 83638160.349564,
+ 0
+ ],
+ [
+ 3,
+ 83638160.59545,
+ 0
+ ],
+ [
+ 3,
+ 83638160.842064,
+ 0
+ ],
+ [
+ 3,
+ 83638161.089304,
+ 0
+ ],
+ [
+ 3,
+ 83638161.336752,
+ 0
+ ],
+ [
+ 3,
+ 83638161.584721,
+ 0
+ ],
+ [
+ 3,
+ 83638161.840294,
+ 0
+ ],
+ [
+ 3,
+ 83638162.087429,
+ 0
+ ],
+ [
+ 3,
+ 83638162.334513,
+ 0
+ ],
+ [
+ 3,
+ 83638162.582221,
+ 0
+ ]
+ ],
+ "schema": {
+ "responsiveness": 2,
+ "stack": 0,
+ "time": 1
+ }
+ },
+ "stackTable": {
+ "data": [
+ [
+ null,
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 1,
+ 0
+ ],
+ [
+ 1,
+ 2,
+ 0
+ ],
+ [
+ 2,
+ 3,
+ 0
+ ],
+ [
+ 3,
+ 4,
+ 0
+ ],
+ [
+ 4,
+ 4,
+ 0
+ ],
+ [
+ 5,
+ 5,
+ 0
+ ],
+ [
+ 6,
+ 6,
+ 0
+ ],
+ [
+ 7,
+ 7,
+ 0
+ ],
+ [
+ 8,
+ 8,
+ 0
+ ],
+ [
+ 9,
+ 9,
+ 0
+ ],
+ [
+ 10,
+ 10,
+ 0
+ ],
+ [
+ 11,
+ 11,
+ 0
+ ],
+ [
+ 12,
+ 12,
+ 0
+ ],
+ [
+ 13,
+ 13,
+ 0
+ ],
+ [
+ 3,
+ 14,
+ 0
+ ],
+ [
+ 15,
+ 15,
+ 0
+ ],
+ [
+ 16,
+ 16,
+ 0
+ ],
+ [
+ 17,
+ 17,
+ 0
+ ],
+ [
+ 18,
+ 18,
+ 0
+ ],
+ [
+ 7,
+ 19,
+ 0
+ ],
+ [
+ 20,
+ 20,
+ 0
+ ],
+ [
+ 21,
+ 21,
+ 0
+ ],
+ [
+ 6,
+ 22,
+ 0
+ ],
+ [
+ 23,
+ 23,
+ 0
+ ],
+ [
+ 5,
+ 14,
+ 0
+ ],
+ [
+ 25,
+ 15,
+ 0
+ ],
+ [
+ 26,
+ 16,
+ 0
+ ],
+ [
+ 27,
+ 17,
+ 0
+ ],
+ [
+ 28,
+ 24,
+ 0
+ ],
+ [
+ 29,
+ 25,
+ 0
+ ],
+ [
+ 30,
+ 26,
+ 0
+ ],
+ [
+ 11,
+ 27,
+ 0
+ ],
+ [
+ 32,
+ 28,
+ 0
+ ],
+ [
+ 10,
+ 29,
+ 0
+ ],
+ [
+ 34,
+ 30,
+ 0
+ ],
+ [
+ 6,
+ 14,
+ 0
+ ],
+ [
+ 36,
+ 15,
+ 0
+ ],
+ [
+ 37,
+ 16,
+ 0
+ ],
+ [
+ 38,
+ 17,
+ 0
+ ],
+ [
+ 39,
+ 24,
+ 0
+ ],
+ [
+ 40,
+ 31,
+ 0
+ ]
+ ],
+ "schema": {
+ "category": 2,
+ "frame": 1,
+ "prefix": 0
+ }
+ },
+ "stringTable": [
+ "__start_thread (in /apex/com.android.runtime/lib64/bionic/libc.so)",
+ "__pthread_start(void*) (in /apex/com.android.runtime/lib64/bionic/libc.so)",
+ "java.lang.Thread.run (in /apex/com.android.art/javalib/core-oj.jar)",
+ "com.android.simpleperf.debuggable.MainActivity$1.run (in /data/app/~~76MchZRJTLK3KXJaRL_qxg==/com.android.simpleperf.debuggable-1oSYM04VyxVPlD5eh3eJsg==/oat/arm64/base.vdex)",
+ "java.lang.Thread.sleep (in [JIT app cache])",
+ "art::Monitor::Wait(art::Thread*, art::ObjPtr<art::mirror::Object>, long, int, bool, art::ThreadState) (in /apex/com.android.art/lib64/libart.so)",
+ "syscall (in /apex/com.android.runtime/lib64/bionic/libc.so)",
+ "[kernel.kallsyms][+ffffff93eb484156] (in [kernel.kallsyms])",
+ "[kernel.kallsyms][+ffffff93eb64c252] (in [kernel.kallsyms])",
+ "[kernel.kallsyms][+ffffff93eb645bee] (in [kernel.kallsyms])",
+ "[kernel.kallsyms][+ffffff93eb647ffa] (in [kernel.kallsyms])",
+ "[kernel.kallsyms][+ffffff93eb64ab86] (in [kernel.kallsyms])",
+ "[kernel.kallsyms][+ffffff93ed05e93e] (in [kernel.kallsyms])",
+ "[kernel.kallsyms][+ffffff93eb58b094] (in [kernel.kallsyms])",
+ "[kernel.kallsyms][+ffffff93eb483f36] (in [kernel.kallsyms])",
+ "[kernel.kallsyms][+ffffff93eb481f72] (in [kernel.kallsyms])",
+ "[kernel.kallsyms][+ffffff93eb5f1b6a] (in [kernel.kallsyms])",
+ "[kernel.kallsyms][+ffffff93eb5424a6] (in [kernel.kallsyms])",
+ "[kernel.kallsyms][+ffffff93eb4820f4] (in [kernel.kallsyms])",
+ "[kernel.kallsyms][+ffffff93eb484126] (in [kernel.kallsyms])",
+ "[kernel.kallsyms][+ffffff93eb502696] (in [kernel.kallsyms])",
+ "[kernel.kallsyms][+ffffff93eb6942fc] (in [kernel.kallsyms])",
+ "__set_errno_internal (in /apex/com.android.runtime/lib64/bionic/libc.so)",
+ "@plt (in /apex/com.android.runtime/lib64/bionic/libc.so)",
+ "[kernel.kallsyms][+ffffff93eb482226] (in [kernel.kallsyms])",
+ "[kernel.kallsyms][+ffffff93eb6188c2] (in [kernel.kallsyms])",
+ "[kernel.kallsyms][+ffffff93eb611850] (in [kernel.kallsyms])",
+ "[kernel.kallsyms][+ffffff93eb64aab2] (in [kernel.kallsyms])",
+ "[kernel.kallsyms][+ffffff93ed04df40] (in [kernel.kallsyms])",
+ "[kernel.kallsyms][+ffffff93eb647f66] (in [kernel.kallsyms])",
+ "[kernel.kallsyms][+ffffff93eb623780] (in [kernel.kallsyms])",
+ "[kernel.kallsyms][+ffffff93eb62322c] (in [kernel.kallsyms])"
+ ],
+ "tid": 7630,
+ "unregisterTime": null
+ }
+ ]
+}
diff --git a/simpleperf/scripts/test/script_testdata/perf_with_tracepoint_event.gecko.json b/simpleperf/scripts/test/script_testdata/perf_with_tracepoint_event.gecko.json
new file mode 100644
index 00000000..2072b536
--- /dev/null
+++ b/simpleperf/scripts/test/script_testdata/perf_with_tracepoint_event.gecko.json
@@ -0,0 +1,550 @@
+{
+ "libs": [],
+ "meta": {
+ "abi": "aarch64",
+ "appBuildID": null,
+ "asyncstack": 1,
+ "categories": [
+ {
+ "color": "yellow",
+ "name": "User",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
+ "color": "orange",
+ "name": "Kernel",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
+ "color": "yellow",
+ "name": "Native",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
+ "color": "green",
+ "name": "DEX",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
+ "color": "green",
+ "name": "OAT",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
+ "color": "blue",
+ "name": "Off-CPU",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
+ "color": "grey",
+ "name": "Other",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
+ "color": "green",
+ "name": "JIT",
+ "subcategories": [
+ "Other"
+ ]
+ }
+ ],
+ "debug": 0,
+ "device": "Google:Pixel 2:walleye",
+ "gcpoison": 0,
+ "interval": 1,
+ "markerSchema": [],
+ "oscpu": null,
+ "platform": null,
+ "presymbolicated": true,
+ "processType": 0,
+ "product": "/data/local/tmp/simpleperf record -e sched:sched_switch -e cpu-cycles sleep 1",
+ "shutdownTime": null,
+ "stackwalk": 1,
+ "startTime": 1512941771000,
+ "version": 24
+ },
+ "pausedRanges": [],
+ "processes": [],
+ "threads": [
+ {
+ "frameTable": {
+ "data": [
+ [
+ 0,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 1,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 2,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 5,
+ 0
+ ],
+ [
+ 3,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 4,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 5,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 6,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 7,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 8,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 9,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 10,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ]
+ ],
+ "schema": {
+ "category": 7,
+ "column": 6,
+ "implementation": 3,
+ "innerWindowID": 2,
+ "line": 5,
+ "location": 0,
+ "optimizations": 4,
+ "relevantForJS": 1,
+ "subcategory": 8
+ }
+ },
+ "markers": {
+ "data": [],
+ "schema": {
+ "category": 4,
+ "data": 5,
+ "endTime": 2,
+ "name": 0,
+ "phase": 3,
+ "startTime": 1
+ }
+ },
+ "name": "sleep",
+ "pid": 9896,
+ "processType": "default",
+ "registerTime": 0,
+ "samples": {
+ "data": [
+ [
+ 0,
+ 536732415.65594,
+ 0
+ ],
+ [
+ 1,
+ 536732415.663024,
+ 0
+ ],
+ [
+ 2,
+ 536732415.855628,
+ 0
+ ],
+ [
+ 3,
+ 536732416.562399,
+ 0
+ ],
+ [
+ 2,
+ 536732416.922451,
+ 0
+ ],
+ [
+ 4,
+ 536732418.120784,
+ 0
+ ],
+ [
+ 2,
+ 536732418.371201,
+ 0
+ ],
+ [
+ 5,
+ 536732419.142503,
+ 0
+ ],
+ [
+ 5,
+ 536732419.14969,
+ 0
+ ],
+ [
+ 2,
+ 536732419.966826,
+ 0
+ ],
+ [
+ 6,
+ 536732420.235419,
+ 0
+ ],
+ [
+ 2,
+ 536732420.924794,
+ 0
+ ],
+ [
+ 7,
+ 536732421.171461,
+ 0
+ ],
+ [
+ 2,
+ 536732421.250211,
+ 0
+ ],
+ [
+ 4,
+ 536732421.445836,
+ 0
+ ],
+ [
+ 2,
+ 536732421.544899,
+ 0
+ ],
+ [
+ 7,
+ 536732421.800888,
+ 0
+ ],
+ [
+ 8,
+ 536732421.836878,
+ 0
+ ],
+ [
+ 9,
+ 536732421.841878,
+ 0
+ ],
+ [
+ 2,
+ 536732421.87318,
+ 0
+ ],
+ [
+ 2,
+ 536732421.900315,
+ 0
+ ],
+ [
+ 5,
+ 536732422.094638,
+ 0
+ ],
+ [
+ 2,
+ 536732422.188128,
+ 0
+ ],
+ [
+ 2,
+ 536732422.43094,
+ 0
+ ],
+ [
+ 2,
+ 536732422.702972,
+ 0
+ ],
+ [
+ 2,
+ 536732423.006357,
+ 0
+ ],
+ [
+ 2,
+ 536732423.138076,
+ 0
+ ],
+ [
+ 2,
+ 536732423.500628,
+ 0
+ ],
+ [
+ 2,
+ 536732423.76943,
+ 0
+ ],
+ [
+ 2,
+ 536732425.676201,
+ 0
+ ],
+ [
+ 2,
+ 536732426.086722,
+ 0
+ ],
+ [
+ 2,
+ 536732426.35693,
+ 0
+ ],
+ [
+ 2,
+ 536732426.801513,
+ 0
+ ],
+ [
+ 2,
+ 536732426.916774,
+ 0
+ ],
+ [
+ 2,
+ 536732427.020263,
+ 0
+ ],
+ [
+ 2,
+ 536732427.455524,
+ 0
+ ],
+ [
+ 2,
+ 536732428.163961,
+ 0
+ ],
+ [
+ 2,
+ 536732428.383753,
+ 0
+ ],
+ [
+ 2,
+ 536732428.868284,
+ 0
+ ],
+ [
+ 2,
+ 536732429.336982,
+ 0
+ ],
+ [
+ 2,
+ 536732429.894222,
+ 0
+ ],
+ [
+ 2,
+ 536732430.261357,
+ 0
+ ],
+ [
+ 10,
+ 536732430.486097,
+ 0
+ ],
+ [
+ 2,
+ 536732433.741565,
+ 0
+ ],
+ [
+ 2,
+ 536732434.293857,
+ 0
+ ]
+ ],
+ "schema": {
+ "responsiveness": 2,
+ "stack": 0,
+ "time": 1
+ }
+ },
+ "stackTable": {
+ "data": [
+ [
+ null,
+ 0,
+ 0
+ ],
+ [
+ null,
+ 1,
+ 0
+ ],
+ [
+ null,
+ 2,
+ 0
+ ],
+ [
+ null,
+ 3,
+ 0
+ ],
+ [
+ null,
+ 4,
+ 0
+ ],
+ [
+ null,
+ 5,
+ 0
+ ],
+ [
+ null,
+ 6,
+ 0
+ ],
+ [
+ null,
+ 7,
+ 0
+ ],
+ [
+ null,
+ 8,
+ 0
+ ],
+ [
+ null,
+ 9,
+ 0
+ ],
+ [
+ null,
+ 10,
+ 0
+ ]
+ ],
+ "schema": {
+ "category": 2,
+ "frame": 1,
+ "prefix": 0
+ }
+ },
+ "stringTable": [
+ "perf_event_exec (in [kernel.kallsyms])",
+ "memcpy (in [kernel.kallsyms])",
+ "__schedule (in [kernel.kallsyms])",
+ "schedule_timeout (in [kernel.kallsyms])",
+ "filemap_fault (in [kernel.kallsyms])",
+ "_raw_spin_unlock_irq (in [kernel.kallsyms])",
+ "__wait_on_bit_lock (in [kernel.kallsyms])",
+ "generic_file_read_iter (in [kernel.kallsyms])",
+ "__do_softirq (in [kernel.kallsyms])",
+ "_raw_spin_unlock_irqrestore (in [kernel.kallsyms])",
+ "__clean_dcache_area_pou (in [kernel.kallsyms])"
+ ],
+ "tid": 9896,
+ "unregisterTime": null
+ }
+ ]
+}
diff --git a/simpleperf/scripts/test/script_testdata/vmlinux b/simpleperf/scripts/test/script_testdata/vmlinux
new file mode 100644
index 00000000..1239e5e3
--- /dev/null
+++ b/simpleperf/scripts/test/script_testdata/vmlinux
Binary files differ
diff --git a/simpleperf/scripts/test/tools_test.py b/simpleperf/scripts/test/tools_test.py
index 9c9fbd7e..2d572301 100644
--- a/simpleperf/scripts/test/tools_test.py
+++ b/simpleperf/scripts/test/tools_test.py
@@ -14,12 +14,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import io
import os
from pathlib import Path
from binary_cache_builder import BinaryCacheBuilder
-from simpleperf_utils import (Addr2Nearestline, BinaryFinder, Objdump, ReadElf,
- SourceFileSearcher, is_windows, remove)
+from simpleperf_utils import (Addr2Nearestline, AddrRange, BinaryFinder, Disassembly, Objdump,
+ ReadElf, SourceFileSearcher, is_windows, remove)
from . test_utils import TestBase, TestHelper
@@ -207,7 +208,7 @@ system/extras/simpleperf/runtest/two_functions.cpp:21:3
'expected_items': [
('main', 0),
('two_functions.cpp:20', 0),
- ('1134: add x29, sp, #16', 0x1134),
+ ('1134: add x29, sp, #0x10', 0x1134),
],
},
'/simpleperf_runtest_two_functions_arm': {
@@ -225,7 +226,7 @@ system/extras/simpleperf/runtest/two_functions.cpp:21:3
'expected_items': [
('main', 0),
('two_functions.cpp:20', 0),
- (r'19f0: movl %eax, 9314(%rip)', 0x19f0),
+ (r'19f0: movl %eax, 0x2462(%rip)', 0x19f0),
],
},
'/simpleperf_runtest_two_functions_x86': {
@@ -234,7 +235,7 @@ system/extras/simpleperf/runtest/two_functions.cpp:21:3
'expected_items': [
('main', 0),
('two_functions.cpp:20', 0),
- (r'16f7: cmpl $100000000, %ecx', 0x16f7),
+ (r'16f7: cmpl $0x5f5e100, %ecx', 0x16f7),
],
},
}
@@ -244,22 +245,94 @@ system/extras/simpleperf/runtest/two_functions.cpp:21:3
dso = test_map[dso_path]
dso_info = objdump.get_dso_info(dso_path, None)
self.assertIsNotNone(dso_info, dso_path)
- disassemble_code = objdump.disassemble_code(dso_info, dso['start_addr'], dso['len'])
- self.assertTrue(disassemble_code, dso_path)
- i = 0
- for expected_line, expected_addr in dso['expected_items']:
- found = False
- while i < len(disassemble_code):
- line, addr = disassemble_code[i]
- if addr == expected_addr and expected_line in line:
- found = True
- i += 1
- break
+ addr_range = AddrRange(dso['start_addr'], dso['len'])
+ disassembly = objdump.disassemble_function(dso_info, addr_range)
+ self.assertTrue(disassembly, dso_path)
+ self._check_disassembly(disassembly, dso_path, dso)
+
+ result = objdump.disassemble_functions(dso_info, [addr_range])
+ self.assertTrue(result, dso_path)
+ self.assertEqual(len(result), 1)
+ self._check_disassembly(result[0], dso_path, dso)
+
+ def _check_disassembly(self, disassembly: Disassembly, dso_path: str, dso) -> None:
+ disassemble_code = disassembly.lines
+ i = 0
+ for expected_line, expected_addr in dso['expected_items']:
+ found = False
+ while i < len(disassemble_code):
+ line, addr = disassemble_code[i]
+ if addr == expected_addr and expected_line in line:
+ found = True
i += 1
- if not found:
- s = '\n'.join('%s:0x%x' % item for item in disassemble_code)
- self.fail('for %s, %s:0x%x not found in disassemble code:\n%s' %
- (dso_path, expected_line, expected_addr, s))
+ break
+ i += 1
+ if not found:
+ s = '\n'.join('%s:0x%x' % item for item in disassemble_code)
+ self.fail('for %s, %s:0x%x not found in disassemble code:\n%s' %
+ (dso_path, expected_line, expected_addr, s))
+
+ def test_objdump_parse_disassembly_for_functions(self):
+ # Parse kernel disassembly.
+ s = """
+ffffffc008000000 <_text>:
+; _text():
+; arch/arm64/kernel/head.S:60
+ffffffc008000000: ccmp x18, #0x0, #0xd, pl
+ffffffc008000004: b 0xffffffc009b2a37c <primary_entry>
+
+ffffffc008000008 <$d.1>:
+ffffffc008000008: 00 00 00 00 .word 0x00000000
+ffffffc0089bbb30 <readl>:
+; readl():
+; include/asm-generic/io.h:218
+ffffffc0089bbb30: paciasp
+ffffffc0089bbb34: stp x29, x30, [sp, #-0x30]!
+ """
+ addr_ranges = [AddrRange(0xffffffc008000000, 8),
+ AddrRange(0xffffffc008000010, 10),
+ AddrRange(0xffffffc0089bbb30, 20)]
+ binary_finder = BinaryFinder(TestHelper.testdata_dir, ReadElf(TestHelper.ndk_path))
+ objdump = Objdump(TestHelper.ndk_path, binary_finder)
+ result = objdump._parse_disassembly_for_functions(io.StringIO(s), addr_ranges)
+ self.assertEqual(len(result), 3)
+ self.assertEqual(
+ result[0].lines,
+ [('ffffffc008000000 <_text>:', 0xffffffc008000000),
+ ('; _text():', 0),
+ ('; arch/arm64/kernel/head.S:60', 0),
+ ('ffffffc008000000: ccmp x18, #0x0, #0xd, pl', 0xffffffc008000000),
+ ('ffffffc008000004: b 0xffffffc009b2a37c <primary_entry>',
+ 0xffffffc008000004),
+ ('', 0)])
+ self.assertEqual(len(result[1].lines), 0)
+ self.assertEqual(result[2].lines, [
+ ('ffffffc0089bbb30 <readl>:', 0xffffffc0089bbb30),
+ ('; readl():', 0),
+ ('; include/asm-generic/io.h:218', 0),
+ ('ffffffc0089bbb30: paciasp', 0xffffffc0089bbb30),
+ ('ffffffc0089bbb34: stp x29, x30, [sp, #-0x30]!', 0xffffffc0089bbb34),
+ ('', 0)])
+
+ # Parse user space library disassembly.
+ s = """
+0000000000200000 <art::gc::collector::ConcurrentCopying::ProcessMarkStack()>:
+; art::gc::collector::ConcurrentCopying::ProcessMarkStack():
+; art/runtime/gc/collector/concurrent_copying.cc:2121
+ 200000: stp x29, x30, [sp, #-0x20]!
+ 200004: stp x20, x19, [sp, #0x10]
+ """
+ addr_ranges = [AddrRange(0x200000, 8)]
+ result = objdump._parse_disassembly_for_functions(io.StringIO(s), addr_ranges)
+ self.assertEqual(len(result), 1)
+ self.assertEqual(result[0].lines, [
+ ('0000000000200000 <art::gc::collector::ConcurrentCopying::ProcessMarkStack()>:',
+ 0x200000),
+ ('; art::gc::collector::ConcurrentCopying::ProcessMarkStack():', 0),
+ ('; art/runtime/gc/collector/concurrent_copying.cc:2121', 0),
+ (' 200000: stp x29, x30, [sp, #-0x20]!', 0x200000),
+ (' 200004: stp x20, x19, [sp, #0x10]', 0x200004),
+ ('', 0)])
def test_readelf(self):
test_map = {
diff --git a/simpleperf/scripts/update.py b/simpleperf/scripts/update.py
index cc30283a..ef2f84d3 100755
--- a/simpleperf/scripts/update.py
+++ b/simpleperf/scripts/update.py
@@ -33,32 +33,31 @@ class InstallEntry(object):
self.need_strip = need_strip
-MINGW = 'local:../../../../prebuilts/gcc/linux-x86/host/x86_64-w64-mingw32-4.8/x86_64-w64-mingw32/'
INSTALL_LIST = [
# simpleperf on device.
InstallEntry('MODULES-IN-system-extras-simpleperf',
- 'simpleperf/android/arm64/simpleperf_ndk64',
+ 'simpleperf/android/arm64/simpleperf_ndk',
'android/arm64/simpleperf'),
InstallEntry('MODULES-IN-system-extras-simpleperf_arm',
- 'simpleperf/android/arm/simpleperf_ndk',
+ 'simpleperf/android/arm/simpleperf_ndk32',
'android/arm/simpleperf'),
InstallEntry('MODULES-IN-system-extras-simpleperf_x86',
- 'simpleperf/android/x86_64/simpleperf_ndk64',
+ 'simpleperf/android/x86_64/simpleperf_ndk',
'android/x86_64/simpleperf'),
InstallEntry('MODULES-IN-system-extras-simpleperf_x86',
- 'simpleperf/android/x86/simpleperf_ndk',
+ 'simpleperf/android/x86/simpleperf_ndk32',
'android/x86/simpleperf'),
+ InstallEntry('MODULES-IN-system-extras-simpleperf_riscv64',
+ 'simpleperf_ndk',
+ 'android/riscv64/simpleperf'),
# simpleperf on host.
InstallEntry('MODULES-IN-system-extras-simpleperf',
- 'simpleperf/linux/x86_64/simpleperf_ndk64',
+ 'simpleperf/linux/x86_64/simpleperf',
'linux/x86_64/simpleperf', True),
InstallEntry('MODULES-IN-system-extras-simpleperf_mac',
- 'simpleperf/darwin/x86_64/simpleperf_ndk64',
+ 'simpleperf/darwin/x86_64/simpleperf',
'darwin/x86_64/simpleperf'),
- InstallEntry('MODULES-IN-system-extras-simpleperf',
- 'simpleperf/windows/x86_64/simpleperf_ndk64.exe',
- 'windows/x86_64/simpleperf.exe', True),
# libsimpleperf_report.so on host
InstallEntry('MODULES-IN-system-extras-simpleperf',
@@ -67,13 +66,6 @@ INSTALL_LIST = [
InstallEntry('MODULES-IN-system-extras-simpleperf_mac',
'simpleperf/darwin/x86_64/libsimpleperf_report.dylib',
'darwin/x86_64/libsimpleperf_report.dylib'),
- InstallEntry('MODULES-IN-system-extras-simpleperf',
- 'simpleperf/windows/x86_64/libsimpleperf_report.dll',
- 'windows/x86_64/libsimpleperf_report.dll', True),
-
- # libwinpthread-1.dll on windows host
- InstallEntry(MINGW + '/bin/libwinpthread-1.dll', 'libwinpthread-1.dll',
- 'windows/x86_64/libwinpthread-1.dll', False),
]
diff --git a/simpleperf/simpleperf.rc b/simpleperf/simpleperf.rc
index 928e9d34..06ad0f5d 100644
--- a/simpleperf/simpleperf.rc
+++ b/simpleperf/simpleperf.rc
@@ -1,3 +1,9 @@
+# Not a service because services can only be stopped with SIGKILL
+# (or SIGTERM+200ms+SIGKILL with gentle_kill), and simpleperf
+# needs more than 200ms to clean up.
-on zygote-start && property:ro.debuggable=1 && property:persist.simpleperf.boot_record=*
- exec_background /system/bin/simpleperf boot-record --log-to-android-buffer --record "${persist.simpleperf.boot_record}"
+on early-init && property:ro.boot.simpleperf.boot_record=*
+ exec_background u:r:su:s0 root root -- /system/bin/simpleperf record -a -g --exclude-perf -o /tmp/boot_perf.data
+
+on property:sys.boot_completed=1 && property:ro.boot.simpleperf.boot_record=*
+ exec u:r:su:s0 root root -- /system/bin/killall simpleperf
diff --git a/simpleperf/test_util.cpp b/simpleperf/test_util.cpp
index d93ddcd8..42dee703 100644
--- a/simpleperf/test_util.cpp
+++ b/simpleperf/test_util.cpp
@@ -17,15 +17,46 @@
#include <stdio.h>
+#include <optional>
+
#include <android-base/logging.h>
#include <android-base/properties.h>
+#include "command.h"
#include "event_attr.h"
#include "event_fd.h"
#include "event_type.h"
+#include "record_file.h"
#include "test_util.h"
-bool IsInNativeAbi() {
+#if defined(__linux__)
+
+static std::optional<bool> CanSampleRegsFor32BitABI() {
+ std::vector<std::unique_ptr<Workload>> workloads;
+ CreateProcesses(1, &workloads);
+ std::string pid = std::to_string(workloads[0]->GetPid());
+ std::unique_ptr<Command> cmd = CreateCommandInstance("record");
+ TemporaryFile tmpfile;
+ if (!cmd->Run({"-p", pid, "--call-graph", "dwarf,8", "--no-unwind", "-e", "cpu-clock:u",
+ "--duration", "3", "-o", tmpfile.path})) {
+ return std::nullopt;
+ }
+ auto reader = RecordFileReader::CreateInstance(tmpfile.path);
+ if (!reader) {
+ return std::nullopt;
+ }
+ for (const std::unique_ptr<Record>& record : reader->DataSection()) {
+ if (record->type() == PERF_RECORD_SAMPLE) {
+ auto sample = static_cast<const SampleRecord*>(record.get());
+ if (sample->regs_user_data.abi == PERF_SAMPLE_REGS_ABI_32) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+std::optional<bool> IsInNativeAbi() {
static int in_native_abi = -1;
if (in_native_abi == -1) {
FILE* fp = popen("uname -m", "re");
@@ -43,16 +74,27 @@ bool IsInNativeAbi() {
if (s.find("arm") == std::string::npos && s.find("aarch64") == std::string::npos) {
in_native_abi = 0;
}
+ if (GetTargetArch() == ARCH_ARM) {
+ // If we can't get ARM registers in samples, probably we are running with a 32-bit
+ // translator on 64-bit only CPUs. Then we should make in_native_abi = 0.
+ if (auto result = CanSampleRegsFor32BitABI(); result.has_value()) {
+ in_native_abi = result.value() ? 1 : 0;
+ } else {
+ in_native_abi = 2;
+ }
+ }
} else if (GetTargetArch() == ARCH_RISCV64) {
if (s.find("riscv") == std::string::npos) {
in_native_abi = 0;
}
}
}
+ if (in_native_abi == 2) {
+ return std::nullopt;
+ }
return in_native_abi == 1;
}
-#if defined(__linux__)
// Check if we can get a non-zero instruction event count by monitoring current thread.
static bool HasNonZeroInstructionEventCount() {
const simpleperf::EventType* type = simpleperf::FindEventTypeByName("instructions", false);
@@ -75,19 +117,26 @@ static bool HasNonZeroInstructionEventCount() {
return false;
}
+bool IsInEmulator() {
+ std::string fingerprint = android::base::GetProperty("ro.system.build.fingerprint", "");
+ return android::base::StartsWith(fingerprint, "google/sdk_gphone") ||
+ android::base::StartsWith(fingerprint, "google/sdk_gpc") ||
+ android::base::StartsWith(fingerprint, "generic/cf") ||
+ android::base::StartsWith(fingerprint, "generic/aosp_cf");
+}
+
bool HasHardwareCounter() {
static int has_hw_counter = -1;
if (has_hw_counter == -1) {
has_hw_counter = 1;
auto arch = GetTargetArch();
- std::string fingerprint = android::base::GetProperty("ro.system.build.fingerprint", "");
- bool is_emulator = android::base::StartsWith(fingerprint, "google/sdk_gphone") ||
- android::base::StartsWith(fingerprint, "google/sdk_gpc") ||
- android::base::StartsWith(fingerprint, "generic/cf");
- if (arch == ARCH_X86_64 || arch == ARCH_X86_32 || is_emulator) {
- // On x86 and x86_64, it's likely to run on an emulator or vm without hardware perf
- // counters. It's hard to enumerate them all. So check the support at runtime.
+ bool in_native_abi = IsInNativeAbi() == std::optional(true);
+
+ if (arch == ARCH_X86_64 || arch == ARCH_X86_32 || !in_native_abi || IsInEmulator()) {
+ // On x86 and x86_64, or when we are not in native abi, it's likely to run on an emulator or
+ // vm without hardware perf counters. It's hard to enumerate them all. So check the support
+ // at runtime.
const simpleperf::EventType* type = simpleperf::FindEventTypeByName("cpu-cycles", false);
CHECK(type != nullptr);
perf_event_attr attr = CreateDefaultPerfEventAttr(*type);
@@ -127,7 +176,12 @@ bool HasPmuCounter() {
bool HasTracepointEvents() {
static int has_tracepoint_events = -1;
if (has_tracepoint_events == -1) {
- has_tracepoint_events = (GetTraceFsDir() != nullptr) ? 1 : 0;
+ has_tracepoint_events = 0;
+ if (const char* dir = GetTraceFsDir(); dir != nullptr) {
+ if (IsDir(std::string(dir) + "/events/sched/sched_switch")) {
+ has_tracepoint_events = 1;
+ }
+ }
}
return has_tracepoint_events == 1;
}
diff --git a/simpleperf/test_util.h b/simpleperf/test_util.h
index 21b89a5e..4264c9a3 100644
--- a/simpleperf/test_util.h
+++ b/simpleperf/test_util.h
@@ -16,6 +16,7 @@
#include <map>
#include <memory>
+#include <optional>
#include <string>
#include <vector>
@@ -68,11 +69,13 @@ void CheckElfFileSymbols(const std::map<std::string, ElfFileSymbol>& symbols);
#define TEST_REQUIRE_HOST_ROOT() TEST_REQUIRE_ROOT()
#endif
-bool IsInNativeAbi();
+std::optional<bool> IsInNativeAbi();
// Used to skip tests not supposed to run on non-native ABIs.
#define OMIT_TEST_ON_NON_NATIVE_ABIS() \
do { \
- if (!IsInNativeAbi()) { \
+ std::optional<bool> in_native_abi = IsInNativeAbi(); \
+ ASSERT_TRUE(in_native_abi.has_value()); \
+ if (!in_native_abi.value()) { \
GTEST_LOG_(INFO) << "Skip this test as it only runs on native ABIs."; \
return; \
} \
@@ -198,3 +201,5 @@ class AppHelper {
std::vector<std::string> installed_packages_;
std::unique_ptr<Workload> app_start_proc_;
};
+
+bool IsInEmulator();
diff --git a/simpleperf/testdata/DisplayBitmaps.apk b/simpleperf/testdata/DisplayBitmaps.apk
index 63b40744..2911edde 100644
--- a/simpleperf/testdata/DisplayBitmaps.apk
+++ b/simpleperf/testdata/DisplayBitmaps.apk
Binary files differ
diff --git a/simpleperf/testdata/DisplayBitmapsTest.apk b/simpleperf/testdata/DisplayBitmapsTest.apk
index 15807198..db5575ef 100644
--- a/simpleperf/testdata/DisplayBitmapsTest.apk
+++ b/simpleperf/testdata/DisplayBitmapsTest.apk
Binary files differ
diff --git a/simpleperf/testdata/data/symfs_for_no_symbol_table_warning/elf b/simpleperf/testdata/data/symfs_for_no_symbol_table_warning/elf
deleted file mode 100644
index a92e41fa..00000000
--- a/simpleperf/testdata/data/symfs_for_no_symbol_table_warning/elf
+++ /dev/null
Binary files differ
diff --git a/simpleperf/testdata/etm/etm_test_loop_small b/simpleperf/testdata/etm/etm_test_loop_small
new file mode 100644
index 00000000..600bf9eb
--- /dev/null
+++ b/simpleperf/testdata/etm/etm_test_loop_small
Binary files differ
diff --git a/simpleperf/testdata/etm/perf_for_small_binary.data b/simpleperf/testdata/etm/perf_for_small_binary.data
new file mode 100644
index 00000000..9b012e50
--- /dev/null
+++ b/simpleperf/testdata/etm/perf_for_small_binary.data
Binary files differ
diff --git a/simpleperf/testdata/etm/perf_inject.data b/simpleperf/testdata/etm/perf_inject.data
index 4c121a32..9f83430b 100644
--- a/simpleperf/testdata/etm/perf_inject.data
+++ b/simpleperf/testdata/etm/perf_inject.data
@@ -20,5 +20,6 @@
10a0->1054:1
10b0->0:1
10ec->0:1
+// build_id: 0x0c9a20bf9c009d0e4e8bbf9fad0300ae00000000
// /data/local/tmp/etm_test_loop
diff --git a/simpleperf/testdata/etm/perf_inject_small.data b/simpleperf/testdata/etm/perf_inject_small.data
new file mode 100644
index 00000000..560b4927
--- /dev/null
+++ b/simpleperf/testdata/etm/perf_inject_small.data
@@ -0,0 +1,32 @@
+14
+14b4-14c4:1
+14c8-14fc:1
+150c-151c:1
+156c-1580:1
+1584-158c:1
+1590-15a4:10
+15a8-15b0:10
+15b4-15c4:2
+15c8-15dc:200
+15e0-15e0:2
+15e4-15f4:8
+15f8-160c:8000
+1610-1610:8
+1640-164c:1
+0
+12
+14c4->14c8:1
+14fc->150c:1
+151c->1640:1
+1580->15a8:1
+158c->0:1
+15a4->1584:1
+15b0->15e4:8
+15dc->15c8:198
+15e0->1590:2
+160c->15f8:7992
+1610->1590:8
+164c->0:1
+// build_id: 0xb9988f5e72de4b2580f98bef0ae8e4a000000000
+// /data/local/tmp/etm/etm_test_loop_small
+
diff --git a/simpleperf/testdata/etm/perf_with_missing_aux_data.data b/simpleperf/testdata/etm/perf_with_missing_aux_data.data
new file mode 100644
index 00000000..781a0a35
--- /dev/null
+++ b/simpleperf/testdata/etm/perf_with_missing_aux_data.data
Binary files differ
diff --git a/simpleperf/testdata/lbr/inject_lbr.data b/simpleperf/testdata/lbr/inject_lbr.data
new file mode 100644
index 00000000..6f141648
--- /dev/null
+++ b/simpleperf/testdata/lbr/inject_lbr.data
@@ -0,0 +1,14 @@
+2
+1910-191b:31
+1940-194d:341
+4
+1914:1
+1940:3
+1944:4
+1948:11
+2
+191b->1910:32
+194d->1940:353
+// build_id: 0x0000000000000000000000000000000000000000
+// /home/yabinc/lbr_test_loop
+
diff --git a/simpleperf/testdata/lbr/perf_lbr.data b/simpleperf/testdata/lbr/perf_lbr.data
new file mode 100644
index 00000000..81fafe62
--- /dev/null
+++ b/simpleperf/testdata/lbr/perf_lbr.data
Binary files differ
diff --git a/simpleperf/thread_tree.cpp b/simpleperf/thread_tree.cpp
index c9abff37..a6d2e81e 100644
--- a/simpleperf/thread_tree.cpp
+++ b/simpleperf/thread_tree.cpp
@@ -411,8 +411,10 @@ void ThreadTree::Update(const Record& record) {
const ForkRecord& r = *static_cast<const ForkRecord*>(&record);
ForkThread(r.data->pid, r.data->tid, r.data->ppid, r.data->ptid);
} else if (record.type() == PERF_RECORD_EXIT) {
- const ExitRecord& r = *static_cast<const ExitRecord*>(&record);
- ExitThread(r.data->pid, r.data->tid);
+ if (!disable_thread_exit_records_) {
+ const ExitRecord& r = *static_cast<const ExitRecord*>(&record);
+ ExitThread(r.data->pid, r.data->tid);
+ }
} else if (record.type() == SIMPLE_PERF_RECORD_KERNEL_SYMBOL) {
const auto& r = *static_cast<const KernelSymbolRecord*>(&record);
Dso::SetKallsyms(std::string(r.kallsyms, r.kallsyms_size));
diff --git a/simpleperf/thread_tree.h b/simpleperf/thread_tree.h
index 8e96ee4c..ea723766 100644
--- a/simpleperf/thread_tree.h
+++ b/simpleperf/thread_tree.h
@@ -100,6 +100,7 @@ class ThreadTree {
}
virtual ~ThreadTree() {}
+ void DisableThreadExitRecords() { disable_thread_exit_records_ = true; }
void SetThreadName(int pid, int tid, const std::string& comm);
bool ForkThread(int pid, int tid, int ppid, int ptid);
virtual ThreadEntry* FindThread(int tid) const;
@@ -167,6 +168,7 @@ class ThreadTree {
bool show_ip_for_unknown_symbol_;
bool show_mark_for_unknown_symbol_;
Symbol unknown_symbol_;
+ bool disable_thread_exit_records_ = false;
};
} // namespace simpleperf
diff --git a/simpleperf/thread_tree_test.cpp b/simpleperf/thread_tree_test.cpp
index f5b71ec0..7f1768e8 100644
--- a/simpleperf/thread_tree_test.cpp
+++ b/simpleperf/thread_tree_test.cpp
@@ -22,6 +22,7 @@
using namespace simpleperf;
+// @CddTest = 6.1/C-0-2
class ThreadTreeTest : public ::testing::Test {
protected:
void AddMap(uint64_t start, uint64_t end, const std::string& name) {
@@ -76,6 +77,7 @@ class ThreadTreeTest : public ::testing::Test {
ThreadTree thread_tree_;
};
+// @CddTest = 6.1/C-0-2
TEST_F(ThreadTreeTest, maps_smoke) {
AddMap(0, 5, "0");
AddMap(10, 15, "1");
@@ -100,6 +102,7 @@ TEST_F(ThreadTreeTest, maps_smoke) {
CheckMaps();
}
+// @CddTest = 6.1/C-0-2
TEST_F(ThreadTreeTest, jit_maps_before_fork) {
// Maps for JIT symfiles can arrive before fork records.
thread_tree_.AddThreadMap(0, 0, 0, 1, 0, "0", map_flags::PROT_JIT_SYMFILE_MAP);
@@ -114,6 +117,7 @@ TEST_F(ThreadTreeTest, jit_maps_before_fork) {
ASSERT_EQ(map->flags, map_flags::PROT_JIT_SYMFILE_MAP);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ThreadTreeTest, reused_tid) {
// Process 1 has thread 1 and 2.
thread_tree_.ForkThread(1, 2, 1, 1);
@@ -123,12 +127,14 @@ TEST_F(ThreadTreeTest, reused_tid) {
thread_tree_.ForkThread(2, 2, 1, 1);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ThreadTreeTest, reused_tid_without_thread_exit) {
// Similar to the above test, but the thread exit record is missing.
thread_tree_.ForkThread(1, 2, 1, 1);
thread_tree_.ForkThread(2, 2, 1, 1);
}
+// @CddTest = 6.1/C-0-2
TEST_F(ThreadTreeTest, add_symbols_for_process) {
std::string symbol_map(
"0x2000 0x20 two\n"
@@ -144,6 +150,7 @@ TEST_F(ThreadTreeTest, add_symbols_for_process) {
ASSERT_STREQ("three", FindSymbol(1, 1, 0x302f)->Name());
}
+// @CddTest = 6.1/C-0-2
TEST_F(ThreadTreeTest, invalid_fork) {
// tid == ptid
ASSERT_FALSE(thread_tree_.ForkThread(1, 2, 1, 2));
diff --git a/simpleperf/tracing.cpp b/simpleperf/tracing.cpp
index ab9c3fa7..9e5d650c 100644
--- a/simpleperf/tracing.cpp
+++ b/simpleperf/tracing.cpp
@@ -279,7 +279,7 @@ enum class FormatParsingState {
};
// Parse lines like: field:char comm[16]; offset:8; size:16; signed:1;
-static TracingField ParseTracingField(const std::string& s) {
+static std::optional<TracingField> ParseTracingField(const std::string& s) {
TracingField field;
std::string name;
std::string value;
@@ -310,7 +310,8 @@ static TracingField ParseTracingField(const std::string& s) {
size_t elem_count;
// Array size may not be a number, like field:u32 rates[IEEE80211_NUM_BANDS].
if (android::base::ParseUint(last_value_part.substr(left_bracket_pos + 1, len),
- &elem_count)) {
+ &elem_count) &&
+ elem_count > 0) {
field.elem_count = elem_count;
}
}
@@ -320,13 +321,20 @@ static TracingField ParseTracingField(const std::string& s) {
field.elem_count = 1;
}
} else if (name == "offset") {
- field.offset = static_cast<size_t>(strtoull(value.c_str(), nullptr, 10));
+ if (!android::base::ParseUint(value, &field.offset)) {
+ return std::nullopt;
+ }
} else if (name == "size") {
- size_t size = static_cast<size_t>(strtoull(value.c_str(), nullptr, 10));
- CHECK_EQ(size % field.elem_count, 0u);
+ size_t size;
+ if (!android::base::ParseUint(value, &size) || size == 0 || size % field.elem_count != 0) {
+ return std::nullopt;
+ }
field.elem_size = size / field.elem_count;
} else if (name == "signed") {
- int is_signed = static_cast<int>(strtoull(value.c_str(), nullptr, 10));
+ int is_signed;
+ if (!android::base::ParseInt(value, &is_signed, 0, 1)) {
+ return std::nullopt;
+ }
field.is_signed = (is_signed == 1);
}
}
@@ -350,8 +358,10 @@ TracingFormat ParseTracingFormat(const std::string& data) {
}
} else if (state == FormatParsingState::READ_FIELDS) {
if (size_t pos = s.find("field:"); pos != std::string::npos) {
- TracingField field = ParseTracingField(s);
- format.fields.push_back(field);
+ // Ignore errors parsing a field. Because it's not critical.
+ if (std::optional<TracingField> field = ParseTracingField(s); field.has_value()) {
+ format.fields.emplace_back(field.value());
+ }
}
}
}
@@ -385,7 +395,7 @@ void Tracing::Dump(size_t indent) {
tracing_file_->Dump(indent);
}
-TracingFormat Tracing::GetTracingFormatHavingId(uint64_t trace_event_id) {
+std::optional<TracingFormat> Tracing::GetTracingFormatHavingId(uint64_t trace_event_id) {
if (tracing_formats_.empty()) {
tracing_formats_ = tracing_file_->LoadTracingFormatsFromEventFiles();
}
@@ -394,8 +404,7 @@ TracingFormat Tracing::GetTracingFormatHavingId(uint64_t trace_event_id) {
return format;
}
}
- LOG(FATAL) << "no tracing format for id " << trace_event_id;
- return TracingFormat();
+ return std::nullopt;
}
std::string Tracing::GetTracingEventNameHavingId(uint64_t trace_event_id) {
diff --git a/simpleperf/tracing.h b/simpleperf/tracing.h
index 9194d026..0836e5ef 100644
--- a/simpleperf/tracing.h
+++ b/simpleperf/tracing.h
@@ -67,23 +67,23 @@ struct StringTracingFieldPlace {
struct TracingFormat {
std::string system_name;
std::string name;
- uint64_t id;
+ uint64_t id = 0;
std::vector<TracingField> fields;
- void GetField(const std::string& name, TracingFieldPlace& place) {
+ void GetField(const std::string& name, TracingFieldPlace& place) const {
const TracingField& field = GetField(name);
place.offset = field.offset;
place.size = field.elem_size;
}
- void GetField(const std::string& name, StringTracingFieldPlace& place) {
+ void GetField(const std::string& name, StringTracingFieldPlace& place) const {
const TracingField& field = GetField(name);
place.offset = field.offset;
place.size = field.elem_count;
}
private:
- const TracingField& GetField(const std::string& name) {
+ const TracingField& GetField(const std::string& name) const {
for (const auto& field : fields) {
if (field.name == name) {
return field;
@@ -101,7 +101,7 @@ class Tracing {
static std::unique_ptr<Tracing> Create(const std::vector<char>& data);
~Tracing();
void Dump(size_t indent);
- TracingFormat GetTracingFormatHavingId(uint64_t trace_event_id);
+ std::optional<TracingFormat> GetTracingFormatHavingId(uint64_t trace_event_id);
std::string GetTracingEventNameHavingId(uint64_t trace_event_id);
const std::string& GetKallsyms() const;
uint32_t GetPageSize() const;
diff --git a/simpleperf/tracing_test.cpp b/simpleperf/tracing_test.cpp
index 0ed82763..f4cec740 100644
--- a/simpleperf/tracing_test.cpp
+++ b/simpleperf/tracing_test.cpp
@@ -32,6 +32,7 @@ static void CheckAdjustFilter(const std::string& filter, bool use_quote,
ASSERT_EQ(android::base::Join(used_fields, ","), used_field_str);
}
+// @CddTest = 6.1/C-0-2
TEST(tracing, adjust_tracepoint_filter) {
std::string filter = "((sig >= 1 && sig < 20) || sig == 32) && comm != \"bash\"";
CheckAdjustFilter(filter, true, filter, "comm,sig");
@@ -67,6 +68,7 @@ std::ostream& operator<<(std::ostream& os, const TracingField& field) {
}
} // namespace simpleperf
+// @CddTest = 6.1/C-0-2
TEST(tracing, ParseTracingFormat) {
std::string data =
"name: sched_wakeup_new\n"
@@ -79,7 +81,9 @@ TEST(tracing, ParseTracingFormat) {
"\n"
"\tfield:char comm[16]; offset:8; size:16; signed:1;\n"
"\tfield:__data_loc char[] name; offset:24; size:4; signed:1;\n";
- TracingFormat format = ParseTracingFormat(data);
+ std::optional<TracingFormat> opt_format = ParseTracingFormat(data);
+ ASSERT_TRUE(opt_format.has_value());
+ const TracingFormat& format = opt_format.value();
ASSERT_EQ(format.name, "sched_wakeup_new");
ASSERT_EQ(format.id, 94);
ASSERT_EQ(format.fields.size(), 6);
diff --git a/simpleperf/utils.cpp b/simpleperf/utils.cpp
index 6357d1ea..ac7517e0 100644
--- a/simpleperf/utils.cpp
+++ b/simpleperf/utils.cpp
@@ -491,4 +491,24 @@ OverflowResult SafeAdd(uint64_t a, uint64_t b) {
return result;
}
+void OverflowSafeAdd(uint64_t& dest, uint64_t add) {
+ if (__builtin_add_overflow(dest, add, &dest)) {
+ LOG(WARNING) << "Branch count overflow happened.";
+ dest = UINT64_MAX;
+ }
+}
+
+// Convert big numbers to human friendly mode. For example,
+// 1000000 will be converted to 1,000,000.
+std::string ReadableCount(uint64_t count) {
+ std::string s = std::to_string(count);
+ for (size_t i = s.size() - 1, j = 1; i > 0; --i, ++j) {
+ if (j == 3) {
+ s.insert(s.begin() + i, ',');
+ j = 0;
+ }
+ }
+ return s;
+}
+
} // namespace simpleperf
diff --git a/simpleperf/utils.h b/simpleperf/utils.h
index cd169889..0409387f 100644
--- a/simpleperf/utils.h
+++ b/simpleperf/utils.h
@@ -37,6 +37,10 @@
namespace simpleperf {
+static constexpr size_t kKilobyte = 1024;
+static constexpr size_t kMegabyte = 1024 * kKilobyte;
+static constexpr uint64_t kGigabyte = 1024 * kMegabyte;
+
static inline uint64_t AlignDown(uint64_t value, uint64_t alignment) {
return value & ~(alignment - 1);
}
@@ -284,6 +288,9 @@ struct OverflowResult {
};
OverflowResult SafeAdd(uint64_t a, uint64_t b);
+void OverflowSafeAdd(uint64_t& dest, uint64_t add);
+
+std::string ReadableCount(uint64_t count);
} // namespace simpleperf
diff --git a/simpleperf/utils_test.cpp b/simpleperf/utils_test.cpp
index 2dfbd901..747c020f 100644
--- a/simpleperf/utils_test.cpp
+++ b/simpleperf/utils_test.cpp
@@ -25,6 +25,7 @@
using namespace simpleperf;
+// @CddTest = 6.1/C-0-2
TEST(utils, ConvertBytesToValue) {
char buf[8];
for (int i = 0; i < 8; ++i) {
@@ -36,6 +37,7 @@ TEST(utils, ConvertBytesToValue) {
ASSERT_EQ(0x0706050403020100ULL, ConvertBytesToValue(buf, 8));
}
+// @CddTest = 6.1/C-0-2
TEST(utils, ArchiveHelper) {
std::unique_ptr<ArchiveHelper> ahelper = ArchiveHelper::CreateInstance(GetTestData(APK_FILE));
ASSERT_TRUE(ahelper);
@@ -61,6 +63,7 @@ TEST(utils, ArchiveHelper) {
ASSERT_FALSE(ArchiveHelper::CreateInstance("/dev/zero"));
}
+// @CddTest = 6.1/C-0-2
TEST(utils, GetCpusFromString) {
ASSERT_EQ(GetCpusFromString("0-2"), std::make_optional<std::set<int>>({0, 1, 2}));
ASSERT_EQ(GetCpusFromString("0,2-3"), std::make_optional<std::set<int>>({0, 2, 3}));
@@ -72,11 +75,13 @@ TEST(utils, GetCpusFromString) {
ASSERT_EQ(GetCpusFromString("3,2-1"), std::nullopt);
}
+// @CddTest = 6.1/C-0-2
TEST(utils, GetTidsFromString) {
ASSERT_EQ(GetTidsFromString("0,12,9", false), std::make_optional(std::set<pid_t>({0, 9, 12})));
ASSERT_EQ(GetTidsFromString("-2", false), std::nullopt);
}
+// @CddTest = 6.1/C-0-2
TEST(utils, GetPidsFromStrings) {
ASSERT_EQ(GetPidsFromStrings({"0,12", "9"}, false, false),
std::make_optional(std::set<pid_t>({0, 9, 12})));
@@ -91,6 +96,7 @@ TEST(utils, GetPidsFromStrings) {
#endif // defined(__linux__)
}
+// @CddTest = 6.1/C-0-2
TEST(utils, LineReader) {
TemporaryFile tmpfile;
close(tmpfile.release());
@@ -105,3 +111,11 @@ TEST(utils, LineReader) {
ASSERT_EQ(*line, "line2");
ASSERT_TRUE(reader.ReadLine() == nullptr);
}
+
+// @CddTest = 6.1/C-0-2
+TEST(utils, ReadableCount) {
+ ASSERT_EQ(ReadableCount(0), "0");
+ ASSERT_EQ(ReadableCount(204), "204");
+ ASSERT_EQ(ReadableCount(1000), "1,000");
+ ASSERT_EQ(ReadableCount(123456789), "123,456,789");
+}
diff --git a/simpleperf/workload_test.cpp b/simpleperf/workload_test.cpp
index f99ec753..e1ac147b 100644
--- a/simpleperf/workload_test.cpp
+++ b/simpleperf/workload_test.cpp
@@ -24,6 +24,7 @@
using namespace simpleperf;
+// @CddTest = 6.1/C-0-2
TEST(workload, success) {
IOEventLoop loop;
ASSERT_TRUE(loop.AddSignalEvent(SIGCHLD, [&]() { return loop.ExitLoop(); }));
@@ -34,6 +35,7 @@ TEST(workload, success) {
ASSERT_TRUE(loop.RunLoop());
}
+// @CddTest = 6.1/C-0-2
TEST(workload, execvp_failure) {
auto workload = Workload::CreateWorkload({"/dev/null"});
ASSERT_TRUE(workload != nullptr);
@@ -54,6 +56,7 @@ static void run_signaled_workload() {
exit(0);
}
+// @CddTest = 6.1/C-0-2
TEST(workload, signaled_warning) {
ASSERT_EXIT(run_signaled_workload(), testing::ExitedWithCode(0),
"child process was terminated by signal");
@@ -72,6 +75,7 @@ static void run_exit_nonzero_workload() {
exit(0);
}
+// @CddTest = 6.1/C-0-2
TEST(workload, exit_nonzero_warning) {
ASSERT_EXIT(run_exit_nonzero_workload(), testing::ExitedWithCode(0),
"child process exited with exit code");
diff --git a/slideshow/Android.bp b/slideshow/Android.bp
index b6c3922a..e1d9b5db 100644
--- a/slideshow/Android.bp
+++ b/slideshow/Android.bp
@@ -19,9 +19,6 @@ license {
cc_binary {
name: "slideshow",
srcs: ["slideshow.cpp"],
- cflags: [
- "-D__STDC_LIMIT_MACROS",
- ],
shared_libs: [
"libminui",
"libpng",
diff --git a/tests/audio/alsa/Android.bp b/tests/audio/alsa/Android.bp
index 0bcc15eb..2c6b84aa 100644
--- a/tests/audio/alsa/Android.bp
+++ b/tests/audio/alsa/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_native_tools_libraries",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "system_extras_tests_license"
diff --git a/tests/binder/benchmarks/Android.bp b/tests/binder/benchmarks/Android.bp
index 6a550688..baef92f7 100644
--- a/tests/binder/benchmarks/Android.bp
+++ b/tests/binder/benchmarks/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_android_core_graphics_stack",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "system_extras_tests_license"
diff --git a/tests/bootloader/Android.mk b/tests/bootloader/Android.mk
index a5421a50..c9fd611d 100644
--- a/tests/bootloader/Android.mk
+++ b/tests/bootloader/Android.mk
@@ -1,8 +1,7 @@
+# TODO: Remove this file, bootloader_unit_test is still referenced on old branches
+
LOCAL_PATH := $(call my-dir)
-# Build a module that has all of the python files as its LOCAL_PICKUP_FILES.
-# Since no action needs to be taken to compile the python source, just
-# use BUILD_PHONY_PACKAGE to give us a target to execute.
include $(CLEAR_VARS)
LOCAL_MODULE := bootloader_unit_test
@@ -11,18 +10,4 @@ LOCAL_LICENSE_CONDITIONS := notice
LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE
LOCAL_MODULE_TAGS := tests
-bootloader_py_files := $(call find-subdir-files, *.py)
-
-bootloader_zip_prefix := $(TARGET_OUT_DATA)/py_bootloader
-bootloader_zip_path := $(bootloader_zip_prefix)/nativetest/py_bootloader
-
-GEN := $(addprefix $(bootloader_zip_path)/, $(bootloader_py_files))
-$(GEN) : PRIVATE_PATH := $(LOCAL_PATH)
-$(GEN) : PRIVATE_CUSTOM_TOOL = cp $< $@
-$(GEN) : $(bootloader_zip_path)/% : $(LOCAL_PATH)/%
- $(transform-generated-source)
-
-LOCAL_PICKUP_FILES := $(bootloader_zip_prefix)/nativetest
-LOCAL_ADDITIONAL_DEPENDENCIES := $(GEN)
-
include $(BUILD_PHONY_PACKAGE)
diff --git a/tests/bootloader/bootloadertest.py b/tests/bootloader/bootloadertest.py
index 963e22f3..8de7ac7b 100644
--- a/tests/bootloader/bootloadertest.py
+++ b/tests/bootloader/bootloadertest.py
@@ -12,7 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import adb
+from __future__ import print_function
+
import argparse
import os
import unittest
@@ -85,9 +86,9 @@ class ShellTest(unittest.TestCase):
except ValueError:
self.fail("slot-count (%s) is not an integer" % val)
except subprocess.CalledProcessError:
- print "Does not appear to be an A/B device."
+ print("Does not appear to be an A/B device.")
if not slotcount:
- print "Does not appear to be an A/B device."
+ print("Does not appear to be an A/B device.")
return slotcount
def test_getvarall(self):
@@ -128,7 +129,7 @@ class ShellTest(unittest.TestCase):
self.exists_yes_no("slot-unbootable:"+slot, var_all)
self.exists_integer("slot-retry-count:"+slot, var_all)
else:
- print "This does not appear to be an A/B device."
+ print("This does not appear to be an A/B device.")
def test_getvar_nonexistent(self):
"""Tests behaviour of nonexistent variables."""
diff --git a/tests/fstest/Android.bp b/tests/fstest/Android.bp
index 8c9ce68e..6cb25233 100644
--- a/tests/fstest/Android.bp
+++ b/tests/fstest/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_android_kernel",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "system_extras_tests_license"
diff --git a/tests/iptables/qtaguid/socketTag.cpp b/tests/iptables/qtaguid/socketTag.cpp
deleted file mode 100644
index f29ae840..00000000
--- a/tests/iptables/qtaguid/socketTag.cpp
+++ /dev/null
@@ -1,398 +0,0 @@
-/*
- * Copyright (C) 2011 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 requied 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.
- *
- */
-
-/*
- * This socket tagging test is to ensure that the
- * netfilter/xt_qtaguid kernel module somewhat behaves as expected
- * with respect to tagging sockets.
- */
-
-#define LOG_TAG "socketTagTest"
-
-#include <assert.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string>
-#include <sys/socket.h>
-#include <sys/types.h>
-
-#include <fstream>
-
-#include <android-base/stringprintf.h>
-#include <gtest/gtest.h>
-#include <utils/Log.h>
-
-#include <testUtil.h>
-
-namespace android {
-
-class SockInfo {
-public:
- SockInfo() : fd(-1), addr(NULL) {};
- int setup(uint64_t tag);
- bool checkTag(uint64_t tag, uid_t uid);
- int fd;
- void *addr;
-};
-
-
-int openCtrl() {
- int ctrl;
- ctrl = open("/proc/net/xt_qtaguid/ctrl", O_RDWR);
- if (!ctrl) {
- testPrintE("qtaguid ctrl open failed: %s", strerror(errno));
- }
- return ctrl;
-}
-
-int doCtrlCommand(const char *fmt, ...)
- __attribute__((__format__(__printf__, 1, 2)));
-
-int doCtrlCommand(const char *fmt, ...) {
- char *buff;
- int ctrl;
- int res;
- va_list argp;
-
- va_start(argp, fmt);
- ctrl = openCtrl();
- vasprintf(&buff, fmt, argp);
- errno = 0;
- res = write(ctrl, buff, strlen(buff));
- testPrintI("cmd: '%s' res=%d %d/%s", buff, res, errno, strerror(errno));
- close(ctrl);
- free(buff);
- va_end(argp);
- return res;
-}
-
-
-int writeModuleParam(const char *param, const char *data) {
- int param_fd;
- int res;
- std::string filename("/sys/module/xt_qtaguid/parameters/");
-
- filename += param;
- param_fd = open(filename.c_str(), O_WRONLY);
- if (param_fd < 0) {
- testPrintE("qtaguid param open failed: %s", strerror(errno));
- return -1;
- }
- res = write(param_fd, data, strlen(data));
- if (res < 0) {
- testPrintE("qtaguid param write failed: %s", strerror(errno));
- }
- close(param_fd);
- return res;
-}
-
-/*----------------------------------------------------------------*/
-int SockInfo::setup(uint64_t tag) {
- fd = socket(AF_INET, SOCK_STREAM, 0);
- if (fd < 0) {
- testPrintE("socket creation failed: %s", strerror(errno));
- return -1;
- }
- if (doCtrlCommand("t %d %" PRIu64, fd, tag) < 0) {
- testPrintE("socket setup: failed to tag");
- close(fd);
- return -1;
- }
- if (!checkTag(tag, getuid())) {
- testPrintE("socket setup: Unexpected results: tag not found");
- close(fd);
- return -1;
- }
- if (doCtrlCommand("u %d", fd) < 0) {
- testPrintE("socket setup: Unexpected results");
- close(fd);
- return -1;
- }
- return 0;
-}
-
-/* checkTag() also tries to lookup the socket address in the kernel and
- * return it when *addr == NULL.
- * This allows for better look ups when another process is also setting the same
- * tag + uid. But it is not fool proof.
- * Without the kernel reporting more info on who setup the socket tag, it is
- * not easily verifiable from user-space.
- * Returns: true if tag found.
- */
-bool SockInfo::checkTag(uint64_t acct_tag, uid_t uid) {
- int res;
- uint64_t k_tag;
- uint32_t k_uid;
- long dummy_count;
- pid_t dummy_pid;
-
- std::ifstream fctrl("/proc/net/xt_qtaguid/ctrl", std::fstream::in);
- if(!fctrl.is_open()) {
- testPrintI("qtaguid ctrl open failed!");
- }
-
- uint64_t full_tag = acct_tag | uid;
- std::string buff = android::base::StringPrintf(" tag=0x%" PRIx64 " (uid=%u)", full_tag, uid);
- if (addr) {
- buff = android::base::StringPrintf("sock=%" PRIxPTR, (uintptr_t)addr) + buff;
- }
-
- testPrintI("looking for '%s'", buff.c_str());
- std::string ctrl_data;
- std::size_t pos = std::string::npos;
- while(std::getline(fctrl, ctrl_data)) {
- testPrintI("<ctrl_raw_data> : %s", ctrl_data.c_str());
- pos = ctrl_data.find(buff);
- if (pos != std::string::npos) {
- if(!addr) {
- testPrintI("matched data : %s", ctrl_data.c_str());
- assert(sizeof(void*) == sizeof(long int));
- res = sscanf(ctrl_data.c_str(),
- "sock=%" SCNxPTR " tag=0x%" SCNx64 " (uid=%" SCNu32 ") pid=%u f_count=%lu",
- (uintptr_t *)&addr, &k_tag, &k_uid, &dummy_pid, &dummy_count );
- if (!(res == 5 && k_tag == full_tag && k_uid == uid)) {
- testPrintE("Unable to read sock addr res=%d", res);
- addr = 0;
- } else {
- testPrintI("Got sock_addr %lx", addr);
- }
- }
- break;
- }
- }
- return pos != std::string::npos;
-}
-
-
-class SocketTaggingTest : public ::testing::Test {
-protected:
- virtual void SetUp() {
- ctrl_fd = -1;
- dev_fd = -1;
- my_uid = getuid();
- my_pid = getpid();
- srand48(my_pid * my_uid);
- // Adjust fake UIDs and tags so that multiple instances can run in parallel.
- fake_uid = testRand();
- fake_uid2 = testRand();
- valid_tag1 = ((uint64_t)my_pid << 48) | ((uint64_t)testRand() << 32);
- valid_tag2 = ((uint64_t)my_pid << 48) | ((uint64_t)testRand() << 32);
- valid_tag2 &= 0xffffff00ffffffffLLU; // Leave some room to make counts visible.
- testPrintI("* start: pid=%lu uid=%lu uid1=0x%lx/%lu uid2=0x%lx/%lu"
- " tag1=0x%" PRIx64 "/%" PRIu64 " tag2=0x%" PRIx64 "/% " PRIu64,
- (unsigned long)my_pid, (unsigned long)my_uid,
- (unsigned long)fake_uid, (unsigned long)fake_uid,
- (unsigned long)fake_uid2, (unsigned long)fake_uid2,
- valid_tag1, valid_tag1, valid_tag2, valid_tag2);
- max_uint_tag = 0xffffffff00000000LLU;
- max_uint_tag = 1LLU << 63 | (((uint64_t)my_pid << 48) ^ max_uint_tag);
-
- testPrintI("kernel has qtaguid");
- ctrl_fd = openCtrl();
- ASSERT_GE(ctrl_fd, 0) << "qtaguid ctrl open failed";
- close(ctrl_fd);
- dev_fd = open("/dev/xt_qtaguid", O_RDONLY);
- EXPECT_GE(dev_fd, 0) << "qtaguid dev open failed";
-
- // We want to clean up any previous faulty test runs.
- testPrintI("delete command does not fail");
- EXPECT_GE(doCtrlCommand("d 0 %u", fake_uid), 0) << "Failed to delete fake_uid";
- EXPECT_GE(doCtrlCommand("d 0 %u", fake_uid2), 0) << "Failed to delete fake_uid2";
- EXPECT_GE(doCtrlCommand("d 0 %u", my_uid), 0) << "Failed to delete my_uid";
-
- testPrintI("setup sock0 and addr via tag");
- ASSERT_FALSE(sock0.setup(valid_tag1)) << "socket0 setup failed";
- testPrintI("setup sock1 and addr via tag");
- ASSERT_FALSE(sock1.setup(valid_tag1)) << "socket1 setup failed";
- }
-
- virtual void TearDown() {
- if (dev_fd >= 0) {
- close(dev_fd);
- }
- if (ctrl_fd >= 0) {
- close(ctrl_fd);
- }
- }
-
- SockInfo sock0;
- SockInfo sock1;
- int ctrl_fd;
- int dev_fd;
- uid_t fake_uid;
- uid_t fake_uid2;
- uid_t my_uid;
- pid_t my_pid;
- uint64_t valid_tag1;
- uint64_t valid_tag2;
- uint64_t max_uint_tag;
- static const uint64_t invalid_tag1 = 0x0000000100000001LLU;
- static const int max_tags = 5;
-};
-
-TEST_F(SocketTaggingTest, TagData) {
- max_uint_tag = 0xffffffff00000000LLU;
- char *max_tags_str;
-
- testPrintI("setup tag limit");
- asprintf(&max_tags_str, "%d", max_tags);
- ASSERT_GE(writeModuleParam("max_sock_tags", max_tags_str), 0) << "Failed to setup tag limit";
-
- testPrintI("tag quota reach limit");
- for (int cnt = 0; cnt < max_tags; cnt++ ) {
- uint64_t tag = valid_tag2 + ((uint64_t)cnt << 32);
- EXPECT_GE(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, tag , fake_uid2), 0)
- << "Tagging within limit failed";
- EXPECT_TRUE(sock0.checkTag(tag, fake_uid2))<< "Unexpected results: tag not found";
- }
-
- testPrintI("tag quota go over limit");
- uint64_t new_tag = valid_tag2 + ((uint64_t)max_tags << 32);
- EXPECT_LT(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, new_tag, fake_uid2), 0);
- EXPECT_TRUE(sock0.checkTag(valid_tag2 + (((uint64_t)max_tags - 1) << 32),
- fake_uid2)) << "Unexpected results: tag not found";
-
- testPrintI("valid untag");
- EXPECT_GE(doCtrlCommand("u %d", sock0.fd), 0);
- EXPECT_FALSE(sock0.checkTag(valid_tag2 + (((uint64_t)max_tags - 1) << 32), fake_uid2))
- << "Untagged tag should not be there";
-
- testPrintI("tag after untag should not free up max tags");
- uint64_t new_tag2 = valid_tag2 + ((uint64_t)max_tags << 32);
- EXPECT_LT(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, new_tag2 , fake_uid2), 0);
- EXPECT_FALSE(sock0.checkTag(valid_tag2 + ((uint64_t)max_tags << 32), fake_uid2))
- << "Tag should not be there";
-
- testPrintI("delete one tag");
- uint64_t new_tag3 = valid_tag2 + (((uint64_t)max_tags / 2) << 32);
- EXPECT_GE(doCtrlCommand("d %" PRIu64 " %u", new_tag3, fake_uid2), 0);
-
- testPrintI("2 tags after 1 delete pass/fail");
- uint64_t new_tag4;
- new_tag4 = valid_tag2 + (((uint64_t)max_tags + 1 ) << 32);
- EXPECT_GE(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, new_tag4 , fake_uid2), 0);
- EXPECT_TRUE(sock0.checkTag(valid_tag2 + (((uint64_t)max_tags + 1) << 32), fake_uid2))
- << "Tag not found";
- new_tag4 = valid_tag2 + (((uint64_t)max_tags + 2 ) << 32);
- EXPECT_LT(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, new_tag4 , fake_uid2), 0);
- EXPECT_FALSE(sock0.checkTag(valid_tag2 + (((uint64_t)max_tags + 2) << 32), fake_uid2))
- << "Tag should not be there";
-
- /* TODO(jpa): test tagging two different sockets with same tags and
- * check refcounts the tag_node should be +2
- */
-}
-
-TEST_F(SocketTaggingTest, InsufficientArgsFails) {
- // Insufficient args. Expected failure
- EXPECT_LE(doCtrlCommand("t"), 0) << "Insufficient args, should fail.";
-}
-
-TEST_F(SocketTaggingTest, BadCommandFails) {
- // Bad command. Expected failure";
- EXPECT_LE(doCtrlCommand("?"), 0) << "Bad command, should fail";
-}
-
-TEST_F(SocketTaggingTest, NoTagNoUid) {
- // no tag, no uid
- EXPECT_GE(doCtrlCommand("t %d", sock0.fd), 0);
- ASSERT_TRUE(sock0.checkTag(0, my_uid)) << "Tag not found";
-}
-
-TEST_F(SocketTaggingTest, InvalidTagFail) {
- // Invalid tag. Expected failure
- EXPECT_LE(doCtrlCommand("t %d %" PRIu64, sock0.fd, invalid_tag1), 0);
- ASSERT_FALSE(sock0.checkTag(invalid_tag1, my_uid)) << "Tag should not be there";
-}
-
-TEST_F(SocketTaggingTest, ValidTagWithNoUid) {
- // Valid tag with no uid
- EXPECT_GE(doCtrlCommand("t %d %" PRIu64, sock0.fd, valid_tag1), 0);
- EXPECT_TRUE(sock0.checkTag(valid_tag1, my_uid)) << "Tag not found";
-}
-
-TEST_F(SocketTaggingTest, ValidUntag) {
- // Valid untag
- EXPECT_GE(doCtrlCommand("t %d %" PRIu64, sock0.fd, valid_tag1), 0);
- EXPECT_TRUE(sock0.checkTag(valid_tag1, my_uid)) << "Tag not found";
- EXPECT_GE(doCtrlCommand("u %d", sock0.fd), 0);
- EXPECT_FALSE(sock0.checkTag(valid_tag1, my_uid)) << "Tag should be removed";
-}
-
-TEST_F(SocketTaggingTest, ValidFirsttag) {
- // Valid 1st tag
- EXPECT_GE(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, valid_tag2, fake_uid), 0);
- EXPECT_TRUE(sock0.checkTag(valid_tag2, fake_uid)) << "Tag not found.";
-}
-
-TEST_F(SocketTaggingTest, ValidReTag) {
- // Valid re-tag
- EXPECT_GE(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, valid_tag2, fake_uid), 0);
- EXPECT_GE(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, valid_tag2, fake_uid), 0);
- EXPECT_TRUE(sock0.checkTag(valid_tag2, fake_uid)) << "Tag not found.";
-}
-
-TEST_F(SocketTaggingTest, ValidReTagWithAcctTagChange) {
- // Valid re-tag with acct_tag change
- EXPECT_GE(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, valid_tag2, fake_uid), 0);
- EXPECT_GE(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, valid_tag1, fake_uid), 0);
- EXPECT_TRUE(sock0.checkTag(valid_tag1, fake_uid)) << "Tag not found.";
-}
-
-TEST_F(SocketTaggingTest, ReTagWithUidChange) {
- // Re-tag with uid change
- EXPECT_GE(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, valid_tag1, fake_uid), 0);
- EXPECT_GE(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, valid_tag2, fake_uid2), 0);
-}
-
-TEST_F(SocketTaggingTest, Valid64BitAcctTag) {
- // Valid 64bit acct tag
- EXPECT_GE(doCtrlCommand("t %d %" PRIu64, sock0.fd, max_uint_tag), 0);
- EXPECT_TRUE(sock0.checkTag(max_uint_tag, my_uid)) << "Tag not found.";
-}
-
-TEST_F(SocketTaggingTest, TagAnotherSocket) {
- testPrintI("Tag two sockets");
- EXPECT_GE(doCtrlCommand("t %d %" PRIu64, sock0.fd, max_uint_tag), 0);
- EXPECT_GE(doCtrlCommand("t %d %" PRIu64 " %u", sock1.fd, valid_tag1, fake_uid2), 0);
- EXPECT_TRUE(sock1.checkTag(valid_tag1, fake_uid2)) << "Tag not found.";
- testPrintI("Untag socket0 of them only.");
- EXPECT_GE(doCtrlCommand("u %d", sock0.fd), 0);
- EXPECT_FALSE(sock0.checkTag(max_uint_tag, fake_uid)) << "Tag should not be there";
- EXPECT_TRUE(sock1.checkTag(valid_tag1, fake_uid2)) << "Tag not found";
- testPrintI("Now untag socket1 as well.");
- EXPECT_GE(doCtrlCommand("u %d", sock1.fd), 0);
- EXPECT_FALSE(sock1.checkTag(valid_tag1, fake_uid2)) << "Tag should not be there";
-}
-
-TEST_F(SocketTaggingTest, TagInvalidSocketFail) {
- // Invalid tag. Expected failure
- close(sock0.fd);
- EXPECT_LE(doCtrlCommand("t %d %" PRIu64 " %u", sock0.fd, valid_tag1, my_uid), 0);
- EXPECT_FALSE(sock0.checkTag(valid_tag1, my_uid)) << "Tag should not be there";
-}
-
-TEST_F(SocketTaggingTest, UntagInvalidSocketFail) {
- // Invalid untag. Expected failure";
- close(sock1.fd);
- EXPECT_LE(doCtrlCommand("u %d", sock1.fd), 0);
-}
-
-} // namespace android
diff --git a/tests/kernel.config/OWNERS b/tests/kernel.config/OWNERS
new file mode 100644
index 00000000..65d27f47
--- /dev/null
+++ b/tests/kernel.config/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 119452
+
+# TODO: we likely want to delete or factor out these tests
+# b/261015480#comment6
+
+smoreland@google.com
diff --git a/tests/memeater/NOTICE b/tests/memeater/NOTICE
deleted file mode 100644
index 25f8ab95..00000000
--- a/tests/memeater/NOTICE
+++ /dev/null
@@ -1,190 +0,0 @@
-
- Copyright (c) 2017, 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.
-
- 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.
-
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
diff --git a/tests/memeater/memeater.c b/tests/memeater/memeater.c
deleted file mode 100644
index b2e0b269..00000000
--- a/tests/memeater/memeater.c
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2008 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.
- *
- * Simple memory eater. Runs as a daemon. Prints the child PID to
- * std so you can easily kill it later.
- * Usage: memeater <size in MB>
- */
-
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <fcntl.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-
-int main(int argc, char *argv[])
-{
- pid_t pid;
- int fd;
- int numMb = argc > 1 ? atoi(argv[1]) : 1;
-
- if (argc < 2) {
- printf("Usage: memeater <num MB to allocate>\n");
- exit(1);
- }
-
- switch (fork()) {
- case -1:
- perror(argv[0]);
- exit(1);
- break;
- case 0: /* child */
- chdir("/");
- umask(0);
- setpgrp();
- setsid();
- /* fork again to fully detach from controlling terminal. */
- switch (pid = fork()) {
- case -1:
- perror("failed to fork");
- break;
- case 0: /* second child */
- /* redirect to /dev/null */
- close(0);
- open("/dev/null", 0);
- for (fd = 3; fd < 256; fd++) {
- close(fd);
- }
-
- printf("Allocating %d MB\n", numMb);
- fflush(stdout);
- /* allocate memory and fill it */
- while (numMb > 0) {
- // Allocate 500MB at a time at most
- int mbToAllocate = numMb > 500 ? 500 : numMb;
- int bytesToAllocate = mbToAllocate * 1024 * 1024;
- char *p = malloc(bytesToAllocate);
- if (p == NULL) {
- printf("Failed to allocate memory\n");
- exit(1);
- }
- for (int j = 0; j < bytesToAllocate; j++) {
- p[j] = j & 0xFF;
- }
- printf("Allocated %d MB %p\n", mbToAllocate, p);
- fflush(stdout);
- numMb -= mbToAllocate;
- }
-
- close(1);
- if (open("/dev/null", O_WRONLY) < 0) {
- perror("/dev/null");
- exit(1);
- }
- close(2);
- dup(1);
-
- /* Sit around doing nothing */
- while (1) {
- usleep(1000000);
- }
- default:
- /* so caller can easily kill it later. */
- printf("%d\n", pid);
- exit(0);
- break;
- }
- break;
- default:
- exit(0);
- break;
- }
- return 0;
-}
-
diff --git a/tests/tcp_nuke_addr/Android.bp b/tests/tcp_nuke_addr/Android.bp
index 1a504395..6de461bb 100644
--- a/tests/tcp_nuke_addr/Android.bp
+++ b/tests/tcp_nuke_addr/Android.bp
@@ -1,4 +1,5 @@
package {
+ default_team: "trendy_team_fwk_core_networking",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "system_extras_tests_license"
diff --git a/tests/timetest/Android.bp b/tests/timetest/Android.bp
index c4f361cb..a46385be 100644
--- a/tests/timetest/Android.bp
+++ b/tests/timetest/Android.bp
@@ -1,6 +1,7 @@
// Copyright 2006 The Android Open Source Project
package {
+ default_team: "trendy_team_build_infra",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "system_extras_tests_license"
diff --git a/toolchain-extras/Android.bp b/toolchain-extras/Android.bp
index f15defda..1e71e358 100644
--- a/toolchain-extras/Android.bp
+++ b/toolchain-extras/Android.bp
@@ -1,4 +1,5 @@
package {
+ default_team: "trendy_team_android_testing_experiences",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -18,9 +19,6 @@ cc_library_static {
native_bridge_supported: true,
vendor_available: true,
product_available: true,
- vndk: {
- enabled: true,
- },
ramdisk_available: true,
vendor_ramdisk_available: true,
recovery_available: true,
@@ -36,9 +34,6 @@ cc_library_static {
native_bridge_supported: true,
vendor_available: true,
product_available: true,
- vndk: {
- enabled: true,
- },
sdk_version: "minimum",
}
@@ -56,9 +51,6 @@ cc_defaults {
native_bridge_supported: true,
vendor_available: true,
product_available: true,
- vndk: {
- enabled: true,
- },
}
cc_defaults {
@@ -98,7 +90,10 @@ cc_library_static {
cc_library_static {
name: "libprofile-clang-extras_cfi_support",
defaults: ["libprofile-clang-platform-defaults"],
-
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
sanitize: {
cfi: true,
config: {
diff --git a/tools/check_elf_alignment.sh b/tools/check_elf_alignment.sh
new file mode 100755
index 00000000..b74f34ae
--- /dev/null
+++ b/tools/check_elf_alignment.sh
@@ -0,0 +1,90 @@
+#!/bin/bash
+progname="${0##*/}"
+progname="${progname%.sh}"
+
+# usage: check_elf_alignment.sh [path to *.so files|path to *.apk]
+
+cleanup_trap() {
+ if [ -n "${tmp}" -a -d "${tmp}" ]; then
+ rm -rf ${tmp}
+ fi
+ exit $1
+}
+
+usage() {
+ echo "Host side script to check the ELF alignment of shared libraries."
+ echo "Shared libraries are reported ALIGNED when their ELF regions are"
+ echo "16 KB or 64 KB aligned. Otherwise they are reported as UNALIGNED."
+ echo
+ echo "Usage: ${progname} [input-path|input-APK]"
+}
+
+if [ ${#} -ne 1 ]; then
+ usage
+ exit
+fi
+
+case ${1} in
+ --help | -h | -\?)
+ usage
+ exit
+ ;;
+
+ *)
+ dir="${1}"
+ ;;
+esac
+
+if ! [ -f "${dir}" -o -d "${dir}" ]; then
+ echo "Invalid file: ${dir}" >&2
+ exit 1
+fi
+
+if [[ "${dir}" == *.apk ]]; then
+ trap 'cleanup_trap' EXIT
+
+ if { zipalign --help 2>&1 | grep -q "\-P <pagesize_kb>"; }; then
+ echo "=== APK zip-alignment ==="
+ zipalign -v -c -P 16 4 "${dir}" | egrep 'lib/arm64-v8a|lib/x86_64|Verification'
+ echo "========================="
+ else
+ echo "NOTICE: Zip alignment check requires build-tools version 35.0.0-rc3 or higher."
+ echo " You can install the latest build-tools by running the below command"
+ echo " and updating your \$PATH:"
+ echo
+ echo " sdkmanager \"build-tools;35.0.0-rc3\""
+ fi
+
+ dir_filename=$(basename "${dir}")
+ tmp=$(mktemp -d -t "${dir_filename%.apk}_out_XXXXX")
+ unzip "${dir}" lib/* -d "${tmp}" >/dev/null 2>&1
+ dir="${tmp}"
+fi
+
+RED="\e[31m"
+GREEN="\e[32m"
+ENDCOLOR="\e[0m"
+
+unaligned_libs=()
+
+echo
+echo "=== ELF alignment ==="
+
+matches="$(find "${dir}" -name "*.so" -type f)"
+IFS=$'\n'
+for match in $matches; do
+ res="$(objdump -p ${match} | grep LOAD | awk '{ print $NF }' | head -1)"
+ if [[ $res =~ "2**14" ]] || [[ $res =~ "2**16" ]]; then
+ echo -e "${match}: ${GREEN}ALIGNED${ENDCOLOR} ($res)"
+ else
+ echo -e "${match}: ${RED}UNALIGNED${ENDCOLOR} ($res)"
+ unaligned_libs+=("${match}")
+ fi
+done
+
+if [ ${#unaligned_libs[@]} -gt 0 ]; then
+ echo -e "${RED}Found ${#unaligned_libs[@]} unaligned libs (only arm64-v8a/x86_64 libs need to be aligned).${ENDCOLOR}"
+elif [ -n "${dir_filename}" ]; then
+ echo -e "ELF Verification Successful"
+fi
+echo "====================="
diff --git a/verity/Android.bp b/verity/Android.bp
index ef09528c..9dfa9886 100644
--- a/verity/Android.bp
+++ b/verity/Android.bp
@@ -1,4 +1,5 @@
package {
+ default_team: "trendy_team_android_kernel",
default_applicable_licenses: ["system_extras_verity_license"],
}
diff --git a/verity/build_verity_tree.cpp b/verity/build_verity_tree.cpp
index 9edc81a1..e9df35c4 100644
--- a/verity/build_verity_tree.cpp
+++ b/verity/build_verity_tree.cpp
@@ -33,11 +33,11 @@ bool generate_verity_tree(const std::string& data_filename,
return false;
}
- struct sparse_file* file;
+ std::unique_ptr<sparse_file, decltype(&sparse_file_destroy)> file(nullptr, sparse_file_destroy);
if (sparse) {
- file = sparse_file_import(data_fd, false, false);
+ file.reset(sparse_file_import(data_fd, false, false));
} else {
- file = sparse_file_import_auto(data_fd, false, verbose);
+ file.reset(sparse_file_import_auto(data_fd, false, verbose));
}
if (!file) {
@@ -45,7 +45,7 @@ bool generate_verity_tree(const std::string& data_filename,
return false;
}
- int64_t len = sparse_file_len(file, false, false);
+ int64_t len = sparse_file_len(file.get(), false, false);
if (len % block_size != 0) {
LOG(ERROR) << "file size " << len << " is not a multiple of " << block_size
<< " byte";
@@ -64,8 +64,7 @@ bool generate_verity_tree(const std::string& data_filename,
? 0
: 1;
};
- sparse_file_callback(file, false, false, hash_callback, builder);
- sparse_file_destroy(file);
+ sparse_file_callback(file.get(), false, false, hash_callback, builder);
if (!builder->BuildHashTree()) {
return false;