summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2021-07-15 01:32:05 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2021-07-15 01:32:05 +0000
commitc1ddcec52bb6a95f04da00965374f4ac744945dd (patch)
tree267b4b32eec1a9c1ee6b136866a9352f2d782157
parente474f50680b5c938d7462172b949578699df2854 (diff)
parent47b3c09c451749824a70a1d392097670a7a4bf95 (diff)
downloadextras-android12-mainline-conscrypt-release.tar.gz
Snap for 7550844 from 47b3c09c451749824a70a1d392097670a7a4bf95 to mainline-conscrypt-releaseandroid-mainline-12.0.0_r8android-mainline-12.0.0_r25android12-mainline-conscrypt-release
Change-Id: Ia9887db7ac5f1fa821cd8ba0b4e03dd7335e1c31
l---------.clang-format1
l---------.clang-format-21
l---------.clang-format-41
-rw-r--r--.clang-format-none7
-rw-r--r--ANRdaemon/ANRdaemon.cpp193
-rw-r--r--ANRdaemon/Android.bp15
-rw-r--r--METADATA3
-rw-r--r--PREUPLOAD.cfg8
-rw-r--r--alloc-stress/Android.bp12
-rw-r--r--alloc-stress/alloc-stress.cpp126
-rw-r--r--alloc-stress/mem-pressure.cpp38
-rw-r--r--app-launcher/Android.bp4
l---------boot_control_copy/.clang-format1
-rw-r--r--boot_control_copy/Android.bp17
-rw-r--r--boot_control_copy/boot_control_copy.cpp122
-rw-r--r--boot_control_copy/bootinfo.cpp49
-rw-r--r--boot_control_copy/bootinfo.h8
-rw-r--r--bootctl/Android.bp18
-rw-r--r--bootctl/bootctl.cpp86
-rw-r--r--boottime_tools/bootanalyze/README.md26
-rwxr-xr-xboottime_tools/bootanalyze/bootanalyze.py289
-rwxr-xr-xboottime_tools/bootanalyze/bootanalyze.sh72
-rw-r--r--boottime_tools/bootanalyze/bugreport_anayze.py2
-rw-r--r--boottime_tools/bootanalyze/config.yaml6
-rw-r--r--boottime_tools/bootanalyze/stressfs/Android.bp4
-rw-r--r--boottime_tools/bootanalyze/stressfs/AndroidManifest.xml17
-rw-r--r--boottime_tools/bootio/Android.bp4
-rw-r--r--brillo_config/Android.mk8
-rw-r--r--checkpoint_gc/Android.bp4
-rw-r--r--checkpoint_gc/checkpoint_gc.sh62
-rw-r--r--cppreopts/Android.bp4
-rw-r--r--cpustats/Android.bp30
-rw-r--r--cpustats/cpustats.c89
-rw-r--r--crypto-perf/Android.bp17
-rw-r--r--crypto-perf/crypto.cpp139
-rw-r--r--ext4_utils/Android.bp28
-rw-r--r--ext4_utils/Android.mk3
-rw-r--r--ext4_utils/blk_alloc_to_base_fs.cpp16
-rw-r--r--ext4_utils/ext4_sb.cpp38
-rw-r--r--ext4_utils/ext4_utils.cpp432
-rw-r--r--ext4_utils/helpers.h36
-rw-r--r--ext4_utils/include/ext4_utils/ext4.h643
-rw-r--r--ext4_utils/include/ext4_utils/ext4_extents.h59
-rw-r--r--ext4_utils/include/ext4_utils/ext4_sb.h39
-rw-r--r--ext4_utils/include/ext4_utils/ext4_utils.h77
-rw-r--r--ext4_utils/include/ext4_utils/jbd2.h123
-rw-r--r--ext4_utils/include/ext4_utils/wipe.h4
-rw-r--r--ext4_utils/include/ext4_utils/xattr.h29
-rw-r--r--ext4_utils/mkuserimg_mke2fs.py17
-rw-r--r--ext4_utils/wipe.cpp86
-rw-r--r--f2fs_utils/Android.bp26
-rw-r--r--f2fs_utils/f2fs_sparseblock.c296
-rw-r--r--f2fs_utils/f2fs_sparseblock.h29
-rwxr-xr-xf2fs_utils/mkf2fsuserimg.sh94
-rw-r--r--ioblame/Android.bp4
-rw-r--r--ioshark/Android.bp4
-rw-r--r--ioshark/compile_ioshark.h6
-rw-r--r--ioshark/compile_ioshark_subr.c2
-rw-r--r--ioshark/ioshark.h2
-rw-r--r--ioshark/ioshark_bench_mmap.c2
l---------iotop/.clang-format1
-rw-r--r--iotop/Android.bp17
-rw-r--r--iotop/iotop.cpp116
-rw-r--r--iotop/tasklist.cpp8
-rw-r--r--iotop/tasklist.h6
-rw-r--r--iotop/taskstats.cpp134
-rw-r--r--iotop/taskstats.h13
-rw-r--r--kexec_tools/Android.bp17
-rw-r--r--kexec_tools/kexec.h10
-rw-r--r--kexec_tools/kexecload.c90
-rw-r--r--latencytop/Android.bp17
-rw-r--r--latencytop/latencytop.c125
-rw-r--r--libfec/Android.bp17
-rw-r--r--libfec/fec_open.cpp2
-rw-r--r--libfec/fec_private.h2
-rw-r--r--libfec/fec_process.cpp2
-rw-r--r--libfec/fec_verity.cpp8
-rw-r--r--libfec/test/Android.bp20
-rw-r--r--libfscrypt/Android.bp17
-rw-r--r--libfscrypt/fscrypt.cpp19
-rw-r--r--libfscrypt/include/fscrypt/fscrypt.h12
-rw-r--r--libfscrypt/tests/Android.bp9
-rw-r--r--libfscrypt/tests/fscrypt_test.cpp13
l---------libjsonpb/.clang-format1
-rw-r--r--libjsonpb/README.md2
-rw-r--r--libjsonpb/TEST_MAPPING8
-rw-r--r--libjsonpb/parse/Android.bp4
-rw-r--r--libjsonpb/parse/include/jsonpb/error_or.h67
-rw-r--r--libjsonpb/parse/include/jsonpb/jsonpb.h13
-rw-r--r--libjsonpb/parse/jsonpb.cpp46
-rw-r--r--libjsonpb/verify/Android.bp7
-rw-r--r--libjsonpb/verify/include/jsonpb/json_schema_test.h29
-rw-r--r--libjsonpb/verify/include/jsonpb/verify.h11
-rw-r--r--libjsonpb/verify/test.cpp65
-rw-r--r--libjsonpb/verify/verify.cpp62
-rw-r--r--memory_replay/Android.bp17
-rw-r--r--memory_replay/main.cpp20
-rw-r--r--memtrack/Android.bp17
-rw-r--r--mmap-perf/Android.bp17
-rw-r--r--module_ndk_libs/README.md2
-rw-r--r--module_ndk_libs/libnativehelper/Android.bp58
-rw-r--r--module_ndk_libs/libnativehelper/NOTICE190
-rw-r--r--module_ndk_libs/libnativehelper/include/android/file_descriptor_jni.h88
-rw-r--r--module_ndk_libs/libnativehelper/include_jni/jni.h1141
-rw-r--r--module_ndk_libs/libnativehelper/libnativehelper.map.txt30
-rw-r--r--multinetwork/Android.bp4
-rw-r--r--multinetwork/common.cpp32
-rw-r--r--multinetwork/common.h13
-rw-r--r--multinetwork/dnschk.cpp78
-rw-r--r--multinetwork/httpurl.cpp38
-rw-r--r--pagecache/Android.bp17
-rw-r--r--partition_tools/Android.bp4
-rw-r--r--partition_tools/aidl/Android.bp4
-rw-r--r--partition_tools/lpmake.cc120
-rw-r--r--perf2cfg/.style.yapf2
-rw-r--r--perf2cfg/Android.bp51
-rw-r--r--perf2cfg/OWNERS5
-rw-r--r--perf2cfg/README.md121
-rw-r--r--perf2cfg/doc/FSM.dot71
-rwxr-xr-xperf2cfg/perf2cfg.py149
-rw-r--r--perf2cfg/perf2cfg/__init__.py13
-rw-r--r--perf2cfg/perf2cfg/analyze.py210
-rw-r--r--perf2cfg/perf2cfg/edit.py549
-rw-r--r--perf2cfg/perf2cfg/events.py53
-rw-r--r--perf2cfg/perf2cfg/exceptions.py24
-rw-r--r--perf2cfg/perf2cfg/parse.py131
-rwxr-xr-xperf2cfg/perf2cfg_test.py27
-rw-r--r--perf2cfg/pylintrc17
-rw-r--r--perf2cfg/tests/__init__.py13
-rw-r--r--perf2cfg/tests/test_edit.py144
-rw-r--r--perf2cfg/tests/test_events.py27
-rw-r--r--perf2cfg/tests/test_parse.py73
-rw-r--r--postinst/Android.bp17
-rw-r--r--power_profile/camera_avg/Application/src/main/AndroidManifest.xml23
-rw-r--r--power_profile/camera_flashlight/Application/src/main/AndroidManifest.xml13
-rw-r--r--power_profile/gps_on/Application/src/main/AndroidManifest.xml17
-rw-r--r--preopt2cachename/Android.bp4
l---------profcollectd/.clang-format1
-rw-r--r--profcollectd/Android.bp64
-rw-r--r--profcollectd/MODULE_LICENSE_APACHE20
-rw-r--r--profcollectd/NOTICE190
-rw-r--r--profcollectd/OWNERS3
-rw-r--r--profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl29
-rw-r--r--profcollectd/libprofcollectd/Android.bp63
-rw-r--r--profcollectd/libprofcollectd/bindings/libbase/Android.bp53
-rw-r--r--profcollectd/libprofcollectd/bindings/libbase/lib.rs34
-rw-r--r--profcollectd/libprofcollectd/bindings/libbase/properties.cpp26
-rw-r--r--profcollectd/libprofcollectd/bindings/libbase/properties.hpp22
-rw-r--r--profcollectd/libprofcollectd/bindings/libflags/Android.bp48
-rw-r--r--profcollectd/libprofcollectd/bindings/libflags/get_flags.cpp28
-rw-r--r--profcollectd/libprofcollectd/bindings/libflags/get_flags.hpp18
-rw-r--r--profcollectd/libprofcollectd/bindings/libflags/lib.rs43
-rw-r--r--profcollectd/libprofcollectd/config.rs138
-rw-r--r--profcollectd/libprofcollectd/lib.rs98
-rw-r--r--profcollectd/libprofcollectd/report.rs84
-rw-r--r--profcollectd/libprofcollectd/scheduler.rs108
-rw-r--r--profcollectd/libprofcollectd/service.rs166
-rw-r--r--profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs79
-rw-r--r--profcollectd/libprofcollectd/trace_provider.rs44
-rw-r--r--profcollectd/profcollectctl.rs72
-rw-r--r--profcollectd/profcollectd.rc20
-rw-r--r--profcollectd/profcollectd.rs46
-rw-r--r--pssbench/Android.mk2
-rw-r--r--puncture_fs/Android.bp17
-rw-r--r--runconuid/Android.bp17
l---------rustfmt.toml1
-rw-r--r--sane_schedstat/Android.bp17
-rw-r--r--showslab/Android.bp17
l---------simpleperf/.clang-format1
-rw-r--r--simpleperf/Android.bp190
-rw-r--r--simpleperf/Android.mk26
-rw-r--r--simpleperf/AndroidTest.xml31
-rw-r--r--simpleperf/CallChainJoiner.cpp16
-rw-r--r--simpleperf/CallChainJoiner.h17
-rw-r--r--simpleperf/CallChainJoiner_test.cpp27
-rw-r--r--simpleperf/ETMDecoder.cpp314
-rw-r--r--simpleperf/ETMDecoder.h26
-rw-r--r--simpleperf/ETMRecorder.cpp59
-rw-r--r--simpleperf/ETMRecorder.h5
-rw-r--r--simpleperf/IOEventLoop.cpp23
-rw-r--r--simpleperf/IOEventLoop.h10
-rw-r--r--simpleperf/IOEventLoop_test.cpp8
-rw-r--r--simpleperf/JITDebugReader.cpp388
-rw-r--r--simpleperf/JITDebugReader.h109
-rw-r--r--simpleperf/MapRecordReader.cpp186
-rw-r--r--simpleperf/MapRecordReader.h77
-rw-r--r--simpleperf/MapRecordReader_test.cpp84
-rw-r--r--simpleperf/OWNERS1
-rw-r--r--simpleperf/OfflineUnwinder.cpp119
-rw-r--r--simpleperf/OfflineUnwinder.h48
-rw-r--r--simpleperf/OfflineUnwinder_impl.h20
-rw-r--r--simpleperf/OfflineUnwinder_test.cpp34
-rw-r--r--simpleperf/ProbeEvents.cpp163
-rw-r--r--simpleperf/ProbeEvents.h54
-rw-r--r--simpleperf/ProbeEvents_test.cpp51
-rw-r--r--simpleperf/README.md2
-rw-r--r--simpleperf/RecordFilter.cpp153
-rw-r--r--simpleperf/RecordFilter.h111
-rw-r--r--simpleperf/RecordFilter_test.cpp184
-rw-r--r--simpleperf/RecordReadThread.cpp65
-rw-r--r--simpleperf/RecordReadThread.h6
-rw-r--r--simpleperf/RecordReadThread_test.cpp20
-rw-r--r--simpleperf/SampleComparator.h29
-rw-r--r--simpleperf/SampleDisplayer.h73
-rw-r--r--simpleperf/app_api/cpp/simpleperf.cpp35
-rw-r--r--simpleperf/app_api/cpp/simpleperf.h1
-rw-r--r--simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java121
-rw-r--r--simpleperf/app_api/java/com/android/simpleperf/RecordOptions.java88
-rw-r--r--simpleperf/build_id.h27
-rw-r--r--simpleperf/callchain.h40
-rw-r--r--simpleperf/cmd_api.cpp84
-rw-r--r--simpleperf/cmd_api_impl.h34
-rw-r--r--simpleperf/cmd_api_test.cpp2
-rw-r--r--simpleperf/cmd_debug_unwind.cpp859
-rw-r--r--simpleperf/cmd_debug_unwind_test.cpp121
-rw-r--r--simpleperf/cmd_dumprecord.cpp414
-rw-r--r--simpleperf/cmd_dumprecord_test.cpp21
-rw-r--r--simpleperf/cmd_help.cpp14
-rw-r--r--simpleperf/cmd_inject.cpp491
-rw-r--r--simpleperf/cmd_inject_impl.h28
-rw-r--r--simpleperf/cmd_inject_test.cpp129
-rw-r--r--simpleperf/cmd_kmem.cpp158
-rw-r--r--simpleperf/cmd_kmem_test.cpp20
-rw-r--r--simpleperf/cmd_list.cpp62
-rw-r--r--simpleperf/cmd_list_test.cpp2
-rw-r--r--simpleperf/cmd_merge.cpp425
-rw-r--r--simpleperf/cmd_merge_test.cpp92
-rw-r--r--simpleperf/cmd_monitor.cpp620
-rw-r--r--simpleperf/cmd_monitor_test.cpp138
-rw-r--r--simpleperf/cmd_record.cpp1245
-rw-r--r--simpleperf/cmd_record_impl.h97
-rw-r--r--simpleperf/cmd_record_test.cpp482
-rw-r--r--simpleperf/cmd_report.cpp425
-rw-r--r--simpleperf/cmd_report_sample.cpp381
-rw-r--r--simpleperf/cmd_report_sample.proto (renamed from simpleperf/report_sample.proto)39
-rw-r--r--simpleperf/cmd_report_sample_test.cpp72
-rw-r--r--simpleperf/cmd_report_test.cpp143
-rw-r--r--simpleperf/cmd_stat.cpp319
-rw-r--r--simpleperf/cmd_stat_impl.h128
-rw-r--r--simpleperf/cmd_stat_test.cpp226
-rw-r--r--simpleperf/cmd_trace_sched.cpp49
-rw-r--r--simpleperf/cmd_trace_sched_test.cpp12
-rw-r--r--simpleperf/command.cpp139
-rw-r--r--simpleperf/command.h143
-rw-r--r--simpleperf/command_test.cpp118
-rw-r--r--simpleperf/cpu_hotplug_test.cpp30
l---------simpleperf/demo/.clang-format1
-rw-r--r--simpleperf/demo/CppApi/app/src/main/AndroidManifest.xml25
-rw-r--r--simpleperf/demo/JavaApi/app/build.gradle4
-rw-r--r--simpleperf/demo/JavaApi/app/src/main/AndroidManifest.xml25
-rw-r--r--simpleperf/demo/README.md2
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/AndroidManifest.xml27
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml29
-rw-r--r--simpleperf/demo/SimpleperfExampleWithNative/app/src/main/AndroidManifest.xml32
-rw-r--r--simpleperf/doc/README.md19
-rw-r--r--simpleperf/doc/android_application_profiling.md12
-rw-r--r--simpleperf/doc/android_platform_profiling.md49
-rw-r--r--simpleperf/doc/executable_commands_reference.md3
-rw-r--r--simpleperf/doc/jit_symbols.md59
-rw-r--r--simpleperf/doc/scripts_reference.md5
-rw-r--r--simpleperf/dso.cpp570
-rw-r--r--simpleperf/dso.h59
-rw-r--r--simpleperf/dso_test.cpp106
-rw-r--r--simpleperf/environment.cpp427
-rw-r--r--simpleperf/environment.h65
-rw-r--r--simpleperf/environment_test.cpp54
-rw-r--r--simpleperf/etm_branch_list.proto66
-rw-r--r--simpleperf/event_attr.cpp30
-rw-r--r--simpleperf/event_attr.h4
-rw-r--r--simpleperf/event_fd.cpp73
-rw-r--r--simpleperf/event_fd.h10
-rw-r--r--simpleperf/event_selection_set.cpp254
-rw-r--r--simpleperf/event_selection_set.h56
-rw-r--r--simpleperf/event_type.cpp566
-rw-r--r--simpleperf/event_type.h73
-rw-r--r--simpleperf/event_type_table.h637
-rw-r--r--simpleperf/get_test_data.h31
-rw-r--r--simpleperf/gtest_main.cpp7
-rw-r--r--simpleperf/include/simpleperf_profcollect.hpp22
-rw-r--r--simpleperf/kallsyms.cpp283
-rw-r--r--simpleperf/kallsyms.h59
-rw-r--r--simpleperf/kallsyms_test.cpp121
-rw-r--r--simpleperf/main.cpp6
-rw-r--r--simpleperf/nonlinux_support/nonlinux_support.cpp16
-rw-r--r--simpleperf/perf_regs.cpp26
-rw-r--r--simpleperf/perf_regs.h18
-rw-r--r--simpleperf/perf_regs_test.cpp37
-rw-r--r--simpleperf/profcollect.cpp56
-rw-r--r--simpleperf/read_apk.cpp19
-rw-r--r--simpleperf/read_apk.h42
-rw-r--r--simpleperf/read_apk_test.cpp34
-rw-r--r--simpleperf/read_dex_file.cpp77
-rw-r--r--simpleperf/read_dex_file.h18
-rw-r--r--simpleperf/read_dex_file_test.cpp22
-rw-r--r--simpleperf/read_elf.cpp518
-rw-r--r--simpleperf/read_elf.h82
-rw-r--r--simpleperf/read_elf_test.cpp117
-rw-r--r--simpleperf/read_symbol_map.cpp108
-rw-r--r--simpleperf/read_symbol_map.h38
-rw-r--r--simpleperf/read_symbol_map_test.cpp56
-rw-r--r--simpleperf/record.cpp331
-rw-r--r--simpleperf/record.h115
-rw-r--r--simpleperf/record_equal_test.h14
-rw-r--r--simpleperf/record_file.h60
-rw-r--r--simpleperf/record_file.proto31
-rw-r--r--simpleperf/record_file_format.h14
-rw-r--r--simpleperf/record_file_reader.cpp137
-rw-r--r--simpleperf/record_file_test.cpp40
-rw-r--r--simpleperf/record_file_writer.cpp120
-rw-r--r--simpleperf/record_lib_interface.cpp10
-rw-r--r--simpleperf/record_lib_test.cpp29
-rw-r--r--simpleperf/record_test.cpp19
-rw-r--r--simpleperf/report_lib_interface.cpp178
-rw-r--r--simpleperf/report_utils.cpp240
-rw-r--r--simpleperf/report_utils.h90
-rw-r--r--simpleperf/report_utils_test.cpp312
-rw-r--r--simpleperf/runtest/Android.bp9
-rw-r--r--simpleperf/runtest/comm_change.cpp4
-rw-r--r--simpleperf/rust/lib.rs66
-rw-r--r--simpleperf/sample_tree.h55
-rw-r--r--simpleperf/sample_tree_test.cpp46
-rwxr-xr-xsimpleperf/scripts/annotate.py47
-rwxr-xr-xsimpleperf/scripts/api_profiler.py13
-rwxr-xr-xsimpleperf/scripts/app_profiler.py40
-rwxr-xr-xsimpleperf/scripts/bin/android/arm/simpleperfbin2559164 -> 2726616 bytes
-rwxr-xr-xsimpleperf/scripts/bin/android/arm64/simpleperfbin3509208 -> 3691656 bytes
-rwxr-xr-xsimpleperf/scripts/bin/android/x86/simpleperfbin4244608 -> 4250392 bytes
-rwxr-xr-xsimpleperf/scripts/bin/android/x86_64/simpleperfbin4160264 -> 4120888 bytes
-rwxr-xr-xsimpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylibbin6271552 -> 6710016 bytes
-rwxr-xr-xsimpleperf/scripts/bin/darwin/x86_64/simpleperfbin7772852 -> 7922704 bytes
-rwxr-xr-xsimpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.sobin4982240 -> 7012680 bytes
-rwxr-xr-xsimpleperf/scripts/bin/linux/x86_64/simpleperfbin6228000 -> 6996120 bytes
-rwxr-xr-xsimpleperf/scripts/bin/windows/x86/libsimpleperf_report.dllbin3947520 -> 0 bytes
-rwxr-xr-xsimpleperf/scripts/bin/windows/x86/libwinpthread-1.dllbin231082 -> 0 bytes
-rwxr-xr-xsimpleperf/scripts/bin/windows/x86/simpleperf.exebin3883008 -> 0 bytes
-rwxr-xr-xsimpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dllbin4187648 -> 4771840 bytes
-rwxr-xr-xsimpleperf/scripts/bin/windows/x86_64/libwinpthread-1.dllbin570473 -> 572009 bytes
-rwxr-xr-xsimpleperf/scripts/bin/windows/x86_64/simpleperf.exebin4193280 -> 4571136 bytes
-rwxr-xr-xsimpleperf/scripts/binary_cache_builder.py57
-rwxr-xr-xsimpleperf/scripts/debug_unwind_reporter.py652
-rwxr-xr-xsimpleperf/scripts/inferno.bat4
-rwxr-xr-xsimpleperf/scripts/inferno.sh3
-rw-r--r--simpleperf/scripts/inferno/Android.bp10
-rwxr-xr-xsimpleperf/scripts/inferno/inferno.py13
-rwxr-xr-xsimpleperf/scripts/pprof_proto_generator.py79
-rw-r--r--simpleperf/scripts/purgatorio/README.md69
-rw-r--r--simpleperf/scripts/purgatorio/images/flame_graph.pngbin0 -> 20084 bytes
-rw-r--r--simpleperf/scripts/purgatorio/images/flame_graph_zoomed.pngbin0 -> 73340 bytes
-rw-r--r--simpleperf/scripts/purgatorio/images/inverted_flame_graph.pngbin0 -> 52148 bytes
-rw-r--r--simpleperf/scripts/purgatorio/images/table.pngbin0 -> 69444 bytes
-rw-r--r--simpleperf/scripts/purgatorio/images/toolbox.pngbin0 -> 61456 bytes
-rw-r--r--simpleperf/scripts/purgatorio/images/user_interface.pngbin0 -> 190267 bytes
-rwxr-xr-xsimpleperf/scripts/purgatorio/purgatorio.py311
-rw-r--r--simpleperf/scripts/purgatorio/templates/index.html.jinja266
-rw-r--r--simpleperf/scripts/purgatorio/templates/main.js245
-rw-r--r--simpleperf/scripts/purgatorio/templates/styles.css133
-rwxr-xr-xsimpleperf/scripts/report.py395
-rw-r--r--simpleperf/scripts/report_html.js4
-rwxr-xr-xsimpleperf/scripts/report_html.py352
-rwxr-xr-xsimpleperf/scripts/report_sample.py2
-rwxr-xr-xsimpleperf/scripts/run_simpleperf_on_device.py6
-rwxr-xr-xsimpleperf/scripts/run_simpleperf_without_usb_connection.py8
-rw-r--r--simpleperf/scripts/script_testdata/java_api-debug_Q.apkbin20887 -> 0 bytes
-rw-r--r--simpleperf/scripts/script_testdata/java_api-debug_prev_Q.apkbin20887 -> 0 bytes
-rw-r--r--simpleperf/scripts/script_testdata/java_api-profile_Q.apkbin20887 -> 0 bytes
-rw-r--r--simpleperf/scripts/script_testdata/java_api-profile_prev_Q.apkbin20887 -> 0 bytes
-rwxr-xr-xsimpleperf/scripts/script_testdata/simpleperf_runtest_two_functions_arm64bin71800 -> 0 bytes
-rw-r--r--simpleperf/scripts/simpleperf_report_lib.py122
-rw-r--r--simpleperf/scripts/simpleperf_utils.py (renamed from simpleperf/scripts/utils.py)563
-rwxr-xr-xsimpleperf/scripts/test.py1787
-rw-r--r--simpleperf/scripts/test/__init__.py17
-rw-r--r--simpleperf/scripts/test/api_profiler_test.py97
-rw-r--r--simpleperf/scripts/test/app_profiler_test.py141
-rw-r--r--simpleperf/scripts/test/app_test.py266
-rw-r--r--simpleperf/scripts/test/binary_cache_builder_test.py108
-rw-r--r--simpleperf/scripts/test/cpp_app_test.py180
-rw-r--r--simpleperf/scripts/test/debug_unwind_reporter_test.py68
-rwxr-xr-xsimpleperf/scripts/test/do_test.py491
-rw-r--r--simpleperf/scripts/test/inferno_test.py40
-rw-r--r--simpleperf/scripts/test/java_app_test.py218
-rw-r--r--simpleperf/scripts/test/kotlin_app_test.py135
-rw-r--r--simpleperf/scripts/test/pprof_proto_generator_test.py126
-rw-r--r--simpleperf/scripts/test/purgatorio_test.py44
-rw-r--r--simpleperf/scripts/test/report_html_test.py182
-rw-r--r--simpleperf/scripts/test/report_lib_test.py202
-rw-r--r--simpleperf/scripts/test/run_simpleperf_on_device_test.py (renamed from tests/memeater/Android.mk)17
-rw-r--r--simpleperf/scripts/test/script_testdata/aggregatable_perf1.data (renamed from simpleperf/scripts/script_testdata/aggregatable_perf1.data)bin2202763 -> 2202763 bytes
-rw-r--r--simpleperf/scripts/test/script_testdata/aggregatable_perf2.data (renamed from simpleperf/scripts/script_testdata/aggregatable_perf2.data)bin2024113 -> 2024113 bytes
-rw-r--r--simpleperf/scripts/test/script_testdata/cpp_api-debug_Q.apk (renamed from simpleperf/scripts/script_testdata/cpp_api-debug_prev_Q.apk)bin2791656 -> 2719357 bytes
-rw-r--r--simpleperf/scripts/test/script_testdata/cpp_api-debug_prev_Q.apk (renamed from simpleperf/scripts/script_testdata/cpp_api-debug_Q.apk)bin2791612 -> 2719397 bytes
-rw-r--r--simpleperf/scripts/test/script_testdata/cpp_api-profile_Q.apk (renamed from simpleperf/scripts/script_testdata/cpp_api-profile_Q.apk)bin5079732 -> 5079732 bytes
-rw-r--r--simpleperf/scripts/test/script_testdata/cpp_api-profile_prev_Q.apk (renamed from simpleperf/scripts/script_testdata/cpp_api-profile_prev_Q.apk)bin5078484 -> 5078484 bytes
-rw-r--r--simpleperf/scripts/test/script_testdata/debug_unwind_report.txt760
-rw-r--r--simpleperf/scripts/test/script_testdata/java_api-debug_Q.apkbin0 -> 1510717 bytes
-rw-r--r--simpleperf/scripts/test/script_testdata/java_api-debug_prev_Q.apkbin0 -> 1526241 bytes
-rw-r--r--simpleperf/scripts/test/script_testdata/java_api-profile_Q.apkbin0 -> 1277389 bytes
-rw-r--r--simpleperf/scripts/test/script_testdata/java_api-profile_prev_Q.apkbin0 -> 1277437 bytes
-rw-r--r--simpleperf/scripts/test/script_testdata/perf_with_long_callchain.data (renamed from simpleperf/scripts/script_testdata/perf_with_long_callchain.data)bin923288 -> 923288 bytes
-rw-r--r--simpleperf/scripts/test/script_testdata/runtest_two_functions_arm64_perf.databin0 -> 2950 bytes
-rwxr-xr-xsimpleperf/scripts/test/script_testdata/simpleperf_runtest_two_functions_arm (renamed from simpleperf/scripts/script_testdata/simpleperf_runtest_two_functions_arm)bin100752 -> 100752 bytes
-rwxr-xr-xsimpleperf/scripts/test/script_testdata/simpleperf_runtest_two_functions_arm64bin0 -> 15944 bytes
-rwxr-xr-xsimpleperf/scripts/test/script_testdata/simpleperf_runtest_two_functions_arm64_without_debug_infobin0 -> 11472 bytes
-rwxr-xr-xsimpleperf/scripts/test/script_testdata/simpleperf_runtest_two_functions_x86 (renamed from simpleperf/scripts/script_testdata/simpleperf_runtest_two_functions_x86)bin9392 -> 9392 bytes
-rwxr-xr-xsimpleperf/scripts/test/script_testdata/simpleperf_runtest_two_functions_x86_64 (renamed from simpleperf/scripts/script_testdata/simpleperf_runtest_two_functions_x86_64)bin10480 -> 10480 bytes
-rw-r--r--simpleperf/scripts/test/script_testdata/two_process_perf.data (renamed from simpleperf/scripts/script_testdata/two_process_perf.data)bin53289 -> 53289 bytes
-rwxr-xr-xsimpleperf/scripts/test/test.py26
-rw-r--r--simpleperf/scripts/test/test_utils.py205
-rw-r--r--simpleperf/scripts/test/tools_test.py298
-rwxr-xr-xsimpleperf/scripts/update.py14
-rw-r--r--simpleperf/simpleperf_app_runner/Android.bp10
-rw-r--r--simpleperf/simpleperf_app_runner/simpleperf_app_runner.cpp195
-rw-r--r--simpleperf/test_util.cpp135
-rw-r--r--simpleperf/test_util.h83
-rw-r--r--simpleperf/testdata/DisplayBitmaps.apkbin2233798 -> 2364912 bytes
-rw-r--r--simpleperf/testdata/DisplayBitmapsTest.apkbin2835879 -> 2932413 bytes
-rw-r--r--simpleperf/testdata/cpp_api.apkbin2830256 -> 2722938 bytes
-rw-r--r--simpleperf/testdata/etm/perf_kernel.databin0 -> 117363 bytes
-rw-r--r--simpleperf/testdata/etm/perf_with_recording_process.databin0 -> 97215 bytes
-rw-r--r--simpleperf/testdata/etm/rq_stats.kobin0 -> 13176 bytes
-rw-r--r--simpleperf/testdata/java_api.apkbin1385845 -> 1522184 bytes
-rw-r--r--simpleperf/testdata/perf_display_bitmaps.databin0 -> 613730 bytes
-rw-r--r--simpleperf/testdata/perf_merge1.databin0 -> 41209 bytes
-rw-r--r--simpleperf/testdata/perf_merge2.databin0 -> 40301 bytes
-rw-r--r--simpleperf/testdata/perf_need_proguard_mapping.databin0 -> 25406 bytes
-rw-r--r--simpleperf/testdata/perf_with_failed_unwinding_debug_info.databin0 -> 92312 bytes
-rw-r--r--simpleperf/testdata/perf_with_generic_git_symbols.databin0 -> 30371 bytes
-rw-r--r--simpleperf/testdata/perf_with_jit_symbol.databin0 -> 280739 bytes
-rw-r--r--simpleperf/testdata/perf_with_tracepoint_event_dynamic_field.databin0 -> 13419 bytes
-rw-r--r--simpleperf/testdata/proguard_mapping.txt36
-rw-r--r--simpleperf/testdata/sysfs/module/fake_kernel_module/notes/note.gnu.build-idbin0 -> 24 bytes
-rw-r--r--simpleperf/thread_tree.cpp140
-rw-r--r--simpleperf/thread_tree.h66
-rw-r--r--simpleperf/thread_tree_test.cpp23
-rw-r--r--simpleperf/tracing.cpp423
-rw-r--r--simpleperf/tracing.h36
-rw-r--r--simpleperf/tracing_test.cpp102
-rw-r--r--simpleperf/utils.cpp143
-rw-r--r--simpleperf/utils.h64
-rw-r--r--simpleperf/utils_test.cpp79
-rw-r--r--simpleperf/workload.cpp14
-rw-r--r--simpleperf/workload.h22
-rw-r--r--simpleperf/workload_test.cpp14
-rw-r--r--slideshow/Android.bp36
-rw-r--r--slideshow/Android.mk21
-rw-r--r--sound/Android.bp17
-rw-r--r--squashfs_utils/Android.bp17
-rw-r--r--su/Android.mk5
-rw-r--r--taskstats/Android.bp17
-rw-r--r--tests/Android.bp31
-rw-r--r--tests/audio/alsa/Android.bp11
-rw-r--r--tests/audio/alsa/pcmtest.cpp23
-rw-r--r--tests/binder/benchmarks/Android.bp9
-rw-r--r--tests/bootloader/Android.mk3
-rw-r--r--tests/cpueater/Android.bp17
-rw-r--r--tests/crypto/Android.bp9
-rw-r--r--tests/directiotest/Android.bp9
-rw-r--r--tests/ext4/Android.mk4
-rw-r--r--tests/framebuffer/Android.bp9
-rw-r--r--tests/fstest/Android.bp9
-rw-r--r--tests/icachetest/Android.bp9
-rw-r--r--tests/iptables/qtaguid/Android.bp9
-rw-r--r--tests/kernel.config/Android.mk9
-rw-r--r--tests/lib/Android.bp11
-rw-r--r--tests/lib/testUtil/Android.bp14
-rw-r--r--tests/lib/testUtil/testUtil.c1
-rw-r--r--tests/memeater/Android.bp38
-rw-r--r--tests/pagingtest/Android.mk3
-rw-r--r--tests/pftest/Android.bp9
-rw-r--r--tests/schedtest/Android.bp9
-rw-r--r--tests/storage/Android.bp9
-rw-r--r--tests/suspend_stress/Android.bp9
-rw-r--r--tests/tcp_nuke_addr/Android.bp9
-rw-r--r--tests/timetest/Android.bp9
-rw-r--r--tests/uevents/Android.bp9
l---------toolchain-extras/.clang-format1
-rw-r--r--toolchain-extras/Android.bp59
-rw-r--r--toolchain-extras/libprofile_clang_extras_blocklist.txt2
-rw-r--r--toolchain-extras/profile-clang-extras-test.cpp21
-rw-r--r--toolchain-extras/profile-clang-extras.cpp21
-rw-r--r--toolchain-extras/profile-clang-openat.cpp55
-rw-r--r--toolchain-extras/profile-extras-test.cpp19
-rw-r--r--toolchain-extras/profile-extras.cpp6
-rw-r--r--vbmeta_tools/Android.bp6
-rw-r--r--verity/Android.bp17
-rw-r--r--verity/build_verity_tree_test.cpp12
-rw-r--r--verity/fec/Android.bp18
-rw-r--r--verity/hash_tree_builder.cpp35
-rw-r--r--verity/include/verity/hash_tree_builder.h7
-rw-r--r--zram-perf/Android.bp4
489 files changed, 28021 insertions, 10506 deletions
diff --git a/.clang-format b/.clang-format
new file mode 120000
index 00000000..9b45e0ae
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1 @@
+.clang-format-4 \ No newline at end of file
diff --git a/.clang-format-2 b/.clang-format-2
new file mode 120000
index 00000000..7ab20d4f
--- /dev/null
+++ b/.clang-format-2
@@ -0,0 +1 @@
+../../build/soong/scripts/system-clang-format-2 \ No newline at end of file
diff --git a/.clang-format-4 b/.clang-format-4
new file mode 120000
index 00000000..ddcf5a29
--- /dev/null
+++ b/.clang-format-4
@@ -0,0 +1 @@
+../../build/soong/scripts/system-clang-format \ No newline at end of file
diff --git a/.clang-format-none b/.clang-format-none
new file mode 100644
index 00000000..1ea7b50e
--- /dev/null
+++ b/.clang-format-none
@@ -0,0 +1,7 @@
+# This clang-format configuration may be included in subdirectories to disable
+# any warning.
+
+DisableFormat: true
+
+# This extra settings is required because of https://reviews.llvm.org/D67843.
+SortIncludes: false
diff --git a/ANRdaemon/ANRdaemon.cpp b/ANRdaemon/ANRdaemon.cpp
index bd433b33..f087ff22 100644
--- a/ANRdaemon/ANRdaemon.cpp
+++ b/ANRdaemon/ANRdaemon.cpp
@@ -33,12 +33,12 @@
#include <binder/IServiceManager.h>
#include <binder/Parcel.h>
-#include <ctime>
#include <cutils/properties.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <ctime>
#include <sys/resource.h>
#include <sys/stat.h>
@@ -59,13 +59,13 @@ using namespace android;
#define LOG_TAG "anrdaemon"
-static const int check_period = 1; // in sec
-static const int tracing_check_period = 500000; // in micro sec
-static const int cpu_stat_entries = 7; // number of cpu stat entries
+static const int check_period = 1; // in sec
+static const int tracing_check_period = 500000; // in micro sec
+static const int cpu_stat_entries = 7; // number of cpu stat entries
static const int min_buffer_size = 16;
static const int max_buffer_size = 2048;
-static const char *min_buffer_size_str = "16";
-static const char *max_buffer_size_str = "2048";
+static const char* min_buffer_size_str = "16";
+static const char* max_buffer_size_str = "2048";
static const int time_buf_size = 20;
static const int path_buf_size = 60;
@@ -82,14 +82,14 @@ typedef struct cpu_stat {
static int idle_threshold = 10;
static bool quit = false;
-static bool suspend= false;
+static bool suspend = false;
static bool dump_requested = false;
static bool err = false;
static char err_msg[100];
static bool tracing = false;
-static const char *buf_size_kb = "2048";
-static const char *apps = "";
+static const char* buf_size_kb = "2048";
+static const char* apps = "";
static uint64_t tag = 0;
static cpu_stat_t new_cpu;
@@ -98,46 +98,36 @@ static cpu_stat_t old_cpu;
/* Log certain kernel activity when enabled */
static bool log_sched = false;
static bool log_stack = false;
-static bool log_irq = false;
-static bool log_sync = false;
+static bool log_irq = false;
+static bool log_sync = false;
static bool log_workq = false;
/* Paths for debugfs controls*/
-static const char* dfs_trace_output_path =
- "/d/tracing/trace";
-static const char* dfs_irq_path =
- "/d/tracing/events/irq/enable";
-static const char* dfs_sync_path =
- "/d/tracing/events/sync/enable";
-static const char* dfs_workq_path =
- "/d/tracing/events/workqueue/enable";
-static const char* dfs_stack_path =
- "/d/tracing/options/stacktrace";
-static const char* dfs_sched_switch_path =
- "/d/tracing/events/sched/sched_switch/enable";
-static const char* dfs_sched_wakeup_path =
- "/d/tracing/events/sched/sched_wakeup/enable";
-static const char* dfs_control_path =
- "/d/tracing/tracing_on";
-static const char* dfs_buffer_size_path =
- "/d/tracing/buffer_size_kb";
+static const char* dfs_trace_output_path = "/d/tracing/trace";
+static const char* dfs_irq_path = "/d/tracing/events/irq/enable";
+static const char* dfs_sync_path = "/d/tracing/events/sync/enable";
+static const char* dfs_workq_path = "/d/tracing/events/workqueue/enable";
+static const char* dfs_stack_path = "/d/tracing/options/stacktrace";
+static const char* dfs_sched_switch_path = "/d/tracing/events/sched/sched_switch/enable";
+static const char* dfs_sched_wakeup_path = "/d/tracing/events/sched/sched_wakeup/enable";
+static const char* dfs_control_path = "/d/tracing/tracing_on";
+static const char* dfs_buffer_size_path = "/d/tracing/buffer_size_kb";
static const char* dfs_tags_property = "debug.atrace.tags.enableflags";
static const char* dfs_apps_property = "debug.atrace.app_cmdlines";
/*
* Read accumulated cpu data from /proc/stat
*/
-static void get_cpu_stat(cpu_stat_t *cpu) {
- FILE *fp = NULL;
- const char *params = "cpu %lu %lu %lu %lu %lu %lu %lu %*d %*d %*d\n";
+static void get_cpu_stat(cpu_stat_t* cpu) {
+ FILE* fp = NULL;
+ const char* params = "cpu %lu %lu %lu %lu %lu %lu %lu %*d %*d %*d\n";
if ((fp = fopen("/proc/stat", "r")) == NULL) {
err = true;
snprintf(err_msg, sizeof(err_msg), "can't read from /proc/stat with errno %d", errno);
} else {
- if (fscanf(fp, params, &cpu->utime, &cpu->ntime,
- &cpu->stime, &cpu->itime, &cpu->iowtime, &cpu->irqtime,
- &cpu->sirqtime) != cpu_stat_entries) {
+ if (fscanf(fp, params, &cpu->utime, &cpu->ntime, &cpu->stime, &cpu->itime, &cpu->iowtime,
+ &cpu->irqtime, &cpu->sirqtime) != cpu_stat_entries) {
/*
* If failed in getting status, new_cpu won't be updated and
* is_heavy_loaded() will return false.
@@ -147,8 +137,8 @@ static void get_cpu_stat(cpu_stat_t *cpu) {
return;
}
- cpu->total = cpu->utime + cpu->ntime + cpu->stime + cpu->itime
- + cpu->iowtime + cpu->irqtime + cpu->sirqtime;
+ cpu->total = cpu->utime + cpu->ntime + cpu->stime + cpu->itime + cpu->iowtime +
+ cpu->irqtime + cpu->sirqtime;
fclose(fp);
}
@@ -162,7 +152,7 @@ static void get_cpu_stat(cpu_stat_t *cpu) {
*/
static bool is_heavy_load(void) {
unsigned long diff_idle, diff_total;
- int threshold = idle_threshold + (tracing?100:0);
+ int threshold = idle_threshold + (tracing ? 100 : 0);
get_cpu_stat(&new_cpu);
diff_idle = new_cpu.itime - old_cpu.itime;
diff_total = new_cpu.total - old_cpu.total;
@@ -195,9 +185,9 @@ static int dfs_enable(bool enable, const char* path) {
snprintf(err_msg, sizeof(err_msg), "Can't open %s. Error: %d", path, errno);
return -1;
}
- const char* control = (enable?"1":"0");
+ const char* control = (enable ? "1" : "0");
ssize_t len = strlen(control);
- int max_try = 10; // Fail if write was interrupted for 10 times
+ int max_try = 10; // Fail if write was interrupted for 10 times
while (write(fd, control, len) != len) {
if (errno == EINTR && max_try-- > 0) {
usleep(100);
@@ -208,7 +198,7 @@ static int dfs_enable(bool enable, const char* path) {
snprintf(err_msg, sizeof(err_msg), "Error %d in writing to %s.", errno, path);
}
close(fd);
- return (err?-1:0);
+ return (err ? -1 : 0);
}
/*
@@ -222,8 +212,7 @@ static void dfs_set_property(uint64_t mtag, const char* mapp, bool enable) {
snprintf(err_msg, sizeof(err_msg), "Failed to set debug tags system properties.");
}
- if (strlen(mapp) > 0
- && property_set(dfs_apps_property, mapp) < 0) {
+ if (strlen(mapp) > 0 && property_set(dfs_apps_property, mapp) < 0) {
err = true;
snprintf(err_msg, sizeof(err_msg), "Failed to set debug applications.");
}
@@ -250,10 +239,9 @@ static void dfs_set_property(uint64_t mtag, const char* mapp, bool enable) {
* Dump the log in a compressed format for systrace to visualize.
* Create a dump file "dump_of_anrdaemon.<current_time>" under /data/misc/anrd
*/
-static void dump_trace()
-{
+static void dump_trace() {
time_t now = time(0);
- struct tm tstruct;
+ struct tm tstruct;
char time_buf[time_buf_size];
char path_buf[path_buf_size];
const char* header = " done\nTRACE:\n";
@@ -296,7 +284,7 @@ static void dump_trace()
return;
}
- const size_t bufSize = 64*1024;
+ const size_t bufSize = 64 * 1024;
in = (uint8_t*)malloc(bufSize);
out = (uint8_t*)malloc(bufSize);
flush = Z_NO_FLUSH;
@@ -412,16 +400,14 @@ static int set_tracing_buffer_size(void) {
snprintf(err_msg, sizeof(err_msg), "Error in writing to atrace buffer size file.");
}
close(fd);
- return (err?-1:0);
-
+ return (err ? -1 : 0);
}
/*
* Main loop to moniter the cpu usage and decided whether to start logging.
*/
static void start(void) {
- if ((set_tracing_buffer_size()) != 0)
- return;
+ if ((set_tracing_buffer_size()) != 0) return;
dfs_set_property(tag, apps, true);
dfs_poke_binder();
@@ -448,8 +434,7 @@ static void start(void) {
* If trace is not running, dump trace right away.
* If trace is running, request to dump trace.
*/
-static void request_dump_trace()
-{
+static void request_dump_trace() {
if (!tracing) {
dump_trace();
} else if (!dump_requested) {
@@ -457,8 +442,7 @@ static void request_dump_trace()
}
}
-static void handle_signal(int signo)
-{
+static void handle_signal(int signo) {
switch (signo) {
case SIGQUIT:
suspend = true;
@@ -482,16 +466,15 @@ static void handle_signal(int signo)
* SIGCONT: Resume the daemon as normal.
* SIGUSR1: Dump the logging to a compressed format for systrace to visualize.
*/
-static void register_sighandler(void)
-{
+static void register_sighandler(void) {
struct sigaction sa;
sigset_t block_mask;
sigemptyset(&block_mask);
- sigaddset (&block_mask, SIGQUIT);
- sigaddset (&block_mask, SIGSTOP);
- sigaddset (&block_mask, SIGCONT);
- sigaddset (&block_mask, SIGUSR1);
+ sigaddset(&block_mask, SIGQUIT);
+ sigaddset(&block_mask, SIGSTOP);
+ sigaddset(&block_mask, SIGCONT);
+ sigaddset(&block_mask, SIGUSR1);
sa.sa_flags = 0;
sa.sa_mask = block_mask;
@@ -503,43 +486,45 @@ static void register_sighandler(void)
}
static void show_help(void) {
-
fprintf(stderr, "usage: ANRdaemon [options] [categoris...]\n");
- fprintf(stdout, "Options includes:\n"
- " -a appname enable app-level tracing for a comma "
- "separated list of cmdlines\n"
- " -t N cpu threshold for logging to start "
- "(uint = 0.01%%, min = 5000, max = 9999, default = 9990)\n"
- " -s N use a trace buffer size of N KB "
- "default to 2048KB\n"
- " -h show helps\n");
- fprintf(stdout, "Categoris includes:\n"
- " am - activity manager\n"
- " sm - sync manager\n"
- " input - input\n"
- " dalvik - dalvik VM\n"
- " audio - Audio\n"
- " gfx - Graphics\n"
- " rs - RenderScript\n"
- " hal - Hardware Modules\n"
- " irq - kernel irq events\n"
- " sched - kernel scheduler activity\n"
- " stack - kernel stack\n"
- " sync - kernel sync activity\n"
- " workq - kernel work queues\n");
- fprintf(stdout, "Control includes:\n"
- " SIGQUIT: terminate the process\n"
- " SIGSTOP: suspend all function of the daemon\n"
- " SIGCONT: resume the normal function\n"
- " SIGUSR1: dump the current logging in a compressed form\n");
+ fprintf(stdout,
+ "Options includes:\n"
+ " -a appname enable app-level tracing for a comma "
+ "separated list of cmdlines\n"
+ " -t N cpu threshold for logging to start "
+ "(uint = 0.01%%, min = 5000, max = 9999, default = 9990)\n"
+ " -s N use a trace buffer size of N KB "
+ "default to 2048KB\n"
+ " -h show helps\n");
+ fprintf(stdout,
+ "Categoris includes:\n"
+ " am - activity manager\n"
+ " sm - sync manager\n"
+ " input - input\n"
+ " dalvik - dalvik VM\n"
+ " audio - Audio\n"
+ " gfx - Graphics\n"
+ " rs - RenderScript\n"
+ " hal - Hardware Modules\n"
+ " irq - kernel irq events\n"
+ " sched - kernel scheduler activity\n"
+ " stack - kernel stack\n"
+ " sync - kernel sync activity\n"
+ " workq - kernel work queues\n");
+ fprintf(stdout,
+ "Control includes:\n"
+ " SIGQUIT: terminate the process\n"
+ " SIGSTOP: suspend all function of the daemon\n"
+ " SIGCONT: resume the normal function\n"
+ " SIGUSR1: dump the current logging in a compressed form\n");
exit(0);
}
-static int get_options(int argc, char *argv[]) {
+static int get_options(int argc, char* argv[]) {
int opt = 0;
int threshold;
while ((opt = getopt(argc, argv, "a:s:t:h")) >= 0) {
- switch(opt) {
+ switch (opt) {
case 'a':
apps = optarg;
break;
@@ -563,8 +548,10 @@ static int get_options(int argc, char *argv[]) {
show_help();
break;
default:
- fprintf(stderr, "Error in getting options.\n"
- "run \"%s -h\" for usage.\n", argv[0]);
+ fprintf(stderr,
+ "Error in getting options.\n"
+ "run \"%s -h\" for usage.\n",
+ argv[0]);
return 1;
}
}
@@ -597,8 +584,10 @@ static int get_options(int argc, char *argv[]) {
} else if (strcmp(argv[i], "sync") == 0) {
log_sync = true;
} else {
- fprintf(stderr, "invalid category: %s\n"
- "run \"%s -h\" for usage.\n", argv[i], argv[0]);
+ fprintf(stderr,
+ "invalid category: %s\n"
+ "run \"%s -h\" for usage.\n",
+ argv[i], argv[0]);
return 1;
}
}
@@ -612,13 +601,10 @@ static int get_options(int argc, char *argv[]) {
return 0;
}
-int main(int argc, char *argv[])
-{
- if(get_options(argc, argv) != 0)
- return 1;
+int main(int argc, char* argv[]) {
+ if (get_options(argc, argv) != 0) return 1;
- if (daemon(0, 0) != 0)
- return 1;
+ if (daemon(0, 0) != 0) return 1;
register_sighandler();
@@ -633,10 +619,9 @@ int main(int argc, char *argv[])
ALOGI("ANRdaemon starting");
start();
- if (err)
- ALOGE("ANRdaemon stopped due to Error: %s", err_msg);
+ if (err) ALOGE("ANRdaemon stopped due to Error: %s", err_msg);
ALOGI("ANRdaemon terminated.");
- return (err?1:0);
+ return (err ? 1 : 0);
}
diff --git a/ANRdaemon/Android.bp b/ANRdaemon/Android.bp
index 9bb9b71e..ef8788b2 100644
--- a/ANRdaemon/Android.bp
+++ b/ANRdaemon/Android.bp
@@ -1,3 +1,18 @@
+package {
+ default_applicable_licenses: ["system_extras_ANRdaemon_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_ANRdaemon_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-BSD",
+ ],
+ // large-scale-change unable to identify any license_text files
+}
+
cc_binary {
name: "anrd",
srcs: ["ANRdaemon.cpp"],
diff --git a/METADATA b/METADATA
new file mode 100644
index 00000000..d97975ca
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,3 @@
+third_party {
+ license_type: NOTICE
+}
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 00000000..4a181034
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,8 @@
+[Builtin Hooks]
+bpfmt = true
+clang_format = true
+rustfmt = true
+
+[Builtin Hooks Options]
+rustfmt = --config-path=rustfmt.toml
+clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
diff --git a/alloc-stress/Android.bp b/alloc-stress/Android.bp
index 896db5ef..4d74841a 100644
--- a/alloc-stress/Android.bp
+++ b/alloc-stress/Android.bp
@@ -14,6 +14,10 @@
// limitations under the License.
//
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_binary {
name: "alloc-stress",
srcs: ["alloc-stress.cpp"],
@@ -26,8 +30,8 @@ cc_binary {
"-Wall",
"-Werror",
"-Wno-missing-field-initializers",
- "-Wno-sign-compare"
- ]
+ "-Wno-sign-compare",
+ ],
}
cc_binary {
@@ -38,6 +42,6 @@ cc_binary {
"-Wall",
"-Werror",
"-Wno-missing-field-initializers",
- "-Wno-sign-compare"
- ]
+ "-Wno-sign-compare",
+ ],
}
diff --git a/alloc-stress/alloc-stress.cpp b/alloc-stress/alloc-stress.cpp
index 1cd8fddf..33c12635 100644
--- a/alloc-stress/alloc-stress.cpp
+++ b/alloc-stress/alloc-stress.cpp
@@ -1,44 +1,46 @@
#include <arpa/inet.h>
-#include <iostream>
-#include <chrono>
#include <cutils/sockets.h>
-#include <hardware/gralloc.h>
-#include <vector>
-#include <tuple>
-#include <algorithm>
-#include <tuple>
-#include <numeric>
#include <fcntl.h>
-#include <string>
-#include <fstream>
+#include <hardware/gralloc.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
+#include <algorithm>
+#include <chrono>
+#include <fstream>
+#include <iostream>
+#include <numeric>
+#include <string>
+#include <tuple>
+#include <vector>
//#define TRACE_CHILD_LIFETIME
#ifdef TRACE_CHILD_LIFETIME
#define ATRACE_TAG ATRACE_TAG_ALWAYS
#include <utils/Trace.h>
-#endif // TRACE_CHILD_LIFETIME
+#endif // TRACE_CHILD_LIFETIME
using namespace std;
-#define ASSERT_TRUE(cond) \
-do { \
- if (!(cond)) {\
- cerr << __func__ << "( " << getpid() << "):" << __LINE__ << " condition:" << #cond << " failed\n" << endl; \
- exit(EXIT_FAILURE); \
- } \
-} while (0)
+#define ASSERT_TRUE(cond) \
+ do { \
+ if (!(cond)) { \
+ cerr << __func__ << "( " << getpid() << "):" << __LINE__ << " condition:" << #cond \
+ << " failed\n" \
+ << endl; \
+ exit(EXIT_FAILURE); \
+ } \
+ } while (0)
class Pipe {
int m_readFd;
int m_writeFd;
- Pipe(const Pipe &) = delete;
- Pipe& operator=(const Pipe &) = delete;
- Pipe& operator=(const Pipe &&) = delete;
-public:
+ Pipe(const Pipe&) = delete;
+ Pipe& operator=(const Pipe&) = delete;
+ Pipe& operator=(const Pipe&&) = delete;
+
+ public:
Pipe(int readFd, int writeFd) : m_readFd{readFd}, m_writeFd{writeFd} {
fcntl(m_readFd, F_SETFD, FD_CLOEXEC);
fcntl(m_writeFd, F_SETFD, FD_CLOEXEC);
@@ -50,26 +52,20 @@ public:
rval.m_writeFd = 0;
}
~Pipe() {
- if (m_readFd)
- close(m_readFd);
- if (m_writeFd)
- close(m_writeFd);
+ if (m_readFd) close(m_readFd);
+ if (m_writeFd) close(m_writeFd);
}
void preserveOverFork(bool preserve) {
if (preserve) {
fcntl(m_readFd, F_SETFD, 0);
- fcntl(m_writeFd, F_SETFD,0);
+ fcntl(m_writeFd, F_SETFD, 0);
} else {
fcntl(m_readFd, F_SETFD, FD_CLOEXEC);
fcntl(m_writeFd, F_SETFD, FD_CLOEXEC);
}
}
- int getReadFd() {
- return m_readFd;
- }
- int getWriteFd() {
- return m_writeFd;
- }
+ int getReadFd() { return m_readFd; }
+ int getWriteFd() { return m_writeFd; }
void signal() {
bool val = true;
int error = write(m_writeFd, &val, sizeof(val));
@@ -85,17 +81,17 @@ public:
int error = read(m_readFd, &val, sizeof(val));
return (error != 1);
}
- template <typename T> void send(const T& v) {
+ template <typename T>
+ void send(const T& v) {
int error = write(m_writeFd, &v, sizeof(T));
ASSERT_TRUE(error >= 0);
}
- template <typename T> void recv(T& v) {
+ template <typename T>
+ void recv(T& v) {
int error = read(m_readFd, &v, sizeof(T));
ASSERT_TRUE(error >= 0);
}
- static Pipe makePipeFromFds(int readFd, int writeFd) {
- return Pipe(readFd, writeFd);
- }
+ static Pipe makePipeFromFds(int readFd, int writeFd) { return Pipe(readFd, writeFd); }
static tuple<Pipe, Pipe> createPipePair() {
int a[2];
int b[2];
@@ -109,9 +105,7 @@ public:
}
};
-pid_t createProcess(Pipe pipe, const char *exName,
- const char *arg, bool use_memcg)
-{
+pid_t createProcess(Pipe pipe, const char* exName, const char* arg, bool use_memcg) {
pipe.preserveOverFork(true);
pid_t pid = fork();
// child proc
@@ -123,33 +117,30 @@ pid_t createProcess(Pipe pipe, const char *exName,
char exPath[PATH_MAX];
ssize_t exPathLen = readlink("/proc/self/exe", exPath, sizeof(exPath));
bool isExPathAvailable =
- exPathLen != -1 && exPathLen < static_cast<ssize_t>(sizeof(exPath));
+ exPathLen != -1 && exPathLen < static_cast<ssize_t>(sizeof(exPath));
if (isExPathAvailable) {
- exPath[exPathLen] = '\0';
+ exPath[exPathLen] = '\0';
}
execl(isExPathAvailable ? exPath : exName, exName, "--worker", arg, readFdStr, writeFdStr,
- use_memcg ? "1" : "0", nullptr);
+ use_memcg ? "1" : "0", nullptr);
ASSERT_TRUE(0);
}
// parent process
else if (pid > 0) {
pipe.preserveOverFork(false);
- }
- else {
+ } else {
ASSERT_TRUE(0);
}
return pid;
}
-
static void write_oomadj_to_lmkd(int oomadj) {
// Connect to lmkd and store our oom_adj
int lmk_procprio_cmd[4];
int sock;
int tries = 10;
- while ((sock = socket_local_client("lmkd",
- ANDROID_SOCKET_NAMESPACE_RESERVED,
- SOCK_SEQPACKET)) < 0) {
+ while ((sock = socket_local_client("lmkd", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET)) <
+ 0) {
usleep(100000);
if (tries-- < 0) break;
}
@@ -198,16 +189,15 @@ static void create_memcg() {
void usage() {
cout << "Application allocates memory until it's killed." << endl
- << "It starts at max oom_score_adj and gradually "
- << "decreases it to 0." << endl
- << "Usage: alloc-stress [-g | --cgroup]" << endl
- << "\t-g | --cgroup\tcreates memory cgroup for the process" << endl;
+ << "It starts at max oom_score_adj and gradually "
+ << "decreases it to 0." << endl
+ << "Usage: alloc-stress [-g | --cgroup]" << endl
+ << "\t-g | --cgroup\tcreates memory cgroup for the process" << endl;
}
size_t s = 4 * (1 << 20);
-void *gptr;
-int main(int argc, char *argv[])
-{
+void* gptr;
+int main(int argc, char* argv[]) {
bool use_memcg = false;
if ((argc > 1) && (std::string(argv[1]) == "--worker")) {
@@ -221,34 +211,31 @@ int main(int argc, char *argv[])
long long allocCount = 0;
while (1) {
p.wait();
- char *ptr = (char*)malloc(s);
+ char* ptr = (char*)malloc(s);
memset(ptr, (int)allocCount >> 10, s);
- for (int i = 0; i < s; i+= 4096) {
+ for (int i = 0; i < s; i += 4096) {
*((long long*)&ptr[i]) = allocCount + i;
}
usleep(10 * 1000);
gptr = ptr;
- //cout << "total alloc: " << allocCount / (1<<20)<< " adj: " << argv[2]<< endl;;
- //cout << "ptr: " << (long long)(void*)ptr << endl;;
+ // cout << "total alloc: " << allocCount / (1<<20)<< " adj: " << argv[2]<< endl;;
+ // cout << "ptr: " << (long long)(void*)ptr << endl;;
p.signal();
allocCount += s;
}
} else {
if (argc == 2) {
- if (std::string(argv[1]) == "--help" ||
- std::string(argv[1]) == "-h") {
+ if (std::string(argv[1]) == "--help" || std::string(argv[1]) == "-h") {
usage();
return 0;
}
- if (std::string(argv[1]) == "--cgroup" ||
- std::string(argv[1]) == "-g") {
+ if (std::string(argv[1]) == "--cgroup" || std::string(argv[1]) == "-g") {
use_memcg = true;
}
}
- cout << "Memory cgroups are "
- << (use_memcg ? "used" : "not used") << endl;
+ cout << "Memory cgroups are " << (use_memcg ? "used" : "not used") << endl;
write_oomadj_to_lmkd(-1000);
for (int i = 1000; i >= 0; i -= 100) {
@@ -256,9 +243,8 @@ int main(int argc, char *argv[])
char arg[16];
pid_t ch_pid;
snprintf(arg, sizeof(arg), "%d", i);
- ch_pid = createProcess(std::move(std::get<1>(pipes)),
- argv[0], arg, use_memcg);
- Pipe &p = std::get<0>(pipes);
+ ch_pid = createProcess(std::move(std::get<1>(pipes)), argv[0], arg, use_memcg);
+ Pipe& p = std::get<0>(pipes);
size_t t = 0;
diff --git a/alloc-stress/mem-pressure.cpp b/alloc-stress/mem-pressure.cpp
index 82b2273c..b0920e69 100644
--- a/alloc-stress/mem-pressure.cpp
+++ b/alloc-stress/mem-pressure.cpp
@@ -8,8 +8,8 @@
#include <sys/wait.h>
#include <unistd.h>
-void *alloc_set(size_t size) {
- void *addr = NULL;
+void* alloc_set(size_t size) {
+ void* addr = NULL;
addr = malloc(size);
if (!addr) {
@@ -20,15 +20,14 @@ void *alloc_set(size_t size) {
return addr;
}
-void add_pressure(size_t *shared, size_t size, size_t step_size,
- size_t duration, const char *oom_score) {
+void add_pressure(size_t* shared, size_t size, size_t step_size, size_t duration,
+ const char* oom_score) {
int fd, ret;
fd = open("/proc/self/oom_score_adj", O_WRONLY);
ret = write(fd, oom_score, strlen(oom_score));
if (ret < 0) {
- printf("Writing oom_score_adj failed with err %s\n",
- strerror(errno));
+ printf("Writing oom_score_adj failed with err %s\n", strerror(errno));
}
close(fd);
@@ -43,31 +42,27 @@ void add_pressure(size_t *shared, size_t size, size_t step_size,
}
}
-void usage()
-{
+void usage() {
printf("Usage: [OPTIONS]\n\n"
" -d N: Duration in microsecond to sleep between each allocation.\n"
" -i N: Number of iterations to run the alloc process.\n"
" -o N: The oom_score to set the child process to before alloc.\n"
- " -s N: Number of bytes to allocate in an alloc process loop.\n"
- );
+ " -s N: Number of bytes to allocate in an alloc process loop.\n");
}
-int main(int argc, char *argv[])
-{
+int main(int argc, char* argv[]) {
pid_t pid;
- size_t *shared;
+ size_t* shared;
int c, i = 0;
size_t duration = 1000;
int iterations = 0;
- const char *oom_score = "899";
- size_t step_size = 2 * 1024 * 1024; // 2 MB
+ const char* oom_score = "899";
+ size_t step_size = 2 * 1024 * 1024; // 2 MB
size_t size = step_size;
while ((c = getopt(argc, argv, "hi:d:o:s:")) != -1) {
- switch (c)
- {
+ switch (c) {
case 'i':
iterations = atoi(optarg);
break;
@@ -85,11 +80,11 @@ int main(int argc, char *argv[])
abort();
default:
abort();
- }
+ }
}
- shared = (size_t *)mmap(NULL, sizeof(size_t), PROT_READ | PROT_WRITE,
- MAP_ANONYMOUS | MAP_SHARED, 0, 0);
+ shared = (size_t*)mmap(NULL, sizeof(size_t), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED,
+ 0, 0);
while (iterations == 0 || i < iterations) {
*shared = 0;
@@ -101,8 +96,7 @@ int main(int argc, char *argv[])
exit(0);
} else {
wait(NULL);
- printf("Child %d allocated %zd MB\n", i,
- *shared / 1024 / 1024);
+ printf("Child %d allocated %zd MB\n", i, *shared / 1024 / 1024);
size = *shared / 2;
}
i++;
diff --git a/app-launcher/Android.bp b/app-launcher/Android.bp
index bf7b2798..3fbc88c8 100644
--- a/app-launcher/Android.bp
+++ b/app-launcher/Android.bp
@@ -1,3 +1,7 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_defaults {
name: "computestats-defaults",
diff --git a/boot_control_copy/.clang-format b/boot_control_copy/.clang-format
new file mode 120000
index 00000000..fd0645fd
--- /dev/null
+++ b/boot_control_copy/.clang-format
@@ -0,0 +1 @@
+../.clang-format-2 \ No newline at end of file
diff --git a/boot_control_copy/Android.bp b/boot_control_copy/Android.bp
index 6c9dbc24..49126d9c 100644
--- a/boot_control_copy/Android.bp
+++ b/boot_control_copy/Android.bp
@@ -1,5 +1,22 @@
// Copyright 2015 The Android Open Source Project
+package {
+ default_applicable_licenses: ["system_extras_boot_control_copy_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_boot_control_copy_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_library_shared {
name: "bootctrl.copy",
relative_install_path: "hw",
diff --git a/boot_control_copy/boot_control_copy.cpp b/boot_control_copy/boot_control_copy.cpp
index d8a5d180..11377856 100644
--- a/boot_control_copy/boot_control_copy.cpp
+++ b/boot_control_copy/boot_control_copy.cpp
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-#include <sys/types.h>
+#include <fcntl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
+#include <sys/types.h>
#include <unistd.h>
-#include <fcntl.h>
#include <errno.h>
#include <inttypes.h>
@@ -26,31 +26,26 @@
#include <string.h>
#include <fs_mgr.h>
-#include <hardware/hardware.h>
#include <hardware/boot_control.h>
+#include <hardware/hardware.h>
#include "bootinfo.h"
-void module_init(boot_control_module_t *module)
-{
-}
+void module_init(boot_control_module_t* module) {}
-unsigned module_getNumberSlots(boot_control_module_t *module)
-{
+unsigned module_getNumberSlots(boot_control_module_t* module) {
return 2;
}
-static bool get_dev_t_for_partition(const char *name, dev_t *out_device)
-{
+static bool get_dev_t_for_partition(const char* name, dev_t* out_device) {
int fd;
struct stat statbuf;
fd = boot_info_open_partition(name, NULL, O_RDONLY);
- if (fd == -1)
- return false;
+ if (fd == -1) return false;
if (fstat(fd, &statbuf) != 0) {
- fprintf(stderr, "WARNING: Error getting information about part %s: %s\n",
- name, strerror(errno));
+ fprintf(stderr, "WARNING: Error getting information about part %s: %s\n", name,
+ strerror(errno));
close(fd);
return false;
}
@@ -59,14 +54,12 @@ static bool get_dev_t_for_partition(const char *name, dev_t *out_device)
return true;
}
-unsigned module_getCurrentSlot(boot_control_module_t *module)
-{
+unsigned module_getCurrentSlot(boot_control_module_t* module) {
struct stat statbuf;
dev_t system_a_dev, system_b_dev;
if (stat("/system", &statbuf) != 0) {
- fprintf(stderr, "WARNING: Error getting information about /system: %s\n",
- strerror(errno));
+ fprintf(stderr, "WARNING: Error getting information about /system: %s\n", strerror(errno));
return 0;
}
@@ -79,24 +72,22 @@ unsigned module_getCurrentSlot(boot_control_module_t *module)
} else if (statbuf.st_dev == system_b_dev) {
return 1;
} else {
- fprintf(stderr, "WARNING: Error determining current slot "
+ fprintf(stderr,
+ "WARNING: Error determining current slot "
"(/system dev_t of %d:%d does not match a=%d:%d or b=%d:%d)\n",
- major(statbuf.st_dev), minor(statbuf.st_dev),
- major(system_a_dev), minor(system_a_dev),
+ major(statbuf.st_dev), minor(statbuf.st_dev), major(system_a_dev), minor(system_a_dev),
major(system_b_dev), minor(system_b_dev));
return 0;
}
}
-int module_markBootSuccessful(boot_control_module_t *module)
-{
+int module_markBootSuccessful(boot_control_module_t* module) {
return 0;
}
-#define COPY_BUF_SIZE (1024*1024)
+#define COPY_BUF_SIZE (1024 * 1024)
-static bool copy_data(int src_fd, int dst_fd, size_t num_bytes)
-{
+static bool copy_data(int src_fd, int dst_fd, size_t num_bytes) {
char copy_buf[COPY_BUF_SIZE];
size_t remaining;
@@ -108,8 +99,7 @@ static bool copy_data(int src_fd, int dst_fd, size_t num_bytes)
num_read = read(src_fd, copy_buf, num_to_read);
} while (num_read == -1 && errno == EINTR);
if (num_read <= 0) {
- fprintf(stderr, "Error reading %zd bytes from source: %s\n",
- num_to_read, strerror(errno));
+ fprintf(stderr, "Error reading %zd bytes from source: %s\n", num_to_read, strerror(errno));
return false;
}
size_t num_to_write = num_read;
@@ -120,8 +110,8 @@ static bool copy_data(int src_fd, int dst_fd, size_t num_bytes)
num_written = write(dst_fd, copy_buf + offset, num_to_write);
} while (num_written == -1 && errno == EINTR);
if (num_written <= 0) {
- fprintf(stderr, "Error writing %zd bytes to destination: %s\n",
- num_to_write, strerror(errno));
+ fprintf(stderr, "Error writing %zd bytes to destination: %s\n", num_to_write,
+ strerror(errno));
return false;
}
num_to_write -= num_written;
@@ -132,15 +122,13 @@ static bool copy_data(int src_fd, int dst_fd, size_t num_bytes)
return true;
}
-int module_setActiveBootSlot(boot_control_module_t *module, unsigned slot)
-{
+int module_setActiveBootSlot(boot_control_module_t* module, unsigned slot) {
BrilloBootInfo info;
int src_fd, dst_fd;
uint64_t src_size, dst_size;
char src_name[32];
- if (slot >= 2)
- return -EINVAL;
+ if (slot >= 2) return -EINVAL;
if (!boot_info_load(&info)) {
fprintf(stderr, "WARNING: Error loading boot-info. Resetting.\n");
@@ -154,9 +142,7 @@ int module_setActiveBootSlot(boot_control_module_t *module, unsigned slot)
info.active_slot = slot;
info.slot_info[slot].bootable = true;
- snprintf(info.bootctrl_suffix,
- sizeof(info.bootctrl_suffix),
- "_%c", slot + 'a');
+ snprintf(info.bootctrl_suffix, sizeof(info.bootctrl_suffix), "_%c", slot + 'a');
if (!boot_info_save(&info)) {
fprintf(stderr, "Error saving boot-info.\n");
@@ -180,7 +166,8 @@ int module_setActiveBootSlot(boot_control_module_t *module, unsigned slot)
if (src_size != dst_size) {
fprintf(stderr,
- "src (%" PRIu64 " bytes) and dst (%" PRIu64 " bytes) "
+ "src (%" PRIu64 " bytes) and dst (%" PRIu64
+ " bytes) "
"have different sizes.\n",
src_size, dst_size);
close(src_fd);
@@ -195,8 +182,7 @@ int module_setActiveBootSlot(boot_control_module_t *module, unsigned slot)
}
if (fsync(dst_fd) != 0) {
- fprintf(stderr, "Error calling fsync on destination: %s\n",
- strerror(errno));
+ fprintf(stderr, "Error calling fsync on destination: %s\n", strerror(errno));
return -errno;
}
@@ -205,12 +191,10 @@ int module_setActiveBootSlot(boot_control_module_t *module, unsigned slot)
return 0;
}
-int module_setSlotAsUnbootable(struct boot_control_module *module, unsigned slot)
-{
+int module_setSlotAsUnbootable(struct boot_control_module* module, unsigned slot) {
BrilloBootInfo info;
- if (slot >= 2)
- return -EINVAL;
+ if (slot >= 2) return -EINVAL;
if (!boot_info_load(&info)) {
fprintf(stderr, "WARNING: Error loading boot-info. Resetting.\n");
@@ -232,12 +216,10 @@ int module_setSlotAsUnbootable(struct boot_control_module *module, unsigned slot
return 0;
}
-int module_isSlotBootable(struct boot_control_module *module, unsigned slot)
-{
+int module_isSlotBootable(struct boot_control_module* module, unsigned slot) {
BrilloBootInfo info;
- if (slot >= 2)
- return -EINVAL;
+ if (slot >= 2) return -EINVAL;
if (!boot_info_load(&info)) {
fprintf(stderr, "WARNING: Error loading boot-info. Resetting.\n");
@@ -252,19 +234,16 @@ int module_isSlotBootable(struct boot_control_module *module, unsigned slot)
return info.slot_info[slot].bootable;
}
-const char* module_getSuffix(boot_control_module_t *module, unsigned slot)
-{
+const char* module_getSuffix(boot_control_module_t* module, unsigned slot) {
static const char* suffix[2] = {"_a", "_b"};
- if (slot >= 2)
- return NULL;
+ if (slot >= 2) return NULL;
return suffix[slot];
}
static struct hw_module_methods_t module_methods = {
- .open = NULL,
+ .open = NULL,
};
-
/* This boot_control HAL implementation emulates A/B by copying the
* contents of the boot partition of the requested slot to the boot
* partition. It hence works with bootloaders that are not yet aware
@@ -272,21 +251,22 @@ static struct hw_module_methods_t module_methods = {
*/
boot_control_module_t HAL_MODULE_INFO_SYM = {
- .common = {
- .tag = HARDWARE_MODULE_TAG,
- .module_api_version = BOOT_CONTROL_MODULE_API_VERSION_0_1,
- .hal_api_version = HARDWARE_HAL_API_VERSION,
- .id = BOOT_CONTROL_HARDWARE_MODULE_ID,
- .name = "Copy Implementation of boot_control HAL",
- .author = "The Android Open Source Project",
- .methods = &module_methods,
- },
- .init = module_init,
- .getNumberSlots = module_getNumberSlots,
- .getCurrentSlot = module_getCurrentSlot,
- .markBootSuccessful = module_markBootSuccessful,
- .setActiveBootSlot = module_setActiveBootSlot,
- .setSlotAsUnbootable = module_setSlotAsUnbootable,
- .isSlotBootable = module_isSlotBootable,
- .getSuffix = module_getSuffix,
+ .common =
+ {
+ .tag = HARDWARE_MODULE_TAG,
+ .module_api_version = BOOT_CONTROL_MODULE_API_VERSION_0_1,
+ .hal_api_version = HARDWARE_HAL_API_VERSION,
+ .id = BOOT_CONTROL_HARDWARE_MODULE_ID,
+ .name = "Copy Implementation of boot_control HAL",
+ .author = "The Android Open Source Project",
+ .methods = &module_methods,
+ },
+ .init = module_init,
+ .getNumberSlots = module_getNumberSlots,
+ .getCurrentSlot = module_getCurrentSlot,
+ .markBootSuccessful = module_markBootSuccessful,
+ .setActiveBootSlot = module_setActiveBootSlot,
+ .setSlotAsUnbootable = module_setSlotAsUnbootable,
+ .isSlotBootable = module_isSlotBootable,
+ .getSuffix = module_getSuffix,
};
diff --git a/boot_control_copy/bootinfo.cpp b/boot_control_copy/bootinfo.cpp
index 55319178..be3e97e2 100644
--- a/boot_control_copy/bootinfo.cpp
+++ b/boot_control_copy/bootinfo.cpp
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-
#include <errno.h>
#include <fcntl.h>
#include <linux/fs.h>
@@ -40,14 +39,12 @@ using android::fs_mgr::ReadFstabFromFile;
// Open the appropriate fstab file and fallback to /fstab.device if
// that's what's being used.
-static bool open_fstab(Fstab* fstab)
-{
+static bool open_fstab(Fstab* fstab) {
return ReadDefaultFstab(fstab) || ReadFstabFromFile("/fstab.device", fstab);
}
-int boot_info_open_partition(const char *name, uint64_t *out_size, int flags)
-{
- char *path;
+int boot_info_open_partition(const char* name, uint64_t* out_size, int flags) {
+ char* path;
int fd;
// We can't use fs_mgr to look up |name| because fstab doesn't list
@@ -79,13 +76,13 @@ int boot_info_open_partition(const char *name, uint64_t *out_size, int flags)
path = strdup(record->blk_device.c_str());
} else {
size_t trimmed_len, name_len;
- const char *end_slash = strrchr(record->blk_device.c_str(), '/');
+ const char* end_slash = strrchr(record->blk_device.c_str(), '/');
if (end_slash == NULL) {
return -1;
}
trimmed_len = end_slash - record->blk_device.c_str() + 1;
name_len = strlen(name);
- path = static_cast<char *>(calloc(trimmed_len + name_len + 1, 1));
+ path = static_cast<char*>(calloc(trimmed_len + name_len + 1, 1));
strncpy(path, record->blk_device.c_str(), trimmed_len);
strncpy(path + trimmed_len, name, name_len);
}
@@ -120,63 +117,51 @@ int boot_info_open_partition(const char *name, uint64_t *out_size, int flags)
// that BrilloBootInfo is laid out this way.
#define BOOTINFO_OFFSET 2048
-bool boot_info_load(BrilloBootInfo *out_info)
-{
+bool boot_info_load(BrilloBootInfo* out_info) {
int fd;
memset(out_info, '\0', sizeof(BrilloBootInfo));
fd = boot_info_open_partition("misc", NULL, O_RDONLY);
- if (fd == -1)
- return false;
+ if (fd == -1) return false;
if (lseek(fd, BOOTINFO_OFFSET, SEEK_SET) != BOOTINFO_OFFSET) {
close(fd);
return false;
}
ssize_t num_read;
do {
- num_read = read(fd, (void*) out_info, sizeof(BrilloBootInfo));
+ num_read = read(fd, (void*)out_info, sizeof(BrilloBootInfo));
} while (num_read == -1 && errno == EINTR);
close(fd);
- if (num_read != sizeof(BrilloBootInfo))
- return false;
+ if (num_read != sizeof(BrilloBootInfo)) return false;
return true;
}
-bool boot_info_save(BrilloBootInfo *info)
-{
+bool boot_info_save(BrilloBootInfo* info) {
int fd;
fd = boot_info_open_partition("misc", NULL, O_RDWR);
- if (fd == -1)
- return false;
+ if (fd == -1) return false;
if (lseek(fd, BOOTINFO_OFFSET, SEEK_SET) != BOOTINFO_OFFSET) {
close(fd);
return false;
}
ssize_t num_written;
do {
- num_written = write(fd, (void*) info, sizeof(BrilloBootInfo));
+ num_written = write(fd, (void*)info, sizeof(BrilloBootInfo));
} while (num_written == -1 && errno == EINTR);
close(fd);
- if (num_written != sizeof(BrilloBootInfo))
- return false;
+ if (num_written != sizeof(BrilloBootInfo)) return false;
return true;
}
-bool boot_info_validate(BrilloBootInfo* info)
-{
- if (info->magic[0] != 'B' ||
- info->magic[1] != 'C' ||
- info->magic[2] != 'c')
- return false;
- if (info->active_slot >= 2)
- return false;
+bool boot_info_validate(BrilloBootInfo* info) {
+ if (info->magic[0] != 'B' || info->magic[1] != 'C' || info->magic[2] != 'c') return false;
+ if (info->active_slot >= 2) return false;
return true;
}
-void boot_info_reset(BrilloBootInfo* info)
-{
+void boot_info_reset(BrilloBootInfo* info) {
memset(info, '\0', sizeof(BrilloBootInfo));
info->magic[0] = 'B';
info->magic[1] = 'C';
diff --git a/boot_control_copy/bootinfo.h b/boot_control_copy/bootinfo.h
index 4b36b2cd..3144b7d5 100644
--- a/boot_control_copy/bootinfo.h
+++ b/boot_control_copy/bootinfo.h
@@ -17,8 +17,8 @@
#ifndef BOOTINFO_H_
#define BOOTINFO_H_
-#include <stdint.h>
#include <stdbool.h>
+#include <stdint.h>
typedef struct BrilloSlotInfo {
uint8_t bootable : 1;
@@ -46,8 +46,8 @@ typedef struct BrilloBootInfo {
} BrilloBootInfo;
// Loading and saving BrillBootInfo instances.
-bool boot_info_load(BrilloBootInfo *out_info);
-bool boot_info_save(BrilloBootInfo *info);
+bool boot_info_load(BrilloBootInfo* out_info);
+bool boot_info_save(BrilloBootInfo* info);
// Returns non-zero if valid.
bool boot_info_validate(BrilloBootInfo* info);
@@ -56,7 +56,7 @@ void boot_info_reset(BrilloBootInfo* info);
// Opens partition by |name|, e.g. "misc" or "boot_a" with |flags|
// (e.g. O_RDONLY or O_RDWR) passed directly to open(2). Returns fd on
// success and -1 on error.
-int boot_info_open_partition(const char *name, uint64_t *out_size, int flags);
+int boot_info_open_partition(const char* name, uint64_t* out_size, int flags);
#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 6
_Static_assert(sizeof(BrilloBootInfo) == 32, "BrilloBootInfo has wrong size");
diff --git a/bootctl/Android.bp b/bootctl/Android.bp
index bbc33267..f63871cf 100644
--- a/bootctl/Android.bp
+++ b/bootctl/Android.bp
@@ -1,5 +1,22 @@
// Copyright 2015 The Android Open Source Project
+package {
+ default_applicable_licenses: ["system_extras_bootctl_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_bootctl_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_binary {
name: "bootctl",
srcs: ["bootctl.cpp"],
@@ -12,6 +29,7 @@ cc_binary {
shared_libs: [
"android.hardware.boot@1.0",
"android.hardware.boot@1.1",
+ "android.hardware.boot@1.2",
"libhidlbase",
"libutils",
],
diff --git a/bootctl/bootctl.cpp b/bootctl/bootctl.cpp
index c8f8b0bc..8ead010d 100644
--- a/bootctl/bootctl.cpp
+++ b/bootctl/bootctl.cpp
@@ -17,8 +17,8 @@
#include <optional>
#include <sstream>
+#include <android/hardware/boot/1.2/IBootControl.h>
#include <sysexits.h>
-#include <android/hardware/boot/1.1/IBootControl.h>
using android::sp;
@@ -33,11 +33,11 @@ using android::hardware::boot::V1_1::MergeStatus;
namespace V1_0 = android::hardware::boot::V1_0;
namespace V1_1 = android::hardware::boot::V1_1;
+namespace V1_2 = android::hardware::boot::V1_2;
-enum BootCtlVersion { BOOTCTL_V1_0, BOOTCTL_V1_1 };
+enum BootCtlVersion { BOOTCTL_V1_0, BOOTCTL_V1_1, BOOTCTL_V1_2 };
-static void usage(FILE* where, BootCtlVersion bootVersion, int /* argc */, char* argv[])
-{
+static void usage(FILE* where, BootCtlVersion bootVersion, int /* argc */, char* argv[]) {
fprintf(where,
"%s - command-line wrapper for the boot HAL.\n"
"\n"
@@ -49,6 +49,7 @@ static void usage(FILE* where, BootCtlVersion bootVersion, int /* argc */, char*
" get-number-slots - Prints number of slots.\n"
" get-current-slot - Prints currently running SLOT.\n"
" mark-boot-successful - Mark current slot as GOOD.\n"
+ " get-active-boot-slot - Prints the SLOT to load on next boot.\n"
" set-active-boot-slot SLOT - On next boot, load and execute SLOT.\n"
" set-slot-as-unbootable SLOT - Mark SLOT as invalid.\n"
" is-slot-bootable SLOT - Returns 0 only if SLOT is bootable.\n"
@@ -70,34 +71,28 @@ static void usage(FILE* where, BootCtlVersion bootVersion, int /* argc */, char*
static int do_hal_info(const sp<V1_0::IBootControl> module) {
module->interfaceDescriptor([&](const auto& descriptor) {
- fprintf(stdout,
- "HAL Version: %s\n",
- descriptor.c_str());
+ fprintf(stdout, "HAL Version: %s\n", descriptor.c_str());
});
return EX_OK;
}
-static int do_get_number_slots(sp<V1_0::IBootControl> module)
-{
+static int do_get_number_slots(sp<V1_0::IBootControl> module) {
uint32_t numSlots = module->getNumberSlots();
fprintf(stdout, "%u\n", numSlots);
return EX_OK;
}
-static int do_get_current_slot(sp<V1_0::IBootControl> module)
-{
+static int do_get_current_slot(sp<V1_0::IBootControl> module) {
Slot curSlot = module->getCurrentSlot();
fprintf(stdout, "%u\n", curSlot);
return EX_OK;
}
-static std::function<void(CommandResult)> generate_callback(CommandResult *crp) {
- return [=](CommandResult cr){
- *crp = cr;
- };
+static std::function<void(CommandResult)> generate_callback(CommandResult* crp) {
+ return [=](CommandResult cr) { *crp = cr; };
}
-static int handle_return(const Return<void> &ret, CommandResult cr, const char* errStr) {
+static int handle_return(const Return<void>& ret, CommandResult cr, const char* errStr) {
if (!ret.isOk()) {
fprintf(stderr, errStr, ret.description().c_str());
return EX_SOFTWARE;
@@ -108,30 +103,31 @@ static int handle_return(const Return<void> &ret, CommandResult cr, const char*
return EX_OK;
}
-static int do_mark_boot_successful(sp<V1_0::IBootControl> module)
-{
+static int do_mark_boot_successful(sp<V1_0::IBootControl> module) {
CommandResult cr;
Return<void> ret = module->markBootSuccessful(generate_callback(&cr));
return handle_return(ret, cr, "Error marking as having booted successfully: %s\n");
}
-static int do_set_active_boot_slot(sp<V1_0::IBootControl> module,
- Slot slot_number)
-{
+static int do_get_active_boot_slot(sp<V1_2::IBootControl> module) {
+ uint32_t slot = module->getActiveBootSlot();
+ fprintf(stdout, "%u\n", slot);
+ return EX_OK;
+}
+
+static int do_set_active_boot_slot(sp<V1_0::IBootControl> module, Slot slot_number) {
CommandResult cr;
Return<void> ret = module->setActiveBootSlot(slot_number, generate_callback(&cr));
return handle_return(ret, cr, "Error setting active boot slot: %s\n");
}
-static int do_set_slot_as_unbootable(sp<V1_0::IBootControl> module,
- Slot slot_number)
-{
+static int do_set_slot_as_unbootable(sp<V1_0::IBootControl> module, Slot slot_number) {
CommandResult cr;
Return<void> ret = module->setSlotAsUnbootable(slot_number, generate_callback(&cr));
return handle_return(ret, cr, "Error setting slot as unbootable: %s\n");
}
-static int handle_return(const Return<BoolResult> &ret, const char* errStr) {
+static int handle_return(const Return<BoolResult>& ret, const char* errStr) {
if (!ret.isOk()) {
fprintf(stderr, errStr, ret.description().c_str());
return EX_SOFTWARE;
@@ -144,20 +140,17 @@ static int handle_return(const Return<BoolResult> &ret, const char* errStr) {
return EX_SOFTWARE;
}
-static int do_is_slot_bootable(sp<V1_0::IBootControl> module, Slot slot_number)
-{
+static int do_is_slot_bootable(sp<V1_0::IBootControl> module, Slot slot_number) {
Return<BoolResult> ret = module->isSlotBootable(slot_number);
return handle_return(ret, "Error calling isSlotBootable(): %s\n");
}
-static int do_is_slot_marked_successful(sp<V1_0::IBootControl> module,
- Slot slot_number)
-{
+static int do_is_slot_marked_successful(sp<V1_0::IBootControl> module, Slot slot_number) {
Return<BoolResult> ret = module->isSlotMarkedSuccessful(slot_number);
return handle_return(ret, "Error calling isSlotMarkedSuccessful(): %s\n");
}
-std::optional<MergeStatus> stringToMergeStatus(const std::string &status) {
+std::optional<MergeStatus> stringToMergeStatus(const std::string& status) {
if (status == "cancelled") return MergeStatus::CANCELLED;
if (status == "merging") return MergeStatus::MERGING;
if (status == "none") return MergeStatus::NONE;
@@ -167,7 +160,7 @@ std::optional<MergeStatus> stringToMergeStatus(const std::string &status) {
}
static int do_set_snapshot_merge_status(sp<V1_1::IBootControl> module, BootCtlVersion bootVersion,
- int argc, char *argv[]) {
+ int argc, char* argv[]) {
if (argc != 3) {
usage(stderr, bootVersion, argc, argv);
exit(EX_USAGE);
@@ -213,20 +206,18 @@ static int do_get_snapshot_merge_status(sp<V1_1::IBootControl> module) {
}
static int do_get_suffix(sp<V1_0::IBootControl> module, Slot slot_number) {
- std::function<void(hidl_string)> cb = [](hidl_string suffix){
+ std::function<void(hidl_string)> cb = [](hidl_string suffix) {
fprintf(stdout, "%s\n", suffix.c_str());
};
Return<void> ret = module->getSuffix(slot_number, cb);
if (!ret.isOk()) {
- fprintf(stderr, "Error calling getSuffix(): %s\n",
- ret.description().c_str());
+ fprintf(stderr, "Error calling getSuffix(): %s\n", ret.description().c_str());
return EX_SOFTWARE;
}
return EX_OK;
}
-static uint32_t parse_slot(BootCtlVersion bootVersion, int pos, int argc, char *argv[])
-{
+static uint32_t parse_slot(BootCtlVersion bootVersion, int pos, int argc, char* argv[]) {
if (pos > argc - 1) {
usage(stderr, bootVersion, argc, argv);
exit(EX_USAGE);
@@ -242,10 +233,10 @@ static uint32_t parse_slot(BootCtlVersion bootVersion, int pos, int argc, char *
return (uint32_t)ret;
}
-int main(int argc, char *argv[])
-{
+int main(int argc, char* argv[]) {
sp<V1_0::IBootControl> v1_0_module;
sp<V1_1::IBootControl> v1_1_module;
+ sp<V1_2::IBootControl> v1_2_module;
BootCtlVersion bootVersion = BOOTCTL_V1_0;
v1_0_module = V1_0::IBootControl::getService();
@@ -258,6 +249,11 @@ int main(int argc, char *argv[])
bootVersion = BOOTCTL_V1_1;
}
+ v1_2_module = V1_2::IBootControl::castFrom(v1_0_module);
+ if (v1_2_module != nullptr) {
+ bootVersion = BOOTCTL_V1_2;
+ }
+
if (argc < 2) {
usage(stderr, bootVersion, argc, argv);
return EX_USAGE;
@@ -286,8 +282,7 @@ int main(int argc, char *argv[])
// Functions present from version 1.1
if (strcmp(argv[1], "set-snapshot-merge-status") == 0 ||
- strcmp(argv[1], "get-snapshot-merge-status") == 0 ) {
-
+ strcmp(argv[1], "get-snapshot-merge-status") == 0) {
if (v1_1_module == nullptr) {
fprintf(stderr, "Error getting bootctrl v1.1 module.\n");
return EX_SOFTWARE;
@@ -299,6 +294,15 @@ int main(int argc, char *argv[])
}
}
+ if (strcmp(argv[1], "get-active-boot-slot") == 0) {
+ if (v1_2_module == nullptr) {
+ fprintf(stderr, "Error getting bootctrl v1.2 module.\n");
+ return EX_SOFTWARE;
+ }
+
+ return do_get_active_boot_slot(v1_2_module);
+ }
+
// Parameter not matched, print usage
usage(stderr, bootVersion, argc, argv);
return EX_USAGE;
diff --git a/boottime_tools/bootanalyze/README.md b/boottime_tools/bootanalyze/README.md
index d6e0412f..038e1452 100644
--- a/boottime_tools/bootanalyze/README.md
+++ b/boottime_tools/bootanalyze/README.md
@@ -1,7 +1,31 @@
-# bootanalyze #
+# bootanalyze
The bootanalyze tool helps to profile boot timing.
+[TOC]
+
+## Preliminaries
+
+* Need to access "su" on the Device Under Test, e.g. a userdebug build.
+* This only works on Linux with Python 2.7, PyYAML and pybootchartgui.
+
+```
+sudo pip install pyyaml
+sudo apt-get install pybootchartgui
+```
+
+## Examples
+
+* bootanalyze.sh provides an example to analyze boot-times and bootcharts.
+```
+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
+```
+
+## config.yaml
Per specific product modify config.yaml file to include
events you are looking for. Config should look like:
diff --git a/boottime_tools/bootanalyze/bootanalyze.py b/boottime_tools/bootanalyze/bootanalyze.py
index ef5c5273..7eea05b9 100755
--- a/boottime_tools/bootanalyze/bootanalyze.py
+++ b/boottime_tools/bootanalyze/bootanalyze.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# Copyright (C) 2016 The Android Open Source Project
#
@@ -36,8 +36,8 @@ import yaml
from datetime import datetime, date
-TIME_DMESG = "\[\s*(\d+\.\d+)\]"
-TIME_LOGCAT = "[0-9]+\.?[0-9]*"
+TIME_DMESG = r"\[\s*(\d+\.\d+)\]"
+TIME_LOGCAT = r"[0-9]+\.?[0-9]*"
KERNEL_TIME_KEY = "kernel"
BOOT_ANIM_END_TIME_KEY = "BootAnimEnd"
KERNEL_BOOT_COMPLETE = "BootComplete_kernel"
@@ -48,8 +48,8 @@ MAX_RETRIES = 5
DEBUG = False
ADB_CMD = "adb"
TIMING_THRESHOLD = 5.0
-BOOT_PROP = "\[ro\.boottime\.([^\]]+)\]:\s+\[(\d+)\]"
-BOOTLOADER_TIME_PROP = "\[ro\.boot\.boottime\]:\s+\[([^\]]+)\]"
+BOOT_PROP = r"\[ro\.boottime\.([^\]]+)\]:\s+\[(\d+)\]"
+BOOTLOADER_TIME_PROP = r"\[ro\.boot\.boottime\]:\s+\[([^\]]+)\]"
max_wait_time = BOOT_TIME_TOO_BIG
@@ -59,10 +59,10 @@ def main():
args = init_arguments()
if args.iterate < 1:
- raise Exception('Number of iteration must be >=1');
+ raise Exception('Number of iteration must be >=1')
if args.iterate > 1 and not args.reboot:
- print "Forcing reboot flag"
+ print("Forcing reboot flag")
args.reboot = True
if args.serial:
@@ -84,21 +84,21 @@ def main():
value = float(kv[1])
components_to_monitor[key] = value
- cfg = yaml.load(args.config)
+ cfg = yaml.load(args.config, Loader=yaml.FullLoader)
if args.stressfs:
if run_adb_cmd('install -r -g ' + args.stressfs) != 0:
- raise Exception('StressFS APK not installed');
+ raise Exception('StressFS APK not installed')
if args.iterate > 1 and args.bootchart:
run_adb_shell_cmd_as_root('touch /data/bootchart/enabled')
search_events_pattern = {key: re.compile(pattern)
- for key, pattern in cfg['events'].iteritems()}
+ for key, pattern in cfg['events'].items()}
timing_events_pattern = {key: re.compile(pattern)
- for key, pattern in cfg['timings'].iteritems()}
+ for key, pattern in cfg['timings'].items()}
shutdown_events_pattern = {key: re.compile(pattern)
- for key, pattern in cfg['shutdown_events'].iteritems()}
+ for key, pattern in cfg['shutdown_events'].items()}
data_points = {}
kernel_timing_points = collections.OrderedDict()
@@ -110,7 +110,7 @@ def main():
shutdown_timing_event_all = collections.OrderedDict()
for it in range(0, args.iterate):
if args.iterate > 1:
- print "Run: {0}".format(it)
+ print("Run: {0}".format(it))
attempt = 1
processing_data = None
timings = None
@@ -122,14 +122,14 @@ def main():
args, search_events_pattern, timing_events_pattern, shutdown_events_pattern, cfg,\
error_time, components_to_monitor)
if shutdown_events:
- for k, v in shutdown_events.iteritems():
+ for k, v in shutdown_events.items():
events = shutdown_event_all.get(k)
if not events:
events = []
shutdown_event_all[k] = events
events.append(v)
if shutdown_timing_events:
- for k, v in shutdown_timing_events.iteritems():
+ for k, v in shutdown_timing_events.items():
events = shutdown_timing_event_all.get(k)
if not events:
events = []
@@ -137,31 +137,31 @@ def main():
events.append(v)
if not processing_data or not boottime_events:
# Processing error
- print "Failed to collect valid samples for run {0}".format(it)
+ 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))
if args.systrace:
grab_systrace(systrace_file_name_prefix + "_run_" + str(it))
- for k, v in processing_data.iteritems():
+ for k, v in processing_data.items():
if k not in data_points:
data_points[k] = []
data_points[k].append(v['value'])
if kernel_timings is not None:
- for k, v in kernel_timings.iteritems():
+ for k, v in kernel_timings.items():
if k not in kernel_timing_points:
kernel_timing_points[k] = []
kernel_timing_points[k].append(v)
if logcat_timings is not None:
- for k, v in logcat_timings.iteritems():
+ for k, v in logcat_timings.items():
if k not in logcat_timing_points:
logcat_timing_points[k] = []
logcat_timing_points[k].append(v)
- for k, v in boottime_events.iteritems():
- if not k in boottime_points:
+ for k, v in boottime_events.items():
+ if k not in boottime_points:
boottime_points[k] = []
boottime_points[k].append(v)
@@ -170,90 +170,90 @@ def main():
run_adb_shell_cmd('"rm -rf /storage/emulated/0/stressfs_data*"')
if args.iterate > 1:
- print "-----------------"
- print "\nshutdown events after {0} runs".format(args.iterate)
- print '{0:30}: {1:<7} {2:<7} {3}'.format("Event", "Mean", "stddev", "#runs")
- for item in shutdown_event_all.items():
+ print("-----------------")
+ print("\nshutdown events after {0} runs".format(args.iterate))
+ print('{0:30}: {1:<7} {2:<7} {3}'.format("Event", "Mean", "stddev", "#runs"))
+ for item in list(shutdown_event_all.items()):
num_runs = len(item[1])
- print '{0:30}: {1:<7.5} {2:<7.5} {3} {4}'.format(
+ print('{0:30}: {1:<7.5} {2:<7.5} {3} {4}'.format(
item[0], sum(item[1])/num_runs, stddev(item[1]),\
"*time taken" if item[0].startswith("init.") else "",\
- num_runs if num_runs != args.iterate else "")
- print "\nshutdown timing events after {0} runs".format(args.iterate)
- print '{0:30}: {1:<7} {2:<7} {3}'.format("Event", "Mean", "stddev", "#runs")
- for item in shutdown_timing_event_all.items():
+ num_runs if num_runs != args.iterate else ""))
+ print("\nshutdown timing events after {0} runs".format(args.iterate))
+ print('{0:30}: {1:<7} {2:<7} {3}'.format("Event", "Mean", "stddev", "#runs"))
+ for item in list(shutdown_timing_event_all.items()):
num_runs = len(item[1])
- print '{0:30}: {1:<7.5} {2:<7.5} {3} {4}'.format(
+ print('{0:30}: {1:<7.5} {2:<7.5} {3} {4}'.format(
item[0], sum(item[1])/num_runs, stddev(item[1]),\
"*time taken" if item[0].startswith("init.") else "",\
- num_runs if num_runs != args.iterate else "")
+ num_runs if num_runs != args.iterate else ""))
- print "-----------------"
- print "ro.boottime.* after {0} runs".format(args.iterate)
- print '{0:30}: {1:<7} {2:<7} {3}'.format("Event", "Mean", "stddev", "#runs")
- for item in boottime_points.items():
+ print("-----------------")
+ print("ro.boottime.* after {0} runs".format(args.iterate))
+ print('{0:30}: {1:<7} {2:<7} {3}'.format("Event", "Mean", "stddev", "#runs"))
+ for item in list(boottime_points.items()):
num_runs = len(item[1])
- print '{0:30}: {1:<7.5} {2:<7.5} {3} {4}'.format(
+ print('{0:30}: {1:<7.5} {2:<7.5} {3} {4}'.format(
item[0], sum(item[1])/num_runs, stddev(item[1]),\
"*time taken" if item[0].startswith("init.") else "",\
- num_runs if num_runs != args.iterate else "")
+ num_runs if num_runs != args.iterate else ""))
if args.timings:
dump_timings_points_summary("Kernel", kernel_timing_points, args)
dump_timings_points_summary("Logcat", logcat_timing_points, args)
- print "-----------------"
- print "Avg values after {0} runs".format(args.iterate)
- print '{0:30}: {1:<7} {2:<7} {3}'.format("Event", "Mean", "stddev", "#runs")
+ print("-----------------")
+ print("Avg values after {0} runs".format(args.iterate))
+ print('{0:30}: {1:<7} {2:<7} {3}'.format("Event", "Mean", "stddev", "#runs"))
average_with_stddev = []
- for item in data_points.items():
+ for item in list(data_points.items()):
average_with_stddev.append((item[0], sum(item[1])/len(item[1]), stddev(item[1]),\
len(item[1])))
for item in sorted(average_with_stddev, key=lambda entry: entry[1]):
- print '{0:30}: {1:<7.5} {2:<7.5} {3}'.format(
- item[0], item[1], item[2], item[3] if item[3] != args.iterate else "")
+ print('{0:30}: {1:<7.5} {2:<7.5} {3}'.format(
+ item[0], item[1], item[2], item[3] if item[3] != args.iterate else ""))
run_adb_shell_cmd_as_root('rm /data/bootchart/enabled')
def dump_timings_points_summary(msg_header, timing_points, args):
averaged_timing_points = []
- for item in timing_points.items():
+ for item in list(timing_points.items()):
average = sum(item[1])/len(item[1])
std_dev = stddev(item[1])
averaged_timing_points.append((item[0], average, std_dev, len(item[1])))
- print "-----------------"
- print msg_header + " timing in order, Avg time values after {0} runs".format(args.iterate)
- print '{0:30}: {1:<7} {2:<7} {3}'.format("Event", "Mean", "stddev", "#runs")
+ print("-----------------")
+ print(msg_header + " timing in order, Avg time values after {0} runs".format(args.iterate))
+ print('{0:30}: {1:<7} {2:<7} {3}'.format("Event", "Mean", "stddev", "#runs"))
for item in averaged_timing_points:
- print '{0:30}: {1:<7.5} {2:<7.5} {3}'.format(
- item[0], item[1], item[2], item[3] if item[3] != args.iterate else "")
+ print('{0:30}: {1:<7.5} {2:<7.5} {3}'.format(
+ item[0], item[1], item[2], item[3] if item[3] != args.iterate else ""))
- print "-----------------"
- print msg_header + " timing top items, Avg time values after {0} runs".format(args.iterate)
- print '{0:30}: {1:<7} {2:<7} {3}'.format("Event", "Mean", "stddev", "#runs")
+ print("-----------------")
+ print(msg_header + " timing top items, Avg time values after {0} runs".format(args.iterate))
+ print('{0:30}: {1:<7} {2:<7} {3}'.format("Event", "Mean", "stddev", "#runs"))
for item in sorted(averaged_timing_points, key=lambda entry: entry[1], reverse=True):
if item[1] < TIMING_THRESHOLD:
break
- print '{0:30}: {1:<7.5} {2:<7.5} {3}'.format(
- item[0], item[1], item[2], item[3] if item[3] != args.iterate else "")
+ print('{0:30}: {1:<7.5} {2:<7.5} {3}'.format(
+ item[0], item[1], item[2], item[3] if item[3] != args.iterate else ""))
def capture_bugreport(bugreport_hint, boot_complete_time):
now = datetime.now()
bugreport_file = ("bugreport-%s-" + bugreport_hint + "-%s.zip") \
% (now.strftime("%Y-%m-%d-%H-%M-%S"), str(boot_complete_time))
- print "Boot up time too big, will capture bugreport %s" % (bugreport_file)
+ print("Boot up time too big, will capture bugreport %s" % (bugreport_file))
os.system(ADB_CMD + " bugreport " + bugreport_file)
def generate_timing_points(timing_events, timings):
timing_points = collections.OrderedDict()
monitor_contention_points = collections.OrderedDict()
- for k, l in timing_events.iteritems():
+ for k, l in timing_events.items():
for v in l:
- name, time_v, dict = extract_timing(v, timings)
+ name, time_v = extract_timing(v, timings)
if name and time_v:
if v.find("SystemServerTimingAsync") > 0:
name = "(" + name + ")"
@@ -271,34 +271,34 @@ def generate_timing_points(timing_events, timings):
return timing_points, monitor_contention_points
def dump_timing_points(msg_header, timing_points):
- print msg_header + " event timing in time order, key: time"
- for item in timing_points.items():
- print '{0:30}: {1:<7.5}'.format(item[0], item[1])
- print "-----------------"
- print msg_header + " event timing top items"
- for item in sorted(timing_points.items(), key=operator.itemgetter(1), reverse = True):
+ print(msg_header + " event timing in time order, key: time")
+ for item in list(timing_points.items()):
+ print('{0:30}: {1:<7.5}'.format(item[0], item[1]))
+ print("-----------------")
+ print(msg_header + " event timing top items")
+ for item in sorted(list(timing_points.items()), key=operator.itemgetter(1), reverse=True):
if item[1] < TIMING_THRESHOLD:
break
- print '{0:30}: {1:<7.5}'.format(
- item[0], item[1])
- print "-----------------"
+ print('{0:30}: {1:<7.5}'.format(
+ item[0], item[1]))
+ print("-----------------")
def dump_monitor_contentions(logcat_monitor_contentions):
- print "Monitor contentions over 100ms:"
- for item in logcat_monitor_contentions.items():
+ print("Monitor contentions over 100ms:")
+ for item in list(logcat_monitor_contentions.items()):
if item[1] > 100:
- print '{0:<7.5}ms: {1}'.format(item[1], item[0])
- print "-----------------"
+ print('{0:<7.5}ms: {1}'.format(item[1], item[0]))
+ print("-----------------")
def handle_reboot_log(capture_log_on_error, shutdown_events_pattern, components_to_monitor):
shutdown_events, shutdown_timing_events = collect_logcat_for_shutdown(capture_log_on_error,\
shutdown_events_pattern, components_to_monitor)
- print "\nshutdown events: time"
- for item in shutdown_events.items():
- print '{0:30}: {1:<7.5}'.format(item[0], item[1])
- print "\nshutdown timing events: time"
- for item in shutdown_timing_events.items():
- print '{0:30}: {1:<7.5}'.format(item[0], item[1])
+ print("\nshutdown events: time")
+ for item in list(shutdown_events.items()):
+ print('{0:30}: {1:<7.5}'.format(item[0], item[1]))
+ print("\nshutdown timing events: time")
+ for item in list(shutdown_timing_events.items()):
+ print('{0:30}: {1:<7.5}'.format(item[0], item[1]))
return shutdown_events, shutdown_timing_events
def iterate(args, search_events_pattern, timings_pattern, shutdown_events_pattern, cfg, error_time,\
@@ -308,7 +308,7 @@ def iterate(args, search_events_pattern, timings_pattern, shutdown_events_patter
if args.reboot:
# sleep to make sure that logcat reader is reading before adb is gone by reboot. ugly but make
# impl simple.
- t = threading.Thread(target = lambda : (time.sleep(2), reboot(args.serial, args.stressfs != '',\
+ t = threading.Thread(target=lambda: (time.sleep(2), reboot(args.serial, args.stressfs != '',\
args.permissive, args.adb_reboot, args.buffersize)))
t.start()
shutdown_events, shutdown_timing_events = handle_reboot_log(True, shutdown_events_pattern,\
@@ -317,24 +317,19 @@ def iterate(args, search_events_pattern, timings_pattern, shutdown_events_patter
dmesg_events, kernel_timing_events = collect_events(search_events_pattern, ADB_CMD +\
' shell su root dmesg -w', timings_pattern,\
- [ KERNEL_BOOT_COMPLETE ], True)
+ [KERNEL_BOOT_COMPLETE], True)
- logcat_stop_events = [ LOGCAT_BOOT_COMPLETE, KERNEL_BOOT_COMPLETE, LAUNCHER_START]
+ logcat_stop_events = [LOGCAT_BOOT_COMPLETE, KERNEL_BOOT_COMPLETE, LAUNCHER_START]
if args.fs_check:
logcat_stop_events.append("FsStat")
logcat_events, logcat_timing_events = collect_events(
search_events_pattern, ADB_CMD + ' logcat -b all -v epoch', timings_pattern,\
logcat_stop_events, False)
- logcat_event_time = extract_time(
- logcat_events, TIME_LOGCAT, float);
- logcat_original_time = extract_time(
- logcat_events, TIME_LOGCAT, str);
- dmesg_event_time = extract_time(
- dmesg_events, TIME_DMESG, float);
+ logcat_event_time = extract_time(logcat_events, TIME_LOGCAT, float)
+ logcat_original_time = extract_time(logcat_events, TIME_LOGCAT, str);
+ dmesg_event_time = extract_time(dmesg_events, TIME_DMESG, float);
boottime_events = fetch_boottime_property()
events = {}
- diff_time = 0
- max_time = 0
events_to_correct = []
replaced_from_dmesg = set()
@@ -342,7 +337,7 @@ def iterate(args, search_events_pattern, timings_pattern, shutdown_events_patter
time_correction_time = 0
if ('time_correction_key' in cfg
and cfg['time_correction_key'] in logcat_events):
- match = search_events[cfg['time_correction_key']].search(
+ match = search_events_pattern[cfg['time_correction_key']].search(
logcat_events[cfg['time_correction_key']])
if match and logcat_event_time[cfg['time_correction_key']]:
time_correction_delta = float(match.group(1))
@@ -351,14 +346,14 @@ def iterate(args, search_events_pattern, timings_pattern, shutdown_events_patter
debug("time_correction_delta = {0}, time_correction_time = {1}".format(
time_correction_delta, time_correction_time))
- for k, v in logcat_event_time.iteritems():
+ for k, v in logcat_event_time.items():
if v <= time_correction_time:
logcat_event_time[k] += time_correction_delta
v = v + time_correction_delta
debug("correcting event to event[{0}, {1}]".format(k, v))
if logcat_event_time.get(KERNEL_TIME_KEY) is None:
- print "kernel time not captured in logcat, cannot get time diff"
+ print("kernel time not captured in logcat, cannot get time diff")
return None, None, None, None, None, None
diffs = []
diffs.append((logcat_event_time[KERNEL_TIME_KEY], logcat_event_time[KERNEL_TIME_KEY]))
@@ -367,13 +362,13 @@ def iterate(args, search_events_pattern, timings_pattern, shutdown_events_patter
logcat_event_time[BOOT_ANIM_END_TIME_KEY] -\
dmesg_event_time[BOOT_ANIM_END_TIME_KEY]))
if not dmesg_event_time.get(KERNEL_BOOT_COMPLETE):
- print "BootAnimEnd time or BootComplete-kernel not captured in both log" +\
- ", cannot get time diff"
+ print("BootAnimEnd time or BootComplete-kernel not captured in both log" +\
+ ", cannot get time diff")
return None, None, None, None, None, None
diffs.append((logcat_event_time[LOGCAT_BOOT_COMPLETE],\
logcat_event_time[LOGCAT_BOOT_COMPLETE] - dmesg_event_time[KERNEL_BOOT_COMPLETE]))
- for k, v in logcat_event_time.iteritems():
+ for k, v in logcat_event_time.items():
debug("event[{0}, {1}]".format(k, v))
events[k] = v
if k in dmesg_event_time:
@@ -399,12 +394,12 @@ def iterate(args, search_events_pattern, timings_pattern, shutdown_events_patter
data_points = collections.OrderedDict()
- print "-----------------"
- print "ro.boottime.*: time"
- for item in boottime_events.items():
- print '{0:30}: {1:<7.5} {2}'.format(item[0], item[1],\
- "*time taken" if item[0].startswith("init.") else "")
- print "-----------------"
+ print("-----------------")
+ print("ro.boottime.*: time")
+ for item in list(boottime_events.items()):
+ print('{0:30}: {1:<7.5} {2}'.format(item[0], item[1],\
+ "*time taken" if item[0].startswith("init.") else ""))
+ print("-----------------")
if args.timings:
kernel_timing_points, _ = generate_timing_points(kernel_timing_events, timings_pattern)
@@ -414,7 +409,7 @@ def iterate(args, search_events_pattern, timings_pattern, shutdown_events_patter
dump_timing_points("Logcat", logcat_timing_points)
dump_monitor_contentions(logcat_monitor_contentions)
- for item in sorted(events.items(), key=operator.itemgetter(1)):
+ for item in sorted(list(events.items()), key=operator.itemgetter(1)):
data_points[item[0]] = {
'value': item[1],
'from_dmesg': item[0] in replaced_from_dmesg,
@@ -435,16 +430,16 @@ def iterate(args, search_events_pattern, timings_pattern, shutdown_events_patter
'from_dmesg': False,
'logcat_value': 0.0
}
- for k, v in data_points.iteritems():
- print '{0:30}: {1:<7.5} {2:1} ({3})'.format(
- k, v['value'], '*' if v['from_dmesg'] else '', v['logcat_value'])
+ for k, v in data_points.items():
+ print('{0:30}: {1:<7.5} {2:1} ({3})'.format(
+ k, v['value'], '*' if v['from_dmesg'] else '', v['logcat_value']))
- print '\n* - event time was obtained from dmesg log\n'
+ print('\n* - event time was obtained from dmesg log\n')
if events[LOGCAT_BOOT_COMPLETE] > error_time and not args.ignore:
capture_bugreport("bootuptoolong", events[LOGCAT_BOOT_COMPLETE])
- for k, v in components_to_monitor.iteritems():
+ for k, v in components_to_monitor.items():
logcat_value_measured = logcat_timing_points.get(k)
kernel_value_measured = kernel_timing_points.get(k)
data_from_data_points = data_points.get(k)
@@ -465,7 +460,7 @@ def iterate(args, search_events_pattern, timings_pattern, shutdown_events_patter
m = re.search(fs_stat_pattern, logcat_events.get("FsStat"))
if m:
fs_stat = m.group(1)
- print 'fs_stat:', fs_stat
+ print('fs_stat:', fs_stat)
if fs_stat:
fs_stat_val = int(fs_stat, 0)
@@ -477,14 +472,14 @@ def iterate(args, search_events_pattern, timings_pattern, shutdown_events_patter
def debug(string):
if DEBUG:
- print string
+ print(string)
def extract_timing(s, patterns):
- for k, p in patterns.iteritems():
+ for _, p in patterns.items():
m = p.search(s)
if m:
- dict = m.groupdict()
- return dict['name'], float(dict['time']), dict
+ timing_dict = m.groupdict()
+ return timing_dict['name'], float(timing_dict['time'])
return None, None
def init_arguments():
@@ -552,14 +547,14 @@ def handle_zygote_event(zygote_pids, events, event, line):
zygote_pids.append(secondary_pid)
if pid == primary_pid: # old one was secondary:
move_to_secondary = []
- for k, l in events.iteritems():
+ for k, l in events.items():
if k.startswith("zygote"):
move_to_secondary.append((k, l))
for item in move_to_secondary:
del events[item[0]]
if item[0].endswith("-secondary"):
- print "Secondary already exists for event %s while found new pid %d, primary %d "\
- % (item[0], secondary_pid, primary_pid)
+ print("Secondary already exists for event %s while found new pid %d, primary %d "\
+ % (item[0], secondary_pid, primary_pid))
else:
events[item[0] + "-secondary"] = item[1]
else:
@@ -584,21 +579,22 @@ def collect_logcat_for_shutdown(capture_log_on_error, shutdown_events_pattern,\
# shutdown does not have timing_events but calculated from checking Xyz - XyzDone / XyzTimeout
timing_events = collections.OrderedDict()
process = subprocess.Popen(ADB_CMD + ' logcat -b all -v epoch', shell=True,
- stdout=subprocess.PIPE);
+ stdout=subprocess.PIPE)
lines = []
capture_log = False
shutdown_start_time = 0
- while (True):
- line = process.stdout.readline().lstrip().rstrip()
+ while True:
+ line = process.stdout.readline()
if not line:
break
+ line = line.decode('utf-8', 'ignore').lstrip().rstrip()
lines.append(line)
- event = get_boot_event(line, shutdown_events_pattern);
+ event = get_boot_event(line, shutdown_events_pattern)
if not event:
continue
time = extract_a_time(line, TIME_LOGCAT, float)
if time is None:
- print "cannot get time from: " + line
+ print("cannot get time from: " + line)
continue
if shutdown_start_time == 0:
shutdown_start_time = time
@@ -609,7 +605,7 @@ def collect_logcat_for_shutdown(capture_log_on_error, shutdown_events_pattern,\
capture_log = True
pair_event = None
if event.endswith('Done'):
- pair_event = event[:-4]
+ pair_event = event[:-4]
elif event.endswith('Timeout'):
pair_event = event[:-7]
if capture_log_on_error:
@@ -618,7 +614,7 @@ def collect_logcat_for_shutdown(capture_log_on_error, shutdown_events_pattern,\
continue
start_time = events.get(pair_event)
if not start_time:
- print "No start event for " + event
+ print("No start event for " + event)
continue
time_spent = time - start_time
timing_event_name = pair_event + "Duration"
@@ -630,7 +626,7 @@ def collect_logcat_for_shutdown(capture_log_on_error, shutdown_events_pattern,\
if capture_log:
now = datetime.now()
log_file = ("shutdownlog-error-%s.txt") % (now.strftime("%Y-%m-%d-%H-%M-%S"))
- print "Shutdown error, capture log to %s" % (log_file)
+ print("Shutdown error, capture log to %s" % (log_file))
with open(log_file, 'w') as f:
f.write('\n'.join(lines))
return events, timing_events
@@ -639,8 +635,7 @@ def collect_logcat_for_shutdown(capture_log_on_error, shutdown_events_pattern,\
def collect_events(search_events, command, timings, stop_events, disable_timing_after_zygote):
events = collections.OrderedDict()
timing_events = {}
- process = subprocess.Popen(command, shell=True,
- stdout=subprocess.PIPE);
+ process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
data_available = stop_events is None
zygote_pids = []
start_time = time.time()
@@ -652,18 +647,18 @@ def collect_events(search_events, command, timings, stop_events, disable_timing_
while True:
time_left = start_time + max_wait_time - time.time()
if time_left <= 0:
- print "timeout waiting for event, continue", time_left
+ print("timeout waiting for event, continue", time_left)
break
read_r = read_poll.poll(time_left * 1000.0)
if len(read_r) > 0 and read_r[0][1] == select.POLLIN:
- line = process.stdout.readline()
+ line = process.stdout.readline().decode('utf-8', 'ignore')
else:
- print "poll timeout waiting for event, continue", time_left
+ print("poll timeout waiting for event, continue", time_left)
break
if not data_available:
- print "Collecting data samples from '%s'. Please wait...\n" % command
+ print("Collecting data samples from '%s'. Please wait...\n" % command)
data_available = True
- event = get_boot_event(line, search_events);
+ event = get_boot_event(line, search_events)
if event:
debug("event[{0}] captured: {1}".format(event, line))
if event == "starting_zygote":
@@ -676,11 +671,11 @@ def collect_events(search_events, command, timings, stop_events, disable_timing_
events[new_event] = line
if event in stop_events:
stop_events.remove(event)
- print "remaining stop_events:", stop_events
+ print("remaining stop_events:", stop_events)
if len(stop_events) == 0:
- break;
+ break
- timing_event = get_boot_event(line, timings);
+ timing_event = get_boot_event(line, timings)
if timing_event and (not disable_timing_after_zygote or not zygote_found):
if timing_event not in timing_events:
timing_events[timing_event] = []
@@ -693,13 +688,13 @@ def collect_events(search_events, command, timings, stop_events, disable_timing_
def fetch_boottime_property():
cmd = ADB_CMD + ' shell su root getprop'
events = {}
- process = subprocess.Popen(cmd, shell=True,
- stdout=subprocess.PIPE);
+ process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
out = process.stdout
pattern = re.compile(BOOT_PROP)
pattern_bootloader = re.compile(BOOTLOADER_TIME_PROP)
bootloader_time = 0.0
for line in out:
+ line = line.decode('utf-8', 'ignore')
match = pattern.match(line)
if match:
if match.group(1).startswith("init."):
@@ -717,14 +712,14 @@ def fetch_boottime_property():
bootloader_time = bootloader_time + time_spent
ordered_event = collections.OrderedDict()
if bootloader_time != 0.0:
- ordered_event["bootloader"] = bootloader_time;
- for item in sorted(events.items(), key=operator.itemgetter(1)):
+ ordered_event["bootloader"] = bootloader_time
+ for item in sorted(list(events.items()), key=operator.itemgetter(1)):
ordered_event[item[0]] = item[1]
return ordered_event
def get_boot_event(line, events):
- for event_key, event_pattern in events.iteritems():
+ for event_key, event_pattern in events.items():
if event_pattern.search(line):
return event_key
return None
@@ -738,27 +733,27 @@ def extract_a_time(line, pattern, date_transform_function):
def extract_time(events, pattern, date_transform_function):
result = collections.OrderedDict()
- for event, data in events.iteritems():
+ for event, data in events.items():
time = extract_a_time(data, pattern, date_transform_function)
if time is not None:
result[event] = time
else:
- print "Failed to find time for event: ", event, data
+ print("Failed to find time for event: ", event, data)
return result
def do_reboot(serial, use_adb_reboot):
original_devices = subprocess.check_output("adb devices", shell=True)
if use_adb_reboot:
- print 'Rebooting the device using adb reboot'
+ print('Rebooting the device using adb reboot')
run_adb_cmd('reboot')
else:
- print 'Rebooting the device using svc power reboot'
+ print('Rebooting the device using svc power reboot')
run_adb_shell_cmd_as_root('svc power reboot')
# Wait for the device to go away
retry = 0
while retry < 20:
- current_devices = subprocess.check_output("adb devices", shell=True)
+ current_devices = subprocess.check_output("adb devices", shell=True).decode('utf-8', 'ignore')
if original_devices != current_devices:
if not serial or (serial and current_devices.find(serial) < 0):
return True
@@ -768,7 +763,7 @@ def do_reboot(serial, use_adb_reboot):
def reboot(serial, use_stressfs, permissive, use_adb_reboot, adb_buffersize=None):
if use_stressfs:
- print 'Starting write to data partition'
+ print('Starting write to data partition')
run_adb_shell_cmd('am start' +\
' -n com.android.car.test.stressfs/.WritingActivity' +\
' -a com.android.car.test.stressfs.START')
@@ -783,7 +778,7 @@ def reboot(serial, use_stressfs, permissive, use_adb_reboot, adb_buffersize=None
break
retry += 1
- print 'Waiting the device'
+ print('Waiting the device')
run_adb_cmd('wait-for-device')
if adb_buffersize is not None:
@@ -818,8 +813,8 @@ def stddev(data):
return math.sqrt(variance)
def grab_bootchart(boot_chart_file_name):
- subprocess.call("./system/core/init/grab-bootchart.sh", shell=True)
- print "Saving boot chart as " + boot_chart_file_name + ".tgz"
+ 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',\
shell=True)
subprocess.call('cp ./bootchart.png ./' + boot_chart_file_name + '.png', shell=True)
@@ -830,7 +825,7 @@ def grab_systrace(systrace_file_name):
f.write("TRACE:\n")
run_adb_shell_cmd_as_root("cat /d/tracing/trace >> " + trace_file)
html_file = systrace_file_name + ".html"
- subprocess.call("./external/chromium-trace/systrace.py --from-file=" + trace_file + " -o " +\
+ subprocess.call("$ANDROID_BUILD_TOP/external/chromium-trace/systrace.py --from-file=" + trace_file + " -o " +\
html_file, shell=True)
if __name__ == '__main__':
diff --git a/boottime_tools/bootanalyze/bootanalyze.sh b/boottime_tools/bootanalyze/bootanalyze.sh
new file mode 100755
index 00000000..7fce2e1c
--- /dev/null
+++ b/boottime_tools/bootanalyze/bootanalyze.sh
@@ -0,0 +1,72 @@
+#!/bin/bash
+
+# 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.
+
+readme() {
+ echo '
+Analyze boot-time & bootchart
+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
+'
+ exit
+}
+
+
+if [[ -z $ANDROID_BUILD_TOP ]]; then
+ echo 'Error: you need to specify ANDROID_BUILD_TOP'
+ readme
+fi
+echo "ANDROID_BUILD_TOP=$ANDROID_BUILD_TOP"
+SCRIPT_DIR="$ANDROID_BUILD_TOP/system/extras/boottime_tools/bootanalyze"
+
+
+if [[ -z $CONFIG_YMAL ]]; then
+ CONFIG_YMAL="$SCRIPT_DIR/config.yaml"
+fi
+echo "CONFIG_YMAL=$CONFIG_YMAL"
+
+
+if [[ -z $RESULTS_DIR ]]; then
+ RESULTS_DIR="$PWD/bootAnalyzeResults"
+fi
+echo "RESULTS_DIR=$RESULTS_DIR"
+mkdir -p $RESULTS_DIR
+
+
+adb shell 'touch /data/bootchart/enabled'
+
+if [[ -z $LOOPS ]]; then
+ LOOPS=1
+fi
+echo "Analyzing boot-time for LOOPS=$LOOPS"
+START=1
+
+SLEEP_SEC=30
+for (( l=$START; l<=$LOOPS; l++ )); do
+ echo -n "Loop: $l"
+ SECONDS=0
+ $SCRIPT_DIR/bootanalyze.py -c $CONFIG_YMAL -G 4M -r -b > "$RESULTS_DIR/boot$l.txt"
+ echo "$SECONDS sec."
+ cp /tmp/android-bootchart/bootchart.tgz "$RESULTS_DIR/bootchart$l.tgz"
+ echo "Sleep for $SLEEP_SEC sec."
+ sleep $SLEEP_SEC
+done
+
+echo
+echo "Complete $LOOPS" \ No newline at end of file
diff --git a/boottime_tools/bootanalyze/bugreport_anayze.py b/boottime_tools/bootanalyze/bugreport_anayze.py
index 2575ebf6..3ea7552e 100644
--- a/boottime_tools/bootanalyze/bugreport_anayze.py
+++ b/boottime_tools/bootanalyze/bugreport_anayze.py
@@ -138,7 +138,7 @@ class Parser:
self.re_log_start = re.compile(LOG_START_PATTERN)
self.re_log_end = re.compile(LOG_END_PATTERN)
self.f = bugreport_file
- cfg = yaml.load(config_file)
+ cfg = yaml.load(config_file, Loader=yaml.FullLoader)
self.event_patterns = {key: re.compile(pattern)
for key, pattern in cfg['events'].iteritems()}
self.timing_patterns = {key: re.compile(pattern)
diff --git a/boottime_tools/bootanalyze/config.yaml b/boottime_tools/bootanalyze/config.yaml
index e4f0ef42..d6590fdb 100644
--- a/boottime_tools/bootanalyze/config.yaml
+++ b/boottime_tools/bootanalyze/config.yaml
@@ -2,6 +2,7 @@
time_correction_key: correction
timings:
system_server: SystemServerTiming(Async)?\s*:\s*(?P<name>\S+) took to complete:\s(?P<time>[0-9]+)ms
+ package_manager: PackageSettingsTiming\s*:\s*(?P<name>\S+) took to complete:\s(?P<time>[0-9]+)ms
system_ui: SystemUIBootTiming?\s*:\s*(?P<name>\S+) took to complete:\s(?P<time>[0-9]+)ms
fs_shutdown: (?P<name>boot_fs_shutdown),(?P<time>[0-9]+),([0-9]+)
ueventd_secs: ueventd:\s(?P<name>\S.+)\stook\s(?P<time>[.0-9]+)\sseconds
@@ -16,6 +17,9 @@ events:
android_init_1st_stage: init first stage started
android_init_2st_stage: init second stage started
late_init: processing action \(late-init\)
+ apexd_activated: apexd.*Marking APEXd as activated
+ apexd_bootstrapping_done: apexd.*Bootstrapping done
+ apexd_ready: apexd.*Marking APEXd as ready
fs: processing action \(fs\)
post-fs: processing action \(post-fs\)
late-fs: processing action \(late-fs\)
@@ -57,7 +61,7 @@ events:
KeyguardShown: KeyguardServiceDelegate.*\*\*\*\* SHOWN CALLED \*\*\*\*
BootComplete: Starting phase 1000
BootComplete_kernel: processing action \(sys\.boot_completed=1\)
- LauncherStart: START.*HOME.*(NexusLauncherActivity|GEL|LensPickerTrampolineActivity|SetupWizardActivity|CarLauncher)
+ LauncherStart: START.*HOME.*(NexusLauncherActivity|GEL|LensPickerTrampolineActivity|SetupWizard|CarLauncher)
FsStat: fs_stat, partition:userdata stat:(0x\S+)
shutdown_events:
ShutdownStart: ShutdownThread:\sNotifying thread to start shutdown
diff --git a/boottime_tools/bootanalyze/stressfs/Android.bp b/boottime_tools/bootanalyze/stressfs/Android.bp
index f342d1aa..c0dee353 100644
--- a/boottime_tools/bootanalyze/stressfs/Android.bp
+++ b/boottime_tools/bootanalyze/stressfs/Android.bp
@@ -14,6 +14,10 @@
//
//
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
android_test {
name: "StressFS",
srcs: ["src/**/*.java"],
diff --git a/boottime_tools/bootanalyze/stressfs/AndroidManifest.xml b/boottime_tools/bootanalyze/stressfs/AndroidManifest.xml
index 8713c511..1ea76da3 100644
--- a/boottime_tools/bootanalyze/stressfs/AndroidManifest.xml
+++ b/boottime_tools/bootanalyze/stressfs/AndroidManifest.xml
@@ -15,21 +15,22 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.car.test.stressfs">
+ package="com.android.car.test.stressfs">
- <original-package android:name="com.android.car.test.stressfs" />
+ <original-package android:name="com.android.car.test.stressfs"/>
<uses-sdk android:minSdkVersion="25"
- android:targetSdkVersion="25" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ android:targetSdkVersion="25"/>
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application android:label="Stress Filesystem"
- android:directBootAware="true"
- android:allowBackup="false">
+ android:directBootAware="true"
+ android:allowBackup="false">
- <activity android:name=".WritingActivity">
+ <activity android:name=".WritingActivity"
+ android:exported="true">
<intent-filter>
- <action android:name="com.android.car.test.stressfs.START" />
+ <action android:name="com.android.car.test.stressfs.START"/>
</intent-filter>
</activity>
<service android:name=".WritingService">
diff --git a/boottime_tools/bootio/Android.bp b/boottime_tools/bootio/Android.bp
index 7c8ef269..bf071163 100644
--- a/boottime_tools/bootio/Android.bp
+++ b/boottime_tools/bootio/Android.bp
@@ -14,6 +14,10 @@
// limitations under the License.
//
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_defaults {
name: "bootio_defaults",
diff --git a/brillo_config/Android.mk b/brillo_config/Android.mk
index cefc0826..0f933ad4 100644
--- a/brillo_config/Android.mk
+++ b/brillo_config/Android.mk
@@ -20,6 +20,8 @@ ifdef OSRELEASED_DIRECTORY
include $(CLEAR_VARS)
LOCAL_MODULE := product_id
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
LOCAL_MODULE_CLASS := FAKE
LOCAL_MODULE_PATH := $(TARGET_OUT_OEM)/$(OSRELEASED_DIRECTORY)
include $(BUILD_SYSTEM)/base_rules.mk
@@ -32,6 +34,8 @@ $(LOCAL_BUILT_MODULE):
include $(CLEAR_VARS)
LOCAL_MODULE := system_id
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/$(OSRELEASED_DIRECTORY)
include $(BUILD_SYSTEM)/base_rules.mk
@@ -43,6 +47,8 @@ $(LOCAL_BUILT_MODULE):
include $(CLEAR_VARS)
LOCAL_MODULE := product_version
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
LOCAL_MODULE_CLASS := FAKE
LOCAL_MODULE_PATH := $(TARGET_OUT_OEM)/$(OSRELEASED_DIRECTORY)
include $(BUILD_SYSTEM)/base_rules.mk
@@ -64,6 +70,8 @@ $(LOCAL_BUILT_MODULE):
include $(CLEAR_VARS)
LOCAL_MODULE := system_version
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/$(OSRELEASED_DIRECTORY)
include $(BUILD_SYSTEM)/base_rules.mk
diff --git a/checkpoint_gc/Android.bp b/checkpoint_gc/Android.bp
index fac9c6a8..fcdaf5c0 100644
--- a/checkpoint_gc/Android.bp
+++ b/checkpoint_gc/Android.bp
@@ -1,4 +1,8 @@
// Checkpointing GC script
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
sh_binary {
name: "checkpoint_gc",
src: "checkpoint_gc.sh",
diff --git a/checkpoint_gc/checkpoint_gc.sh b/checkpoint_gc/checkpoint_gc.sh
index beaedcbc..b3080463 100644
--- a/checkpoint_gc/checkpoint_gc.sh
+++ b/checkpoint_gc/checkpoint_gc.sh
@@ -23,50 +23,62 @@
# TARGET_SLOT="${1}"
STATUS_FD="${2}"
-DIRTY_SEGMENTS_THRESHOLD=100
SLEEP=5
TIME=0
-MAX_TIME=3600
+MAX_TIME=1200
-NAME=`while read dev dir type opt; do
- if [ /data = ${dir} -a f2fs = ${type} ]; then
- real_dev=$(realpath $dev)
- echo ${real_dev##*/}
- break
- fi
-done < /proc/mounts`
-if [ -z "${NAME}" ]; then
+# We only need to run this if we're using f2fs
+if [ ! -f /dev/sys/fs/by-name/userdata/gc_urgent ]; then
exit 0
fi
-log -pi -t checkpoint_gc Turning on GC for ${NAME}
-read OLD_SLEEP < /sys/fs/f2fs/${NAME}/gc_urgent_sleep_time || exit 1
-echo 50 > /sys/fs/f2fs/${NAME}/gc_urgent_sleep_time || exit 1
-echo 1 > /sys/fs/f2fs/${NAME}/gc_urgent || exit 1
-read DIRTY_SEGMENTS_START < /sys/fs/f2fs/${NAME}/dirty_segments
-DIRTY_SEGMENTS=${DIRTY_SEGMENTS_START}
-TODO_SEGMENTS=$((${DIRTY_SEGMENTS_START}-${DIRTY_SEGMENTS_THRESHOLD}))
-while [ ${DIRTY_SEGMENTS} -gt ${DIRTY_SEGMENTS_THRESHOLD} ]; do
- log -pi -t checkpoint_gc dirty segments:${DIRTY_SEGMENTS} \(threshold:${DIRTY_SEGMENTS_THRESHOLD}\)
- PROGRESS=`echo "(${DIRTY_SEGMENTS_START}-${DIRTY_SEGMENTS})/${TODO_SEGMENTS}"|bc -l`
+# 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
+ read START < /dev/sys/fs/by-name/userdata/unusable
+else
+ METRIC="dirty segments"
+ THRESHOLD=200
+ read START < /dev/sys/fs/by-name/userdata/dirty_segments
+fi
+
+log -pi -t checkpoint_gc Turning on GC for userdata
+echo 2 > /dev/sys/fs/by-name/userdata/gc_urgent || exit 1
+
+
+CURRENT=${START}
+TODO=$((${START}-${THRESHOLD}))
+while [ ${CURRENT} -gt ${THRESHOLD} ]; do
+ log -pi -t checkpoint_gc ${METRIC}:${CURRENT} \(threshold:${THRESHOLD}\)
+ PROGRESS=`echo "(${START}-${CURRENT})/${TODO}"|bc -l`
if [[ $PROGRESS == -* ]]; then
PROGRESS=0
fi
print -u${STATUS_FD} "global_progress ${PROGRESS}"
- read DIRTY_SEGMENTS < /sys/fs/f2fs/${NAME}/dirty_segments
+ if [ ${UNUSABLE} -eq 1 ]; then
+ read CURRENT < /dev/sys/fs/by-name/userdata/unusable
+ else
+ read CURRENT < /dev/sys/fs/by-name/userdata/dirty_segments
+ fi
sleep ${SLEEP}
TIME=$((${TIME}+${SLEEP}))
if [ ${TIME} -gt ${MAX_TIME} ]; then
+ log -pi -t checkpoint_gc Timed out with gc threshold not met.
break
fi
# In case someone turns it off behind our back
- echo 1 > /sys/fs/f2fs/${NAME}/gc_urgent
+ echo 2 > /dev/sys/fs/by-name/userdata/gc_urgent
done
-log -pi -t checkpoint_gc Turning off GC for ${NAME}
-echo 0 > /sys/fs/f2fs/${NAME}/gc_urgent
-echo ${OLD_SLEEP} > /sys/fs/f2fs/${NAME}/gc_urgent_sleep_time
+# It could be a while before the system reboots for the update...
+# Leaving on low level GC can help ensure the boot for ota is faster
+# If powerhints decides to turn it off, we'll just rely on normal GC
+log -pi -t checkpoint_gc Leaving on low GC for userdata
+echo 2 > /dev/sys/fs/by-name/userdata/gc_urgent
sync
print -u${STATUS_FD} "global_progress 1.0"
diff --git a/cppreopts/Android.bp b/cppreopts/Android.bp
index 9e9c4a11..70d349d5 100644
--- a/cppreopts/Android.bp
+++ b/cppreopts/Android.bp
@@ -14,6 +14,10 @@
// limitations under the License.
//
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
sh_binary {
name: "preloads_copy.sh",
diff --git a/cpustats/Android.bp b/cpustats/Android.bp
index 6b0229be..db3aae51 100644
--- a/cpustats/Android.bp
+++ b/cpustats/Android.bp
@@ -1,3 +1,33 @@
+package {
+ default_applicable_licenses: ["system_extras_cpustats_license"],
+}
+
+// Added automatically by a large-scale-change that took the approach of
+// 'apply every license found to every target'. While this makes sure we respect
+// every license restriction, it may not be entirely correct.
+//
+// e.g. GPL in an MIT project might only apply to the contrib/ directory.
+//
+// Please consider splitting the single license below into multiple licenses,
+// taking care not to lose any license_kind information, and overriding the
+// default license using the 'licenses: [...]' property on targets as needed.
+//
+// For unused files, consider creating a 'fileGroup' with "//visibility:private"
+// to attach the license to, and including a comment whether the files may be
+// used in the current project.
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_cpustats_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ "SPDX-license-identifier-BSD",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_binary {
name: "cpustats",
srcs: ["cpustats.c"],
diff --git a/cpustats/cpustats.c b/cpustats/cpustats.c
index b2b7c07c..998a97c6 100644
--- a/cpustats/cpustats.c
+++ b/cpustats/cpustats.c
@@ -43,31 +43,35 @@ struct freq_info {
struct cpu_info {
long unsigned utime, ntime, stime, itime, iowtime, irqtime, sirqtime;
- struct freq_info *freqs;
+ struct freq_info* freqs;
int freq_count;
};
-#define die(...) { fprintf(stderr, __VA_ARGS__); exit(EXIT_FAILURE); }
+#define die(...) \
+ { \
+ fprintf(stderr, __VA_ARGS__); \
+ exit(EXIT_FAILURE); \
+ }
static struct cpu_info old_total_cpu, new_total_cpu, *old_cpus, *new_cpus;
static int cpu_count, delay, iterations;
static char minimal, aggregate_freq_stats;
static int get_cpu_count();
-static int get_cpu_count_from_file(char *filename);
-static long unsigned get_cpu_total_time(struct cpu_info *cpu);
+static int get_cpu_count_from_file(char* filename);
+static long unsigned get_cpu_total_time(struct cpu_info* cpu);
static int get_freq_scales_count(int cpu);
static void print_stats();
-static void print_cpu_stats(char *label, struct cpu_info *new_cpu, struct cpu_info *old_cpu,
- char print_freq);
-static void print_freq_stats(struct cpu_info *new_cpu, struct cpu_info *old_cpu);
+static void print_cpu_stats(char* label, struct cpu_info* new_cpu, struct cpu_info* old_cpu,
+ char print_freq);
+static void print_freq_stats(struct cpu_info* new_cpu, struct cpu_info* old_cpu);
static void read_stats();
static void read_freq_stats(int cpu);
static char should_aggregate_freq_stats();
static char should_print_freq_stats();
-static void usage(char *cmd);
+static void usage(char* cmd);
-int main(int argc, char *argv[]) {
+int main(int argc, char* argv[]) {
struct cpu_info *tmp_cpus, tmp_total_cpu;
int i, freq_count;
@@ -187,8 +191,8 @@ static int get_cpu_count() {
/*
* Get the number of CPUs from a given filename.
*/
-static int get_cpu_count_from_file(char *filename) {
- FILE *file;
+static int get_cpu_count_from_file(char* filename) {
+ FILE* file;
char line[MAX_BUF_SIZE];
int cpu_count;
@@ -213,7 +217,7 @@ static int get_cpu_count_from_file(char *filename) {
* Get the number of frequency states a given CPU can be scaled to.
*/
static int get_freq_scales_count(int cpu) {
- FILE *file;
+ FILE* file;
char filename[MAX_BUF_SIZE];
long unsigned freq;
int count = 0;
@@ -225,7 +229,7 @@ static int get_freq_scales_count(int cpu) {
freq = 0;
fscanf(file, "%lu %*d\n", &freq);
if (freq) count++;
- } while(freq);
+ } while (freq);
fclose(file);
return count;
@@ -235,15 +239,15 @@ static int get_freq_scales_count(int cpu) {
* Read the CPU and frequency stats for all cpus.
*/
static void read_stats() {
- FILE *file;
+ FILE* file;
char scanline[MAX_BUF_SIZE];
int i;
file = fopen("/proc/stat", "r");
if (!file) die("Could not open /proc/stat.\n");
- fscanf(file, "cpu %lu %lu %lu %lu %lu %lu %lu %*d %*d %*d\n",
- &new_total_cpu.utime, &new_total_cpu.ntime, &new_total_cpu.stime, &new_total_cpu.itime,
- &new_total_cpu.iowtime, &new_total_cpu.irqtime, &new_total_cpu.sirqtime);
+ fscanf(file, "cpu %lu %lu %lu %lu %lu %lu %lu %*d %*d %*d\n", &new_total_cpu.utime,
+ &new_total_cpu.ntime, &new_total_cpu.stime, &new_total_cpu.itime, &new_total_cpu.iowtime,
+ &new_total_cpu.irqtime, &new_total_cpu.sirqtime);
if (aggregate_freq_stats) {
for (i = 0; i < new_total_cpu.freq_count; i++) {
new_total_cpu.freqs[i].time = 0;
@@ -264,7 +268,7 @@ static void read_stats() {
* Read the frequency stats for a given cpu.
*/
static void read_freq_stats(int cpu) {
- FILE *file;
+ FILE* file;
char filename[MAX_BUF_SIZE];
int i;
@@ -272,8 +276,7 @@ static void read_freq_stats(int cpu) {
file = fopen(filename, "r");
for (i = 0; i < new_cpus[cpu].freq_count; i++) {
if (file) {
- fscanf(file, "%u %lu\n", &new_cpus[cpu].freqs[i].freq,
- &new_cpus[cpu].freqs[i].time);
+ fscanf(file, "%u %lu\n", &new_cpus[cpu].freqs[i].freq, &new_cpus[cpu].freqs[i].time);
} else {
/* The CPU has been off lined for some reason */
new_cpus[cpu].freqs[i].freq = old_cpus[cpu].freqs[i].freq;
@@ -284,14 +287,13 @@ static void read_freq_stats(int cpu) {
new_total_cpu.freqs[i].time += new_cpus[cpu].freqs[i].time;
}
}
- if (file)
- fclose(file);
+ if (file) fclose(file);
}
/*
* Get the sum of the cpu time from all categories.
*/
-static long unsigned get_cpu_total_time(struct cpu_info *cpu) {
+static long unsigned get_cpu_total_time(struct cpu_info* cpu) {
return (cpu->utime + cpu->ntime + cpu->stime + cpu->itime + cpu->iowtime + cpu->irqtime +
cpu->sirqtime);
}
@@ -317,34 +319,26 @@ static void print_stats() {
/*
* Print the stats for a single CPU.
*/
-static void print_cpu_stats(char *label, struct cpu_info *new_cpu, struct cpu_info *old_cpu,
- char print_freq) {
+static void print_cpu_stats(char* label, struct cpu_info* new_cpu, struct cpu_info* old_cpu,
+ char print_freq) {
long int total_delta_time;
if (!minimal) {
total_delta_time = get_cpu_total_time(new_cpu) - get_cpu_total_time(old_cpu);
printf("%s: User %ld + Nice %ld + Sys %ld + Idle %ld + IOW %ld + IRQ %ld + SIRQ %ld = "
- "%ld\n", label,
- new_cpu->utime - old_cpu->utime,
- new_cpu->ntime - old_cpu->ntime,
- new_cpu->stime - old_cpu->stime,
- new_cpu->itime - old_cpu->itime,
- new_cpu->iowtime - old_cpu->iowtime,
- new_cpu->irqtime - old_cpu->irqtime,
- new_cpu->sirqtime - old_cpu->sirqtime,
- total_delta_time);
+ "%ld\n",
+ label, new_cpu->utime - old_cpu->utime, new_cpu->ntime - old_cpu->ntime,
+ new_cpu->stime - old_cpu->stime, new_cpu->itime - old_cpu->itime,
+ new_cpu->iowtime - old_cpu->iowtime, new_cpu->irqtime - old_cpu->irqtime,
+ new_cpu->sirqtime - old_cpu->sirqtime, total_delta_time);
if (print_freq) {
print_freq_stats(new_cpu, old_cpu);
}
} else {
- printf("%s,%ld,%ld,%ld,%ld,%ld,%ld,%ld", label,
- new_cpu->utime - old_cpu->utime,
- new_cpu->ntime - old_cpu->ntime,
- new_cpu->stime - old_cpu->stime,
- new_cpu->itime - old_cpu->itime,
- new_cpu->iowtime - old_cpu->iowtime,
- new_cpu->irqtime - old_cpu->irqtime,
- new_cpu->sirqtime - old_cpu->sirqtime);
+ printf("%s,%ld,%ld,%ld,%ld,%ld,%ld,%ld", label, new_cpu->utime - old_cpu->utime,
+ new_cpu->ntime - old_cpu->ntime, new_cpu->stime - old_cpu->stime,
+ new_cpu->itime - old_cpu->itime, new_cpu->iowtime - old_cpu->iowtime,
+ new_cpu->irqtime - old_cpu->irqtime, new_cpu->sirqtime - old_cpu->sirqtime);
print_freq_stats(new_cpu, old_cpu);
printf("\n");
}
@@ -353,7 +347,7 @@ static void print_cpu_stats(char *label, struct cpu_info *new_cpu, struct cpu_in
/*
* Print the CPU stats for a single CPU.
*/
-static void print_freq_stats(struct cpu_info *new_cpu, struct cpu_info *old_cpu) {
+static void print_freq_stats(struct cpu_info* new_cpu, struct cpu_info* old_cpu) {
long int delta_time, total_delta_time;
int i;
@@ -375,7 +369,7 @@ static void print_freq_stats(struct cpu_info *new_cpu, struct cpu_info *old_cpu)
} else {
for (i = 0; i < new_cpu->freq_count; i++) {
printf(",%u,%ld", new_cpu->freqs[i].freq,
- new_cpu->freqs[i].time - old_cpu->freqs[i].time);
+ new_cpu->freqs[i].time - old_cpu->freqs[i].time);
}
}
}
@@ -394,7 +388,7 @@ static char should_print_freq_stats() {
for (i = 1; i < cpu_count; i++) {
for (j = 0; j < new_cpus[i].freq_count; j++) {
if (new_cpus[i].freqs[j].time - old_cpus[i].freqs[j].time !=
- new_cpus[0].freqs[j].time - old_cpus[0].freqs[j].time) {
+ new_cpus[0].freqs[j].time - old_cpus[0].freqs[j].time) {
return 1;
}
}
@@ -429,8 +423,9 @@ static char should_aggregate_freq_stats() {
/*
* Print the usage message.
*/
-static void usage(char *cmd) {
- fprintf(stderr, "Usage %s [ -n iterations ] [ -d delay ] [ -c cpu ] [ -m ] [ -h ]\n"
+static void usage(char* cmd) {
+ fprintf(stderr,
+ "Usage %s [ -n iterations ] [ -d delay ] [ -c cpu ] [ -m ] [ -h ]\n"
" -n num Updates to show before exiting.\n"
" -d num Seconds to wait between updates.\n"
" -m Display minimal output.\n"
diff --git a/crypto-perf/Android.bp b/crypto-perf/Android.bp
index 60116891..8f575ca9 100644
--- a/crypto-perf/Android.bp
+++ b/crypto-perf/Android.bp
@@ -1,3 +1,20 @@
+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",
diff --git a/crypto-perf/crypto.cpp b/crypto-perf/crypto.cpp
index c1860764..a7228324 100644
--- a/crypto-perf/crypto.cpp
+++ b/crypto-perf/crypto.cpp
@@ -1,11 +1,11 @@
+#include <ctype.h>
+#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <unistd.h>
-#include <sys/time.h>
-#include <sched.h>
#include <sys/resource.h>
-#include <ctype.h>
+#include <sys/time.h>
+#include <unistd.h>
#define USEC_PER_SEC 1000000ULL
#define MAX_COUNT 1000000000ULL
#define NUM_INSTS_GARBAGE 18
@@ -21,22 +21,21 @@ void usage() {
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("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) {
+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;
+ 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) {
+ } else if (strcmp(argv[i], "--locked_freq") == 0) {
save_value = &cmd_data->locked_freq;
} else {
printf("Unknown option %s\n", argv[i]);
@@ -47,14 +46,13 @@ int processOptions(int argc, char **argv, command_data_t *cmd_data) {
// 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]))) {
+ 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;
}
@@ -63,54 +61,53 @@ int processOptions(int argc, char **argv, command_data_t *cmd_data) {
* 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 ;");
+ "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 ;");
+ "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) {
+int main(int argc, char** argv) {
usage();
command_data_t cmd_data;
- if(processOptions(argc, argv, &cmd_data) == -1) {
+ if (processOptions(argc, argv, &cmd_data) == -1) {
usage();
return -1;
}
@@ -120,51 +117,49 @@ int main(int argc, char **argv) {
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;
+ perror("sched_setaffinity failed");
+ return 1;
}
gettimeofday(&begin_time, NULL);
while (count < MAX_COUNT) {
- garbage_encrypt();
- 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: %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));
+ (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));
+ 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++;
+ 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: %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));
+ (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));
+ 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 4efecf7d..10aa765f 100644
--- a/ext4_utils/Android.bp
+++ b/ext4_utils/Android.bp
@@ -1,5 +1,22 @@
// Copyright 2010 The Android Open Source Project
+package {
+ default_applicable_licenses: ["system_extras_ext4_utils_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_ext4_utils_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_library {
name: "libext4_utils",
host_supported: true,
@@ -75,6 +92,17 @@ prebuilt_etc {
src: "mke2fs.conf",
}
+// TODO(b/157625953): Can't embedded into the other package because of missing variant
+filegroup {
+ name: "mke2fs_conf",
+ srcs: [
+ "mke2fs.conf",
+ ],
+ visibility: [
+ "//system/apex/apexer",
+ ],
+}
+
cc_binary_host {
name: "blk_alloc_to_base_fs",
srcs: ["blk_alloc_to_base_fs.cpp"],
diff --git a/ext4_utils/Android.mk b/ext4_utils/Android.mk
index 259fbd89..b55613fb 100644
--- a/ext4_utils/Android.mk
+++ b/ext4_utils/Android.mk
@@ -8,6 +8,9 @@ LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := mke2fs.conf
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := $(LOCAL_PATH)/NOTICE
LOCAL_SRC_FILES := $(LOCAL_MODULE)
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_IS_HOST_MODULE := true
diff --git a/ext4_utils/blk_alloc_to_base_fs.cpp b/ext4_utils/blk_alloc_to_base_fs.cpp
index 664648d1..65f60247 100644
--- a/ext4_utils/blk_alloc_to_base_fs.cpp
+++ b/ext4_utils/blk_alloc_to_base_fs.cpp
@@ -29,15 +29,13 @@
#endif
#define ___STRING(x) __STRING(x)
-static void usage(char *filename)
-{
+static void usage(char* filename) {
fprintf(stderr, "Usage: %s input_blk_alloc_file output_base_fs_file \n", filename);
}
-int main(int argc, char **argv)
-{
+int main(int argc, char** argv) {
FILE *blk_alloc_file = NULL, *base_fs_file = NULL;
- char filename[MAX_PATH+1], file_version[MAX_FILE_VERSION+1], *spaced_allocs = NULL;
+ char filename[MAX_PATH + 1], file_version[MAX_FILE_VERSION + 1], *spaced_allocs = NULL;
size_t spaced_allocs_len = 0;
if (argc != 3) {
@@ -54,7 +52,8 @@ int main(int argc, char **argv)
fprintf(stderr, "failed to open %s: %s\n", argv[2], strerror(errno));
exit(EXIT_FAILURE);
}
- if (fscanf(blk_alloc_file, "Base EXT4 version %" ___STRING(MAX_FILE_VERSION) "s", file_version) > 0) {
+ if (fscanf(blk_alloc_file, "Base EXT4 version %" ___STRING(MAX_FILE_VERSION) "s",
+ file_version) > 0) {
int c;
printf("%s is already in *.base_fs format, just copying into %s...\n", argv[1], argv[2]);
rewind(blk_alloc_file);
@@ -67,7 +66,7 @@ int main(int argc, char **argv)
rewind(blk_alloc_file);
}
fprintf(base_fs_file, "Base EXT4 version 1.0\n");
- while(fscanf(blk_alloc_file, "%" ___STRING(MAX_PATH) "s ", filename) != EOF) {
+ while (fscanf(blk_alloc_file, "%" ___STRING(MAX_PATH) "s ", filename) != EOF) {
int i;
fprintf(base_fs_file, "%s ", filename);
if (getline(&spaced_allocs, &spaced_allocs_len, blk_alloc_file) == -1) {
@@ -77,7 +76,8 @@ int main(int argc, char **argv)
for (i = 0; spaced_allocs[i]; i++) {
if (spaced_allocs[i] == ' ') {
if (!isspace(spaced_allocs[i + 1])) fputc(',', base_fs_file);
- } else fputc(spaced_allocs[i], base_fs_file);
+ } else
+ fputc(spaced_allocs[i], base_fs_file);
}
}
free(spaced_allocs);
diff --git a/ext4_utils/ext4_sb.cpp b/ext4_utils/ext4_sb.cpp
index 5c3e4f3f..3d8f4c9e 100644
--- a/ext4_utils/ext4_sb.cpp
+++ b/ext4_utils/ext4_sb.cpp
@@ -18,27 +18,27 @@
#include "ext4_utils/ext4_sb.h"
-int ext4_parse_sb(struct ext4_super_block *sb, struct fs_info *info)
-{
- uint64_t len_blocks;
+int ext4_parse_sb(struct ext4_super_block* sb, struct fs_info* info) {
+ uint64_t len_blocks;
- if (sb->s_magic != EXT4_SUPER_MAGIC)
- return -EINVAL;
+ if (sb->s_magic != EXT4_SUPER_MAGIC) return -EINVAL;
- info->block_size = 1024 << sb->s_log_block_size;
- info->blocks_per_group = sb->s_blocks_per_group;
- info->inodes_per_group = sb->s_inodes_per_group;
- info->inode_size = sb->s_inode_size;
- info->inodes = sb->s_inodes_count;
- info->feat_ro_compat = sb->s_feature_ro_compat;
- info->feat_compat = sb->s_feature_compat;
- info->feat_incompat = sb->s_feature_incompat;
- info->bg_desc_reserve_blocks = sb->s_reserved_gdt_blocks;
- info->label = sb->s_volume_name;
+ info->block_size = 1024 << sb->s_log_block_size;
+ info->blocks_per_group = sb->s_blocks_per_group;
+ info->inodes_per_group = sb->s_inodes_per_group;
+ info->inode_size = sb->s_inode_size;
+ info->inodes = sb->s_inodes_count;
+ info->feat_ro_compat = sb->s_feature_ro_compat;
+ info->feat_compat = sb->s_feature_compat;
+ info->feat_incompat = sb->s_feature_incompat;
+ info->bg_desc_reserve_blocks = sb->s_reserved_gdt_blocks;
+ info->bg_desc_size = (sb->s_feature_incompat & EXT4_FEATURE_INCOMPAT_64BIT)
+ ? sb->s_desc_size
+ : EXT4_MIN_DESC_SIZE;
+ info->label = sb->s_volume_name;
- len_blocks = ((uint64_t)sb->s_blocks_count_hi << 32) +
- sb->s_blocks_count_lo;
- info->len = (uint64_t)info->block_size * len_blocks;
+ len_blocks = ((uint64_t)sb->s_blocks_count_hi << 32) + sb->s_blocks_count_lo;
+ info->len = (uint64_t)info->block_size * len_blocks;
- return 0;
+ return 0;
}
diff --git a/ext4_utils/ext4_utils.cpp b/ext4_utils/ext4_utils.cpp
index 7c411c89..632d8292 100644
--- a/ext4_utils/ext4_utils.cpp
+++ b/ext4_utils/ext4_utils.cpp
@@ -36,6 +36,8 @@
#include <sys/disk.h>
#endif
+#include "helpers.h"
+
int force = 0;
struct fs_info info;
struct fs_aux_info aux_info;
@@ -43,268 +45,270 @@ struct fs_aux_info aux_info;
jmp_buf setjmp_env;
/* returns 1 if a is a power of b */
-static int is_power_of(int a, int b)
-{
- while (a > b) {
- if (a % b)
- return 0;
- a /= b;
- }
-
- return (a == b) ? 1 : 0;
+static int is_power_of(int a, int b) {
+ while (a > b) {
+ if (a % b) return 0;
+ a /= b;
+ }
+
+ return (a == b) ? 1 : 0;
}
-int bitmap_get_bit(u8 *bitmap, u32 bit)
-{
- if (bitmap[bit / 8] & (1 << (bit % 8)))
- return 1;
+int bitmap_get_bit(u8* bitmap, u32 bit) {
+ if (bitmap[bit / 8] & (1 << (bit % 8))) return 1;
- return 0;
+ return 0;
}
-void bitmap_clear_bit(u8 *bitmap, u32 bit)
-{
- bitmap[bit / 8] &= ~(1 << (bit % 8));
+void bitmap_clear_bit(u8* bitmap, u32 bit) {
+ bitmap[bit / 8] &= ~(1 << (bit % 8));
- return;
+ return;
}
/* Returns 1 if the bg contains a backup superblock. On filesystems with
the sparse_super feature, only block groups 0, 1, and powers of 3, 5,
and 7 have backup superblocks. Otherwise, all block groups have backup
superblocks */
-int ext4_bg_has_super_block(int bg)
-{
- /* Without sparse_super, every block group has a superblock */
- if (!(info.feat_ro_compat & EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER))
- return 1;
+int ext4_bg_has_super_block(int bg) {
+ /* Without sparse_super, every block group has a superblock */
+ if (!(info.feat_ro_compat & EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER)) return 1;
- if (bg == 0 || bg == 1)
- return 1;
+ if (bg == 0 || bg == 1) return 1;
- if (is_power_of(bg, 3) || is_power_of(bg, 5) || is_power_of(bg, 7))
- return 1;
+ if (is_power_of(bg, 3) || is_power_of(bg, 5) || is_power_of(bg, 7)) return 1;
- return 0;
+ return 0;
}
/* Function to read the primary superblock */
-void read_sb(int fd, struct ext4_super_block *sb)
-{
- off64_t ret;
-
- ret = lseek64(fd, 1024, SEEK_SET);
- if (ret < 0)
- critical_error_errno("failed to seek to superblock");
-
- 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");
+void read_sb(int fd, struct ext4_super_block* sb) {
+ off64_t ret;
+
+ ret = lseek64(fd, 1024, SEEK_SET);
+ if (ret < 0) critical_error_errno("failed to seek to superblock");
+
+ 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");
}
/* Compute the rest of the parameters of the filesystem from the basic info */
-void ext4_create_fs_aux_info()
-{
- aux_info.first_data_block = (info.block_size > 1024) ? 0 : 1;
- aux_info.len_blocks = info.len / info.block_size;
- aux_info.inode_table_blocks = DIV_ROUND_UP(info.inodes_per_group * info.inode_size,
- info.block_size);
- aux_info.groups = DIV_ROUND_UP(aux_info.len_blocks - aux_info.first_data_block,
- info.blocks_per_group);
- aux_info.blocks_per_ind = info.block_size / sizeof(u32);
- aux_info.blocks_per_dind = aux_info.blocks_per_ind * aux_info.blocks_per_ind;
- aux_info.blocks_per_tind = aux_info.blocks_per_dind * aux_info.blocks_per_dind;
-
- aux_info.bg_desc_blocks =
- DIV_ROUND_UP(aux_info.groups * sizeof(struct ext2_group_desc),
- info.block_size);
-
- aux_info.default_i_flags = EXT4_NOATIME_FL;
-
- u32 last_group_size = aux_info.len_blocks == info.blocks_per_group
- ? aux_info.len_blocks : aux_info.len_blocks % info.blocks_per_group;
- u32 last_header_size = 2 + aux_info.inode_table_blocks;
- if (ext4_bg_has_super_block((int)aux_info.groups - 1))
- last_header_size += 1 + aux_info.bg_desc_blocks +
- info.bg_desc_reserve_blocks;
- if (aux_info.groups <= 1 && last_group_size < last_header_size) {
- critical_error("filesystem size too small");
- }
- if (last_group_size > 0 && last_group_size < last_header_size) {
- aux_info.groups--;
- aux_info.len_blocks -= last_group_size;
- }
-
- /* A zero-filled superblock to be written firstly to the block
- * device to mark the file-system as invalid
- */
- aux_info.sb_zero = (struct ext4_super_block *)calloc(1, info.block_size);
- if (!aux_info.sb_zero)
- critical_error_errno("calloc");
-
- /* The write_data* functions expect only block aligned calls.
- * This is not an issue, except when we write out the super
- * block on a system with a block size > 1K. So, we need to
- * deal with that here.
- */
- aux_info.sb_block = (struct ext4_super_block *)calloc(1, info.block_size);
- if (!aux_info.sb_block)
- critical_error_errno("calloc");
-
- if (info.block_size > 1024)
- aux_info.sb = (struct ext4_super_block *)((char *)aux_info.sb_block + 1024);
- else
- aux_info.sb = aux_info.sb_block;
-
- /* Alloc an array to hold the pointers to the backup superblocks */
- aux_info.backup_sb = (struct ext4_super_block **)calloc(aux_info.groups, sizeof(char *));
-
- if (!aux_info.sb)
- critical_error_errno("calloc");
-
- aux_info.bg_desc = (struct ext2_group_desc *)calloc(info.block_size, aux_info.bg_desc_blocks);
- if (!aux_info.bg_desc)
- critical_error_errno("calloc");
- aux_info.xattrs = NULL;
+void ext4_create_fs_aux_info() {
+ aux_info.first_data_block = (info.block_size > 1024) ? 0 : 1;
+ aux_info.len_blocks = info.len / info.block_size;
+ aux_info.inode_table_blocks =
+ DIV_ROUND_UP(info.inodes_per_group * info.inode_size, info.block_size);
+ aux_info.groups =
+ DIV_ROUND_UP(aux_info.len_blocks - aux_info.first_data_block, info.blocks_per_group);
+ aux_info.blocks_per_ind = info.block_size / sizeof(u32);
+ aux_info.blocks_per_dind = aux_info.blocks_per_ind * aux_info.blocks_per_ind;
+ aux_info.blocks_per_tind = aux_info.blocks_per_dind * aux_info.blocks_per_dind;
+
+ aux_info.bg_desc_blocks =
+ DIV_ROUND_UP(aux_info.groups * (size_t)info.bg_desc_size, info.block_size);
+
+ aux_info.default_i_flags = EXT4_NOATIME_FL;
+
+ u32 last_group_size = aux_info.len_blocks == info.blocks_per_group
+ ? aux_info.len_blocks
+ : aux_info.len_blocks % info.blocks_per_group;
+ u32 last_header_size = 2 + aux_info.inode_table_blocks;
+ if (ext4_bg_has_super_block((int)aux_info.groups - 1))
+ last_header_size += 1 + aux_info.bg_desc_blocks + info.bg_desc_reserve_blocks;
+ if (aux_info.groups <= 1 && last_group_size < last_header_size) {
+ critical_error("filesystem size too small");
+ }
+ if (last_group_size > 0 && last_group_size < last_header_size) {
+ aux_info.groups--;
+ aux_info.len_blocks -= last_group_size;
+ }
+
+ /* A zero-filled superblock to be written firstly to the block
+ * device to mark the file-system as invalid
+ */
+ aux_info.sb_zero = (struct ext4_super_block*)calloc(1, info.block_size);
+ if (!aux_info.sb_zero) critical_error_errno("calloc");
+
+ /* The write_data* functions expect only block aligned calls.
+ * This is not an issue, except when we write out the super
+ * block on a system with a block size > 1K. So, we need to
+ * deal with that here.
+ */
+ aux_info.sb_block = (struct ext4_super_block*)calloc(1, info.block_size);
+ if (!aux_info.sb_block) critical_error_errno("calloc");
+
+ if (info.block_size > 1024)
+ aux_info.sb = (struct ext4_super_block*)((char*)aux_info.sb_block + 1024);
+ else
+ aux_info.sb = aux_info.sb_block;
+
+ /* Alloc an array to hold the pointers to the backup superblocks */
+ aux_info.backup_sb = (struct ext4_super_block**)calloc(aux_info.groups, sizeof(char*));
+
+ if (!aux_info.sb) critical_error_errno("calloc");
+
+ aux_info.bg_desc =
+ (struct ext2_group_desc*)calloc(aux_info.groups, sizeof(struct ext2_group_desc));
+ if (!aux_info.bg_desc) critical_error_errno("calloc");
+ aux_info.xattrs = NULL;
}
-void ext4_free_fs_aux_info()
-{
- unsigned int i;
-
- for (i=0; i<aux_info.groups; i++) {
- if (aux_info.backup_sb[i])
- free(aux_info.backup_sb[i]);
- }
- free(aux_info.sb_block);
- free(aux_info.sb_zero);
- free(aux_info.bg_desc);
+void ext4_free_fs_aux_info() {
+ unsigned int i;
+
+ for (i = 0; i < aux_info.groups; i++) {
+ if (aux_info.backup_sb[i]) free(aux_info.backup_sb[i]);
+ }
+ free(aux_info.sb_block);
+ free(aux_info.sb_zero);
+ free(aux_info.bg_desc);
}
-void ext4_parse_sb_info(struct ext4_super_block *sb)
-{
- if (sb->s_magic != EXT4_SUPER_MAGIC)
- error("superblock magic incorrect");
+void ext4_parse_sb_info(struct ext4_super_block* sb) {
+ if (sb->s_magic != EXT4_SUPER_MAGIC) error("superblock magic incorrect");
- if ((sb->s_state & EXT4_VALID_FS) != EXT4_VALID_FS)
- error("filesystem state not valid");
+ if ((sb->s_state & EXT4_VALID_FS) != EXT4_VALID_FS) error("filesystem state not valid");
- ext4_parse_sb(sb, &info);
+ ext4_parse_sb(sb, &info);
- ext4_create_fs_aux_info();
+ ext4_create_fs_aux_info();
- memcpy(aux_info.sb, sb, sizeof(*sb));
+ memcpy(aux_info.sb, sb, sizeof(*sb));
- if (aux_info.first_data_block != sb->s_first_data_block)
- critical_error("first data block does not match");
+ if (aux_info.first_data_block != sb->s_first_data_block)
+ critical_error("first data block does not match");
}
-u64 get_block_device_size(int fd)
-{
- u64 size = 0;
- int ret;
+u64 get_block_device_size(int fd) {
+ u64 size = 0;
+ int ret;
#if defined(__linux__)
- ret = ioctl(fd, BLKGETSIZE64, &size);
+ ret = ioctl(fd, BLKGETSIZE64, &size);
#elif defined(__APPLE__) && defined(__MACH__)
- ret = ioctl(fd, DKIOCGETBLOCKCOUNT, &size);
+ ret = ioctl(fd, DKIOCGETBLOCKCOUNT, &size);
#else
- close(fd);
- return 0;
+ close(fd);
+ return 0;
#endif
- if (ret)
- return 0;
+ if (ret) return 0;
- return size;
+ return size;
}
-int is_block_device_fd(int fd __attribute__((unused)))
-{
+int is_block_device_fd(int fd __attribute__((unused))) {
#ifdef _WIN32
- return 0;
+ return 0;
#else
- struct stat st;
- int ret = fstat(fd, &st);
- if (ret < 0)
- return 0;
+ struct stat st;
+ int ret = fstat(fd, &st);
+ if (ret < 0) return 0;
- return S_ISBLK(st.st_mode);
+ return S_ISBLK(st.st_mode);
#endif
}
-u64 get_file_size(int fd)
-{
- struct stat buf;
- int ret;
- u64 reserve_len = 0;
- s64 computed_size;
-
- ret = fstat(fd, &buf);
- if (ret)
- return 0;
-
- if (info.len < 0)
- reserve_len = -info.len;
-
- if (S_ISREG(buf.st_mode))
- computed_size = buf.st_size - reserve_len;
- else if (S_ISBLK(buf.st_mode))
- computed_size = get_block_device_size(fd) - reserve_len;
- else
- computed_size = 0;
-
- if (computed_size < 0) {
- warn("Computed filesystem size less than 0");
- computed_size = 0;
- }
-
- return computed_size;
+u64 get_file_size(int fd) {
+ struct stat buf;
+ int ret;
+ u64 reserve_len = 0;
+ s64 computed_size;
+
+ ret = fstat(fd, &buf);
+ if (ret) return 0;
+
+ if (info.len < 0) reserve_len = -info.len;
+
+ if (S_ISREG(buf.st_mode))
+ computed_size = buf.st_size - reserve_len;
+ else if (S_ISBLK(buf.st_mode))
+ computed_size = get_block_device_size(fd) - reserve_len;
+ else
+ computed_size = 0;
+
+ if (computed_size < 0) {
+ warn("Computed filesystem size less than 0");
+ computed_size = 0;
+ }
+
+ return computed_size;
}
-int read_ext(int fd, int verbose)
-{
- off64_t ret;
- struct ext4_super_block sb;
-
- read_sb(fd, &sb);
-
- ext4_parse_sb_info(&sb);
-
- ret = lseek64(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);
- if (ret < 0)
- critical_error_errno("failed to seek to block group descriptors");
-
- ret = read(fd, aux_info.bg_desc, info.block_size * aux_info.bg_desc_blocks);
- if (ret < 0)
- critical_error_errno("failed to read block group descriptors");
- if (ret != (int)info.block_size * (int)aux_info.bg_desc_blocks)
- critical_error("failed to read all of block group descriptors");
-
- if (verbose) {
- printf("Found filesystem with parameters:\n");
- printf(" Size: %" PRIu64 "\n", info.len);
- printf(" Block size: %d\n", info.block_size);
- printf(" Blocks per group: %d\n", info.blocks_per_group);
- printf(" Inodes per group: %d\n", info.inodes_per_group);
- printf(" Inode size: %d\n", info.inode_size);
- printf(" Label: %s\n", info.label);
- printf(" Blocks: %" PRIext4u64 "\n", aux_info.len_blocks);
- printf(" Block groups: %d\n", aux_info.groups);
- printf(" Reserved block group size: %d\n", info.bg_desc_reserve_blocks);
- printf(" Used %d/%d inodes and %d/%d blocks\n",
- aux_info.sb->s_inodes_count - aux_info.sb->s_free_inodes_count,
- aux_info.sb->s_inodes_count,
- aux_info.sb->s_blocks_count_lo - aux_info.sb->s_free_blocks_count_lo,
- aux_info.sb->s_blocks_count_lo);
- }
-
- return 0;
+static void read_block_group_descriptors(int fd) {
+ size_t size = info.block_size * (size_t)aux_info.bg_desc_blocks;
+ void* buf = malloc(size);
+ ssize_t ret;
+
+ if (!buf) critical_error("failed to alloc buffer");
+
+ ret = read(fd, buf, size);
+ if (ret < 0) {
+ free(buf);
+ critical_error_errno("failed to read block group descriptors");
+ }
+ if (ret != size) {
+ free(buf);
+ critical_error("failed to read all the block group descriptors");
+ }
+ const struct ext4_group_desc* gdp = (const struct ext4_group_desc*)buf;
+ bool extended = (info.bg_desc_size >= EXT4_MIN_DESC_SIZE_64BIT);
+ for (size_t i = 0; i < aux_info.groups; i++) {
+ aux_info.bg_desc[i].bg_block_bitmap =
+ (extended ? (u64)gdp->bg_block_bitmap_hi << 32 : 0) | gdp->bg_block_bitmap_lo;
+ aux_info.bg_desc[i].bg_inode_bitmap =
+ (extended ? (u64)gdp->bg_inode_bitmap_hi << 32 : 0) | gdp->bg_inode_bitmap_lo;
+ aux_info.bg_desc[i].bg_inode_table =
+ (extended ? (u64)gdp->bg_inode_table_hi << 32 : 0) | gdp->bg_inode_table_lo;
+ aux_info.bg_desc[i].bg_free_blocks_count =
+ (extended ? (u32)gdp->bg_free_blocks_count_hi << 16 : 0) |
+ gdp->bg_free_blocks_count_lo;
+ aux_info.bg_desc[i].bg_free_inodes_count =
+ (extended ? (u32)gdp->bg_free_inodes_count_hi << 16 : 0) |
+ gdp->bg_free_inodes_count_lo;
+ aux_info.bg_desc[i].bg_used_dirs_count =
+ (extended ? (u32)gdp->bg_used_dirs_count_hi << 16 : 0) | gdp->bg_used_dirs_count_lo;
+ aux_info.bg_desc[i].bg_flags = gdp->bg_flags;
+ gdp = (const struct ext4_group_desc*)((u8*)gdp + info.bg_desc_size);
+ }
+ free(buf);
}
+int read_ext(int fd, int verbose) {
+ off64_t ret;
+ struct ext4_super_block sb;
+
+ read_sb(fd, &sb);
+
+ ext4_parse_sb_info(&sb);
+
+ ret = lseek64(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);
+ if (ret < 0) critical_error_errno("failed to seek to block group descriptors");
+
+ read_block_group_descriptors(fd);
+
+ if (verbose) {
+ printf("Found filesystem with parameters:\n");
+ printf(" Size: %" PRIu64 "\n", info.len);
+ printf(" Block size: %d\n", info.block_size);
+ printf(" Blocks per group: %d\n", info.blocks_per_group);
+ printf(" Inodes per group: %d\n", info.inodes_per_group);
+ printf(" Inode size: %d\n", info.inode_size);
+ printf(" Label: %s\n", info.label);
+ printf(" Blocks: %" PRIext4u64 "\n", aux_info.len_blocks);
+ printf(" Block groups: %d\n", aux_info.groups);
+ printf(" Reserved block group size: %d\n", info.bg_desc_reserve_blocks);
+ printf(" Block group descriptor size: %d\n", info.bg_desc_size);
+ printf(" Used %d/%d inodes and %d/%d blocks\n",
+ aux_info.sb->s_inodes_count - aux_info.sb->s_free_inodes_count,
+ aux_info.sb->s_inodes_count,
+ aux_info.sb->s_blocks_count_lo - aux_info.sb->s_free_blocks_count_lo,
+ aux_info.sb->s_blocks_count_lo);
+ }
+
+ return 0;
+}
diff --git a/ext4_utils/helpers.h b/ext4_utils/helpers.h
new file mode 100644
index 00000000..c7509ec6
--- /dev/null
+++ b/ext4_utils/helpers.h
@@ -0,0 +1,36 @@
+/*
+ * 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 <stdio.h>
+#include <string.h>
+
+#define warn(fmt, args...) \
+ do { \
+ fprintf(stderr, "warning: %s: " fmt "\n", __func__, ##args); \
+ } while (0)
+#define error(fmt, args...) \
+ do { \
+ fprintf(stderr, "error: %s: " fmt "\n", __func__, ##args); \
+ if (!force) longjmp(setjmp_env, EXIT_FAILURE); \
+ } while (0)
+#define error_errno(s, args...) error(s ": %s", ##args, strerror(errno))
+#define critical_error(fmt, args...) \
+ do { \
+ fprintf(stderr, "critical error: %s: " fmt "\n", __func__, ##args); \
+ longjmp(setjmp_env, EXIT_FAILURE); \
+ } while (0)
+#define critical_error_errno(s, args...) critical_error(s ": %s", ##args, strerror(errno))
diff --git a/ext4_utils/include/ext4_utils/ext4.h b/ext4_utils/include/ext4_utils/ext4.h
index 4bb1031f..54e5d86a 100644
--- a/ext4_utils/include/ext4_utils/ext4.h
+++ b/ext4_utils/include/ext4_utils/ext4.h
@@ -17,14 +17,20 @@
#undef EXT4FS_DEBUG
#ifdef EXT4FS_DEBUG
-#define ext4_debug(f, a...) do { printk(KERN_DEBUG "EXT4-fs DEBUG (%s, %d): %s:", __FILE__, __LINE__, __func__); printk(KERN_DEBUG f, ## a); } while (0)
+#define ext4_debug(f, a...) \
+ do { \
+ printk(KERN_DEBUG "EXT4-fs DEBUG (%s, %d): %s:", __FILE__, __LINE__, __func__); \
+ printk(KERN_DEBUG f, ##a); \
+ } while (0)
#else
-#define ext4_debug(f, a...) do {} while (0)
+#define ext4_debug(f, a...) \
+ do { \
+ } while (0)
#endif
-#define EXT4_ERROR_INODE(inode, fmt, a...) ext4_error_inode(__func__, (inode), (fmt), ## a);
+#define EXT4_ERROR_INODE(inode, fmt, a...) ext4_error_inode(__func__, (inode), (fmt), ##a);
-#define EXT4_ERROR_FILE(file, fmt, a...) ext4_error_file(__func__, (file), (fmt), ## a);
+#define EXT4_ERROR_FILE(file, fmt, a...) ext4_error_file(__func__, (file), (fmt), ##a);
typedef int ext4_grpblk_t;
@@ -59,32 +65,31 @@ typedef unsigned int ext4_group_t;
#define EXT4_MB_STREAM_ALLOC 0x0800
struct ext4_allocation_request {
+ struct inode* inode;
- struct inode *inode;
+ unsigned int len;
- unsigned int len;
+ ext4_lblk_t logical;
- ext4_lblk_t logical;
+ ext4_lblk_t lleft;
- ext4_lblk_t lleft;
+ ext4_lblk_t lright;
- ext4_lblk_t lright;
+ ext4_fsblk_t goal;
- ext4_fsblk_t goal;
+ ext4_fsblk_t pleft;
- ext4_fsblk_t pleft;
+ ext4_fsblk_t pright;
- ext4_fsblk_t pright;
-
- unsigned int flags;
+ unsigned int flags;
};
-#define EXT4_BAD_INO 1
-#define EXT4_ROOT_INO 2
-#define EXT4_BOOT_LOADER_INO 5
-#define EXT4_UNDEL_DIR_INO 6
-#define EXT4_RESIZE_INO 7
-#define EXT4_JOURNAL_INO 8
+#define EXT4_BAD_INO 1
+#define EXT4_ROOT_INO 2
+#define EXT4_BOOT_LOADER_INO 5
+#define EXT4_UNDEL_DIR_INO 6
+#define EXT4_RESIZE_INO 7
+#define EXT4_JOURNAL_INO 8
#define EXT4_GOOD_OLD_FIRST_INO 11
@@ -96,35 +101,36 @@ struct ext4_allocation_request {
#define EXT4_BLOCK_SIZE(s) (EXT4_MIN_BLOCK_SIZE << (s)->s_log_block_size)
#define EXT4_ADDR_PER_BLOCK(s) (EXT4_BLOCK_SIZE(s) / sizeof(__u32))
#define EXT4_BLOCK_SIZE_BITS(s) ((s)->s_log_block_size + 10)
-#define EXT4_INODE_SIZE(s) (((s)->s_rev_level == EXT4_GOOD_OLD_REV) ? EXT4_GOOD_OLD_INODE_SIZE : (s)->s_inode_size)
-#define EXT4_FIRST_INO(s) (((s)->s_rev_level == EXT4_GOOD_OLD_REV) ? EXT4_GOOD_OLD_FIRST_INO : (s)->s_first_ino)
+#define EXT4_INODE_SIZE(s) \
+ (((s)->s_rev_level == EXT4_GOOD_OLD_REV) ? EXT4_GOOD_OLD_INODE_SIZE : (s)->s_inode_size)
+#define EXT4_FIRST_INO(s) \
+ (((s)->s_rev_level == EXT4_GOOD_OLD_REV) ? EXT4_GOOD_OLD_FIRST_INO : (s)->s_first_ino)
#define EXT4_BLOCK_ALIGN(size, blkbits) EXT4_ALIGN((size), (1 << (blkbits)))
-struct ext4_group_desc
-{
- __le32 bg_block_bitmap_lo;
- __le32 bg_inode_bitmap_lo;
- __le32 bg_inode_table_lo;
- __le16 bg_free_blocks_count_lo;
- __le16 bg_free_inodes_count_lo;
- __le16 bg_used_dirs_count_lo;
- __le16 bg_flags;
- __u32 bg_reserved[2];
- __le16 bg_itable_unused_lo;
- __le16 bg_checksum;
- __le32 bg_block_bitmap_hi;
- __le32 bg_inode_bitmap_hi;
- __le32 bg_inode_table_hi;
- __le16 bg_free_blocks_count_hi;
- __le16 bg_free_inodes_count_hi;
- __le16 bg_used_dirs_count_hi;
- __le16 bg_itable_unused_hi;
- __u32 bg_reserved2[3];
+struct ext4_group_desc {
+ __le32 bg_block_bitmap_lo;
+ __le32 bg_inode_bitmap_lo;
+ __le32 bg_inode_table_lo;
+ __le16 bg_free_blocks_count_lo;
+ __le16 bg_free_inodes_count_lo;
+ __le16 bg_used_dirs_count_lo;
+ __le16 bg_flags;
+ __u32 bg_reserved[2];
+ __le16 bg_itable_unused_lo;
+ __le16 bg_checksum;
+ __le32 bg_block_bitmap_hi;
+ __le32 bg_inode_bitmap_hi;
+ __le32 bg_inode_table_hi;
+ __le16 bg_free_blocks_count_hi;
+ __le16 bg_free_inodes_count_hi;
+ __le16 bg_used_dirs_count_hi;
+ __le16 bg_itable_unused_hi;
+ __u32 bg_reserved2[3];
};
-#define EXT4_BG_INODE_UNINIT 0x0001
-#define EXT4_BG_BLOCK_UNINIT 0x0002
-#define EXT4_BG_INODE_ZEROED 0x0004
+#define EXT4_BG_INODE_UNINIT 0x0001
+#define EXT4_BG_BLOCK_UNINIT 0x0002
+#define EXT4_BG_INODE_ZEROED 0x0004
#define EXT4_MIN_DESC_SIZE 32
#define EXT4_MIN_DESC_SIZE_64BIT 64
@@ -140,64 +146,67 @@ struct ext4_group_desc
#define EXT4_TIND_BLOCK (EXT4_DIND_BLOCK + 1)
#define EXT4_N_BLOCKS (EXT4_TIND_BLOCK + 1)
-#define EXT4_SECRM_FL 0x00000001
-#define EXT4_UNRM_FL 0x00000002
-#define EXT4_COMPR_FL 0x00000004
-#define EXT4_SYNC_FL 0x00000008
-#define EXT4_IMMUTABLE_FL 0x00000010
-#define EXT4_APPEND_FL 0x00000020
-#define EXT4_NODUMP_FL 0x00000040
-#define EXT4_NOATIME_FL 0x00000080
+#define EXT4_SECRM_FL 0x00000001
+#define EXT4_UNRM_FL 0x00000002
+#define EXT4_COMPR_FL 0x00000004
+#define EXT4_SYNC_FL 0x00000008
+#define EXT4_IMMUTABLE_FL 0x00000010
+#define EXT4_APPEND_FL 0x00000020
+#define EXT4_NODUMP_FL 0x00000040
+#define EXT4_NOATIME_FL 0x00000080
#define EXT4_DIRTY_FL 0x00000100
-#define EXT4_COMPRBLK_FL 0x00000200
-#define EXT4_NOCOMPR_FL 0x00000400
-#define EXT4_ECOMPR_FL 0x00000800
-
-#define EXT4_INDEX_FL 0x00001000
-#define EXT4_IMAGIC_FL 0x00002000
-#define EXT4_JOURNAL_DATA_FL 0x00004000
-#define EXT4_NOTAIL_FL 0x00008000
-#define EXT4_DIRSYNC_FL 0x00010000
-#define EXT4_TOPDIR_FL 0x00020000
-#define EXT4_HUGE_FILE_FL 0x00040000
-#define EXT4_EXTENTS_FL 0x00080000
-#define EXT4_EA_INODE_FL 0x00200000
-#define EXT4_EOFBLOCKS_FL 0x00400000
-#define EXT4_RESERVED_FL 0x80000000
-
-#define EXT4_FL_USER_VISIBLE 0x004BDFFF
-#define EXT4_FL_USER_MODIFIABLE 0x004B80FF
-
-#define EXT4_FL_INHERITED (EXT4_SECRM_FL | EXT4_UNRM_FL | EXT4_COMPR_FL | EXT4_SYNC_FL | EXT4_IMMUTABLE_FL | EXT4_APPEND_FL | EXT4_NODUMP_FL | EXT4_NOATIME_FL | EXT4_NOCOMPR_FL | EXT4_JOURNAL_DATA_FL | EXT4_NOTAIL_FL | EXT4_DIRSYNC_FL)
+#define EXT4_COMPRBLK_FL 0x00000200
+#define EXT4_NOCOMPR_FL 0x00000400
+#define EXT4_ECOMPR_FL 0x00000800
+
+#define EXT4_INDEX_FL 0x00001000
+#define EXT4_IMAGIC_FL 0x00002000
+#define EXT4_JOURNAL_DATA_FL 0x00004000
+#define EXT4_NOTAIL_FL 0x00008000
+#define EXT4_DIRSYNC_FL 0x00010000
+#define EXT4_TOPDIR_FL 0x00020000
+#define EXT4_HUGE_FILE_FL 0x00040000
+#define EXT4_EXTENTS_FL 0x00080000
+#define EXT4_EA_INODE_FL 0x00200000
+#define EXT4_EOFBLOCKS_FL 0x00400000
+#define EXT4_RESERVED_FL 0x80000000
+
+#define EXT4_FL_USER_VISIBLE 0x004BDFFF
+#define EXT4_FL_USER_MODIFIABLE 0x004B80FF
+
+#define EXT4_FL_INHERITED \
+ (EXT4_SECRM_FL | EXT4_UNRM_FL | EXT4_COMPR_FL | EXT4_SYNC_FL | EXT4_IMMUTABLE_FL | \
+ EXT4_APPEND_FL | EXT4_NODUMP_FL | EXT4_NOATIME_FL | EXT4_NOCOMPR_FL | EXT4_JOURNAL_DATA_FL | \
+ EXT4_NOTAIL_FL | EXT4_DIRSYNC_FL)
#define EXT4_REG_FLMASK (~(EXT4_DIRSYNC_FL | EXT4_TOPDIR_FL))
#define EXT4_OTHER_FLMASK (EXT4_NODUMP_FL | EXT4_NOATIME_FL)
struct ext4_new_group_data {
- __u32 group;
- __u64 block_bitmap;
- __u64 inode_bitmap;
- __u64 inode_table;
- __u32 blocks_count;
- __u16 reserved_blocks;
- __u16 unused;
- __u32 free_blocks_count;
+ __u32 group;
+ __u64 block_bitmap;
+ __u64 inode_bitmap;
+ __u64 inode_table;
+ __u32 blocks_count;
+ __u16 reserved_blocks;
+ __u16 unused;
+ __u32 free_blocks_count;
};
#define EXT4_GET_BLOCKS_CREATE 0x0001
#define EXT4_GET_BLOCKS_UNINIT_EXT 0x0002
-#define EXT4_GET_BLOCKS_CREATE_UNINIT_EXT (EXT4_GET_BLOCKS_UNINIT_EXT| EXT4_GET_BLOCKS_CREATE)
+#define EXT4_GET_BLOCKS_CREATE_UNINIT_EXT (EXT4_GET_BLOCKS_UNINIT_EXT | EXT4_GET_BLOCKS_CREATE)
#define EXT4_GET_BLOCKS_DELALLOC_RESERVE 0x0004
#define EXT4_GET_BLOCKS_PRE_IO 0x0008
#define EXT4_GET_BLOCKS_CONVERT 0x0010
-#define EXT4_GET_BLOCKS_IO_CREATE_EXT (EXT4_GET_BLOCKS_PRE_IO| EXT4_GET_BLOCKS_CREATE_UNINIT_EXT)
+#define EXT4_GET_BLOCKS_IO_CREATE_EXT (EXT4_GET_BLOCKS_PRE_IO | EXT4_GET_BLOCKS_CREATE_UNINIT_EXT)
-#define EXT4_GET_BLOCKS_IO_CONVERT_EXT (EXT4_GET_BLOCKS_CONVERT| EXT4_GET_BLOCKS_CREATE_UNINIT_EXT)
+#define EXT4_GET_BLOCKS_IO_CONVERT_EXT (EXT4_GET_BLOCKS_CONVERT | EXT4_GET_BLOCKS_CREATE_UNINIT_EXT)
#define EXT4_FREE_BLOCKS_METADATA 0x0001
#define EXT4_FREE_BLOCKS_FORGET 0x0002
@@ -231,83 +240,107 @@ struct ext4_new_group_data {
#define EXT4_MAX_BLOCK_FILE_PHYS 0xFFFFFFFF
struct ext4_inode {
- __le16 i_mode;
- __le16 i_uid;
- __le32 i_size_lo;
- __le32 i_atime;
- __le32 i_ctime;
- __le32 i_mtime;
- __le32 i_dtime;
- __le16 i_gid;
- __le16 i_links_count;
- __le32 i_blocks_lo;
- __le32 i_flags;
- union {
- struct {
- __le32 l_i_version;
- } linux1;
- struct {
- __u32 h_i_translator;
- } hurd1;
- struct {
- __u32 m_i_reserved1;
- } masix1;
- } osd1;
- __le32 i_block[EXT4_N_BLOCKS];
- __le32 i_generation;
- __le32 i_file_acl_lo;
- __le32 i_size_high;
- __le32 i_obso_faddr;
- union {
- struct {
- __le16 l_i_blocks_high;
- __le16 l_i_file_acl_high;
- __le16 l_i_uid_high;
- __le16 l_i_gid_high;
- __u32 l_i_reserved2;
- } linux2;
- struct {
- __le16 h_i_reserved1;
- __u16 h_i_mode_high;
- __u16 h_i_uid_high;
- __u16 h_i_gid_high;
- __u32 h_i_author;
- } hurd2;
- struct {
- __le16 h_i_reserved1;
- __le16 m_i_file_acl_high;
- __u32 m_i_reserved2[2];
- } masix2;
- } osd2;
- __le16 i_extra_isize;
- __le16 i_pad1;
- __le32 i_ctime_extra;
- __le32 i_mtime_extra;
- __le32 i_atime_extra;
- __le32 i_crtime;
- __le32 i_crtime_extra;
- __le32 i_version_hi;
+ __le16 i_mode;
+ __le16 i_uid;
+ __le32 i_size_lo;
+ __le32 i_atime;
+ __le32 i_ctime;
+ __le32 i_mtime;
+ __le32 i_dtime;
+ __le16 i_gid;
+ __le16 i_links_count;
+ __le32 i_blocks_lo;
+ __le32 i_flags;
+ union {
+ struct {
+ __le32 l_i_version;
+ } linux1;
+ struct {
+ __u32 h_i_translator;
+ } hurd1;
+ struct {
+ __u32 m_i_reserved1;
+ } masix1;
+ } osd1;
+ __le32 i_block[EXT4_N_BLOCKS];
+ __le32 i_generation;
+ __le32 i_file_acl_lo;
+ __le32 i_size_high;
+ __le32 i_obso_faddr;
+ union {
+ struct {
+ __le16 l_i_blocks_high;
+ __le16 l_i_file_acl_high;
+ __le16 l_i_uid_high;
+ __le16 l_i_gid_high;
+ __u32 l_i_reserved2;
+ } linux2;
+ struct {
+ __le16 h_i_reserved1;
+ __u16 h_i_mode_high;
+ __u16 h_i_uid_high;
+ __u16 h_i_gid_high;
+ __u32 h_i_author;
+ } hurd2;
+ struct {
+ __le16 h_i_reserved1;
+ __le16 m_i_file_acl_high;
+ __u32 m_i_reserved2[2];
+ } masix2;
+ } osd2;
+ __le16 i_extra_isize;
+ __le16 i_pad1;
+ __le32 i_ctime_extra;
+ __le32 i_mtime_extra;
+ __le32 i_atime_extra;
+ __le32 i_crtime;
+ __le32 i_crtime_extra;
+ __le32 i_version_hi;
};
struct move_extent {
- __u32 reserved;
- __u32 donor_fd;
- __u64 orig_start;
- __u64 donor_start;
- __u64 len;
- __u64 moved_len;
+ __u32 reserved;
+ __u32 donor_fd;
+ __u64 orig_start;
+ __u64 donor_start;
+ __u64 len;
+ __u64 moved_len;
};
#define EXT4_EPOCH_BITS 2
#define EXT4_EPOCH_MASK ((1 << EXT4_EPOCH_BITS) - 1)
#define EXT4_NSEC_MASK (~0UL << EXT4_EPOCH_BITS)
-#define EXT4_FITS_IN_INODE(ext4_inode, einode, field) ((offsetof(typeof(*(ext4_inode)), field) + sizeof((ext4_inode)->field)) <= (EXT4_GOOD_OLD_INODE_SIZE + (einode)->i_extra_isize))
-#define EXT4_INODE_SET_XTIME(xtime, inode, raw_inode) do { (raw_inode)->xtime = cpu_to_le32((inode)->xtime.tv_sec); if (EXT4_FITS_IN_INODE(raw_inode, EXT4_I(inode), xtime ## _extra)) (raw_inode)->xtime ## _extra = ext4_encode_extra_time(&(inode)->xtime); } while (0)
-#define EXT4_EINODE_SET_XTIME(xtime, einode, raw_inode) do { if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime)) (raw_inode)->xtime = cpu_to_le32((einode)->xtime.tv_sec); if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime ## _extra)) (raw_inode)->xtime ## _extra = ext4_encode_extra_time(&(einode)->xtime); } while (0)
-#define EXT4_INODE_GET_XTIME(xtime, inode, raw_inode) do { (inode)->xtime.tv_sec = (signed)le32_to_cpu((raw_inode)->xtime); if (EXT4_FITS_IN_INODE(raw_inode, EXT4_I(inode), xtime ## _extra)) ext4_decode_extra_time(&(inode)->xtime, (raw_inode)->xtime ## _extra); } while (0)
-
-#define EXT4_EINODE_GET_XTIME(xtime, einode, raw_inode) do { if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime)) (einode)->xtime.tv_sec = (signed)le32_to_cpu((raw_inode)->xtime); if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime ## _extra)) ext4_decode_extra_time(&(einode)->xtime, (raw_inode)->xtime ## _extra); } while (0)
+#define EXT4_FITS_IN_INODE(ext4_inode, einode, field) \
+ ((offsetof(typeof(*(ext4_inode)), field) + sizeof((ext4_inode)->field)) <= \
+ (EXT4_GOOD_OLD_INODE_SIZE + (einode)->i_extra_isize))
+#define EXT4_INODE_SET_XTIME(xtime, inode, raw_inode) \
+ do { \
+ (raw_inode)->xtime = cpu_to_le32((inode)->xtime.tv_sec); \
+ if (EXT4_FITS_IN_INODE(raw_inode, EXT4_I(inode), xtime##_extra)) \
+ (raw_inode)->xtime##_extra = ext4_encode_extra_time(&(inode)->xtime); \
+ } while (0)
+#define EXT4_EINODE_SET_XTIME(xtime, einode, raw_inode) \
+ do { \
+ if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime)) \
+ (raw_inode)->xtime = cpu_to_le32((einode)->xtime.tv_sec); \
+ if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime##_extra)) \
+ (raw_inode)->xtime##_extra = ext4_encode_extra_time(&(einode)->xtime); \
+ } while (0)
+#define EXT4_INODE_GET_XTIME(xtime, inode, raw_inode) \
+ do { \
+ (inode)->xtime.tv_sec = (signed)le32_to_cpu((raw_inode)->xtime); \
+ if (EXT4_FITS_IN_INODE(raw_inode, EXT4_I(inode), xtime##_extra)) \
+ ext4_decode_extra_time(&(inode)->xtime, (raw_inode)->xtime##_extra); \
+ } while (0)
+
+#define EXT4_EINODE_GET_XTIME(xtime, einode, raw_inode) \
+ do { \
+ if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime)) \
+ (einode)->xtime.tv_sec = (signed)le32_to_cpu((raw_inode)->xtime); \
+ if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime##_extra)) \
+ ext4_decode_extra_time(&(einode)->xtime, (raw_inode)->xtime##_extra); \
+ } while (0)
#define i_disk_version osd1.linux1.l_i_version
#define i_reserved1 osd1.linux1.l_i_reserved1
@@ -319,48 +352,48 @@ struct move_extent {
#define i_gid_high osd2.linux2.l_i_gid_high
#define i_reserved2 osd2.linux2.l_i_reserved2
-#define EXT4_VALID_FS 0x0001
-#define EXT4_ERROR_FS 0x0002
-#define EXT4_ORPHAN_FS 0x0004
-
-#define EXT2_FLAGS_SIGNED_HASH 0x0001
-#define EXT2_FLAGS_UNSIGNED_HASH 0x0002
-#define EXT2_FLAGS_TEST_FILESYS 0x0004
-
-#define EXT4_MOUNT_OLDALLOC 0x00002
-#define EXT4_MOUNT_GRPID 0x00004
-#define EXT4_MOUNT_DEBUG 0x00008
-#define EXT4_MOUNT_ERRORS_CONT 0x00010
-#define EXT4_MOUNT_ERRORS_RO 0x00020
-#define EXT4_MOUNT_ERRORS_PANIC 0x00040
-#define EXT4_MOUNT_MINIX_DF 0x00080
-#define EXT4_MOUNT_NOLOAD 0x00100
-#define EXT4_MOUNT_DATA_FLAGS 0x00C00
-#define EXT4_MOUNT_JOURNAL_DATA 0x00400
-#define EXT4_MOUNT_ORDERED_DATA 0x00800
-#define EXT4_MOUNT_WRITEBACK_DATA 0x00C00
-#define EXT4_MOUNT_UPDATE_JOURNAL 0x01000
-#define EXT4_MOUNT_NO_UID32 0x02000
-#define EXT4_MOUNT_XATTR_USER 0x04000
-#define EXT4_MOUNT_POSIX_ACL 0x08000
-#define EXT4_MOUNT_NO_AUTO_DA_ALLOC 0x10000
-#define EXT4_MOUNT_BARRIER 0x20000
-#define EXT4_MOUNT_NOBH 0x40000
-#define EXT4_MOUNT_QUOTA 0x80000
-#define EXT4_MOUNT_USRQUOTA 0x100000
-#define EXT4_MOUNT_GRPQUOTA 0x200000
-#define EXT4_MOUNT_DIOREAD_NOLOCK 0x400000
-#define EXT4_MOUNT_JOURNAL_CHECKSUM 0x800000
-#define EXT4_MOUNT_JOURNAL_ASYNC_COMMIT 0x1000000
-#define EXT4_MOUNT_I_VERSION 0x2000000
-#define EXT4_MOUNT_DELALLOC 0x8000000
-#define EXT4_MOUNT_DATA_ERR_ABORT 0x10000000
-#define EXT4_MOUNT_BLOCK_VALIDITY 0x20000000
-#define EXT4_MOUNT_DISCARD 0x40000000
+#define EXT4_VALID_FS 0x0001
+#define EXT4_ERROR_FS 0x0002
+#define EXT4_ORPHAN_FS 0x0004
+
+#define EXT2_FLAGS_SIGNED_HASH 0x0001
+#define EXT2_FLAGS_UNSIGNED_HASH 0x0002
+#define EXT2_FLAGS_TEST_FILESYS 0x0004
+
+#define EXT4_MOUNT_OLDALLOC 0x00002
+#define EXT4_MOUNT_GRPID 0x00004
+#define EXT4_MOUNT_DEBUG 0x00008
+#define EXT4_MOUNT_ERRORS_CONT 0x00010
+#define EXT4_MOUNT_ERRORS_RO 0x00020
+#define EXT4_MOUNT_ERRORS_PANIC 0x00040
+#define EXT4_MOUNT_MINIX_DF 0x00080
+#define EXT4_MOUNT_NOLOAD 0x00100
+#define EXT4_MOUNT_DATA_FLAGS 0x00C00
+#define EXT4_MOUNT_JOURNAL_DATA 0x00400
+#define EXT4_MOUNT_ORDERED_DATA 0x00800
+#define EXT4_MOUNT_WRITEBACK_DATA 0x00C00
+#define EXT4_MOUNT_UPDATE_JOURNAL 0x01000
+#define EXT4_MOUNT_NO_UID32 0x02000
+#define EXT4_MOUNT_XATTR_USER 0x04000
+#define EXT4_MOUNT_POSIX_ACL 0x08000
+#define EXT4_MOUNT_NO_AUTO_DA_ALLOC 0x10000
+#define EXT4_MOUNT_BARRIER 0x20000
+#define EXT4_MOUNT_NOBH 0x40000
+#define EXT4_MOUNT_QUOTA 0x80000
+#define EXT4_MOUNT_USRQUOTA 0x100000
+#define EXT4_MOUNT_GRPQUOTA 0x200000
+#define EXT4_MOUNT_DIOREAD_NOLOCK 0x400000
+#define EXT4_MOUNT_JOURNAL_CHECKSUM 0x800000
+#define EXT4_MOUNT_JOURNAL_ASYNC_COMMIT 0x1000000
+#define EXT4_MOUNT_I_VERSION 0x2000000
+#define EXT4_MOUNT_DELALLOC 0x8000000
+#define EXT4_MOUNT_DATA_ERR_ABORT 0x10000000
+#define EXT4_MOUNT_BLOCK_VALIDITY 0x20000000
+#define EXT4_MOUNT_DISCARD 0x40000000
#define clear_opt(o, opt) o &= ~EXT4_MOUNT_##opt
#define set_opt(o, opt) o |= EXT4_MOUNT_##opt
-#define test_opt(sb, opt) (EXT4_SB(sb)->s_mount_opt & EXT4_MOUNT_##opt)
+#define test_opt(sb, opt) (EXT4_SB(sb)->s_mount_opt & EXT4_MOUNT_##opt)
#define ext4_set_bit ext2_set_bit
#define ext4_set_bit_atomic ext2_set_bit_atomic
@@ -371,84 +404,84 @@ struct move_extent {
#define ext4_find_next_zero_bit ext2_find_next_zero_bit
#define ext4_find_next_bit ext2_find_next_bit
-#define EXT4_DFL_MAX_MNT_COUNT 20
-#define EXT4_DFL_CHECKINTERVAL 0
+#define EXT4_DFL_MAX_MNT_COUNT 20
+#define EXT4_DFL_CHECKINTERVAL 0
-#define EXT4_ERRORS_CONTINUE 1
-#define EXT4_ERRORS_RO 2
-#define EXT4_ERRORS_PANIC 3
+#define EXT4_ERRORS_CONTINUE 1
+#define EXT4_ERRORS_RO 2
+#define EXT4_ERRORS_PANIC 3
#define EXT4_ERRORS_DEFAULT EXT4_ERRORS_CONTINUE
struct ext4_super_block {
- __le32 s_inodes_count;
- __le32 s_blocks_count_lo;
- __le32 s_r_blocks_count_lo;
- __le32 s_free_blocks_count_lo;
- __le32 s_free_inodes_count;
- __le32 s_first_data_block;
- __le32 s_log_block_size;
- __le32 s_obso_log_frag_size;
- __le32 s_blocks_per_group;
- __le32 s_obso_frags_per_group;
- __le32 s_inodes_per_group;
- __le32 s_mtime;
- __le32 s_wtime;
- __le16 s_mnt_count;
- __le16 s_max_mnt_count;
- __le16 s_magic;
- __le16 s_state;
- __le16 s_errors;
- __le16 s_minor_rev_level;
- __le32 s_lastcheck;
- __le32 s_checkinterval;
- __le32 s_creator_os;
- __le32 s_rev_level;
- __le16 s_def_resuid;
- __le16 s_def_resgid;
-
- __le32 s_first_ino;
- __le16 s_inode_size;
- __le16 s_block_group_nr;
- __le32 s_feature_compat;
- __le32 s_feature_incompat;
- __le32 s_feature_ro_compat;
- __u8 s_uuid[16];
- char s_volume_name[16];
- char s_last_mounted[64];
- __le32 s_algorithm_usage_bitmap;
-
- __u8 s_prealloc_blocks;
- __u8 s_prealloc_dir_blocks;
- __le16 s_reserved_gdt_blocks;
-
- __u8 s_journal_uuid[16];
- __le32 s_journal_inum;
- __le32 s_journal_dev;
- __le32 s_last_orphan;
- __le32 s_hash_seed[4];
- __u8 s_def_hash_version;
- __u8 s_reserved_char_pad;
- __le16 s_desc_size;
- __le32 s_default_mount_opts;
- __le32 s_first_meta_bg;
- __le32 s_mkfs_time;
- __le32 s_jnl_blocks[17];
-
- __le32 s_blocks_count_hi;
- __le32 s_r_blocks_count_hi;
- __le32 s_free_blocks_count_hi;
- __le16 s_min_extra_isize;
- __le16 s_want_extra_isize;
- __le32 s_flags;
- __le16 s_raid_stride;
- __le16 s_mmp_interval;
- __le64 s_mmp_block;
- __le32 s_raid_stripe_width;
- __u8 s_log_groups_per_flex;
- __u8 s_reserved_char_pad2;
- __le16 s_reserved_pad;
- __le64 s_kbytes_written;
- __u32 s_reserved[160];
+ __le32 s_inodes_count;
+ __le32 s_blocks_count_lo;
+ __le32 s_r_blocks_count_lo;
+ __le32 s_free_blocks_count_lo;
+ __le32 s_free_inodes_count;
+ __le32 s_first_data_block;
+ __le32 s_log_block_size;
+ __le32 s_obso_log_frag_size;
+ __le32 s_blocks_per_group;
+ __le32 s_obso_frags_per_group;
+ __le32 s_inodes_per_group;
+ __le32 s_mtime;
+ __le32 s_wtime;
+ __le16 s_mnt_count;
+ __le16 s_max_mnt_count;
+ __le16 s_magic;
+ __le16 s_state;
+ __le16 s_errors;
+ __le16 s_minor_rev_level;
+ __le32 s_lastcheck;
+ __le32 s_checkinterval;
+ __le32 s_creator_os;
+ __le32 s_rev_level;
+ __le16 s_def_resuid;
+ __le16 s_def_resgid;
+
+ __le32 s_first_ino;
+ __le16 s_inode_size;
+ __le16 s_block_group_nr;
+ __le32 s_feature_compat;
+ __le32 s_feature_incompat;
+ __le32 s_feature_ro_compat;
+ __u8 s_uuid[16];
+ char s_volume_name[16];
+ char s_last_mounted[64];
+ __le32 s_algorithm_usage_bitmap;
+
+ __u8 s_prealloc_blocks;
+ __u8 s_prealloc_dir_blocks;
+ __le16 s_reserved_gdt_blocks;
+
+ __u8 s_journal_uuid[16];
+ __le32 s_journal_inum;
+ __le32 s_journal_dev;
+ __le32 s_last_orphan;
+ __le32 s_hash_seed[4];
+ __u8 s_def_hash_version;
+ __u8 s_reserved_char_pad;
+ __le16 s_desc_size;
+ __le32 s_default_mount_opts;
+ __le32 s_first_meta_bg;
+ __le32 s_mkfs_time;
+ __le32 s_jnl_blocks[17];
+
+ __le32 s_blocks_count_hi;
+ __le32 s_r_blocks_count_hi;
+ __le32 s_free_blocks_count_hi;
+ __le16 s_min_extra_isize;
+ __le16 s_want_extra_isize;
+ __le32 s_flags;
+ __le16 s_raid_stride;
+ __le16 s_mmp_interval;
+ __le64 s_mmp_block;
+ __le32 s_raid_stripe_width;
+ __u8 s_log_groups_per_flex;
+ __u8 s_reserved_char_pad2;
+ __le16 s_reserved_pad;
+ __le64 s_kbytes_written;
+ __u32 s_reserved[160];
};
#define EXT4_SB(sb) (sb)
@@ -461,23 +494,31 @@ struct ext4_super_block {
#define EXT4_OS_FREEBSD 3
#define EXT4_OS_LITES 4
-#define EXT4_GOOD_OLD_REV 0
-#define EXT4_DYNAMIC_REV 1
+#define EXT4_GOOD_OLD_REV 0
+#define EXT4_DYNAMIC_REV 1
#define EXT4_CURRENT_REV EXT4_GOOD_OLD_REV
#define EXT4_MAX_SUPP_REV EXT4_DYNAMIC_REV
#define EXT4_GOOD_OLD_INODE_SIZE 128
-#define EXT4_HAS_COMPAT_FEATURE(sb,mask) ((EXT4_SB(sb)->s_es->s_feature_compat & cpu_to_le32(mask)) != 0)
-#define EXT4_HAS_RO_COMPAT_FEATURE(sb,mask) ((EXT4_SB(sb)->s_es->s_feature_ro_compat & cpu_to_le32(mask)) != 0)
-#define EXT4_HAS_INCOMPAT_FEATURE(sb,mask) ((EXT4_SB(sb)->s_es->s_feature_incompat & cpu_to_le32(mask)) != 0)
-#define EXT4_SET_COMPAT_FEATURE(sb,mask) EXT4_SB(sb)->s_es->s_feature_compat |= cpu_to_le32(mask)
-#define EXT4_SET_RO_COMPAT_FEATURE(sb,mask) EXT4_SB(sb)->s_es->s_feature_ro_compat |= cpu_to_le32(mask)
-#define EXT4_SET_INCOMPAT_FEATURE(sb,mask) EXT4_SB(sb)->s_es->s_feature_incompat |= cpu_to_le32(mask)
-#define EXT4_CLEAR_COMPAT_FEATURE(sb,mask) EXT4_SB(sb)->s_es->s_feature_compat &= ~cpu_to_le32(mask)
-#define EXT4_CLEAR_RO_COMPAT_FEATURE(sb,mask) EXT4_SB(sb)->s_es->s_feature_ro_compat &= ~cpu_to_le32(mask)
-#define EXT4_CLEAR_INCOMPAT_FEATURE(sb,mask) EXT4_SB(sb)->s_es->s_feature_incompat &= ~cpu_to_le32(mask)
+#define EXT4_HAS_COMPAT_FEATURE(sb, mask) \
+ ((EXT4_SB(sb)->s_es->s_feature_compat & cpu_to_le32(mask)) != 0)
+#define EXT4_HAS_RO_COMPAT_FEATURE(sb, mask) \
+ ((EXT4_SB(sb)->s_es->s_feature_ro_compat & cpu_to_le32(mask)) != 0)
+#define EXT4_HAS_INCOMPAT_FEATURE(sb, mask) \
+ ((EXT4_SB(sb)->s_es->s_feature_incompat & cpu_to_le32(mask)) != 0)
+#define EXT4_SET_COMPAT_FEATURE(sb, mask) EXT4_SB(sb)->s_es->s_feature_compat |= cpu_to_le32(mask)
+#define EXT4_SET_RO_COMPAT_FEATURE(sb, mask) \
+ EXT4_SB(sb)->s_es->s_feature_ro_compat |= cpu_to_le32(mask)
+#define EXT4_SET_INCOMPAT_FEATURE(sb, mask) \
+ EXT4_SB(sb)->s_es->s_feature_incompat |= cpu_to_le32(mask)
+#define EXT4_CLEAR_COMPAT_FEATURE(sb, mask) \
+ EXT4_SB(sb)->s_es->s_feature_compat &= ~cpu_to_le32(mask)
+#define EXT4_CLEAR_RO_COMPAT_FEATURE(sb, mask) \
+ EXT4_SB(sb)->s_es->s_feature_ro_compat &= ~cpu_to_le32(mask)
+#define EXT4_CLEAR_INCOMPAT_FEATURE(sb, mask) \
+ EXT4_SB(sb)->s_es->s_feature_incompat &= ~cpu_to_le32(mask)
#define EXT4_FEATURE_COMPAT_DIR_PREALLOC 0x0001
#define EXT4_FEATURE_COMPAT_IMAGIC_INODES 0x0002
@@ -502,21 +543,28 @@ struct ext4_super_block {
#define EXT4_FEATURE_INCOMPAT_COMPRESSION 0x0001
#define EXT4_FEATURE_INCOMPAT_FILETYPE 0x0002
-#define EXT4_FEATURE_INCOMPAT_RECOVER 0x0004
-#define EXT4_FEATURE_INCOMPAT_JOURNAL_DEV 0x0008
+#define EXT4_FEATURE_INCOMPAT_RECOVER 0x0004
+#define EXT4_FEATURE_INCOMPAT_JOURNAL_DEV 0x0008
#define EXT4_FEATURE_INCOMPAT_META_BG 0x0010
-#define EXT4_FEATURE_INCOMPAT_EXTENTS 0x0040
+#define EXT4_FEATURE_INCOMPAT_EXTENTS 0x0040
#define EXT4_FEATURE_INCOMPAT_64BIT 0x0080
#define EXT4_FEATURE_INCOMPAT_MMP 0x0100
#define EXT4_FEATURE_INCOMPAT_FLEX_BG 0x0200
-#define EXT4_FEATURE_INCOMPAT_EA_INODE 0x0400
-#define EXT4_FEATURE_INCOMPAT_DIRDATA 0x1000
+#define EXT4_FEATURE_INCOMPAT_EA_INODE 0x0400
+#define EXT4_FEATURE_INCOMPAT_DIRDATA 0x1000
#define EXT4_FEATURE_INCOMPAT_ENCRYPT 0x10000
#define EXT4_FEATURE_INCOMPAT_CASEFOLD 0x20000
#define EXT4_FEATURE_COMPAT_SUPP EXT2_FEATURE_COMPAT_EXT_ATTR
-#define EXT4_FEATURE_INCOMPAT_SUPP (EXT4_FEATURE_INCOMPAT_FILETYPE| EXT4_FEATURE_INCOMPAT_RECOVER| EXT4_FEATURE_INCOMPAT_META_BG| EXT4_FEATURE_INCOMPAT_EXTENTS| EXT4_FEATURE_INCOMPAT_64BIT| EXT4_FEATURE_INCOMPAT_FLEX_BG)
-#define EXT4_FEATURE_RO_COMPAT_SUPP (EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER| EXT4_FEATURE_RO_COMPAT_LARGE_FILE| EXT4_FEATURE_RO_COMPAT_GDT_CSUM| EXT4_FEATURE_RO_COMPAT_DIR_NLINK | EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE | EXT4_FEATURE_RO_COMPAT_BTREE_DIR | EXT4_FEATURE_RO_COMPAT_HUGE_FILE)
+#define EXT4_FEATURE_INCOMPAT_SUPP \
+ (EXT4_FEATURE_INCOMPAT_FILETYPE | EXT4_FEATURE_INCOMPAT_RECOVER | \
+ EXT4_FEATURE_INCOMPAT_META_BG | EXT4_FEATURE_INCOMPAT_EXTENTS | EXT4_FEATURE_INCOMPAT_64BIT | \
+ EXT4_FEATURE_INCOMPAT_FLEX_BG)
+#define EXT4_FEATURE_RO_COMPAT_SUPP \
+ (EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER | EXT4_FEATURE_RO_COMPAT_LARGE_FILE | \
+ EXT4_FEATURE_RO_COMPAT_GDT_CSUM | EXT4_FEATURE_RO_COMPAT_DIR_NLINK | \
+ EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE | EXT4_FEATURE_RO_COMPAT_BTREE_DIR | \
+ EXT4_FEATURE_RO_COMPAT_HUGE_FILE)
#define EXT4_DEF_RESUID 0
#define EXT4_DEF_RESGID 0
@@ -534,25 +582,25 @@ struct ext4_super_block {
#define EXT4_DEFM_JMODE_WBACK 0x0060
#define EXT4_DEF_MIN_BATCH_TIME 0
-#define EXT4_DEF_MAX_BATCH_TIME 15000
+#define EXT4_DEF_MAX_BATCH_TIME 15000
#define EXT4_FLEX_SIZE_DIR_ALLOC_SCHEME 4
#define EXT4_NAME_LEN 255
struct ext4_dir_entry {
- __le32 inode;
- __le16 rec_len;
- __le16 name_len;
- char name[EXT4_NAME_LEN];
+ __le32 inode;
+ __le16 rec_len;
+ __le16 name_len;
+ char name[EXT4_NAME_LEN];
};
struct ext4_dir_entry_2 {
- __le32 inode;
- __le16 rec_len;
- __u8 name_len;
- __u8 file_type;
- char name[EXT4_NAME_LEN];
+ __le32 inode;
+ __le16 rec_len;
+ __u8 name_len;
+ __u8 file_type;
+ char name[EXT4_NAME_LEN];
};
#define EXT4_FT_UNKNOWN 0
@@ -568,10 +616,12 @@ struct ext4_dir_entry_2 {
#define EXT4_DIR_PAD 4
#define EXT4_DIR_ROUND (EXT4_DIR_PAD - 1)
-#define EXT4_DIR_REC_LEN(name_len) (((name_len) + 8 + EXT4_DIR_ROUND) & ~EXT4_DIR_ROUND)
-#define EXT4_MAX_REC_LEN ((1<<16)-1)
+#define EXT4_DIR_REC_LEN(name_len) (((name_len) + 8 + EXT4_DIR_ROUND) & ~EXT4_DIR_ROUND)
+#define EXT4_MAX_REC_LEN ((1 << 16) - 1)
-#define is_dx(dir) (EXT4_HAS_COMPAT_FEATURE((dir)->i_sb, EXT4_FEATURE_COMPAT_DIR_INDEX) && (EXT4_I(dir)->i_flags & EXT4_INDEX_FL))
+#define is_dx(dir) \
+ (EXT4_HAS_COMPAT_FEATURE((dir)->i_sb, EXT4_FEATURE_COMPAT_DIR_INDEX) && \
+ (EXT4_I(dir)->i_flags & EXT4_INDEX_FL))
#define EXT4_DIR_LINK_MAX(dir) (!is_dx(dir) && (dir)->i_nlink >= EXT4_LINK_MAX)
#define EXT4_DIR_LINK_EMPTY(dir) ((dir)->i_nlink == 2 || (dir)->i_nlink == 1)
@@ -583,4 +633,3 @@ struct ext4_dir_entry_2 {
#define DX_HASH_TEA_UNSIGNED 5
#endif
-
diff --git a/ext4_utils/include/ext4_utils/ext4_extents.h b/ext4_utils/include/ext4_utils/ext4_extents.h
index 68fc4a45..e69b017b 100644
--- a/ext4_utils/include/ext4_utils/ext4_extents.h
+++ b/ext4_utils/include/ext4_utils/ext4_extents.h
@@ -30,36 +30,36 @@
#define EXT_STATS_
struct ext4_extent {
- __le32 ee_block;
- __le16 ee_len;
- __le16 ee_start_hi;
- __le32 ee_start_lo;
+ __le32 ee_block;
+ __le16 ee_len;
+ __le16 ee_start_hi;
+ __le32 ee_start_lo;
};
struct ext4_extent_idx {
- __le32 ei_block;
- __le32 ei_leaf_lo;
- __le16 ei_leaf_hi;
- __u16 ei_unused;
+ __le32 ei_block;
+ __le32 ei_leaf_lo;
+ __le16 ei_leaf_hi;
+ __u16 ei_unused;
};
struct ext4_extent_header {
- __le16 eh_magic;
- __le16 eh_entries;
- __le16 eh_max;
- __le16 eh_depth;
- __le32 eh_generation;
+ __le16 eh_magic;
+ __le16 eh_entries;
+ __le16 eh_max;
+ __le16 eh_depth;
+ __le32 eh_generation;
};
#define EXT4_EXT_MAGIC 0xf30a
struct ext4_ext_path {
- ext4_fsblk_t p_block;
- __u16 p_depth;
- struct ext4_extent *p_ext;
- struct ext4_extent_idx *p_idx;
- struct ext4_extent_header *p_hdr;
- struct buffer_head *p_bh;
+ ext4_fsblk_t p_block;
+ __u16 p_depth;
+ struct ext4_extent* p_ext;
+ struct ext4_extent_idx* p_idx;
+ struct ext4_extent_header* p_hdr;
+ struct buffer_head* p_bh;
};
#define EXT4_EXT_CACHE_NO 0
@@ -75,14 +75,17 @@ struct ext4_ext_path {
#define EXT_INIT_MAX_LEN (1UL << 15)
#define EXT_UNINIT_MAX_LEN (EXT_INIT_MAX_LEN - 1)
-#define EXT_FIRST_EXTENT(__hdr__) ((struct ext4_extent *) (((char *) (__hdr__)) + sizeof(struct ext4_extent_header)))
-#define EXT_FIRST_INDEX(__hdr__) ((struct ext4_extent_idx *) (((char *) (__hdr__)) + sizeof(struct ext4_extent_header)))
-#define EXT_HAS_FREE_INDEX(__path__) (le16_to_cpu((__path__)->p_hdr->eh_entries) < le16_to_cpu((__path__)->p_hdr->eh_max))
-#define EXT_LAST_EXTENT(__hdr__) (EXT_FIRST_EXTENT((__hdr__)) + le16_to_cpu((__hdr__)->eh_entries) - 1)
-#define EXT_LAST_INDEX(__hdr__) (EXT_FIRST_INDEX((__hdr__)) + le16_to_cpu((__hdr__)->eh_entries) - 1)
-#define EXT_MAX_EXTENT(__hdr__) (EXT_FIRST_EXTENT((__hdr__)) + le16_to_cpu((__hdr__)->eh_max) - 1)
-#define EXT_MAX_INDEX(__hdr__) (EXT_FIRST_INDEX((__hdr__)) + le16_to_cpu((__hdr__)->eh_max) - 1)
+#define EXT_FIRST_EXTENT(__hdr__) \
+ ((struct ext4_extent*)(((char*)(__hdr__)) + sizeof(struct ext4_extent_header)))
+#define EXT_FIRST_INDEX(__hdr__) \
+ ((struct ext4_extent_idx*)(((char*)(__hdr__)) + sizeof(struct ext4_extent_header)))
+#define EXT_HAS_FREE_INDEX(__path__) \
+ (le16_to_cpu((__path__)->p_hdr->eh_entries) < le16_to_cpu((__path__)->p_hdr->eh_max))
+#define EXT_LAST_EXTENT(__hdr__) \
+ (EXT_FIRST_EXTENT((__hdr__)) + le16_to_cpu((__hdr__)->eh_entries) - 1)
+#define EXT_LAST_INDEX(__hdr__) \
+ (EXT_FIRST_INDEX((__hdr__)) + le16_to_cpu((__hdr__)->eh_entries) - 1)
+#define EXT_MAX_EXTENT(__hdr__) (EXT_FIRST_EXTENT((__hdr__)) + le16_to_cpu((__hdr__)->eh_max) - 1)
+#define EXT_MAX_INDEX(__hdr__) (EXT_FIRST_INDEX((__hdr__)) + le16_to_cpu((__hdr__)->eh_max) - 1)
#endif
-
-
diff --git a/ext4_utils/include/ext4_utils/ext4_sb.h b/ext4_utils/include/ext4_utils/ext4_sb.h
index ab8b42f7..81d8e73a 100644
--- a/ext4_utils/include/ext4_utils/ext4_sb.h
+++ b/ext4_utils/include/ext4_utils/ext4_sb.h
@@ -28,27 +28,28 @@ extern "C" {
#include <stdbool.h>
struct fs_info {
- int64_t len; /* If set to 0, ask the block device for the size,
- * if less than 0, reserve that much space at the
- * end of the partition, else use the size given. */
- uint32_t block_size;
- uint32_t blocks_per_group;
- uint32_t flash_erase_block_size;
- uint32_t flash_logical_block_size;
- uint32_t inodes_per_group;
- uint32_t inode_size;
- uint32_t inodes;
- uint32_t journal_blocks;
- uint32_t feat_ro_compat;
- uint32_t feat_compat;
- uint32_t feat_incompat;
- uint32_t bg_desc_reserve_blocks;
- const char *label;
- uint8_t no_journal;
- bool block_device; /* target fd is a block device? */
+ int64_t len; /* If set to 0, ask the block device for the size,
+ * if less than 0, reserve that much space at the
+ * end of the partition, else use the size given. */
+ uint32_t block_size;
+ uint32_t blocks_per_group;
+ uint32_t flash_erase_block_size;
+ uint32_t flash_logical_block_size;
+ uint32_t inodes_per_group;
+ uint32_t inode_size;
+ uint32_t inodes;
+ uint32_t journal_blocks;
+ uint32_t feat_ro_compat;
+ uint32_t feat_compat;
+ uint32_t feat_incompat;
+ uint32_t bg_desc_reserve_blocks;
+ uint16_t bg_desc_size;
+ const char* label;
+ uint8_t no_journal;
+ bool block_device; /* target fd is a block device? */
};
-int ext4_parse_sb(struct ext4_super_block *sb, struct fs_info *info);
+int ext4_parse_sb(struct ext4_super_block* sb, struct fs_info* info);
#ifdef __cplusplus
}
diff --git a/ext4_utils/include/ext4_utils/ext4_utils.h b/ext4_utils/include/ext4_utils/ext4_utils.h
index 27156145..48f3ee78 100644
--- a/ext4_utils/include/ext4_utils/ext4_utils.h
+++ b/ext4_utils/include/ext4_utils/ext4_utils.h
@@ -29,14 +29,14 @@ extern "C" {
#include <sys/types.h>
#include <unistd.h>
-#include <sys/types.h>
#include <errno.h>
+#include <setjmp.h>
#include <stdarg.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <setjmp.h>
-#include <stdint.h>
+#include <sys/types.h>
#if defined(__APPLE__) && defined(__MACH__)
#define lseek64 lseek
@@ -49,12 +49,6 @@ extern "C" {
extern int force;
-#define warn(fmt, args...) do { fprintf(stderr, "warning: %s: " fmt "\n", __func__, ## args); } while (0)
-#define error(fmt, args...) do { fprintf(stderr, "error: %s: " fmt "\n", __func__, ## args); if (!force) longjmp(setjmp_env, EXIT_FAILURE); } while (0)
-#define error_errno(s, args...) error(s ": %s", ##args, strerror(errno))
-#define critical_error(fmt, args...) do { fprintf(stderr, "critical error: %s: " fmt "\n", __func__, ## args); longjmp(setjmp_env, EXIT_FAILURE); } while (0)
-#define critical_error_errno(s, args...) critical_error(s ": %s", ##args, strerror(errno))
-
#define EXT4_JNL_BACKUP_BLOCKS 1
#ifndef __cplusplus
@@ -63,8 +57,8 @@ extern int force;
#endif
#endif
-#define DIV_ROUND_UP(x, y) (((x) + (y) - 1)/(y))
-#define EXT4_ALIGN(x, y) ((y) * DIV_ROUND_UP((x), (y)))
+#define DIV_ROUND_UP(x, y) (((x) + (y)-1) / (y))
+#define EXT4_ALIGN(x, y) ((y)*DIV_ROUND_UP((x), (y)))
/* XXX */
#define cpu_to_le32(x) (x)
@@ -91,35 +85,32 @@ struct block_group_info;
struct xattr_list_element;
struct ext2_group_desc {
- u32 bg_block_bitmap;
- u32 bg_inode_bitmap;
- u32 bg_inode_table;
- u16 bg_free_blocks_count;
- u16 bg_free_inodes_count;
- u16 bg_used_dirs_count;
- u16 bg_flags;
- u32 bg_reserved[2];
- u16 bg_reserved16;
- u16 bg_checksum;
+ u64 bg_block_bitmap;
+ u64 bg_inode_bitmap;
+ u64 bg_inode_table;
+ u32 bg_free_blocks_count;
+ u32 bg_free_inodes_count;
+ u32 bg_used_dirs_count;
+ u16 bg_flags;
};
struct fs_aux_info {
- struct ext4_super_block *sb;
- struct ext4_super_block *sb_block;
- struct ext4_super_block *sb_zero;
- struct ext4_super_block **backup_sb;
- struct ext2_group_desc *bg_desc;
- struct block_group_info *bgs;
- struct xattr_list_element *xattrs;
- u32 first_data_block;
- u64 len_blocks;
- u32 inode_table_blocks;
- u32 groups;
- u32 bg_desc_blocks;
- u32 default_i_flags;
- u64 blocks_per_ind;
- u64 blocks_per_dind;
- u64 blocks_per_tind;
+ struct ext4_super_block* sb;
+ struct ext4_super_block* sb_block;
+ struct ext4_super_block* sb_zero;
+ struct ext4_super_block** backup_sb;
+ struct ext2_group_desc* bg_desc;
+ struct block_group_info* bgs;
+ struct xattr_list_element* xattrs;
+ u32 first_data_block;
+ u64 len_blocks;
+ u32 inode_table_blocks;
+ u32 groups;
+ u32 bg_desc_blocks;
+ u32 default_i_flags;
+ u64 blocks_per_ind;
+ u64 blocks_per_dind;
+ u64 blocks_per_tind;
};
extern struct fs_info info;
@@ -127,12 +118,12 @@ extern struct fs_aux_info aux_info;
extern jmp_buf setjmp_env;
-int bitmap_get_bit(u8 *bitmap, u32 bit); // vold
-u64 get_block_device_size(int fd); // recovery
-int is_block_device_fd(int fd); // wipe.c
-u64 get_file_size(int fd); // fs_mgr
-
-int read_ext(int fd, int verbose); // vold
+int bitmap_get_bit(u8* bitmap, u32 bit); // vold
+u64 get_block_device_size(int fd); // recovery
+int is_block_device_fd(int fd); // wipe.c
+u64 get_file_size(int fd); // fs_mgr
+int ext4_bg_has_super_block(int bg);
+int read_ext(int fd, int verbose); // vold
#ifdef __cplusplus
}
diff --git a/ext4_utils/include/ext4_utils/jbd2.h b/ext4_utils/include/ext4_utils/jbd2.h
index bac58c2e..6b7b161c 100644
--- a/ext4_utils/include/ext4_utils/jbd2.h
+++ b/ext4_utils/include/ext4_utils/jbd2.h
@@ -21,11 +21,11 @@
#define JBD2_DEFAULT_MAX_COMMIT_AGE 5
-#define jbd_debug(f, a...)
+#define jbd_debug(f, a...)
#define JBD2_MIN_JOURNAL_BLOCKS 1024
-#define JBD2_MAGIC_NUMBER 0xc03b3998U
+#define JBD2_MAGIC_NUMBER 0xc03b3998U
#define JBD2_DESCRIPTOR_BLOCK 1
#define JBD2_COMMIT_BLOCK 2
@@ -33,11 +33,10 @@
#define JBD2_SUPERBLOCK_V2 4
#define JBD2_REVOKE_BLOCK 5
-typedef struct journal_header_s
-{
- __be32 h_magic;
- __be32 h_blocktype;
- __be32 h_sequence;
+typedef struct journal_header_s {
+ __be32 h_magic;
+ __be32 h_blocktype;
+ __be32 h_sequence;
} journal_header_t;
#define JBD2_CRC32_CHKSUM 1
@@ -49,74 +48,73 @@ typedef struct journal_header_s
#define JBD2_CHECKSUM_BYTES (32 / sizeof(__u32))
struct commit_header {
- __be32 h_magic;
- __be32 h_blocktype;
- __be32 h_sequence;
- unsigned char h_chksum_type;
- unsigned char h_chksum_size;
- unsigned char h_padding[2];
- __be32 h_chksum[JBD2_CHECKSUM_BYTES];
- __be64 h_commit_sec;
- __be32 h_commit_nsec;
+ __be32 h_magic;
+ __be32 h_blocktype;
+ __be32 h_sequence;
+ unsigned char h_chksum_type;
+ unsigned char h_chksum_size;
+ unsigned char h_padding[2];
+ __be32 h_chksum[JBD2_CHECKSUM_BYTES];
+ __be64 h_commit_sec;
+ __be32 h_commit_nsec;
};
-typedef struct journal_block_tag_s
-{
- __be32 t_blocknr;
- __be32 t_flags;
- __be32 t_blocknr_high;
+typedef struct journal_block_tag_s {
+ __be32 t_blocknr;
+ __be32 t_flags;
+ __be32 t_blocknr_high;
} journal_block_tag_t;
#define JBD2_TAG_SIZE32 (offsetof(journal_block_tag_t, t_blocknr_high))
#define JBD2_TAG_SIZE64 (sizeof(journal_block_tag_t))
-typedef struct jbd2_journal_revoke_header_s
-{
- journal_header_t r_header;
- __be32 r_count;
+typedef struct jbd2_journal_revoke_header_s {
+ journal_header_t r_header;
+ __be32 r_count;
} jbd2_journal_revoke_header_t;
-#define JBD2_FLAG_ESCAPE 1
-#define JBD2_FLAG_SAME_UUID 2
-#define JBD2_FLAG_DELETED 4
-#define JBD2_FLAG_LAST_TAG 8
+#define JBD2_FLAG_ESCAPE 1
+#define JBD2_FLAG_SAME_UUID 2
+#define JBD2_FLAG_DELETED 4
+#define JBD2_FLAG_LAST_TAG 8
-typedef struct journal_superblock_s
-{
+typedef struct journal_superblock_s {
+ journal_header_t s_header;
- journal_header_t s_header;
+ __be32 s_blocksize;
+ __be32 s_maxlen;
+ __be32 s_first;
- __be32 s_blocksize;
- __be32 s_maxlen;
- __be32 s_first;
+ __be32 s_sequence;
+ __be32 s_start;
- __be32 s_sequence;
- __be32 s_start;
+ __be32 s_errno;
- __be32 s_errno;
+ __be32 s_feature_compat;
+ __be32 s_feature_incompat;
+ __be32 s_feature_ro_compat;
- __be32 s_feature_compat;
- __be32 s_feature_incompat;
- __be32 s_feature_ro_compat;
+ __u8 s_uuid[16];
- __u8 s_uuid[16];
+ __be32 s_nr_users;
- __be32 s_nr_users;
+ __be32 s_dynsuper;
- __be32 s_dynsuper;
+ __be32 s_max_transaction;
+ __be32 s_max_trans_data;
- __be32 s_max_transaction;
- __be32 s_max_trans_data;
+ __u32 s_padding[44];
- __u32 s_padding[44];
-
- __u8 s_users[16*48];
+ __u8 s_users[16 * 48];
} journal_superblock_t;
-#define JBD2_HAS_COMPAT_FEATURE(j,mask) ((j)->j_format_version >= 2 && ((j)->j_superblock->s_feature_compat & cpu_to_be32((mask))))
-#define JBD2_HAS_RO_COMPAT_FEATURE(j,mask) ((j)->j_format_version >= 2 && ((j)->j_superblock->s_feature_ro_compat & cpu_to_be32((mask))))
-#define JBD2_HAS_INCOMPAT_FEATURE(j,mask) ((j)->j_format_version >= 2 && ((j)->j_superblock->s_feature_incompat & cpu_to_be32((mask))))
+#define JBD2_HAS_COMPAT_FEATURE(j, mask) \
+ ((j)->j_format_version >= 2 && ((j)->j_superblock->s_feature_compat & cpu_to_be32((mask))))
+#define JBD2_HAS_RO_COMPAT_FEATURE(j, mask) \
+ ((j)->j_format_version >= 2 && ((j)->j_superblock->s_feature_ro_compat & cpu_to_be32((mask))))
+#define JBD2_HAS_INCOMPAT_FEATURE(j, mask) \
+ ((j)->j_format_version >= 2 && ((j)->j_superblock->s_feature_incompat & cpu_to_be32((mask))))
#define JBD2_FEATURE_COMPAT_CHECKSUM 0x00000001
@@ -126,16 +124,17 @@ typedef struct journal_superblock_s
#define JBD2_KNOWN_COMPAT_FEATURES JBD2_FEATURE_COMPAT_CHECKSUM
#define JBD2_KNOWN_ROCOMPAT_FEATURES 0
-#define JBD2_KNOWN_INCOMPAT_FEATURES (JBD2_FEATURE_INCOMPAT_REVOKE | JBD2_FEATURE_INCOMPAT_64BIT | JBD2_FEATURE_INCOMPAT_ASYNC_COMMIT)
-
-#define BJ_None 0
-#define BJ_Metadata 1
-#define BJ_Forget 2
-#define BJ_IO 3
-#define BJ_Shadow 4
-#define BJ_LogCtl 5
-#define BJ_Reserved 6
+#define JBD2_KNOWN_INCOMPAT_FEATURES \
+ (JBD2_FEATURE_INCOMPAT_REVOKE | JBD2_FEATURE_INCOMPAT_64BIT | \
+ JBD2_FEATURE_INCOMPAT_ASYNC_COMMIT)
+
+#define BJ_None 0
+#define BJ_Metadata 1
+#define BJ_Forget 2
+#define BJ_IO 3
+#define BJ_Shadow 4
+#define BJ_LogCtl 5
+#define BJ_Reserved 6
#define BJ_Types 7
#endif
-
diff --git a/ext4_utils/include/ext4_utils/wipe.h b/ext4_utils/include/ext4_utils/wipe.h
index d7d089e7..09c32955 100644
--- a/ext4_utils/include/ext4_utils/wipe.h
+++ b/ext4_utils/include/ext4_utils/wipe.h
@@ -27,9 +27,9 @@ extern "C" {
* wiping of block devices. 0 otherwise. For now, only Linux does.
*/
#ifdef __linux__
-# define WIPE_IS_SUPPORTED 1
+#define WIPE_IS_SUPPORTED 1
#else
-# define WIPE_IS_SUPPORTED 0
+#define WIPE_IS_SUPPORTED 0
#endif
int wipe_block_device(int fd, s64 len);
diff --git a/ext4_utils/include/ext4_utils/xattr.h b/ext4_utils/include/ext4_utils/xattr.h
index 0f323fdc..4251c288 100644
--- a/ext4_utils/include/ext4_utils/xattr.h
+++ b/ext4_utils/include/ext4_utils/xattr.h
@@ -7,16 +7,16 @@
#define EXT4_XATTR_INDEX_SECURITY 6
struct ext4_xattr_header {
- __le32 h_magic;
- __le32 h_refcount;
- __le32 h_blocks;
- __le32 h_hash;
- __le32 h_checksum;
- __u32 h_reserved[3];
+ __le32 h_magic;
+ __le32 h_refcount;
+ __le32 h_blocks;
+ __le32 h_hash;
+ __le32 h_checksum;
+ __u32 h_reserved[3];
};
struct ext4_xattr_ibody_header {
- __le32 h_magic;
+ __le32 h_magic;
};
struct ext4_xattr_entry {
@@ -30,16 +30,13 @@ struct ext4_xattr_entry {
};
#define EXT4_XATTR_PAD_BITS 2
-#define EXT4_XATTR_PAD (1<<EXT4_XATTR_PAD_BITS)
-#define EXT4_XATTR_ROUND (EXT4_XATTR_PAD-1)
+#define EXT4_XATTR_PAD (1 << EXT4_XATTR_PAD_BITS)
+#define EXT4_XATTR_ROUND (EXT4_XATTR_PAD - 1)
#define EXT4_XATTR_LEN(name_len) \
- (((name_len) + EXT4_XATTR_ROUND + \
- sizeof(struct ext4_xattr_entry)) & ~EXT4_XATTR_ROUND)
+ (((name_len) + EXT4_XATTR_ROUND + sizeof(struct ext4_xattr_entry)) & ~EXT4_XATTR_ROUND)
#define EXT4_XATTR_NEXT(entry) \
- ((struct ext4_xattr_entry *)( \
- (char *)(entry) + EXT4_XATTR_LEN((entry)->e_name_len)))
-#define EXT4_XATTR_SIZE(size) \
- (((size) + EXT4_XATTR_ROUND) & ~EXT4_XATTR_ROUND)
-#define IS_LAST_ENTRY(entry) (*(uint32_t *)(entry) == 0)
+ ((struct ext4_xattr_entry*)((char*)(entry) + EXT4_XATTR_LEN((entry)->e_name_len)))
+#define EXT4_XATTR_SIZE(size) (((size) + EXT4_XATTR_ROUND) & ~EXT4_XATTR_ROUND)
+#define IS_LAST_ENTRY(entry) (*(uint32_t*)(entry) == 0)
#endif /* !_SYSTEM_EXTRAS_EXT4_UTILS_XATTR_H */
diff --git a/ext4_utils/mkuserimg_mke2fs.py b/ext4_utils/mkuserimg_mke2fs.py
index 4633426b..d4a14f87 100644
--- a/ext4_utils/mkuserimg_mke2fs.py
+++ b/ext4_utils/mkuserimg_mke2fs.py
@@ -35,6 +35,8 @@ def RunCommand(cmd, env):
env_copy = os.environ.copy()
env_copy.update(env)
+ cmd[0] = FindProgram(cmd[0])
+
logging.info("Env: %s", env)
logging.info("Running: " + " ".join(cmd))
@@ -44,6 +46,21 @@ def RunCommand(cmd, env):
return output, p.returncode
+def FindProgram(prog_name):
+ """Finds the path to prog_name.
+
+ Args:
+ prog_name: the program name to find.
+ Returns:
+ path to the progName if found. The program is searched in the same directory
+ where this script is located at. If not found, progName is returned.
+ """
+ exec_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
+ prog_path = os.path.join(exec_dir, prog_name)
+ if os.path.exists(prog_path):
+ return prog_path
+ else:
+ return prog_name
def ParseArguments(argv):
"""Parses the input arguments to the program."""
diff --git a/ext4_utils/wipe.cpp b/ext4_utils/wipe.cpp
index c2db3353..445c9739 100644
--- a/ext4_utils/wipe.cpp
+++ b/ext4_utils/wipe.cpp
@@ -27,60 +27,60 @@
#include <linux/fs.h>
#include <sys/ioctl.h>
+#include "helpers.h"
+
#ifndef BLKDISCARD
-#define BLKDISCARD _IO(0x12,119)
+#define BLKDISCARD _IO(0x12, 119)
#endif
#ifndef BLKSECDISCARD
-#define BLKSECDISCARD _IO(0x12,125)
+#define BLKSECDISCARD _IO(0x12, 125)
#endif
-int wipe_block_device(int fd, s64 len)
-{
- u64 range[2];
- int ret;
-
- if (!is_block_device_fd(fd)) {
- // Wiping only makes sense on a block device.
- return 0;
- }
-
- range[0] = 0;
- range[1] = len;
- ret = ioctl(fd, BLKSECDISCARD, &range);
- if (ret < 0) {
- range[0] = 0;
- range[1] = len;
- ret = ioctl(fd, BLKDISCARD, &range);
- if (ret < 0) {
- warn("Discard failed\n");
- return 1;
- } else {
- char buf[4096] = {0};
-
- if (!android::base::WriteFully(fd, buf, 4096)) {
- warn("Writing zeros failed\n");
- return 1;
- }
- fsync(fd);
- warn("Wipe via secure discard failed, used discard instead\n");
- return 0;
- }
- }
-
- return 0;
+int wipe_block_device(int fd, s64 len) {
+ u64 range[2];
+ int ret;
+
+ if (!is_block_device_fd(fd)) {
+ // Wiping only makes sense on a block device.
+ return 0;
+ }
+
+ range[0] = 0;
+ range[1] = len;
+ ret = ioctl(fd, BLKSECDISCARD, &range);
+ if (ret < 0) {
+ range[0] = 0;
+ range[1] = len;
+ ret = ioctl(fd, BLKDISCARD, &range);
+ if (ret < 0) {
+ warn("Discard failed\n");
+ return 1;
+ } else {
+ char buf[4096] = {0};
+
+ if (!android::base::WriteFully(fd, buf, 4096)) {
+ warn("Writing zeros failed\n");
+ return 1;
+ }
+ fsync(fd);
+ warn("Wipe via secure discard failed, used discard instead\n");
+ return 0;
+ }
+ }
+
+ return 0;
}
-#else /* __linux__ */
+#else /* __linux__ */
#error "Missing block device wiping implementation for this platform!"
#endif
-#else /* WIPE_IS_SUPPORTED */
+#else /* WIPE_IS_SUPPORTED */
-int wipe_block_device(int fd __attribute__((unused)), s64 len __attribute__((unused)))
-{
- /* Wiping is not supported on this platform. */
- return 1;
+int wipe_block_device(int fd __attribute__((unused)), s64 len __attribute__((unused))) {
+ /* Wiping is not supported on this platform. */
+ return 1;
}
-#endif /* WIPE_IS_SUPPORTED */
+#endif /* WIPE_IS_SUPPORTED */
diff --git a/f2fs_utils/Android.bp b/f2fs_utils/Android.bp
index c3d3995c..dc74d6d3 100644
--- a/f2fs_utils/Android.bp
+++ b/f2fs_utils/Android.bp
@@ -1,5 +1,22 @@
// Copyright 2017 The Android Open Source Project
+package {
+ default_applicable_licenses: ["system_extras_f2fs_utils_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_f2fs_utils_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_library_shared {
name: "libf2fs_sparseblock",
cflags: ["-Werror"],
@@ -13,10 +30,9 @@ cc_library_shared {
include_dirs: [
"external/f2fs-tools/include",
- "system/core/include/log",
],
- export_include_dirs: ["."]
+ export_include_dirs: ["."],
}
cc_binary {
@@ -32,7 +48,6 @@ cc_binary {
include_dirs: [
"external/f2fs-tools/include",
- "system/core/include/log",
],
}
@@ -40,6 +55,9 @@ sh_binary {
name: "mkf2fsuserimg.sh",
src: "mkf2fsuserimg.sh",
- required: ["make_f2fs", "sload_f2fs"],
+ required: [
+ "make_f2fs",
+ "sload_f2fs",
+ ],
host_supported: true,
}
diff --git a/f2fs_utils/f2fs_sparseblock.c b/f2fs_utils/f2fs_sparseblock.c
index 0ec1f30c..df59d7c4 100644
--- a/f2fs_utils/f2fs_sparseblock.c
+++ b/f2fs_utils/f2fs_sparseblock.c
@@ -16,28 +16,29 @@
#include <log/log.h>
-#define D_DISP_u32(ptr, member) \
- do { \
- SLOGD("%-30s" "\t\t[0x%#08x : %u]\n", \
- #member, le32_to_cpu((ptr)->member), le32_to_cpu((ptr)->member) ); \
- } while (0);
-
-#define D_DISP_u64(ptr, member) \
- do { \
- SLOGD("%-30s" "\t\t[0x%#016llx : %llu]\n", \
- #member, le64_to_cpu((ptr)->member), le64_to_cpu((ptr)->member) ); \
- } while (0);
-
-#define segno_in_journal(jnl, i) ((jnl)->sit_j.entries[i].segno)
-
-#define sit_in_journal(jnl, i) ((jnl)->sit_j.entries[i].se)
-
-static void dbg_print_raw_sb_info(struct f2fs_super_block *sb)
-{
- SLOGD("\n");
- SLOGD("+--------------------------------------------------------+\n");
- SLOGD("| Super block |\n");
- SLOGD("+--------------------------------------------------------+\n");
+#define D_DISP_u32(ptr, member) \
+ do { \
+ SLOGV("%-30s" \
+ "\t\t[0x%#08x : %u]\n", \
+ #member, le32_to_cpu((ptr)->member), le32_to_cpu((ptr)->member)); \
+ } while (0);
+
+#define D_DISP_u64(ptr, member) \
+ do { \
+ SLOGV("%-30s" \
+ "\t\t[0x%#016llx : %llu]\n", \
+ #member, le64_to_cpu((ptr)->member), le64_to_cpu((ptr)->member)); \
+ } while (0);
+
+#define segno_in_journal(jnl, i) ((jnl)->sit_j.entries[i].segno)
+
+#define sit_in_journal(jnl, i) ((jnl)->sit_j.entries[i].se)
+
+static void dbg_print_raw_sb_info(struct f2fs_super_block* sb) {
+ SLOGV("\n");
+ SLOGV("+--------------------------------------------------------+\n");
+ SLOGV("| Super block |\n");
+ SLOGV("+--------------------------------------------------------+\n");
D_DISP_u32(sb, magic);
D_DISP_u32(sb, major_ver);
@@ -72,14 +73,13 @@ static void dbg_print_raw_sb_info(struct f2fs_super_block *sb)
D_DISP_u32(sb, node_ino);
D_DISP_u32(sb, meta_ino);
D_DISP_u32(sb, cp_payload);
- SLOGD("\n");
+ SLOGV("\n");
}
-static void dbg_print_raw_ckpt_struct(struct f2fs_checkpoint *cp)
-{
- SLOGD("\n");
- SLOGD("+--------------------------------------------------------+\n");
- SLOGD("| Checkpoint |\n");
- SLOGD("+--------------------------------------------------------+\n");
+static void dbg_print_raw_ckpt_struct(struct f2fs_checkpoint* cp) {
+ SLOGV("\n");
+ SLOGV("+--------------------------------------------------------+\n");
+ SLOGV("| Checkpoint |\n");
+ SLOGV("+--------------------------------------------------------+\n");
D_DISP_u64(cp, checkpoint_ver);
D_DISP_u64(cp, user_block_count);
@@ -99,7 +99,6 @@ static void dbg_print_raw_ckpt_struct(struct f2fs_checkpoint *cp)
D_DISP_u32(cp, cur_node_blkoff[1]);
D_DISP_u32(cp, cur_node_blkoff[2]);
-
D_DISP_u32(cp, alloc_type[CURSEG_HOT_DATA]);
D_DISP_u32(cp, alloc_type[CURSEG_WARM_DATA]);
D_DISP_u32(cp, alloc_type[CURSEG_COLD_DATA]);
@@ -123,44 +122,41 @@ static void dbg_print_raw_ckpt_struct(struct f2fs_checkpoint *cp)
D_DISP_u64(cp, elapsed_time);
D_DISP_u32(cp, sit_nat_version_bitmap[0]);
- SLOGD("\n\n");
+ SLOGV("\n\n");
}
-static void dbg_print_info_struct(struct f2fs_info *info)
-{
- SLOGD("\n");
- SLOGD("+--------------------------------------------------------+\n");
- SLOGD("| F2FS_INFO |\n");
- SLOGD("+--------------------------------------------------------+\n");
- SLOGD("blocks_per_segment: %" PRIu64, info->blocks_per_segment);
- SLOGD("block_size: %d", info->block_size);
- SLOGD("sit_bmp loc: %p", info->sit_bmp);
- SLOGD("sit_bmp_size: %d", info->sit_bmp_size);
- SLOGD("blocks_per_sit: %" PRIu64, info->blocks_per_sit);
- SLOGD("sit_blocks loc: %p", info->sit_blocks);
- SLOGD("sit_sums loc: %p", info->sit_sums);
- SLOGD("sit_sums num: %d", le16_to_cpu(info->sit_sums->journal.n_sits));
+static void dbg_print_info_struct(struct f2fs_info* info) {
+ SLOGV("\n");
+ SLOGV("+--------------------------------------------------------+\n");
+ SLOGV("| F2FS_INFO |\n");
+ SLOGV("+--------------------------------------------------------+\n");
+ SLOGV("blocks_per_segment: %" PRIu64, info->blocks_per_segment);
+ SLOGV("block_size: %d", info->block_size);
+ SLOGV("sit_bmp loc: %p", info->sit_bmp);
+ SLOGV("sit_bmp_size: %d", info->sit_bmp_size);
+ 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));
unsigned int i;
- for(i = 0; i < (le16_to_cpu(info->sit_sums->journal.n_sits)); i++) {
- SLOGD("entry %d in journal entries is for segment %d", i,
+ for (i = 0; i < (le16_to_cpu(info->sit_sums->journal.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)));
}
- SLOGD("cp_blkaddr: %" PRIu64, info->cp_blkaddr);
- SLOGD("cp_valid_cp_blkaddr: %" PRIu64, info->cp_valid_cp_blkaddr);
- SLOGD("sit_blkaddr: %" PRIu64, info->sit_blkaddr);
- SLOGD("nat_blkaddr: %" PRIu64, info->nat_blkaddr);
- SLOGD("ssa_blkaddr: %" PRIu64, info->ssa_blkaddr);
- SLOGD("main_blkaddr: %" PRIu64, info->main_blkaddr);
- SLOGD("total_user_used: %" PRIu64, info->total_user_used);
- SLOGD("total_blocks: %" PRIu64, info->total_blocks);
- SLOGD("\n\n");
+ SLOGV("cp_blkaddr: %" PRIu64, info->cp_blkaddr);
+ SLOGV("cp_valid_cp_blkaddr: %" PRIu64, info->cp_valid_cp_blkaddr);
+ SLOGV("sit_blkaddr: %" PRIu64, info->sit_blkaddr);
+ SLOGV("nat_blkaddr: %" PRIu64, info->nat_blkaddr);
+ SLOGV("ssa_blkaddr: %" PRIu64, info->ssa_blkaddr);
+ SLOGV("main_blkaddr: %" PRIu64, info->main_blkaddr);
+ SLOGV("total_user_used: %" PRIu64, info->total_user_used);
+ SLOGV("total_blocks: %" PRIu64, info->total_blocks);
+ SLOGV("\n\n");
}
-
/* read blocks */
-static int read_structure(int fd, unsigned long long start, void *buf, ssize_t len)
-{
+static int read_structure(int fd, unsigned long long start, void* buf, ssize_t len) {
off64_t ret;
ret = lseek64(fd, start, SEEK_SET);
@@ -181,53 +177,46 @@ static int read_structure(int fd, unsigned long long start, void *buf, ssize_t l
return 0;
}
-static int read_structure_blk(int fd, unsigned long long start_blk, void *buf, size_t len)
-{
- return read_structure(fd, F2FS_BLKSIZE*start_blk, buf, F2FS_BLKSIZE * len);
+static int read_structure_blk(int fd, unsigned long long start_blk, void* buf, size_t len) {
+ return read_structure(fd, F2FS_BLKSIZE * start_blk, buf, F2FS_BLKSIZE * len);
}
-static int read_f2fs_sb(int fd, struct f2fs_super_block *sb)
-{
+static int read_f2fs_sb(int fd, struct f2fs_super_block* sb) {
int rc;
rc = read_structure(fd, F2FS_SUPER_OFFSET, sb, sizeof(*sb));
if (le32_to_cpu(sb->magic) != F2FS_SUPER_MAGIC) {
- SLOGE("Not a valid F2FS super block. Magic:%#08x != %#08x",
- le32_to_cpu(sb->magic), F2FS_SUPER_MAGIC);
+ SLOGE("Not a valid F2FS super block. Magic:%#08x != %#08x", le32_to_cpu(sb->magic),
+ F2FS_SUPER_MAGIC);
return -1;
}
return 0;
}
-unsigned int get_f2fs_filesystem_size_sec(char *dev)
-{
+unsigned int get_f2fs_filesystem_size_sec(char* dev) {
int fd;
if ((fd = open(dev, O_RDONLY)) < 0) {
SLOGE("Cannot open device to get filesystem size ");
return 0;
}
struct f2fs_super_block sb;
- if(read_f2fs_sb(fd, &sb))
- return 0;
- return (unsigned int)(le64_to_cpu(sb.block_count)*F2FS_BLKSIZE/DEFAULT_SECTOR_SIZE);
+ if (read_f2fs_sb(fd, &sb)) return 0;
+ return (unsigned int)(le64_to_cpu(sb.block_count) * F2FS_BLKSIZE / DEFAULT_SECTOR_SIZE);
}
-static struct f2fs_checkpoint *validate_checkpoint(block_t cp_addr,
- unsigned long long *version, int fd)
-{
+static struct f2fs_checkpoint* validate_checkpoint(block_t cp_addr, unsigned long long* version,
+ int fd) {
unsigned char *cp_block_1, *cp_block_2;
- struct f2fs_checkpoint *cp_block;
+ struct f2fs_checkpoint* cp_block;
uint64_t cp1_version = 0, cp2_version = 0;
cp_block_1 = malloc(F2FS_BLKSIZE);
- if (!cp_block_1)
- return NULL;
+ if (!cp_block_1) return NULL;
/* Read the 1st cp block in this CP pack */
- if (read_structure_blk(fd, cp_addr, cp_block_1, 1))
- goto invalid_cp1;
+ if (read_structure_blk(fd, cp_addr, cp_block_1, 1)) goto invalid_cp1;
/* get the version number */
- cp_block = (struct f2fs_checkpoint *)cp_block_1;
+ cp_block = (struct f2fs_checkpoint*)cp_block_1;
cp1_version = le64_to_cpu(cp_block->checkpoint_ver);
@@ -241,14 +230,14 @@ static struct f2fs_checkpoint *validate_checkpoint(block_t cp_addr,
goto invalid_cp2;
}
- cp_block = (struct f2fs_checkpoint *)cp_block_2;
+ cp_block = (struct f2fs_checkpoint*)cp_block_2;
cp2_version = le64_to_cpu(cp_block->checkpoint_ver);
if (cp2_version == cp1_version) {
*version = cp2_version;
free(cp_block_2);
- return (struct f2fs_checkpoint *)cp_block_1;
+ return (struct f2fs_checkpoint*)cp_block_1;
}
/* There must be something wrong with this checkpoint */
@@ -259,8 +248,8 @@ invalid_cp1:
return NULL;
}
-int get_valid_checkpoint_info(int fd, struct f2fs_super_block *sb, struct f2fs_checkpoint **cp, struct f2fs_info *info)
-{
+int get_valid_checkpoint_info(int fd, struct f2fs_super_block* sb, struct f2fs_checkpoint** cp,
+ struct f2fs_info* info) {
struct f2fs_checkpoint *cp1, *cp2, *cur_cp;
unsigned long blk_size;
unsigned long long cp1_version = 0, cp2_version = 0;
@@ -308,26 +297,25 @@ fail_no_cp:
return -EINVAL;
}
-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) / info->blocks_per_segment;
+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) /
+ info->blocks_per_segment;
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));
- if (!info->sit_blocks)
- return -1;
+ if (!info->sit_blocks) return -1;
- for(sit_block = 0; sit_block<num_sit_blocks; sit_block++) {
+ for (sit_block = 0; sit_block < num_sit_blocks; sit_block++) {
off64_t address = info->sit_blkaddr + sit_block;
- if (f2fs_test_bit(sit_block, info->sit_bmp))
- address += info->blocks_per_sit;
+ if (f2fs_test_bit(sit_block, info->sit_bmp)) address += info->blocks_per_sit;
- SLOGD("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))) {
- SLOGE("Could not read sit block at block %"PRIu64, address);
+ 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))) {
+ SLOGE("Could not read sit block at block %" PRIu64, address);
free(info->sit_blocks);
info->sit_blocks = NULL;
return -1;
@@ -336,29 +324,27 @@ static int gather_sit_info(int fd, struct f2fs_info *info)
return 0;
}
-static inline int is_set_ckpt_flags(struct f2fs_checkpoint *cp, unsigned int f)
-{
+static inline int is_set_ckpt_flags(struct f2fs_checkpoint* cp, unsigned int f) {
unsigned int ckpt_flags = le32_to_cpu(cp->ckpt_flags);
return !!(ckpt_flags & f);
}
-static inline uint64_t sum_blk_addr(struct f2fs_checkpoint *cp, struct f2fs_info *info, int base, int type)
-{
- return info->cp_valid_cp_blkaddr + le32_to_cpu(cp->cp_pack_total_block_count)
- - (base + 1) + type;
+static inline uint64_t sum_blk_addr(struct f2fs_checkpoint* cp, struct f2fs_info* info, int base,
+ int type) {
+ return info->cp_valid_cp_blkaddr + le32_to_cpu(cp->cp_pack_total_block_count) - (base + 1) +
+ type;
}
-static int get_sit_summary(int fd, struct f2fs_info *info, struct f2fs_checkpoint *cp)
-{
+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));
- if (!info->sit_sums)
- return -1;
+ if (!info->sit_sums) return -1;
/* CURSEG_COLD_DATA where the journaled SIT entries are. */
if (is_set_ckpt_flags(cp, CP_COMPACT_SUM_FLAG)) {
- if (read_structure_blk(fd, info->cp_valid_cp_blkaddr + le32_to_cpu(cp->cp_pack_start_sum), buffer, 1))
+ 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);
} else {
@@ -368,19 +354,17 @@ static int get_sit_summary(int fd, struct f2fs_info *info, struct f2fs_checkpoin
else
blk_addr = sum_blk_addr(cp, info, NR_CURSEG_DATA_TYPE, CURSEG_COLD_DATA);
- if (read_structure_blk(fd, blk_addr, buffer, 1))
- return -1;
+ if (read_structure_blk(fd, blk_addr, buffer, 1)) return -1;
memcpy(info->sit_sums, buffer, sizeof(struct f2fs_summary_block));
}
return 0;
}
-struct f2fs_info *generate_f2fs_info(int fd)
-{
- struct f2fs_super_block *sb = NULL;
- struct f2fs_checkpoint *cp = NULL;
- struct f2fs_info *info;
+struct f2fs_info* generate_f2fs_info(int fd) {
+ struct f2fs_super_block* sb = NULL;
+ struct f2fs_checkpoint* cp = NULL;
+ struct f2fs_info* info;
info = calloc(1, sizeof(*info));
if (!info) {
@@ -389,7 +373,7 @@ struct f2fs_info *generate_f2fs_info(int fd)
}
sb = malloc(sizeof(*sb));
- if(!sb) {
+ if (!sb) {
SLOGE("Out of memory!");
free(info);
return NULL;
@@ -409,11 +393,11 @@ struct f2fs_info *generate_f2fs_info(int fd)
info->main_blkaddr = le32_to_cpu(sb->main_blkaddr);
info->block_size = F2FS_BLKSIZE;
info->total_blocks = sb->block_count;
- info->blocks_per_sit = (le32_to_cpu(sb->segment_count_sit) >> 1) << le32_to_cpu(sb->log_blocks_per_seg);
+ info->blocks_per_sit = (le32_to_cpu(sb->segment_count_sit) >> 1)
+ << le32_to_cpu(sb->log_blocks_per_seg);
info->blocks_per_segment = 1U << le32_to_cpu(sb->log_blocks_per_seg);
- if (get_valid_checkpoint_info(fd, sb, &cp, info))
- goto error;
+ if (get_valid_checkpoint_info(fd, sb, &cp, info)) goto error;
dbg_print_raw_ckpt_struct(cp);
info->total_user_used = le32_to_cpu(cp->valid_block_count);
@@ -422,20 +406,21 @@ struct f2fs_info *generate_f2fs_info(int fd)
/* get sit validity bitmap */
info->sit_bmp = malloc(bmp_size);
- if(!info->sit_bmp) {
+ if (!info->sit_bmp) {
SLOGE("Out of memory!");
goto error;
}
info->sit_bmp_size = bmp_size;
- if (read_structure(fd, info->cp_valid_cp_blkaddr * F2FS_BLKSIZE
- + offsetof(struct f2fs_checkpoint, sit_nat_version_bitmap),
- info->sit_bmp, bmp_size)) {
+ if (read_structure(fd,
+ info->cp_valid_cp_blkaddr * F2FS_BLKSIZE +
+ offsetof(struct f2fs_checkpoint, sit_nat_version_bitmap),
+ info->sit_bmp, bmp_size)) {
SLOGE("Error getting SIT validity bitmap");
goto error;
}
- if (gather_sit_info(fd , info)) {
+ if (gather_sit_info(fd, info)) {
SLOGE("Error getting SIT information");
goto error;
}
@@ -452,8 +437,7 @@ error:
return NULL;
}
-void free_f2fs_info(struct f2fs_info *info)
-{
+void free_f2fs_info(struct f2fs_info* info) {
if (info) {
free(info->sit_blocks);
info->sit_blocks = NULL;
@@ -467,23 +451,22 @@ void free_f2fs_info(struct f2fs_info *info)
free(info);
}
-uint64_t get_num_blocks_used(struct f2fs_info *info)
-{
+uint64_t get_num_blocks_used(struct f2fs_info* info) {
return info->main_blkaddr + info->total_user_used;
}
-int f2fs_test_bit(unsigned int nr, const char *p)
-{
+int f2fs_test_bit(unsigned int nr, const char* p) {
int mask;
- char *addr = (char *)p;
+ char* addr = (char*)p;
addr += (nr >> 3);
mask = 1 << (7 - (nr & 0x07));
return (mask & *addr) != 0;
}
-int run_on_used_blocks(uint64_t startblock, struct f2fs_info *info, int (*func)(uint64_t pos, void *data), void *data) {
- struct f2fs_sit_entry * sit_entry;
+int run_on_used_blocks(uint64_t startblock, struct f2fs_info* info,
+ int (*func)(uint64_t pos, void* data), void* data) {
+ struct f2fs_sit_entry* sit_entry;
uint64_t sit_block_num_cur = 0, segnum = 0, block_offset;
uint64_t block;
unsigned int used, found, i;
@@ -498,11 +481,11 @@ int run_on_used_blocks(uint64_t startblock, struct f2fs_info *info, int (*func)(
}
} else {
/* Main Section */
- segnum = (block - info->main_blkaddr)/info->blocks_per_segment;
+ segnum = (block - info->main_blkaddr) / info->blocks_per_segment;
/* check the SIT entries in the journal */
found = 0;
- for(i = 0; i < le16_to_cpu(info->sit_sums->journal.n_sits); i++) {
+ 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);
found = 1;
@@ -513,7 +496,8 @@ int run_on_used_blocks(uint64_t startblock, struct f2fs_info *info, int (*func)(
/* 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 =
+ &info->sit_blocks[sit_block_num_cur].entries[segnum % SIT_ENTRY_PER_BLOCK];
}
block_offset = (block - info->main_blkaddr) % info->blocks_per_segment;
@@ -523,10 +507,9 @@ int run_on_used_blocks(uint64_t startblock, struct f2fs_info *info, int (*func)(
continue;
}
- used = f2fs_test_bit(block_offset, (char *)sit_entry->valid_map);
- if(used)
- if (func(block, data))
- return -1;
+ used = f2fs_test_bit(block_offset, (char*)sit_entry->valid_map);
+ if (used)
+ if (func(block, data)) return -1;
}
block++;
@@ -534,27 +517,24 @@ int run_on_used_blocks(uint64_t startblock, struct f2fs_info *info, int (*func)(
return 0;
}
-struct privdata
-{
+struct privdata {
int count;
int infd;
int outfd;
char* buf;
- char *zbuf;
+ char* zbuf;
int done;
- struct f2fs_info *info;
+ struct f2fs_info* info;
};
-
/*
* This is a simple test program. It performs a block to block copy of a
* filesystem, replacing blocks identified as unused with 0's.
*/
-int copy_used(uint64_t pos, void *data)
-{
- struct privdata *d = data;
- char *buf;
+int copy_used(uint64_t pos, void* data) {
+ struct privdata* d = data;
+ char* buf;
int pdone = (pos * 100) / d->info->total_blocks;
if (pdone > d->done) {
d->done = pdone;
@@ -563,7 +543,7 @@ int copy_used(uint64_t pos, void *data)
d->count++;
buf = d->buf;
- if(read_structure_blk(d->infd, (unsigned long long)pos, d->buf, 1)) {
+ if (read_structure_blk(d->infd, (unsigned long long)pos, d->buf, 1)) {
printf("Error reading!!!\n");
return -1;
}
@@ -587,19 +567,17 @@ int copy_used(uint64_t pos, void *data)
return 0;
}
-int main(int argc, char **argv)
-{
- if (argc != 3)
- printf("Usage: %s fs_file_in fs_file_out\n", argv[0]);
- char *in = argv[1];
- char *out = argv[2];
+int main(int argc, char** argv) {
+ if (argc != 3) printf("Usage: %s fs_file_in fs_file_out\n", argv[0]);
+ char* in = argv[1];
+ char* out = argv[2];
int infd, outfd;
if ((infd = open(in, O_RDONLY)) < 0) {
SLOGE("Cannot open device");
return 0;
}
- if ((outfd = open(out, O_WRONLY|O_CREAT, S_IRUSR | S_IWUSR)) < 0) {
+ if ((outfd = open(out, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR)) < 0) {
SLOGE("Cannot open output");
return 0;
}
@@ -608,13 +586,13 @@ int main(int argc, char **argv)
d.infd = infd;
d.outfd = outfd;
d.count = 0;
- struct f2fs_info *info = generate_f2fs_info(infd);
+ struct f2fs_info* info = generate_f2fs_info(infd);
if (!info) {
printf("Failed to generate info!");
return -1;
}
- char *buf = malloc(F2FS_BLKSIZE);
- char *zbuf = calloc(1, F2FS_BLKSIZE);
+ char* buf = malloc(F2FS_BLKSIZE);
+ char* zbuf = calloc(1, F2FS_BLKSIZE);
d.buf = buf;
d.zbuf = zbuf;
d.done = 0;
diff --git a/f2fs_utils/f2fs_sparseblock.h b/f2fs_utils/f2fs_sparseblock.h
index e5c5f846..e047ed28 100644
--- a/f2fs_utils/f2fs_sparseblock.h
+++ b/f2fs_utils/f2fs_sparseblock.h
@@ -23,13 +23,13 @@
extern "C" {
#endif
-#define ver_after(a, b) (typecheck(unsigned long long, a) && \
- typecheck(unsigned long long, b) && \
- ((long long)((a) - (b)) > 0))
+#define ver_after(a, b) \
+ (typecheck(unsigned long long, a) && typecheck(unsigned long long, b) && \
+ ((long long)((a) - (b)) > 0))
-#define ver_equal(a, b) (typecheck(unsigned long long, a) && \
- typecheck(unsigned long long, b) && \
- ((long long)((a) - (b)) == 0))
+#define ver_equal(a, b) \
+ (typecheck(unsigned long long, a) && typecheck(unsigned long long, b) && \
+ ((long long)((a) - (b)) == 0))
struct f2fs_sit_block;
struct f2fs_summary_block;
@@ -38,11 +38,11 @@ struct f2fs_info {
uint64_t blocks_per_segment;
uint32_t block_size;
- char *sit_bmp;
+ char* sit_bmp;
uint32_t sit_bmp_size;
uint64_t blocks_per_sit;
- struct f2fs_sit_block *sit_blocks;
- struct f2fs_summary_block *sit_sums;
+ struct f2fs_sit_block* sit_blocks;
+ struct f2fs_summary_block* sit_sums;
uint64_t cp_blkaddr;
uint64_t cp_valid_cp_blkaddr;
@@ -59,11 +59,12 @@ struct f2fs_info {
uint64_t total_blocks;
};
-uint64_t get_num_blocks_used(struct f2fs_info *info);
-struct f2fs_info *generate_f2fs_info(int fd);
-void free_f2fs_info(struct f2fs_info *info);
-unsigned int get_f2fs_filesystem_size_sec(char *dev);
-int run_on_used_blocks(uint64_t startblock, struct f2fs_info *info, int (*func)(uint64_t pos, void *data), void *data);
+uint64_t get_num_blocks_used(struct f2fs_info* info);
+struct f2fs_info* generate_f2fs_info(int fd);
+void free_f2fs_info(struct f2fs_info* info);
+unsigned int get_f2fs_filesystem_size_sec(char* dev);
+int run_on_used_blocks(uint64_t startblock, struct f2fs_info* info,
+ int (*func)(uint64_t pos, void* data), void* data);
#ifdef __cplusplus
}
diff --git a/f2fs_utils/mkf2fsuserimg.sh b/f2fs_utils/mkf2fsuserimg.sh
index 264f0ab9..013ff543 100755
--- a/f2fs_utils/mkf2fsuserimg.sh
+++ b/f2fs_utils/mkf2fsuserimg.sh
@@ -8,7 +8,12 @@ Usage:
${0##*/} OUTPUT_FILE SIZE
[-S] [-C FS_CONFIG] [-f SRC_DIR] [-D PRODUCT_OUT]
[-s FILE_CONTEXTS] [-t MOUNT_POINT] [-T TIMESTAMP]
- [-L LABEL] [--prjquota] [--casefold]
+ [-L LABEL] [--prjquota] [--casefold] [--compression] [--readonly]
+ [--sldc <num> [sload compression sub-options]]
+<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.
+Note: must conserve the option order
EOT
}
@@ -85,34 +90,91 @@ if [[ "$1" == "--casefold" ]]; then
shift;
fi
+if [[ "$1" == "--compression" ]]; then
+ COMPRESS_SUPPORT=1
+ MKFS_OPTS+=" -O compression,extra_attr"
+ shift;
+fi
+if [[ "$1" == "--readonly" ]]; then
+ MKFS_OPTS+=" -O ro"
+ READONLY=1
+ shift;
+fi
+
+if [[ "$1" == "--sldc" ]]; then
+ if [ -z "$COMPRESS_SUPPORT" ]; then
+ echo "--sldc needs --compression flag"
+ exit 3
+ fi
+ SLOAD_OPTS+=" -c"
+ shift
+ SLDC_NUM_ARGS=$1
+ case $SLDC_NUM_ARGS in
+ ''|*[!0-9]*)
+ echo "--sldc needs a number"
+ exit 3 ;;
+ esac
+ shift
+ while [ $SLDC_NUM_ARGS -gt 0 ]; do
+ SLOAD_OPTS+=" $1"
+ shift
+ (( SLDC_NUM_ARGS-- ))
+ done
+fi
+
if [ -z $SIZE ]; then
echo "Need size of filesystem"
exit 2
fi
-if [ "$SPARSE_IMG" = "false" ]; then
+function _truncate()
+{
+ if [ "$SPARSE_IMG" = "true" ]; then
+ return
+ fi
+
TRUNCATE_CMD="truncate -s $SIZE $OUTPUT_FILE"
echo $TRUNCATE_CMD
$TRUNCATE_CMD
if [ $? -ne 0 ]; then
exit 3
fi
-fi
+}
-MAKE_F2FS_CMD="make_f2fs -g android $MKFS_OPTS $OUTPUT_FILE"
-echo $MAKE_F2FS_CMD
-$MAKE_F2FS_CMD
-if [ $? -ne 0 ]; then
- if [ "$SPARSE_IMG" = "false" ]; then
+function _build()
+{
+ MAKE_F2FS_CMD="make_f2fs -g android $MKFS_OPTS $OUTPUT_FILE"
+ echo $MAKE_F2FS_CMD
+ $MAKE_F2FS_CMD
+ if [ $? -ne 0 ]; then
+ if [ "$SPARSE_IMG" = "false" ]; then
+ rm -f $OUTPUT_FILE
+ fi
+ exit 4
+ fi
+
+ 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}'`
+ # allow 1: Filesystem errors corrected
+ ret=$?
+ if [ $ret -ne 0 ] && [ $ret -ne 1 ]; then
rm -f $OUTPUT_FILE
+ exit 4
fi
- exit 4
-fi
+ SIZE=$(((MB_SIZE + 6) * 1024 * 1024))
+}
-SLOAD_F2FS_CMD="sload_f2fs $SLOAD_OPTS $OUTPUT_FILE"
-echo $SLOAD_F2FS_CMD
-$SLOAD_F2FS_CMD
-if [ $? -ne 0 ]; then
- rm -f $OUTPUT_FILE
- exit 4
+_truncate
+_build
+
+# readonly + compress can reduce the image
+if [ "$READONLY" ] && [ "$COMPRESS_SUPPORT" ]; then
+ if [ "$SPARSE_IMG" = "true" ]; then
+ MKFS_OPTS+=" -S $SIZE"
+ rm -f $OUTPUT_FILE && touch $OUTPUT_FILE
+ fi
+ _truncate
+ _build
fi
+exit 0
diff --git a/ioblame/Android.bp b/ioblame/Android.bp
index 4b0fa711..7b179b8d 100644
--- a/ioblame/Android.bp
+++ b/ioblame/Android.bp
@@ -1,3 +1,7 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
sh_binary_host {
name: "ioblame",
src: "ioblame.sh",
diff --git a/ioshark/Android.bp b/ioshark/Android.bp
index 0f9ba29b..02952a35 100644
--- a/ioshark/Android.bp
+++ b/ioshark/Android.bp
@@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_defaults {
name: "ioshark_defaults",
diff --git a/ioshark/compile_ioshark.h b/ioshark/compile_ioshark.h
index 5dd8b7b7..5a39aeb7 100644
--- a/ioshark/compile_ioshark.h
+++ b/ioshark/compile_ioshark.h
@@ -14,6 +14,12 @@
* limitations under the License.
*/
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "ioshark.h"
+
#define FILE_DB_HASHSIZE 8192
struct files_db_s {
diff --git a/ioshark/compile_ioshark_subr.c b/ioshark/compile_ioshark_subr.c
index 9f2028a1..43fb2544 100644
--- a/ioshark/compile_ioshark_subr.c
+++ b/ioshark/compile_ioshark_subr.c
@@ -32,7 +32,7 @@ static struct files_db_s *files_db_buckets[FILE_DB_HASHSIZE];
static int current_fileno = 1;
static int num_objects = 0;
-static int filename_cache_lookup(char *filename);;
+static int filename_cache_lookup(char *filename);
void
files_db_write_objects(FILE *fp)
diff --git a/ioshark/ioshark.h b/ioshark/ioshark.h
index 2e87b563..ab692be3 100644
--- a/ioshark/ioshark.h
+++ b/ioshark/ioshark.h
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#pragma once
+
/*
* Format of the parsed workload files.
* 1) Header
diff --git a/ioshark/ioshark_bench_mmap.c b/ioshark/ioshark_bench_mmap.c
index 55d70606..e8b3acce 100644
--- a/ioshark/ioshark_bench_mmap.c
+++ b/ioshark/ioshark_bench_mmap.c
@@ -180,7 +180,7 @@ ioshark_handle_mmap(void *db_node,
exit(EXIT_FAILURE);
}
/*
- * The size of the file better accomodate offset + len
+ * The size of the file better accommodate offset + len
* Else there is an issue with pre-creation
*/
assert(offset + len <= statbuf.st_size);
diff --git a/iotop/.clang-format b/iotop/.clang-format
new file mode 120000
index 00000000..fd0645fd
--- /dev/null
+++ b/iotop/.clang-format
@@ -0,0 +1 @@
+../.clang-format-2 \ No newline at end of file
diff --git a/iotop/Android.bp b/iotop/Android.bp
index 9b5e40cc..d7dc3ea0 100644
--- a/iotop/Android.bp
+++ b/iotop/Android.bp
@@ -12,6 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_applicable_licenses: ["system_extras_iotop_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_iotop_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_binary {
name: "iotop",
diff --git a/iotop/iotop.cpp b/iotop/iotop.cpp
index fe476bf5..06b82b1b 100644
--- a/iotop/iotop.cpp
+++ b/iotop/iotop.cpp
@@ -30,7 +30,7 @@
constexpr uint64_t NSEC_PER_SEC = 1000000000;
static uint64_t BytesToKB(uint64_t bytes) {
- return (bytes + 1024-1) / 1024;
+ return (bytes + 1024 - 1) / 1024;
}
static float TimeToTgidPercent(uint64_t ns, int time, const TaskStatistics& stats) {
@@ -125,38 +125,38 @@ int main(int argc, char* argv[]) {
break;
}
switch (c) {
- case 'a':
- accumulated = true;
- break;
- case 'd':
- delay = atoi(optarg);
- break;
- case 'h':
- usage(argv[0]);
- return(EXIT_SUCCESS);
- case 'm':
- limit = atoi(optarg);
- break;
- case 'n':
- cycles = atoi(optarg);
- break;
- case 's': {
- sorter = GetSorter(optarg);
- if (sorter == nullptr) {
- LOG(ERROR) << "Invalid sort column \"" << optarg << "\"";
+ case 'a':
+ accumulated = true;
+ break;
+ case 'd':
+ delay = atoi(optarg);
+ break;
+ case 'h':
usage(argv[0]);
- return EXIT_FAILURE;
+ return (EXIT_SUCCESS);
+ case 'm':
+ limit = atoi(optarg);
+ break;
+ case 'n':
+ cycles = atoi(optarg);
+ break;
+ case 's': {
+ sorter = GetSorter(optarg);
+ if (sorter == nullptr) {
+ LOG(ERROR) << "Invalid sort column \"" << optarg << "\"";
+ usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+ break;
}
- break;
- }
- case 'P':
- processes = true;
- break;
- case '?':
- usage(argv[0]);
- return EXIT_FAILURE;
- default:
- abort();
+ case 'P':
+ processes = true;
+ break;
+ case '?':
+ usage(argv[0]);
+ return EXIT_FAILURE;
+ default:
+ abort();
}
}
@@ -224,25 +224,14 @@ int main(int argc, char* argv[]) {
printf("\n");
}
if (accumulated) {
- printf("%6s %-16s %20s %14s %34s\n", "", "",
- "---- IO (KiB) ----", "--- faults ---", "----------- delayed on ----------");
+ printf("%6s %-16s %20s %14s %34s\n", "", "", "---- IO (KiB) ----", "--- faults ---",
+ "----------- delayed on ----------");
} else {
- printf("%6s %-16s %20s %14s %34s\n", "", "",
- "--- IO (KiB/s) ---", "--- faults ---", "----------- delayed on ----------");
+ printf("%6s %-16s %20s %14s %34s\n", "", "", "--- IO (KiB/s) ---", "--- faults ---",
+ "----------- delayed on ----------");
}
- printf("%6s %-16s %6s %6s %6s %6s %6s %-5s %-5s %-5s %-5s %-5s\n",
- "PID",
- "Command",
- "read",
- "write",
- "total",
- "major",
- "minor",
- "IO",
- "swap",
- "sched",
- "mem",
- "total");
+ printf("%6s %-16s %6s %6s %6s %6s %6s %-5s %-5s %-5s %-5s %-5s\n", "PID", "Command",
+ "read", "write", "total", "major", "minor", "IO", "swap", "sched", "mem", "total");
int n = limit;
const int delay_div = accumulated ? 1 : delay;
uint64_t total_read = 0;
@@ -263,24 +252,21 @@ int main(int argc, char* argv[]) {
n--;
}
- printf("%6d %-16s %6" PRIu64 " %6" PRIu64 " %6" PRIu64 " %6" PRIu64 " %6" PRIu64" %5.2f%% %5.2f%% %5.2f%% %5.2f%% %5.2f%%\n",
- statistics.pid(),
- statistics.comm().c_str(),
- BytesToKB(statistics.read()) / delay_div,
- BytesToKB(statistics.write()) / delay_div,
- BytesToKB(statistics.read_write()) / delay_div,
- statistics.majflt(), statistics.minflt(),
- TimeToTgidPercent(statistics.delay_io(), delay, statistics),
- TimeToTgidPercent(statistics.delay_swap(), delay, statistics),
- TimeToTgidPercent(statistics.delay_sched(), delay, statistics),
- TimeToTgidPercent(statistics.delay_mem(), delay, statistics),
- TimeToTgidPercent(statistics.delay_total(), delay, statistics));
+ printf("%6d %-16s %6" PRIu64 " %6" PRIu64 " %6" PRIu64 " %6" PRIu64 " %6" PRIu64
+ " %5.2f%% %5.2f%% %5.2f%% %5.2f%% %5.2f%%\n",
+ statistics.pid(), statistics.comm().c_str(),
+ BytesToKB(statistics.read()) / delay_div, BytesToKB(statistics.write()) / delay_div,
+ BytesToKB(statistics.read_write()) / delay_div, statistics.majflt(),
+ statistics.minflt(), TimeToTgidPercent(statistics.delay_io(), delay, statistics),
+ TimeToTgidPercent(statistics.delay_swap(), delay, statistics),
+ TimeToTgidPercent(statistics.delay_sched(), delay, statistics),
+ TimeToTgidPercent(statistics.delay_mem(), delay, statistics),
+ TimeToTgidPercent(statistics.delay_total(), delay, statistics));
}
- printf("%6s %-16s %6" PRIu64 " %6" PRIu64 " %6" PRIu64 " %6" PRIu64 " %6" PRIu64"\n", "", "TOTAL",
- BytesToKB(total_read) / delay_div,
- BytesToKB(total_write) / delay_div,
- BytesToKB(total_read_write) / delay_div,
- total_majflt / delay_div, total_minflt / delay_div);
+ printf("%6s %-16s %6" PRIu64 " %6" PRIu64 " %6" PRIu64 " %6" PRIu64 " %6" PRIu64 "\n", "",
+ "TOTAL", BytesToKB(total_read) / delay_div, BytesToKB(total_write) / delay_div,
+ BytesToKB(total_read_write) / delay_div, total_majflt / delay_div,
+ total_minflt / delay_div);
second = false;
diff --git a/iotop/tasklist.cpp b/iotop/tasklist.cpp
index 07087414..dc649265 100644
--- a/iotop/tasklist.cpp
+++ b/iotop/tasklist.cpp
@@ -12,11 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
#include <map>
#include <memory>
@@ -27,7 +27,7 @@
#include "tasklist.h"
-template<typename Func>
+template <typename Func>
static bool ScanPidsInDir(const std::string& name, Func f) {
std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(name.c_str()), closedir);
if (!dir) {
@@ -59,7 +59,5 @@ bool TaskList::Scan(std::map<pid_t, std::vector<pid_t>>& tgid_map) {
bool TaskList::ScanPid(pid_t tgid, std::vector<pid_t>& pid_list) {
std::string filename = android::base::StringPrintf("/proc/%d/task", tgid);
- return ScanPidsInDir(filename, [&pid_list](pid_t pid) {
- pid_list.push_back(pid);
- });
+ return ScanPidsInDir(filename, [&pid_list](pid_t pid) { pid_list.push_back(pid); });
}
diff --git a/iotop/tasklist.h b/iotop/tasklist.h
index 1a19c8fb..071dc591 100644
--- a/iotop/tasklist.h
+++ b/iotop/tasklist.h
@@ -19,12 +19,12 @@
#define _IOTOP_TASKLIST_H
class TaskList {
-public:
+ public:
static bool Scan(std::map<pid_t, std::vector<pid_t>>&);
-private:
+ private:
TaskList() {}
static bool ScanPid(pid_t pid, std::vector<pid_t>&);
};
-#endif // _IOTOP_TASKLIST_H
+#endif // _IOTOP_TASKLIST_H
diff --git a/iotop/taskstats.cpp b/iotop/taskstats.cpp
index eff1d4df..263c92a9 100644
--- a/iotop/taskstats.cpp
+++ b/iotop/taskstats.cpp
@@ -13,9 +13,9 @@
// limitations under the License.
#include <linux/taskstats.h>
-#include <netlink/socket.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h>
+#include <netlink/socket.h>
#include <algorithm>
#include <memory>
@@ -24,13 +24,10 @@
#include "taskstats.h"
-TaskstatsSocket::TaskstatsSocket()
- : nl_(nullptr, nl_socket_free), family_id_(0) {
-}
+TaskstatsSocket::TaskstatsSocket() : nl_(nullptr, nl_socket_free), family_id_(0) {}
bool TaskstatsSocket::Open() {
- std::unique_ptr<nl_sock, decltype(&nl_socket_free)> nl(
- nl_socket_alloc(), nl_socket_free);
+ std::unique_ptr<nl_sock, decltype(&nl_socket_free)> nl(nl_socket_alloc(), nl_socket_free);
if (!nl.get()) {
LOG(ERROR) << "Failed to allocate netlink socket";
return false;
@@ -44,7 +41,8 @@ bool TaskstatsSocket::Open() {
int family_id = genl_ctrl_resolve(nl.get(), TASKSTATS_GENL_NAME);
if (family_id < 0) {
- LOG(ERROR) << nl_geterror(family_id) << std::endl << "Unable to determine taskstats family id (does your kernel support taskstats?)";
+ LOG(ERROR) << nl_geterror(family_id) << std::endl
+ << "Unable to determine taskstats family id (does your kernel support taskstats?)";
return false;
}
@@ -63,25 +61,23 @@ struct TaskStatsRequest {
taskstats stats;
};
-static pid_t ParseAggregateTaskStats(nlattr* attr, int attr_size,
- taskstats* stats) {
+static pid_t ParseAggregateTaskStats(nlattr* attr, int attr_size, taskstats* stats) {
pid_t received_pid = -1;
nla_for_each_attr(attr, attr, attr_size, attr_size) {
switch (nla_type(attr)) {
- case TASKSTATS_TYPE_PID:
- case TASKSTATS_TYPE_TGID:
- received_pid = nla_get_u32(attr);
- break;
- case TASKSTATS_TYPE_STATS:
- {
- int len = static_cast<int>(sizeof(*stats));
- len = std::min(len, nla_len(attr));
- nla_memcpy(stats, attr, len);
- return received_pid;
- }
- default:
- LOG(ERROR) << "unexpected attribute inside AGGR";
- return -1;
+ case TASKSTATS_TYPE_PID:
+ case TASKSTATS_TYPE_TGID:
+ received_pid = nla_get_u32(attr);
+ break;
+ case TASKSTATS_TYPE_STATS: {
+ int len = static_cast<int>(sizeof(*stats));
+ len = std::min(len, nla_len(attr));
+ nla_memcpy(stats, attr, len);
+ return received_pid;
+ }
+ default:
+ LOG(ERROR) << "unexpected attribute inside AGGR";
+ return -1;
}
}
@@ -96,28 +92,27 @@ static int ParseTaskStats(nl_msg* msg, void* arg) {
nla_for_each_attr(attr, attr, remaining, remaining) {
switch (nla_type(attr)) {
- case TASKSTATS_TYPE_AGGR_PID:
- case TASKSTATS_TYPE_AGGR_TGID:
- {
- nlattr* nested_attr = static_cast<nlattr*>(nla_data(attr));
- taskstats stats;
- pid_t ret;
-
- ret = ParseAggregateTaskStats(nested_attr, nla_len(attr), &stats);
- if (ret < 0) {
- LOG(ERROR) << "Bad AGGR_PID contents";
- } else if (ret == taskstats_request->requested_pid) {
- taskstats_request->stats = stats;
- } else {
- LOG(WARNING) << "got taskstats for unexpected pid " << ret <<
- " (expected " << taskstats_request->requested_pid << ", continuing...";
+ case TASKSTATS_TYPE_AGGR_PID:
+ case TASKSTATS_TYPE_AGGR_TGID: {
+ nlattr* nested_attr = static_cast<nlattr*>(nla_data(attr));
+ taskstats stats;
+ pid_t ret;
+
+ ret = ParseAggregateTaskStats(nested_attr, nla_len(attr), &stats);
+ if (ret < 0) {
+ LOG(ERROR) << "Bad AGGR_PID contents";
+ } else if (ret == taskstats_request->requested_pid) {
+ taskstats_request->stats = stats;
+ } else {
+ LOG(WARNING) << "got taskstats for unexpected pid " << ret << " (expected "
+ << taskstats_request->requested_pid << ", continuing...";
+ }
+ break;
}
- break;
- }
- case TASKSTATS_TYPE_NULL:
- break;
- default:
- LOG(ERROR) << "unexpected attribute in taskstats";
+ case TASKSTATS_TYPE_NULL:
+ break;
+ default:
+ LOG(ERROR) << "unexpected attribute in taskstats";
}
}
return NL_OK;
@@ -127,11 +122,10 @@ bool TaskstatsSocket::GetStats(int pid, int type, TaskStatistics& stats) {
TaskStatsRequest taskstats_request = TaskStatsRequest();
taskstats_request.requested_pid = pid;
- std::unique_ptr<nl_msg, decltype(&nlmsg_free)> message(nlmsg_alloc(),
- nlmsg_free);
+ std::unique_ptr<nl_msg, decltype(&nlmsg_free)> message(nlmsg_alloc(), nlmsg_free);
- genlmsg_put(message.get(), NL_AUTO_PID, NL_AUTO_SEQ, family_id_, 0, 0,
- TASKSTATS_CMD_GET, TASKSTATS_VERSION);
+ genlmsg_put(message.get(), NL_AUTO_PID, NL_AUTO_SEQ, family_id_, 0, 0, TASKSTATS_CMD_GET,
+ TASKSTATS_VERSION);
nla_put_u32(message.get(), type, pid);
int result = nl_send_auto_complete(nl_.get(), message.get());
@@ -139,8 +133,7 @@ bool TaskstatsSocket::GetStats(int pid, int type, TaskStatistics& stats) {
return false;
}
- std::unique_ptr<nl_cb, decltype(&nl_cb_put)> callbacks(
- nl_cb_alloc(NL_CB_DEFAULT), nl_cb_put);
+ std::unique_ptr<nl_cb, decltype(&nl_cb_put)> callbacks(nl_cb_alloc(NL_CB_DEFAULT), nl_cb_put);
nl_cb_set(callbacks.get(), NL_CB_VALID, NL_CB_CUSTOM, &ParseTaskStats,
static_cast<void*>(&taskstats_request));
@@ -188,8 +181,7 @@ TaskStatistics::TaskStatistics(const taskstats& taskstats_stats) {
reclaim_delay_count_ = taskstats_stats.freepages_count;
reclaim_delay_ns_ = taskstats_stats.freepages_delay_total;
- total_delay_ns_ =
- cpu_delay_ns_ + block_io_delay_ns_ + swap_in_delay_ns_ + reclaim_delay_ns_;
+ total_delay_ns_ = cpu_delay_ns_ + block_io_delay_ns_ + swap_in_delay_ns_ + reclaim_delay_ns_;
cpu_time_real_ = taskstats_stats.cpu_run_real_total;
cpu_time_virtual_ = taskstats_stats.cpu_run_virtual_total;
@@ -207,9 +199,9 @@ TaskStatistics::TaskStatistics(const taskstats& taskstats_stats) {
void TaskStatistics::AddPidToTgid(const TaskStatistics& pid_statistics) {
// tgid statistics already contain delay values totalled across all pids
// only add IO statistics
- read_bytes_ += pid_statistics.read_bytes_;
- write_bytes_ += pid_statistics.write_bytes_;
- read_write_bytes_ += pid_statistics.read_write_bytes_;
+ read_bytes_ += pid_statistics.read_bytes_;
+ write_bytes_ += pid_statistics.write_bytes_;
+ read_write_bytes_ += pid_statistics.read_write_bytes_;
cancelled_write_bytes_ += pid_statistics.cancelled_write_bytes_;
if (pid_ == pid_statistics.pid_) {
comm_ = pid_statistics.comm_;
@@ -224,22 +216,22 @@ void TaskStatistics::AddPidToTgid(const TaskStatistics& pid_statistics) {
// Store new statistics and return the delta from the old statistics
TaskStatistics TaskStatistics::Update(const TaskStatistics& new_statistics) {
TaskStatistics delta = new_statistics;
- delta.minflt_ -= minflt_;
- delta.majflt_ -= majflt_;
- delta.cpu_delay_count_ -= cpu_delay_count_;
- delta.cpu_delay_ns_ -= cpu_delay_ns_;
- delta.block_io_delay_count_ -= block_io_delay_count_;
- delta.block_io_delay_ns_ -= block_io_delay_ns_;
- delta.swap_in_delay_count_ -= swap_in_delay_count_;
- delta.swap_in_delay_ns_ -= swap_in_delay_ns_;
- delta.reclaim_delay_count_ -= reclaim_delay_count_;
- delta.reclaim_delay_ns_ -= reclaim_delay_ns_;
- delta.total_delay_ns_ -= total_delay_ns_;
- delta.cpu_time_real_ -= cpu_time_real_;
- delta.cpu_time_virtual_ -= cpu_time_virtual_;
- delta.read_bytes_ -= read_bytes_;
- delta.write_bytes_ -= write_bytes_;
- delta.read_write_bytes_ -= read_write_bytes_;
+ delta.minflt_ -= minflt_;
+ delta.majflt_ -= majflt_;
+ delta.cpu_delay_count_ -= cpu_delay_count_;
+ delta.cpu_delay_ns_ -= cpu_delay_ns_;
+ delta.block_io_delay_count_ -= block_io_delay_count_;
+ delta.block_io_delay_ns_ -= block_io_delay_ns_;
+ delta.swap_in_delay_count_ -= swap_in_delay_count_;
+ delta.swap_in_delay_ns_ -= swap_in_delay_ns_;
+ delta.reclaim_delay_count_ -= reclaim_delay_count_;
+ delta.reclaim_delay_ns_ -= reclaim_delay_ns_;
+ delta.total_delay_ns_ -= total_delay_ns_;
+ delta.cpu_time_real_ -= cpu_time_real_;
+ delta.cpu_time_virtual_ -= cpu_time_virtual_;
+ delta.read_bytes_ -= read_bytes_;
+ delta.write_bytes_ -= write_bytes_;
+ delta.read_write_bytes_ -= read_write_bytes_;
delta.cancelled_write_bytes_ -= cancelled_write_bytes_;
*this = new_statistics;
return delta;
diff --git a/iotop/taskstats.h b/iotop/taskstats.h
index 7aaae10a..9a82abdd 100644
--- a/iotop/taskstats.h
+++ b/iotop/taskstats.h
@@ -24,7 +24,7 @@ struct nl_sock;
struct taskstats;
class TaskStatistics {
-public:
+ public:
explicit TaskStatistics(const taskstats&);
TaskStatistics() = default;
TaskStatistics(const TaskStatistics&) = default;
@@ -48,7 +48,7 @@ public:
void set_pid(pid_t pid) { pid_ = pid; }
-private:
+ private:
std::string comm_;
uid_t uid_;
gid_t gid_;
@@ -84,17 +84,18 @@ private:
};
class TaskstatsSocket {
-public:
+ public:
TaskstatsSocket();
bool Open();
void Close();
bool GetPidStats(int, TaskStatistics&);
bool GetTgidStats(int, TaskStatistics&);
-private:
+
+ private:
bool GetStats(int, int, TaskStatistics& stats);
- std::unique_ptr<nl_sock, void(*)(nl_sock*)> nl_;
+ std::unique_ptr<nl_sock, void (*)(nl_sock*)> nl_;
int family_id_;
};
-#endif // _IOTOP_TASKSTATS_H
+#endif // _IOTOP_TASKSTATS_H
diff --git a/kexec_tools/Android.bp b/kexec_tools/Android.bp
index bcfc2ee6..d5b4af30 100644
--- a/kexec_tools/Android.bp
+++ b/kexec_tools/Android.bp
@@ -13,6 +13,23 @@
// limitations under the License.
// Copyright The Android Open Source Project
+package {
+ default_applicable_licenses: ["system_extras_kexec_tools_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_kexec_tools_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_binary {
name: "kexecload",
srcs: ["kexecload.c"],
diff --git a/kexec_tools/kexec.h b/kexec_tools/kexec.h
index c45b1b31..a772fa6c 100644
--- a/kexec_tools/kexec.h
+++ b/kexec_tools/kexec.h
@@ -1,24 +1,24 @@
#ifndef _SYS_KEXEC_H
#define _SYS_KEXEC_H
-#include <sys/cdefs.h>
#include <linux/kexec.h>
-#include <unistd.h>
+#include <sys/cdefs.h>
#include <sys/syscall.h>
+#include <unistd.h>
#include "kexec.h"
#define KEXEC_SEGMENT_MAX 16
#define KEXEC_TYPE_DEFAULT 0
-#define KEXEC_TYPE_CRASH 1
+#define KEXEC_TYPE_CRASH 1
/*
* Prototypes
*/
static inline long kexec_load(unsigned int entry, unsigned long nr_segments,
- struct kexec_segment *segment, unsigned long flags) {
- return syscall(__NR_kexec_load, entry, nr_segments, segment, flags);
+ struct kexec_segment* segment, unsigned long flags) {
+ return syscall(__NR_kexec_load, entry, nr_segments, segment, flags);
}
#endif /* _SYS_KEXEC_H */
diff --git a/kexec_tools/kexecload.c b/kexec_tools/kexecload.c
index 71365435..cef01475 100644
--- a/kexec_tools/kexecload.c
+++ b/kexec_tools/kexecload.c
@@ -1,10 +1,10 @@
#include <errno.h>
-#include <getopt.h>
#include <fcntl.h>
-#include <sys/mman.h>
-#include <stdlib.h>
+#include <getopt.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
+#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
@@ -14,20 +14,19 @@
#include "kexec.h"
// Offsets same as in kernel asm/kexec.h
-#define KEXEC_ARM_ATAGS_OFFSET 0x1000
+#define KEXEC_ARM_ATAGS_OFFSET 0x1000
#define KEXEC_ARM_ZIMAGE_OFFSET 0x8000
#define MEMORY_SIZE 0x0800000
// Physical buffer address cannot overlap with other regions
#define START_ADDRESS 0x44000000
-#define ROUND_TO_PAGE(address,pagesize) (((address) + (pagesize) - 1) & (~((pagesize) - 1)))
+#define ROUND_TO_PAGE(address, pagesize) (((address) + (pagesize)-1) & (~((pagesize)-1)))
/*
* Gives file position and resets current position to begining of file
*/
-int get_file_size(int f)
-{
+int get_file_size(int f) {
struct stat st;
fstat(f, &st);
return st.st_size;
@@ -48,40 +47,33 @@ int test_kexeccall() {
return 0;
}
-void usage(void)
-{
+void usage(void) {
fprintf(stderr,
"usage: kexecload [ <option> ] <atags path> <kernel path>\n"
"\n"
"options:\n"
" -t tests syscall\n"
- " -s <start address> specify start address of kernel\n"
- );
+ " -s <start address> specify start address of kernel\n");
}
/*
* Loads kexec into the kernel and sets kexec on crash
*/
-int main(int argc, char *argv[])
-{
+int main(int argc, char* argv[]) {
int rv;
- int atag_file,
- zimage_file;
- int atag_size,
- zimage_size;
- void *atag_buffer;
- void *zimage_buffer;
+ int atag_file, zimage_file;
+ int atag_size, zimage_size;
+ void* atag_buffer;
+ void* zimage_buffer;
struct kexec_segment segment[2];
int page_size = getpagesize();
- void *start_address = (void *)START_ADDRESS;
+ void* start_address = (void*)START_ADDRESS;
int c;
- const struct option longopts[] = {
- {"start_address", required_argument, 0, 's'},
- {"test", 0, 0, 't'},
- {"help", 0, 0, 'h'},
- {0, 0, 0, 0}
- };
+ const struct option longopts[] = {{"start_address", required_argument, 0, 's'},
+ {"test", 0, 0, 't'},
+ {"help", 0, 0, 'h'},
+ {0, 0, 0, 0}};
while (1) {
c = getopt_long(argc, argv, "s:th", longopts, NULL);
@@ -90,19 +82,19 @@ int main(int argc, char *argv[])
}
/* Alphabetical cases */
switch (c) {
- case 's':
- start_address = (void *) strtoul(optarg, 0, 16);
- break;
- case 'h':
- usage();
- return 1;
- case 't':
- test_kexeccall();
- return 1;
- case '?':
- return 1;
- default:
- abort();
+ case 's':
+ start_address = (void*)strtoul(optarg, 0, 16);
+ break;
+ case 'h':
+ usage();
+ return 1;
+ case 't':
+ test_kexeccall();
+ return 1;
+ case '?':
+ return 1;
+ default:
+ abort();
}
}
@@ -118,7 +110,8 @@ int main(int argc, char *argv[])
zimage_file = open(argv[1], O_RDONLY);
if (atag_file < 0 || zimage_file < 0) {
- fprintf(stderr, "Error during opening of atag file or the zImage file %s\n", strerror(errno));
+ fprintf(stderr, "Error during opening of atag file or the zImage file %s\n",
+ strerror(errno));
return 1;
}
@@ -130,26 +123,27 @@ int main(int argc, char *argv[])
return 1;
}
- atag_buffer = (char *) mmap(NULL, atag_size, PROT_READ, MAP_POPULATE | MAP_PRIVATE, atag_file, 0);
- zimage_buffer = (char *) mmap(NULL, zimage_size, PROT_READ, MAP_POPULATE | MAP_PRIVATE, zimage_file, 0);
+ atag_buffer = (char*)mmap(NULL, atag_size, PROT_READ, MAP_POPULATE | MAP_PRIVATE, atag_file, 0);
+ zimage_buffer =
+ (char*)mmap(NULL, zimage_size, PROT_READ, MAP_POPULATE | MAP_PRIVATE, zimage_file, 0);
- if(atag_buffer == MAP_FAILED || zimage_buffer == MAP_FAILED) {
+ if (atag_buffer == MAP_FAILED || zimage_buffer == MAP_FAILED) {
fprintf(stderr, "Unable to map files into memory");
return 1;
}
segment[0].buf = zimage_buffer;
segment[0].bufsz = zimage_size;
- segment[0].mem = (void *) ((uintptr_t) start_address + KEXEC_ARM_ZIMAGE_OFFSET);
+ segment[0].mem = (void*)((uintptr_t)start_address + KEXEC_ARM_ZIMAGE_OFFSET);
segment[0].memsz = zimage_size;
segment[1].buf = atag_buffer;
segment[1].bufsz = atag_size;
- segment[1].mem = (void *) ((uintptr_t) start_address + KEXEC_ARM_ATAGS_OFFSET);
+ segment[1].mem = (void*)((uintptr_t)start_address + KEXEC_ARM_ATAGS_OFFSET);
segment[1].memsz = atag_size;
- rv = kexec_load(((uintptr_t) start_address + KEXEC_ARM_ZIMAGE_OFFSET),
- 2, (void *) segment, KEXEC_ARCH_DEFAULT | KEXEC_ON_CRASH);
+ rv = kexec_load(((uintptr_t)start_address + KEXEC_ARM_ZIMAGE_OFFSET), 2, (void*)segment,
+ KEXEC_ARCH_DEFAULT | KEXEC_ON_CRASH);
if (rv != 0) {
fprintf(stderr, "Kexec_load returned non-zero exit code: %d with errno %d\n", rv, errno);
@@ -160,6 +154,4 @@ int main(int argc, char *argv[])
printf("New kernel should start at 0x%08x\n", START_ADDRESS + KEXEC_ARM_ZIMAGE_OFFSET);
return 0;
-
}
-
diff --git a/latencytop/Android.bp b/latencytop/Android.bp
index fab36312..64a3c20d 100644
--- a/latencytop/Android.bp
+++ b/latencytop/Android.bp
@@ -12,6 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_applicable_licenses: ["system_extras_latencytop_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_latencytop_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_binary {
name: "latencytop",
diff --git a/latencytop/latencytop.c b/latencytop/latencytop.c
index d20bd7f5..84bf8744 100644
--- a/latencytop/latencytop.c
+++ b/latencytop/latencytop.c
@@ -26,46 +26,47 @@
#define MAX_LINE 512
#define MAX_FILENAME 64
-const char *EXPECTED_VERSION = "Latency Top version : v0.1\n";
-const char *SYSCTL_FILE = "/proc/sys/kernel/latencytop";
-const char *GLOBAL_STATS_FILE = "/proc/latency_stats";
-const char *THREAD_STATS_FILE_FORMAT = "/proc/%d/task/%d/latency";
+const char* EXPECTED_VERSION = "Latency Top version : v0.1\n";
+const char* SYSCTL_FILE = "/proc/sys/kernel/latencytop";
+const char* GLOBAL_STATS_FILE = "/proc/latency_stats";
+const char* THREAD_STATS_FILE_FORMAT = "/proc/%d/task/%d/latency";
struct latency_entry {
- struct latency_entry *next;
+ struct latency_entry* next;
unsigned long count;
unsigned long max;
unsigned long total;
char reason[MAX_LINE];
};
-static inline void check_latencytop() { }
+static inline void check_latencytop() {}
-static struct latency_entry *read_global_stats(struct latency_entry *list, int erase);
-static struct latency_entry *read_process_stats(struct latency_entry *list, int erase, int pid);
-static struct latency_entry *read_thread_stats(struct latency_entry *list, int erase, int pid, int tid, int fatal);
+static struct latency_entry* read_global_stats(struct latency_entry* list, int erase);
+static struct latency_entry* read_process_stats(struct latency_entry* list, int erase, int pid);
+static struct latency_entry* read_thread_stats(struct latency_entry* list, int erase, int pid,
+ int tid, int fatal);
-static struct latency_entry *alloc_latency_entry(void);
+static struct latency_entry* alloc_latency_entry(void);
static void set_latencytop(int on);
-static struct latency_entry *read_latency_file(FILE *f, struct latency_entry *list);
+static struct latency_entry* read_latency_file(FILE* f, struct latency_entry* list);
-static struct latency_entry *find_latency_entry(struct latency_entry *e, char *reason);
-static void print_latency_entries(struct latency_entry *head);
+static struct latency_entry* find_latency_entry(struct latency_entry* e, char* reason);
+static void print_latency_entries(struct latency_entry* head);
static void signal_handler(int sig);
static void disable_latencytop(void);
static int numcmp(const long long a, const long long b);
-static int lat_cmp(const void *a, const void *b);
+static int lat_cmp(const void* a, const void* b);
static void clear_screen(void);
-static void usage(const char *cmd);
+static void usage(const char* cmd);
-struct latency_entry *free_entries;
+struct latency_entry* free_entries;
-int main(int argc, char *argv[]) {
- struct latency_entry *e;
+int main(int argc, char* argv[]) {
+ struct latency_entry* e;
int delay, iterations;
int pid, tid;
int count, erase;
@@ -74,7 +75,7 @@ int main(int argc, char *argv[]) {
delay = 1;
iterations = 0;
pid = tid = 0;
-
+
for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-d")) {
if (i >= argc - 1) {
@@ -118,7 +119,8 @@ int main(int argc, char *argv[]) {
}
if (tid && !pid) {
- fprintf(stderr, "If you provide a thread ID with -t, you must provide a process ID with -p.\n");
+ fprintf(stderr,
+ "If you provide a thread ID with -t, you must provide a process ID with -p.\n");
exit(EXIT_FAILURE);
}
@@ -137,7 +139,6 @@ int main(int argc, char *argv[]) {
erase = 1;
while ((iterations == 0) || (count++ < iterations)) {
-
sleep(delay);
e = NULL;
@@ -170,9 +171,9 @@ int main(int argc, char *argv[]) {
return 0;
}
-static struct latency_entry *read_global_stats(struct latency_entry *list, int erase) {
- FILE *f;
- struct latency_entry *e;
+static struct latency_entry* read_global_stats(struct latency_entry* list, int erase) {
+ FILE* f;
+ struct latency_entry* e;
if (erase) {
f = fopen(GLOBAL_STATS_FILE, "w");
@@ -183,7 +184,7 @@ static struct latency_entry *read_global_stats(struct latency_entry *list, int e
fprintf(f, "erase\n");
fclose(f);
}
-
+
f = fopen(GLOBAL_STATS_FILE, "r");
if (!f) {
fprintf(stderr, "Could not open global latency stats file: %s\n", strerror(errno));
@@ -197,11 +198,11 @@ static struct latency_entry *read_global_stats(struct latency_entry *list, int e
return e;
}
-static struct latency_entry *read_process_stats(struct latency_entry *list, int erase, int pid) {
+static struct latency_entry* read_process_stats(struct latency_entry* list, int erase, int pid) {
char dirname[MAX_FILENAME];
- DIR *dir;
- struct dirent *ent;
- struct latency_entry *e;
+ DIR* dir;
+ struct dirent* ent;
+ struct latency_entry* e;
int tid;
sprintf(dirname, "/proc/%d/task", pid);
@@ -214,8 +215,7 @@ static struct latency_entry *read_process_stats(struct latency_entry *list, int
e = list;
while ((ent = readdir(dir))) {
- if (!isdigit(ent->d_name[0]))
- continue;
+ if (!isdigit(ent->d_name[0])) continue;
tid = atoi(ent->d_name);
@@ -227,10 +227,11 @@ static struct latency_entry *read_process_stats(struct latency_entry *list, int
return e;
}
-static struct latency_entry *read_thread_stats(struct latency_entry *list, int erase, int pid, int tid, int fatal) {
+static struct latency_entry* read_thread_stats(struct latency_entry* list, int erase, int pid,
+ int tid, int fatal) {
char filename[MAX_FILENAME];
- FILE *f;
- struct latency_entry *e;
+ FILE* f;
+ struct latency_entry* e;
sprintf(filename, THREAD_STATS_FILE_FORMAT, pid, tid);
@@ -248,7 +249,7 @@ static struct latency_entry *read_thread_stats(struct latency_entry *list, int e
fprintf(f, "erase\n");
fclose(f);
}
-
+
f = fopen(GLOBAL_STATS_FILE, "r");
if (!f) {
if (fatal) {
@@ -267,8 +268,8 @@ static struct latency_entry *read_thread_stats(struct latency_entry *list, int e
return e;
}
-static struct latency_entry *alloc_latency_entry(void) {
- struct latency_entry *e;
+static struct latency_entry* alloc_latency_entry(void) {
+ struct latency_entry* e;
if (free_entries) {
e = free_entries;
@@ -284,14 +285,13 @@ static struct latency_entry *alloc_latency_entry(void) {
return e;
}
-static struct latency_entry *find_latency_entry(struct latency_entry *head, char *reason) {
- struct latency_entry *e;
+static struct latency_entry* find_latency_entry(struct latency_entry* head, char* reason) {
+ struct latency_entry* e;
e = head;
while (e) {
- if (!strcmp(e->reason, reason))
- return e;
+ if (!strcmp(e->reason, reason)) return e;
e = e->next;
}
@@ -299,7 +299,7 @@ static struct latency_entry *find_latency_entry(struct latency_entry *head, char
}
static void set_latencytop(int on) {
- FILE *f;
+ FILE* f;
f = fopen(SYSCTL_FILE, "w");
if (!f) {
@@ -312,7 +312,7 @@ static void set_latencytop(int on) {
fclose(f);
}
-static struct latency_entry *read_latency_file(FILE *f, struct latency_entry *list) {
+static struct latency_entry* read_latency_file(FILE* f, struct latency_entry* list) {
struct latency_entry *e, *head;
char line[MAX_LINE];
unsigned long count, max, total;
@@ -337,8 +337,7 @@ static struct latency_entry *read_latency_file(FILE *f, struct latency_entry *li
e = find_latency_entry(head, reason);
if (e) {
e->count += count;
- if (max > e->max)
- e->max = max;
+ if (max > e->max) e->max = max;
e->total += total;
} else {
e = alloc_latency_entry();
@@ -355,7 +354,7 @@ static struct latency_entry *read_latency_file(FILE *f, struct latency_entry *li
return head;
}
-static void print_latency_entries(struct latency_entry *head) {
+static void print_latency_entries(struct latency_entry* head) {
struct latency_entry *e, **array;
unsigned long average;
int i, count;
@@ -368,7 +367,7 @@ static void print_latency_entries(struct latency_entry *head) {
}
e = head;
- array = calloc(count, sizeof(struct latency_entry *));
+ array = calloc(count, sizeof(struct latency_entry*));
if (!array) {
fprintf(stderr, "Error allocating array: %s\n", strerror(errno));
exit(EXIT_FAILURE);
@@ -378,17 +377,14 @@ static void print_latency_entries(struct latency_entry *head) {
e = e->next;
}
- qsort(array, count, sizeof(struct latency_entry *), &lat_cmp);
+ qsort(array, count, sizeof(struct latency_entry*), &lat_cmp);
printf("%10s %10s %7s %s\n", "Maximum", "Average", "Count", "Reason");
for (i = 0; i < count; i++) {
e = array[i];
average = e->total / e->count;
- printf("%4lu.%02lu ms %4lu.%02lu ms %7ld %s\n",
- e->max / 1000, (e->max % 1000) / 10,
- average / 1000, (average % 1000) / 10,
- e->count,
- e->reason);
+ printf("%4lu.%02lu ms %4lu.%02lu ms %7ld %s\n", e->max / 1000, (e->max % 1000) / 10,
+ average / 1000, (average % 1000) / 10, e->count, e->reason);
}
free(array);
@@ -406,14 +402,15 @@ static void clear_screen(void) {
printf("\n\n");
}
-static void usage(const char *cmd) {
- fprintf(stderr, "Usage: %s [ -d delay ] [ -n iterations ] [ -p pid [ -t tid ] ] [ -h ]\n"
- " -d delay Time to sleep between updates.\n"
- " -n iterations Number of updates to show (0 = infinite).\n"
- " -p pid Process to monitor (default is all).\n"
- " -t tid Thread (within specified process) to monitor (default is all).\n"
- " -h Display this help screen.\n",
- cmd);
+static void usage(const char* cmd) {
+ fprintf(stderr,
+ "Usage: %s [ -d delay ] [ -n iterations ] [ -p pid [ -t tid ] ] [ -h ]\n"
+ " -d delay Time to sleep between updates.\n"
+ " -n iterations Number of updates to show (0 = infinite).\n"
+ " -p pid Process to monitor (default is all).\n"
+ " -t tid Thread (within specified process) to monitor (default is all).\n"
+ " -h Display this help screen.\n",
+ cmd);
}
static int numcmp(const long long a, const long long b) {
@@ -422,11 +419,11 @@ static int numcmp(const long long a, const long long b) {
return 0;
}
-static int lat_cmp(const void *a, const void *b) {
+static int lat_cmp(const void* a, const void* b) {
const struct latency_entry *pa, *pb;
- pa = (*((struct latency_entry **)a));
- pb = (*((struct latency_entry **)b));
+ pa = (*((struct latency_entry**)a));
+ pb = (*((struct latency_entry**)b));
return numcmp(pb->max, pa->max);
}
diff --git a/libfec/Android.bp b/libfec/Android.bp
index 3da6fe7c..6bd44dd9 100644
--- a/libfec/Android.bp
+++ b/libfec/Android.bp
@@ -1,5 +1,22 @@
// Copyright 2015 The Android Open Source Project
+package {
+ default_applicable_licenses: ["system_extras_libfec_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_libfec_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_defaults {
name: "libfec_default",
diff --git a/libfec/fec_open.cpp b/libfec/fec_open.cpp
index fe458097..175207b8 100644
--- a/libfec/fec_open.cpp
+++ b/libfec/fec_open.cpp
@@ -468,7 +468,7 @@ int fec_ecc_get_metadata(struct fec_handle *f, struct fec_ecc_metadata *data)
check(f->data_size < f->size);
check(f->ecc.start >= f->data_size);
check(f->ecc.start < f->size);
- check(f->ecc.start % FEC_BLOCKSIZE == 0)
+ check(f->ecc.start % FEC_BLOCKSIZE == 0);
data->valid = f->ecc.valid;
data->roots = f->ecc.roots;
diff --git a/libfec/fec_private.h b/libfec/fec_private.h
index b28c4294..bbd8b281 100644
--- a/libfec/fec_private.h
+++ b/libfec/fec_private.h
@@ -47,7 +47,7 @@
/* verity definitions */
#define VERITY_METADATA_SIZE (8 * FEC_BLOCKSIZE)
#define VERITY_TABLE_ARGS 10 /* mandatory arguments */
-#define VERITY_MIN_TABLE_SIZE (VERITY_TABLE_ARGS * 2) /* for a sanity check */
+#define VERITY_MIN_TABLE_SIZE (VERITY_TABLE_ARGS * 2) /* for quick validation */
#define VERITY_MAX_TABLE_SIZE (VERITY_METADATA_SIZE - sizeof(verity_header))
/* verity header and metadata */
diff --git a/libfec/fec_process.cpp b/libfec/fec_process.cpp
index 6e0ddd11..f11b8b2c 100644
--- a/libfec/fec_process.cpp
+++ b/libfec/fec_process.cpp
@@ -44,7 +44,7 @@ ssize_t process(fec_handle *f, uint8_t *buf, size_t count, uint64_t offset,
read_func func)
{
check(f);
- check(buf)
+ check(buf);
check(func);
if (count == 0) {
diff --git a/libfec/fec_verity.cpp b/libfec/fec_verity.cpp
index 0290c1ff..45c197ff 100644
--- a/libfec/fec_verity.cpp
+++ b/libfec/fec_verity.cpp
@@ -123,9 +123,9 @@ uint64_t verity_get_size(uint64_t file_size, uint32_t *verity_levels,
int hashtree_info::get_hash(const uint8_t *block, uint8_t *hash) {
auto md = EVP_get_digestbynid(nid_);
- check(md)
+ check(md);
auto mdctx = EVP_MD_CTX_new();
- check(mdctx)
+ check(mdctx);
EVP_DigestInit_ex(mdctx, md, nullptr);
EVP_DigestUpdate(mdctx, salt.data(), salt.size());
@@ -134,7 +134,7 @@ int hashtree_info::get_hash(const uint8_t *block, uint8_t *hash) {
EVP_DigestFinal_ex(mdctx, hash, &hash_size);
EVP_MD_CTX_free(mdctx);
- check(hash_size == digest_length_)
+ check(hash_size == digest_length_);
return 0;
}
@@ -170,7 +170,7 @@ bool hashtree_info::check_block_hash(const uint8_t *expected,
bool hashtree_info::check_block_hash_with_index(uint64_t index,
const uint8_t *block) {
- check(index < data_blocks)
+ check(index < data_blocks);
const uint8_t *expected = &hash_data[index * padded_digest_length_];
return check_block_hash(expected, block);
diff --git a/libfec/test/Android.bp b/libfec/test/Android.bp
index 75182a27..d6be78e5 100644
--- a/libfec/test/Android.bp
+++ b/libfec/test/Android.bp
@@ -1,3 +1,12 @@
+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_libfec_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_extras_libfec_license"],
+}
+
cc_defaults {
name: "fec_test_defaults",
@@ -47,10 +56,14 @@ cc_test_host {
srcs: ["fec_unittest.cpp"],
gtest: true,
- required: [
- "avbtool",
- "fec",
+ data: [
+ ":avbtool",
+ ":fec",
],
+ compile_multilib: "first",
+ test_options: {
+ unit_test: true,
+ },
static_libs: [
"libverity_tree",
"libfec",
@@ -59,7 +72,6 @@ cc_test_host {
"libcrypto_utils",
"libext4_utils",
"libsquashfs_utils",
- "libgtest_prod",
"libcrypto",
"libcutils",
"liblog",
diff --git a/libfscrypt/Android.bp b/libfscrypt/Android.bp
index 65b94ed9..afdc7f4e 100644
--- a/libfscrypt/Android.bp
+++ b/libfscrypt/Android.bp
@@ -1,5 +1,22 @@
// Copyright 2018 The Android Open Source Project
+package {
+ default_applicable_licenses: ["system_extras_libfscrypt_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_libfscrypt_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_library {
name: "libfscrypt",
recovery_available: true,
diff --git a/libfscrypt/fscrypt.cpp b/libfscrypt/fscrypt.cpp
index a52ed90c..f6e97f11 100644
--- a/libfscrypt/fscrypt.cpp
+++ b/libfscrypt/fscrypt.cpp
@@ -98,7 +98,7 @@ static void log_ls(const char* dirname) {
std::array<const char*, 3> argv = {"ls", "-laZ", dirname};
int status = 0;
auto res =
- logwrap_fork_execvp(argv.size(), argv.data(), &status, false, LOG_ALOG, false, nullptr);
+ logwrap_fork_execvp(argv.size(), argv.data(), &status, false, LOG_ALOG, false, nullptr);
if (res != 0) {
PLOG(ERROR) << argv[0] << " " << argv[1] << " " << argv[2] << "failed";
return;
@@ -196,16 +196,14 @@ bool ParseOptionsForApiLevel(unsigned int first_api_level, const std::string& op
} else {
options->filenames_mode = FSCRYPT_MODE_AES_256_CTS;
}
- if (parts.size() > 1 && !parts[1].empty()){
+ if (parts.size() > 1 && !parts[1].empty()) {
if (!LookupModeByName(filenames_modes, parts[1], &options->filenames_mode)) {
LOG(ERROR) << "Invalid file names encryption mode: " << parts[1];
return false;
}
}
// Default to v2 after Q
- constexpr unsigned int pre_gki_level = 29;
- auto is_gki = first_api_level > pre_gki_level;
- options->version = is_gki ? 2 : 1;
+ options->version = first_api_level > __ANDROID_API_Q__ ? 2 : 1;
options->flags = 0;
options->use_hw_wrapped_key = false;
if (parts.size() > 2 && !parts[2].empty()) {
@@ -234,7 +232,8 @@ bool ParseOptionsForApiLevel(unsigned int first_api_level, const std::string& op
// For everything else, use 16-byte padding. This is more secure (it helps
// hide the length of filenames), and it makes the inputs evenly divisible
// into cipher blocks which is more efficient for encryption and decryption.
- if (!is_gki && options->version == 1 && options->filenames_mode == FSCRYPT_MODE_AES_256_CTS) {
+ if (first_api_level <= __ANDROID_API_Q__ && options->version == 1 &&
+ options->filenames_mode == FSCRYPT_MODE_AES_256_CTS) {
options->flags |= FSCRYPT_POLICY_FLAGS_PAD_4;
} else {
options->flags |= FSCRYPT_POLICY_FLAGS_PAD_16;
@@ -245,12 +244,16 @@ bool ParseOptionsForApiLevel(unsigned int first_api_level, const std::string& op
// encryption modes.
if (options->contents_mode == FSCRYPT_MODE_ADIANTUM) {
if (options->filenames_mode != FSCRYPT_MODE_ADIANTUM) {
- LOG(ERROR) << "Adiantum must be both contents and filenames mode or neither, invalid options: " << options_string;
+ LOG(ERROR) << "Adiantum must be both contents and filenames mode or neither, invalid "
+ "options: "
+ << options_string;
return false;
}
options->flags |= FSCRYPT_POLICY_FLAG_DIRECT_KEY;
} else if (options->filenames_mode == FSCRYPT_MODE_ADIANTUM) {
- LOG(ERROR) << "Adiantum must be both contents and filenames mode or neither, invalid options: " << options_string;
+ LOG(ERROR)
+ << "Adiantum must be both contents and filenames mode or neither, invalid options: "
+ << options_string;
return false;
}
diff --git a/libfscrypt/include/fscrypt/fscrypt.h b/libfscrypt/include/fscrypt/fscrypt.h
index b1ba1dfe..f3779d00 100644
--- a/libfscrypt/include/fscrypt/fscrypt.h
+++ b/libfscrypt/include/fscrypt/fscrypt.h
@@ -19,12 +19,6 @@
#include <string>
-#ifndef FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32
-// When FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32 is added to Bionic's linux/fscrypt.h
-// then this whole stanza should be removed.
-#define FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32 0x10
-#endif
-
bool fscrypt_is_native();
static const char* fscrypt_unencrypted_folder = "/unencrypted";
@@ -69,8 +63,8 @@ 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.filenames_mode == rhs.filenames_mode) && (lhs.flags == rhs.flags) &&
+ (lhs.use_hw_wrapped_key == rhs.use_hw_wrapped_key);
}
inline bool operator!=(const EncryptionOptions& lhs, const EncryptionOptions& rhs) {
@@ -88,4 +82,4 @@ inline bool operator!=(const EncryptionPolicy& lhs, const EncryptionPolicy& rhs)
} // namespace fscrypt
} // namespace android
-#endif // _FSCRYPT_H_
+#endif // _FSCRYPT_H_
diff --git a/libfscrypt/tests/Android.bp b/libfscrypt/tests/Android.bp
index 985b425f..c630fc6d 100644
--- a/libfscrypt/tests/Android.bp
+++ b/libfscrypt/tests/Android.bp
@@ -12,6 +12,15 @@
// 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_libfscrypt_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_extras_libfscrypt_license"],
+}
+
cc_test {
name: "libfscrypt_unit_test",
diff --git a/libfscrypt/tests/fscrypt_test.cpp b/libfscrypt/tests/fscrypt_test.cpp
index 4fbd742c..70eb1780 100644
--- a/libfscrypt/tests/fscrypt_test.cpp
+++ b/libfscrypt/tests/fscrypt_test.cpp
@@ -38,7 +38,7 @@ const EncryptionOptions TestString(unsigned int first_api_level, const std::stri
}
#define TEST_STRING(first_api_level, instring, outstring) \
- SCOPED_TRACE(instring); \
+ SCOPED_TRACE(instring); \
auto options = TestString(first_api_level, instring, outstring);
TEST(fscrypt, ParseOptions) {
@@ -179,11 +179,12 @@ TEST(fscrypt, ParseOptions) {
}
TEST(fscrypt, ComparePolicies) {
-#define TEST_INEQUALITY(foo, field, value) { \
- auto bar = foo; \
- bar.field = value; \
- EXPECT_NE(foo, bar); \
-}
+#define TEST_INEQUALITY(foo, field, value) \
+ { \
+ auto bar = foo; \
+ bar.field = value; \
+ EXPECT_NE(foo, bar); \
+ }
EncryptionPolicy foo;
foo.key_raw_ref = "foo";
EncryptionOptions foo_options;
diff --git a/libjsonpb/.clang-format b/libjsonpb/.clang-format
new file mode 120000
index 00000000..fd0645fd
--- /dev/null
+++ b/libjsonpb/.clang-format
@@ -0,0 +1 @@
+../.clang-format-2 \ No newline at end of file
diff --git a/libjsonpb/README.md b/libjsonpb/README.md
index 5562c8f7..46bd84c8 100644
--- a/libjsonpb/README.md
+++ b/libjsonpb/README.md
@@ -50,7 +50,7 @@ code must be converted to use `libjsonpbparse` for consistency.
This library provides functions and tests to examine a JSON file and validate
it against a Protobuf message definition.
-In addition to a sanity check that `libprotobuf` can convert the JSON file to a
+In addition to a validity check that `libprotobuf` can convert the JSON file to a
Protobuf message (using `libjsonpbparse`), it also checks the following:
- Whether there are fields unknown to the schema. All fields in the JSON file
diff --git a/libjsonpb/TEST_MAPPING b/libjsonpb/TEST_MAPPING
deleted file mode 100644
index 69e5a25d..00000000
--- a/libjsonpb/TEST_MAPPING
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "presubmit": [
- {
- "name": "libjsonpbverify_test",
- "host": true
- }
- ]
-}
diff --git a/libjsonpb/parse/Android.bp b/libjsonpb/parse/Android.bp
index eaec342a..ddb95128 100644
--- a/libjsonpb/parse/Android.bp
+++ b/libjsonpb/parse/Android.bp
@@ -15,6 +15,10 @@
// A convenient library to convert any JSON string to a specific Protobuf
// message using reflection.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_library_static {
name: "libjsonpbparse",
host_supported: true,
diff --git a/libjsonpb/parse/include/jsonpb/error_or.h b/libjsonpb/parse/include/jsonpb/error_or.h
index 66e22969..3fd3e997 100644
--- a/libjsonpb/parse/include/jsonpb/error_or.h
+++ b/libjsonpb/parse/include/jsonpb/error_or.h
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-
#pragma once
#include <string>
@@ -27,45 +26,45 @@ namespace jsonpb {
template <typename T>
struct ErrorOr {
- template <class... Args>
- explicit ErrorOr(Args&&... args) : data_(kIndex1, std::forward<Args>(args)...) {}
- T& operator*() {
- CHECK(ok());
- return *std::get_if<1u>(&data_);
- }
- const T& operator*() const {
- CHECK(ok());
- return *std::get_if<1u>(&data_);
- }
- T* operator->() {
- CHECK(ok());
- return std::get_if<1u>(&data_);
- }
- const T* operator->() const {
- CHECK(ok());
- return std::get_if<1u>(&data_);
- }
- const std::string& error() const {
- CHECK(!ok());
- 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);
- }
+ template <class... Args>
+ explicit ErrorOr(Args&&... args) : data_(kIndex1, std::forward<Args>(args)...) {}
+ T& operator*() {
+ CHECK(ok());
+ return *std::get_if<1u>(&data_);
+ }
+ const T& operator*() const {
+ CHECK(ok());
+ return *std::get_if<1u>(&data_);
+ }
+ T* operator->() {
+ CHECK(ok());
+ return std::get_if<1u>(&data_);
+ }
+ const T* operator->() const {
+ CHECK(ok());
+ return std::get_if<1u>(&data_);
+ }
+ const std::string& error() const {
+ CHECK(!ok());
+ 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);
+ }
- 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) {}
+ 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) {}
- std::variant<std::string, T> data_;
+ std::variant<std::string, T> data_;
};
template <typename T>
inline ErrorOr<T> MakeError(const std::string& message) {
- return ErrorOr<T>::MakeError(message);
+ return ErrorOr<T>::MakeError(message);
}
} // namespace jsonpb
diff --git a/libjsonpb/parse/include/jsonpb/jsonpb.h b/libjsonpb/parse/include/jsonpb/jsonpb.h
index 350db7fb..0b91a220 100644
--- a/libjsonpb/parse/include/jsonpb/jsonpb.h
+++ b/libjsonpb/parse/include/jsonpb/jsonpb.h
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-
#pragma once
#include <string>
@@ -37,12 +36,12 @@ ErrorOr<std::monostate> JsonStringToMessage(const std::string& content,
// when the android tree gets updated
template <typename T>
ErrorOr<T> JsonStringToMessage(const std::string& content) {
- ErrorOr<T> ret;
- auto error = internal::JsonStringToMessage(content, &*ret);
- if (!error.ok()) {
- return MakeError<T>(error.error());
- }
- return ret;
+ ErrorOr<T> ret;
+ auto error = internal::JsonStringToMessage(content, &*ret);
+ if (!error.ok()) {
+ return MakeError<T>(error.error());
+ }
+ return ret;
}
// TODO: MessageToJsonString is a newly added function in protobuf
diff --git a/libjsonpb/parse/jsonpb.cpp b/libjsonpb/parse/jsonpb.cpp
index d7feb670..3a042e71 100644
--- a/libjsonpb/parse/jsonpb.cpp
+++ b/libjsonpb/parse/jsonpb.cpp
@@ -33,40 +33,40 @@ using google::protobuf::util::TypeResolver;
static constexpr char kTypeUrlPrefix[] = "type.googleapis.com";
std::string GetTypeUrl(const Message& message) {
- return std::string(kTypeUrlPrefix) + "/" + message.GetDescriptor()->full_name();
+ return std::string(kTypeUrlPrefix) + "/" + message.GetDescriptor()->full_name();
}
ErrorOr<std::string> MessageToJsonString(const Message& message) {
- std::unique_ptr<TypeResolver> resolver(
- NewTypeResolverForDescriptorPool(kTypeUrlPrefix, DescriptorPool::generated_pool()));
+ std::unique_ptr<TypeResolver> resolver(
+ NewTypeResolverForDescriptorPool(kTypeUrlPrefix, DescriptorPool::generated_pool()));
- google::protobuf::util::JsonOptions options;
- options.add_whitespace = true;
+ google::protobuf::util::JsonOptions options;
+ options.add_whitespace = true;
- std::string json;
- auto status = BinaryToJsonString(resolver.get(), GetTypeUrl(message),
- message.SerializeAsString(), &json, options);
+ std::string json;
+ auto status = BinaryToJsonString(resolver.get(), GetTypeUrl(message), message.SerializeAsString(),
+ &json, options);
- if (!status.ok()) {
- return MakeError<std::string>(status.error_message().as_string());
- }
- return ErrorOr<std::string>(std::move(json));
+ if (!status.ok()) {
+ return MakeError<std::string>(status.error_message().as_string());
+ }
+ return ErrorOr<std::string>(std::move(json));
}
namespace internal {
ErrorOr<std::monostate> JsonStringToMessage(const std::string& content, Message* message) {
- std::unique_ptr<TypeResolver> resolver(
- NewTypeResolverForDescriptorPool(kTypeUrlPrefix, DescriptorPool::generated_pool()));
+ std::unique_ptr<TypeResolver> resolver(
+ NewTypeResolverForDescriptorPool(kTypeUrlPrefix, DescriptorPool::generated_pool()));
- std::string binary;
- auto status = JsonToBinaryString(resolver.get(), GetTypeUrl(*message), content, &binary);
- if (!status.ok()) {
- return MakeError<std::monostate>(status.error_message().as_string());
- }
- if (!message->ParseFromString(binary)) {
- return MakeError<std::monostate>("Fail to parse.");
- }
- return ErrorOr<std::monostate>();
+ std::string binary;
+ auto status = JsonToBinaryString(resolver.get(), GetTypeUrl(*message), content, &binary);
+ if (!status.ok()) {
+ return MakeError<std::monostate>(status.error_message().as_string());
+ }
+ if (!message->ParseFromString(binary)) {
+ return MakeError<std::monostate>("Fail to parse.");
+ }
+ return ErrorOr<std::monostate>();
}
} // namespace internal
diff --git a/libjsonpb/verify/Android.bp b/libjsonpb/verify/Android.bp
index b32b9b4f..a6641855 100644
--- a/libjsonpb/verify/Android.bp
+++ b/libjsonpb/verify/Android.bp
@@ -15,6 +15,10 @@
// This static library defines parameterized tests that enforce additional restrictions when
// using Protobuf as schema for JSON files. The reason is that the JSON parser that
// libprotobuf-cpp-full provides is relatively relaxed.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_library_static {
name: "libjsonpbverify",
host_supported: true,
@@ -64,7 +68,4 @@ cc_test_host {
"-Werror",
"-Wno-unused-parameter",
],
- test_suites: [
- "general-tests",
- ],
}
diff --git a/libjsonpb/verify/include/jsonpb/json_schema_test.h b/libjsonpb/verify/include/jsonpb/json_schema_test.h
index 3db19310..6fa834b2 100644
--- a/libjsonpb/verify/include/jsonpb/json_schema_test.h
+++ b/libjsonpb/verify/include/jsonpb/json_schema_test.h
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-
#pragma once
#include <unistd.h>
@@ -46,12 +45,9 @@ class JsonSchemaTestConfig {
/**
* If it returns true, tests are skipped when the file is not found.
*/
- virtual bool optional() const {
- return false;
- }
+ virtual bool optional() const { return false; }
};
-using JsonSchemaTestConfigFactory =
- std::function<std::unique_ptr<JsonSchemaTestConfig>()>;
+using JsonSchemaTestConfigFactory = std::function<std::unique_ptr<JsonSchemaTestConfig>()>;
template <typename T>
class AbstractJsonSchemaTestConfig : public JsonSchemaTestConfig {
@@ -68,22 +64,18 @@ class AbstractJsonSchemaTestConfig : public JsonSchemaTestConfig {
template <typename T>
JsonSchemaTestConfigFactory MakeTestParam(const std::string& path) {
- return [path]() {
- return std::make_unique<AbstractJsonSchemaTestConfig<T>>(path);
- };
+ return [path]() { return std::make_unique<AbstractJsonSchemaTestConfig<T>>(path); };
}
-class JsonSchemaTest
- : public ::testing::TestWithParam<JsonSchemaTestConfigFactory> {
+class JsonSchemaTest : public ::testing::TestWithParam<JsonSchemaTestConfigFactory> {
public:
void SetUp() override {
- auto&& config =
- ::testing::TestWithParam<JsonSchemaTestConfigFactory>::GetParam()();
+ auto&& config = ::testing::TestWithParam<JsonSchemaTestConfigFactory>::GetParam()();
file_path_ = config->file_path();
if (access(file_path_.c_str(), F_OK) == -1) {
- ASSERT_EQ(ENOENT, errno) << "File '" << file_path_ << "' is not accessible: "
- << strerror(errno);
+ ASSERT_EQ(ENOENT, errno) << "File '" << file_path_
+ << "' is not accessible: " << strerror(errno);
ASSERT_TRUE(config->optional()) << "Missing mandatory file " << file_path_;
GTEST_SKIP();
}
@@ -92,12 +84,9 @@ class JsonSchemaTest
object_ = config->CreateMessage();
auto res = internal::JsonStringToMessage(json_, object_.get());
- ASSERT_TRUE(res.ok()) << "Invalid format of file " << file_path_
- << ": " << res.error();
- }
- google::protobuf::Message* message() const {
- return object_.get();
+ ASSERT_TRUE(res.ok()) << "Invalid format of file " << file_path_ << ": " << res.error();
}
+ google::protobuf::Message* message() const { return object_.get(); }
std::string file_path_;
std::string json_;
std::unique_ptr<google::protobuf::Message> object_;
diff --git a/libjsonpb/verify/include/jsonpb/verify.h b/libjsonpb/verify/include/jsonpb/verify.h
index c05b13d2..bb247e7e 100644
--- a/libjsonpb/verify/include/jsonpb/verify.h
+++ b/libjsonpb/verify/include/jsonpb/verify.h
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-
#pragma once
#include <sstream>
@@ -64,8 +63,8 @@ namespace jsonpb {
// path: path to navigate inside JSON tree. For example, {"foo", "bar"} for
// the value "string" in
// {"foo": {"bar" : "string"}}
-bool AllFieldsAreKnown(const google::protobuf::Message& message,
- const std::string& json, std::string* error);
+bool AllFieldsAreKnown(const google::protobuf::Message& message, const std::string& json,
+ std::string* error);
// Format the given JSON string according to Prototype T. This will serialize
// the JSON string to a Prototype message, then re-print the message as JSON. By
@@ -78,14 +77,12 @@ bool AllFieldsAreKnown(const google::protobuf::Message& message,
// scratch_space: The scratch space to use to store the Protobuf message. It
// must be a pointer
// to the schema that the JSON string conforms to.
-bool EqReformattedJson(const std::string& json,
- google::protobuf::Message* scratch_space,
+bool EqReformattedJson(const std::string& json, google::protobuf::Message* scratch_space,
std::string* error);
namespace internal {
// See EqReformattedJson().
-ErrorOr<std::string> FormatJson(const std::string& json,
- google::protobuf::Message* scratch_space);
+ErrorOr<std::string> FormatJson(const std::string& json, google::protobuf::Message* scratch_space);
} // namespace internal
diff --git a/libjsonpb/verify/test.cpp b/libjsonpb/verify/test.cpp
index cb98f47f..2c69d6c3 100644
--- a/libjsonpb/verify/test.cpp
+++ b/libjsonpb/verify/test.cpp
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-
#include <limits>
#include <sstream>
@@ -46,15 +45,12 @@ class JsonKeyTest : public LibJsonpbVerifyTest {
}
template <typename T>
- void TestParseOkWithUnknownKey(const std::string& field_name,
- const std::string& json_key) {
+ void TestParseOkWithUnknownKey(const std::string& field_name, const std::string& json_key) {
std::string json = "{\"" + json_key + "\": \"test\"}";
auto object = JsonStringToMessage<T>(json);
ASSERT_TRUE(object.ok()) << object.error();
- EXPECT_EQ(
- "test",
- object->GetReflection()->GetString(
- *object, object->GetDescriptor()->FindFieldByName(field_name)));
+ EXPECT_EQ("test", object->GetReflection()->GetString(
+ *object, object->GetDescriptor()->FindFieldByName(field_name)));
std::string error;
ASSERT_FALSE(AllFieldsAreKnown(*object, json, &error))
<< "AllFieldsAreKnown should return false";
@@ -148,8 +144,7 @@ TEST_F(JsonKeyTest, NoJsonNameQuxQuux) {
class EmbeddedJsonKeyTest : public LibJsonpbVerifyTest {
public:
- ErrorOr<Parent> TestEmbeddedError(const std::string& json,
- const std::string& unknown_key) {
+ ErrorOr<Parent> TestEmbeddedError(const std::string& json, const std::string& unknown_key) {
auto object = JsonStringToMessage<Parent>(json);
if (!object.ok()) return object;
std::string error;
@@ -184,30 +179,28 @@ TEST_F(EmbeddedJsonKeyTest, Ok) {
}
TEST_F(EmbeddedJsonKeyTest, FooBar) {
- auto object = TestEmbeddedError(
- "{\"with_json_name\": {\"foo_bar\": \"test\"}}", "foo_bar");
+ auto object = TestEmbeddedError("{\"with_json_name\": {\"foo_bar\": \"test\"}}", "foo_bar");
ASSERT_TRUE(object.ok()) << object.error();
EXPECT_EQ("test", object->with_json_name().foo_bar());
}
TEST_F(EmbeddedJsonKeyTest, BarBaz) {
- auto object = TestEmbeddedError(
- "{\"repeated_with_json_name\": [{\"barBaz\": \"test\"}]}", "barBaz");
+ auto object =
+ TestEmbeddedError("{\"repeated_with_json_name\": [{\"barBaz\": \"test\"}]}", "barBaz");
ASSERT_TRUE(object.ok()) << object.error();
ASSERT_EQ(1u, object->repeated_with_json_name().size());
EXPECT_EQ("test", object->repeated_with_json_name().begin()->barbaz());
}
TEST_F(EmbeddedJsonKeyTest, NoJsonName) {
- auto object = TestEmbeddedError(
- "{\"no_json_name\": {\"QUXQUUX\": \"test\"}}", "QUXQUUX");
+ auto object = TestEmbeddedError("{\"no_json_name\": {\"QUXQUUX\": \"test\"}}", "QUXQUUX");
ASSERT_TRUE(object.ok()) << object.error();
EXPECT_EQ("test", object->no_json_name().qux_quux());
}
TEST_F(EmbeddedJsonKeyTest, QuxQuux) {
- auto object = TestEmbeddedError(
- "{\"repeated_no_json_name\": [{\"QUXQUUX\": \"test\"}]}", "QUXQUUX");
+ auto object =
+ TestEmbeddedError("{\"repeated_no_json_name\": [{\"QUXQUUX\": \"test\"}]}", "QUXQUUX");
ASSERT_TRUE(object.ok()) << object.error();
ASSERT_EQ(1u, object->repeated_no_json_name().size());
EXPECT_EQ("test", object->repeated_no_json_name().begin()->qux_quux());
@@ -215,24 +208,23 @@ TEST_F(EmbeddedJsonKeyTest, QuxQuux) {
class ScalarTest : public LibJsonpbVerifyTest {
public:
- ::testing::AssertionResult IsJsonEq(const std::string& l,
- const std::string& r) {
- Json::Reader reader;
+ ::testing::AssertionResult IsJsonEq(const std::string& l, const std::string& r) {
+ Json::CharReaderBuilder builder;
+ std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
Json::Value lvalue;
- if (!reader.parse(l, lvalue))
- return ::testing::AssertionFailure()
- << reader.getFormattedErrorMessages();
+ std::string errorMessage;
+ if (!reader->parse(&*l.begin(), &*l.end(), &lvalue, &errorMessage))
+ return ::testing::AssertionFailure() << errorMessage;
Json::Value rvalue;
- if (!reader.parse(r, rvalue))
- return ::testing::AssertionFailure()
- << reader.getFormattedErrorMessages();
- Json::StyledWriter writer;
+ if (!reader->parse(&*r.begin(), &*r.end(), &rvalue, &errorMessage))
+ return ::testing::AssertionFailure() << errorMessage;
+ Json::StreamWriterBuilder factory;
return lvalue == rvalue
? (::testing::AssertionSuccess() << "Both are \n"
- << writer.write(lvalue))
- : (::testing::AssertionFailure()
- << writer.write(lvalue) << "\n does not equal \n"
- << writer.write(rvalue));
+ << Json::writeString(factory, lvalue))
+ : (::testing::AssertionFailure() << Json::writeString(factory, lvalue)
+ << "\n does not equal \n"
+ << Json::writeString(factory, rvalue));
}
bool EqReformattedJson(const std::string& json, std::string* error) {
@@ -262,9 +254,8 @@ TEST_F(ScalarTest, Ok) {
}
using ScalarTestErrorParam = std::tuple<const char*, const char*>;
-class ScalarTestError
- : public ScalarTest,
- public ::testing::WithParamInterface<ScalarTestErrorParam> {};
+class ScalarTestError : public ScalarTest,
+ public ::testing::WithParamInterface<ScalarTestErrorParam> {};
TEST_P(ScalarTestError, Test) {
std::string json;
@@ -273,8 +264,7 @@ TEST_P(ScalarTestError, Test) {
auto formatted = FormatJson(json, &scalar_);
ASSERT_TRUE(formatted.ok()) << formatted.error();
EXPECT_FALSE(IsJsonEq(json, *formatted)) << message;
- EXPECT_FALSE(EqReformattedJson(json, &error_))
- << "EqReformattedJson should return false";
+ EXPECT_FALSE(EqReformattedJson(json, &error_)) << "EqReformattedJson should return false";
}
static const std::vector<ScalarTestErrorParam> gScalarTestErrorParams = {
@@ -287,8 +277,7 @@ static const std::vector<ScalarTestErrorParam> gScalarTestErrorParams = {
{"{\"e\": 1}", "Should not allow integers for enums"},
};
-INSTANTIATE_TEST_SUITE_P(Jsonpb, ScalarTestError,
- ::testing::ValuesIn(gScalarTestErrorParams));
+INSTANTIATE_TEST_SUITE_P(Jsonpb, ScalarTestError, ::testing::ValuesIn(gScalarTestErrorParams));
int main(int argc, char** argv) {
using ::testing::AddGlobalTestEnvironment;
diff --git a/libjsonpb/verify/verify.cpp b/libjsonpb/verify/verify.cpp
index c411de81..a4767502 100644
--- a/libjsonpb/verify/verify.cpp
+++ b/libjsonpb/verify/verify.cpp
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-
#include <jsonpb/verify.h>
#include <iostream>
@@ -47,13 +46,11 @@ const std::string& GetJsonName(const FieldDescriptor& field_descriptor) {
// bumped.
FieldDescriptorProto proto;
field_descriptor.CopyTo(&proto);
- return proto.has_json_name() ? field_descriptor.json_name()
- : field_descriptor.name();
+ return proto.has_json_name() ? field_descriptor.json_name() : field_descriptor.name();
}
bool AllFieldsAreKnown(const Message& message, const Json::Value& json,
- std::vector<std::string>* path,
- std::stringstream* error) {
+ std::vector<std::string>* path, std::stringstream* error) {
if (!json.isObject()) {
*error << base::Join(*path, ".") << ": Not a JSON object\n";
return false;
@@ -69,14 +66,12 @@ bool AllFieldsAreKnown(const Message& message, const Json::Value& json,
}
std::set<std::string> unknown_keys;
- std::set_difference(json_keys.begin(), json_keys.end(), known_keys.begin(),
- known_keys.end(),
+ std::set_difference(json_keys.begin(), json_keys.end(), known_keys.begin(), known_keys.end(),
std::inserter(unknown_keys, unknown_keys.begin()));
if (!unknown_keys.empty()) {
*error << base::Join(*path, ".") << ": contains unknown keys: ["
- << base::Join(unknown_keys, ", ")
- << "]. Keys must be a known field name of "
+ << base::Join(unknown_keys, ", ") << "]. Keys must be a known field name of "
<< descriptor->full_name() << "(or its json_name option if set): ["
<< base::Join(known_keys, ", ") << "]\n";
return false;
@@ -89,8 +84,7 @@ bool AllFieldsAreKnown(const Message& message, const Json::Value& json,
std::vector<const FieldDescriptor*> set_field_descriptors;
reflection->ListFields(message, &set_field_descriptors);
for (auto&& field_descriptor : set_field_descriptors) {
- if (field_descriptor->cpp_type() !=
- FieldDescriptor::CppType::CPPTYPE_MESSAGE) {
+ if (field_descriptor->cpp_type() != FieldDescriptor::CppType::CPPTYPE_MESSAGE) {
continue;
}
if (field_descriptor->is_map()) {
@@ -101,20 +95,17 @@ bool AllFieldsAreKnown(const Message& message, const Json::Value& json,
const Json::Value& json_value = json[json_name];
if (field_descriptor->is_repeated()) {
- auto&& fields =
- reflection->GetRepeatedFieldRef<Message>(message, field_descriptor);
+ auto&& fields = reflection->GetRepeatedFieldRef<Message>(message, field_descriptor);
if (json_value.type() != Json::ValueType::arrayValue) {
- *error << base::Join(*path, ".")
- << ": not a JSON list. This should not happen.\n";
+ *error << base::Join(*path, ".") << ": not a JSON list. This should not happen.\n";
success = false;
continue;
}
if (json_value.size() != static_cast<size_t>(fields.size())) {
- *error << base::Join(*path, ".") << ": JSON list has size "
- << json_value.size() << " but message has size " << fields.size()
- << ". This should not happen.\n";
+ *error << base::Join(*path, ".") << ": JSON list has size " << json_value.size()
+ << " but message has size " << fields.size() << ". This should not happen.\n";
success = false;
continue;
}
@@ -122,8 +113,8 @@ bool AllFieldsAreKnown(const Message& message, const Json::Value& json,
std::unique_ptr<Message> scratch_space(fields.NewMessage());
for (int i = 0; i < fields.size(); ++i) {
path->push_back(json_name + "[" + std::to_string(i) + "]");
- auto res = AllFieldsAreKnown(fields.Get(i, scratch_space.get()),
- json_value[i], path, error);
+ auto res =
+ AllFieldsAreKnown(fields.Get(i, scratch_space.get()), json_value[i], path, error);
path->pop_back();
if (!res) {
success = false;
@@ -142,12 +133,12 @@ bool AllFieldsAreKnown(const Message& message, const Json::Value& json,
return success;
}
-bool AllFieldsAreKnown(const google::protobuf::Message& message,
- const std::string& json, std::string* error) {
- Json::Reader reader;
+bool AllFieldsAreKnown(const google::protobuf::Message& message, const std::string& json,
+ std::string* error) {
+ Json::CharReaderBuilder builder;
+ std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
Json::Value value;
- if (!reader.parse(json, value)) {
- *error = reader.getFormattedErrorMessages();
+ if (!reader->parse(&*json.begin(), &*json.end(), &value, error)) {
return false;
}
@@ -160,13 +151,12 @@ bool AllFieldsAreKnown(const google::protobuf::Message& message,
return true;
}
-bool EqReformattedJson(const std::string& json,
- google::protobuf::Message* scratch_space,
+bool EqReformattedJson(const std::string& json, google::protobuf::Message* scratch_space,
std::string* error) {
- Json::Reader reader;
+ Json::CharReaderBuilder builder;
+ std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
Json::Value old_json;
- if (!reader.parse(json, old_json)) {
- *error = reader.getFormattedErrorMessages();
+ if (!reader->parse(&*json.begin(), &*json.end(), &old_json, error)) {
return false;
}
@@ -176,8 +166,7 @@ bool EqReformattedJson(const std::string& json,
return false;
}
Json::Value new_json;
- if (!reader.parse(*new_json_string, new_json)) {
- *error = reader.getFormattedErrorMessages();
+ if (!reader->parse(&*new_json_string->begin(), &*new_json_string->end(), &new_json, error)) {
return false;
}
@@ -198,8 +187,10 @@ bool EqReformattedJson(const std::string& json,
"option\n"
" for appropriate fields.\n"
"\n"
- "Reformatted JSON is printed below.\n"
- << Json::StyledWriter().write(new_json);
+ "Reformatted JSON is printed below.\n";
+ Json::StreamWriterBuilder factory;
+ std::unique_ptr<Json::StreamWriter> const writer(factory.newStreamWriter());
+ writer->write(new_json, &ss);
*error = ss.str();
return false;
}
@@ -207,8 +198,7 @@ bool EqReformattedJson(const std::string& json,
}
namespace internal {
-ErrorOr<std::string> FormatJson(const std::string& json,
- google::protobuf::Message* scratch_space) {
+ErrorOr<std::string> FormatJson(const std::string& json, google::protobuf::Message* scratch_space) {
auto res = internal::JsonStringToMessage(json, scratch_space);
if (!res.ok()) {
return MakeError<std::string>(res.error());
diff --git a/memory_replay/Android.bp b/memory_replay/Android.bp
index ab5a380f..8fb9dbc1 100644
--- a/memory_replay/Android.bp
+++ b/memory_replay/Android.bp
@@ -14,6 +14,23 @@
// limitations under the License.
//
+package {
+ default_applicable_licenses: ["system_extras_memory_replay_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_memory_replay_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_defaults {
name: "memory_flag_defaults",
host_supported: false,
diff --git a/memory_replay/main.cpp b/memory_replay/main.cpp
index 9873ec70..e610305e 100644
--- a/memory_replay/main.cpp
+++ b/memory_replay/main.cpp
@@ -37,6 +37,7 @@
constexpr size_t kDefaultMaxThreads = 512;
static size_t GetMaxAllocs(const AllocEntry* entries, size_t num_entries) {
+ size_t max_allocs = 0;
size_t num_allocs = 0;
for (size_t i = 0; i < num_entries; i++) {
switch (entries[i].type) {
@@ -45,15 +46,28 @@ static size_t GetMaxAllocs(const AllocEntry* entries, size_t num_entries) {
case MALLOC:
case CALLOC:
case MEMALIGN:
+ if (entries[i].ptr != 0) {
+ num_allocs++;
+ }
+ break;
case REALLOC:
- num_allocs++;
+ if (entries[i].ptr == 0 && entries[i].u.old_ptr != 0) {
+ num_allocs--;
+ } else if (entries[i].ptr != 0 && entries[i].u.old_ptr == 0) {
+ num_allocs++;
+ }
break;
case FREE:
- num_allocs--;
+ if (entries[i].ptr != 0) {
+ num_allocs--;
+ }
break;
}
+ if (num_allocs > max_allocs) {
+ max_allocs = num_allocs;
+ }
}
- return num_allocs;
+ return max_allocs;
}
static void ProcessDump(const AllocEntry* entries, size_t num_entries, size_t max_threads) {
diff --git a/memtrack/Android.bp b/memtrack/Android.bp
index 457140e9..d04c6ffa 100644
--- a/memtrack/Android.bp
+++ b/memtrack/Android.bp
@@ -12,6 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_applicable_licenses: ["system_extras_memtrack_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_memtrack_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_defaults {
name: "memtrack_defaults",
diff --git a/mmap-perf/Android.bp b/mmap-perf/Android.bp
index 4841bee1..d989e5fe 100644
--- a/mmap-perf/Android.bp
+++ b/mmap-perf/Android.bp
@@ -14,6 +14,23 @@
// limitations under the License.
//
+package {
+ default_applicable_licenses: ["system_extras_mmap-perf_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_mmap-perf_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_benchmark {
name: "mmapPerf",
diff --git a/module_ndk_libs/README.md b/module_ndk_libs/README.md
new file mode 100644
index 00000000..18ec2a03
--- /dev/null
+++ b/module_ndk_libs/README.md
@@ -0,0 +1,2 @@
+This directory contains the NDK libraries and headers for native libraries in
+Mainline modules.
diff --git a/module_ndk_libs/libnativehelper/Android.bp b/module_ndk_libs/libnativehelper/Android.bp
new file mode 100644
index 00000000..43043738
--- /dev/null
+++ b/module_ndk_libs/libnativehelper/Android.bp
@@ -0,0 +1,58 @@
+// Copyright (C) 2009 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 upstream source of these headers and map.txt is the
+// platform/libnativehelper project in the ART branch. Any changes here should
+// be synced here and vice versa.
+
+package {
+ default_applicable_licenses: [
+ "system_extras_module_ndk_libs_libnativehelper_license",
+ ],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_module_ndk_libs_libnativehelper_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
+ndk_headers {
+ name: "ndk_jni.h",
+ from: "include_jni",
+ to: "",
+ srcs: ["include_jni/jni.h"],
+ license: "NOTICE",
+}
+
+ndk_headers {
+ name: "libnativehelper_ndk_headers",
+ from: "include",
+ to: "",
+ srcs: ["include/android/*.h"],
+ license: "NOTICE",
+}
+
+ndk_library {
+ name: "libnativehelper",
+ symbol_file: "libnativehelper.map.txt",
+ first_version: "S",
+}
diff --git a/module_ndk_libs/libnativehelper/NOTICE b/module_ndk_libs/libnativehelper/NOTICE
new file mode 100644
index 00000000..c5b1efa7
--- /dev/null
+++ b/module_ndk_libs/libnativehelper/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-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.
+
+ 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/module_ndk_libs/libnativehelper/include/android/file_descriptor_jni.h b/module_ndk_libs/libnativehelper/include/android/file_descriptor_jni.h
new file mode 100644
index 00000000..26529b99
--- /dev/null
+++ b/module_ndk_libs/libnativehelper/include/android/file_descriptor_jni.h
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+/**
+ * @addtogroup FileDescriptor File Descriptor
+ * @{
+ */
+
+/**
+ * @file file_descriptor_jni.h
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+#include <jni.h>
+
+#if !defined(__BIONIC__) && !defined(__INTRODUCED_IN)
+#define __INTRODUCED_IN(x)
+#endif
+
+__BEGIN_DECLS
+
+/**
+ * Returns a new java.io.FileDescriptor.
+ *
+ * The FileDescriptor created represents an invalid Unix file descriptor (represented by
+ * a file descriptor value of -1).
+ *
+ * Callers of this method should be aware that it can fail, returning NULL with a pending Java
+ * exception.
+ *
+ * Available since API level 31.
+ *
+ * \param env a pointer to the JNI Native Interface of the current thread.
+ * \return a java.io.FileDescriptor on success, nullptr if insufficient heap memory is available.
+ */
+jobject AFileDescriptor_create(JNIEnv* env) __INTRODUCED_IN(31);
+
+/**
+ * Returns the Unix file descriptor represented by the given java.io.FileDescriptor.
+ *
+ * A return value of -1 indicates that \a fileDescriptor represents an invalid file descriptor.
+ *
+ * Aborts the program if \a fileDescriptor is not a java.io.FileDescriptor instance.
+ *
+ * Available since API level 31.
+ *
+ * \param env a pointer to the JNI Native Interface of the current thread.
+ * \param fileDescriptor a java.io.FileDescriptor instance.
+ * \return the Unix file descriptor wrapped by \a fileDescriptor.
+ */
+int AFileDescriptor_getFd(JNIEnv* env, jobject fileDescriptor) __INTRODUCED_IN(31);
+
+/**
+ * Sets the Unix file descriptor represented by the given java.io.FileDescriptor.
+ *
+ * This function performs no validation of the Unix file descriptor argument, \a fd. Android uses
+ * the value -1 to represent an invalid file descriptor, all other values are considered valid.
+ * The validity of a file descriptor can be checked with FileDescriptor#valid().
+ *
+ * Aborts the program if \a fileDescriptor is not a java.io.FileDescriptor instance.
+ *
+ * Available since API level 31.
+ *
+ * \param env a pointer to the JNI Native Interface of the current thread.
+ * \param fileDescriptor a java.io.FileDescriptor instance.
+ * \param fd a Unix file descriptor that \a fileDescriptor will subsequently represent.
+ */
+void AFileDescriptor_setFd(JNIEnv* env, jobject fileDescriptor, int fd) __INTRODUCED_IN(31);
+
+__END_DECLS
+
+/** @} */
diff --git a/module_ndk_libs/libnativehelper/include_jni/jni.h b/module_ndk_libs/libnativehelper/include_jni/jni.h
new file mode 100644
index 00000000..67417a5f
--- /dev/null
+++ b/module_ndk_libs/libnativehelper/include_jni/jni.h
@@ -0,0 +1,1141 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+/*
+ * JNI specification, as defined by Sun:
+ * http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html
+ *
+ * Everything here is expected to be VM-neutral.
+ */
+
+#pragma once
+
+#include <stdarg.h>
+#include <stdint.h>
+
+/* Primitive types that match up with Java equivalents. */
+typedef uint8_t jboolean; /* unsigned 8 bits */
+typedef int8_t jbyte; /* signed 8 bits */
+typedef uint16_t jchar; /* unsigned 16 bits */
+typedef int16_t jshort; /* signed 16 bits */
+typedef int32_t jint; /* signed 32 bits */
+typedef int64_t jlong; /* signed 64 bits */
+typedef float jfloat; /* 32-bit IEEE 754 */
+typedef double jdouble; /* 64-bit IEEE 754 */
+
+/* "cardinal indices and sizes" */
+typedef jint jsize;
+
+#ifdef __cplusplus
+/*
+ * Reference types, in C++
+ */
+class _jobject {};
+class _jclass : public _jobject {};
+class _jstring : public _jobject {};
+class _jarray : public _jobject {};
+class _jobjectArray : public _jarray {};
+class _jbooleanArray : public _jarray {};
+class _jbyteArray : public _jarray {};
+class _jcharArray : public _jarray {};
+class _jshortArray : public _jarray {};
+class _jintArray : public _jarray {};
+class _jlongArray : public _jarray {};
+class _jfloatArray : public _jarray {};
+class _jdoubleArray : public _jarray {};
+class _jthrowable : public _jobject {};
+
+typedef _jobject* jobject;
+typedef _jclass* jclass;
+typedef _jstring* jstring;
+typedef _jarray* jarray;
+typedef _jobjectArray* jobjectArray;
+typedef _jbooleanArray* jbooleanArray;
+typedef _jbyteArray* jbyteArray;
+typedef _jcharArray* jcharArray;
+typedef _jshortArray* jshortArray;
+typedef _jintArray* jintArray;
+typedef _jlongArray* jlongArray;
+typedef _jfloatArray* jfloatArray;
+typedef _jdoubleArray* jdoubleArray;
+typedef _jthrowable* jthrowable;
+typedef _jobject* jweak;
+
+
+#else /* not __cplusplus */
+
+/*
+ * Reference types, in C.
+ */
+typedef void* jobject;
+typedef jobject jclass;
+typedef jobject jstring;
+typedef jobject jarray;
+typedef jarray jobjectArray;
+typedef jarray jbooleanArray;
+typedef jarray jbyteArray;
+typedef jarray jcharArray;
+typedef jarray jshortArray;
+typedef jarray jintArray;
+typedef jarray jlongArray;
+typedef jarray jfloatArray;
+typedef jarray jdoubleArray;
+typedef jobject jthrowable;
+typedef jobject jweak;
+
+#endif /* not __cplusplus */
+
+struct _jfieldID; /* opaque structure */
+typedef struct _jfieldID* jfieldID; /* field IDs */
+
+struct _jmethodID; /* opaque structure */
+typedef struct _jmethodID* jmethodID; /* method IDs */
+
+struct JNIInvokeInterface;
+
+typedef union jvalue {
+ jboolean z;
+ jbyte b;
+ jchar c;
+ jshort s;
+ jint i;
+ jlong j;
+ jfloat f;
+ jdouble d;
+ jobject l;
+} jvalue;
+
+typedef enum jobjectRefType {
+ JNIInvalidRefType = 0,
+ JNILocalRefType = 1,
+ JNIGlobalRefType = 2,
+ JNIWeakGlobalRefType = 3
+} jobjectRefType;
+
+typedef struct {
+ const char* name;
+ const char* signature;
+ void* fnPtr;
+} JNINativeMethod;
+
+struct _JNIEnv;
+struct _JavaVM;
+typedef const struct JNINativeInterface* C_JNIEnv;
+
+#if defined(__cplusplus)
+typedef _JNIEnv JNIEnv;
+typedef _JavaVM JavaVM;
+#else
+typedef const struct JNINativeInterface* JNIEnv;
+typedef const struct JNIInvokeInterface* JavaVM;
+#endif
+
+/*
+ * Table of interface function pointers.
+ */
+struct JNINativeInterface {
+ void* reserved0;
+ void* reserved1;
+ void* reserved2;
+ void* reserved3;
+
+ jint (*GetVersion)(JNIEnv *);
+
+ jclass (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*,
+ jsize);
+ jclass (*FindClass)(JNIEnv*, const char*);
+
+ jmethodID (*FromReflectedMethod)(JNIEnv*, jobject);
+ jfieldID (*FromReflectedField)(JNIEnv*, jobject);
+ /* spec doesn't show jboolean parameter */
+ jobject (*ToReflectedMethod)(JNIEnv*, jclass, jmethodID, jboolean);
+
+ jclass (*GetSuperclass)(JNIEnv*, jclass);
+ jboolean (*IsAssignableFrom)(JNIEnv*, jclass, jclass);
+
+ /* spec doesn't show jboolean parameter */
+ jobject (*ToReflectedField)(JNIEnv*, jclass, jfieldID, jboolean);
+
+ jint (*Throw)(JNIEnv*, jthrowable);
+ jint (*ThrowNew)(JNIEnv *, jclass, const char *);
+ jthrowable (*ExceptionOccurred)(JNIEnv*);
+ void (*ExceptionDescribe)(JNIEnv*);
+ void (*ExceptionClear)(JNIEnv*);
+ void (*FatalError)(JNIEnv*, const char*);
+
+ jint (*PushLocalFrame)(JNIEnv*, jint);
+ jobject (*PopLocalFrame)(JNIEnv*, jobject);
+
+ jobject (*NewGlobalRef)(JNIEnv*, jobject);
+ void (*DeleteGlobalRef)(JNIEnv*, jobject);
+ void (*DeleteLocalRef)(JNIEnv*, jobject);
+ jboolean (*IsSameObject)(JNIEnv*, jobject, jobject);
+
+ jobject (*NewLocalRef)(JNIEnv*, jobject);
+ jint (*EnsureLocalCapacity)(JNIEnv*, jint);
+
+ jobject (*AllocObject)(JNIEnv*, jclass);
+ jobject (*NewObject)(JNIEnv*, jclass, jmethodID, ...);
+ jobject (*NewObjectV)(JNIEnv*, jclass, jmethodID, va_list);
+ jobject (*NewObjectA)(JNIEnv*, jclass, jmethodID, const jvalue*);
+
+ jclass (*GetObjectClass)(JNIEnv*, jobject);
+ jboolean (*IsInstanceOf)(JNIEnv*, jobject, jclass);
+ jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
+
+ jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
+ jobject (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);
+ jobject (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
+ jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);
+ jboolean (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);
+ jboolean (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
+ jbyte (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);
+ jbyte (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);
+ jbyte (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
+ jchar (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);
+ jchar (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);
+ jchar (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
+ jshort (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);
+ jshort (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);
+ jshort (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
+ jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
+ jint (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);
+ jint (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
+ jlong (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);
+ jlong (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);
+ jlong (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
+ jfloat (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...);
+ jfloat (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list);
+ jfloat (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
+ jdouble (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...);
+ jdouble (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list);
+ jdouble (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
+ void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
+ void (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);
+ void (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
+
+ jobject (*CallNonvirtualObjectMethod)(JNIEnv*, jobject, jclass,
+ jmethodID, ...);
+ jobject (*CallNonvirtualObjectMethodV)(JNIEnv*, jobject, jclass,
+ jmethodID, va_list);
+ jobject (*CallNonvirtualObjectMethodA)(JNIEnv*, jobject, jclass,
+ jmethodID, const jvalue*);
+ jboolean (*CallNonvirtualBooleanMethod)(JNIEnv*, jobject, jclass,
+ jmethodID, ...);
+ jboolean (*CallNonvirtualBooleanMethodV)(JNIEnv*, jobject, jclass,
+ jmethodID, va_list);
+ jboolean (*CallNonvirtualBooleanMethodA)(JNIEnv*, jobject, jclass,
+ jmethodID, const jvalue*);
+ jbyte (*CallNonvirtualByteMethod)(JNIEnv*, jobject, jclass,
+ jmethodID, ...);
+ jbyte (*CallNonvirtualByteMethodV)(JNIEnv*, jobject, jclass,
+ jmethodID, va_list);
+ jbyte (*CallNonvirtualByteMethodA)(JNIEnv*, jobject, jclass,
+ jmethodID, const jvalue*);
+ jchar (*CallNonvirtualCharMethod)(JNIEnv*, jobject, jclass,
+ jmethodID, ...);
+ jchar (*CallNonvirtualCharMethodV)(JNIEnv*, jobject, jclass,
+ jmethodID, va_list);
+ jchar (*CallNonvirtualCharMethodA)(JNIEnv*, jobject, jclass,
+ jmethodID, const jvalue*);
+ jshort (*CallNonvirtualShortMethod)(JNIEnv*, jobject, jclass,
+ jmethodID, ...);
+ jshort (*CallNonvirtualShortMethodV)(JNIEnv*, jobject, jclass,
+ jmethodID, va_list);
+ jshort (*CallNonvirtualShortMethodA)(JNIEnv*, jobject, jclass,
+ jmethodID, const jvalue*);
+ jint (*CallNonvirtualIntMethod)(JNIEnv*, jobject, jclass,
+ jmethodID, ...);
+ jint (*CallNonvirtualIntMethodV)(JNIEnv*, jobject, jclass,
+ jmethodID, va_list);
+ jint (*CallNonvirtualIntMethodA)(JNIEnv*, jobject, jclass,
+ jmethodID, const jvalue*);
+ jlong (*CallNonvirtualLongMethod)(JNIEnv*, jobject, jclass,
+ jmethodID, ...);
+ jlong (*CallNonvirtualLongMethodV)(JNIEnv*, jobject, jclass,
+ jmethodID, va_list);
+ jlong (*CallNonvirtualLongMethodA)(JNIEnv*, jobject, jclass,
+ jmethodID, const jvalue*);
+ jfloat (*CallNonvirtualFloatMethod)(JNIEnv*, jobject, jclass,
+ jmethodID, ...);
+ jfloat (*CallNonvirtualFloatMethodV)(JNIEnv*, jobject, jclass,
+ jmethodID, va_list);
+ jfloat (*CallNonvirtualFloatMethodA)(JNIEnv*, jobject, jclass,
+ jmethodID, const jvalue*);
+ jdouble (*CallNonvirtualDoubleMethod)(JNIEnv*, jobject, jclass,
+ jmethodID, ...);
+ jdouble (*CallNonvirtualDoubleMethodV)(JNIEnv*, jobject, jclass,
+ jmethodID, va_list);
+ jdouble (*CallNonvirtualDoubleMethodA)(JNIEnv*, jobject, jclass,
+ jmethodID, const jvalue*);
+ void (*CallNonvirtualVoidMethod)(JNIEnv*, jobject, jclass,
+ jmethodID, ...);
+ void (*CallNonvirtualVoidMethodV)(JNIEnv*, jobject, jclass,
+ jmethodID, va_list);
+ void (*CallNonvirtualVoidMethodA)(JNIEnv*, jobject, jclass,
+ jmethodID, const jvalue*);
+
+ jfieldID (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
+
+ jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID);
+ jboolean (*GetBooleanField)(JNIEnv*, jobject, jfieldID);
+ jbyte (*GetByteField)(JNIEnv*, jobject, jfieldID);
+ jchar (*GetCharField)(JNIEnv*, jobject, jfieldID);
+ jshort (*GetShortField)(JNIEnv*, jobject, jfieldID);
+ jint (*GetIntField)(JNIEnv*, jobject, jfieldID);
+ jlong (*GetLongField)(JNIEnv*, jobject, jfieldID);
+ jfloat (*GetFloatField)(JNIEnv*, jobject, jfieldID);
+ jdouble (*GetDoubleField)(JNIEnv*, jobject, jfieldID);
+
+ void (*SetObjectField)(JNIEnv*, jobject, jfieldID, jobject);
+ void (*SetBooleanField)(JNIEnv*, jobject, jfieldID, jboolean);
+ void (*SetByteField)(JNIEnv*, jobject, jfieldID, jbyte);
+ void (*SetCharField)(JNIEnv*, jobject, jfieldID, jchar);
+ void (*SetShortField)(JNIEnv*, jobject, jfieldID, jshort);
+ void (*SetIntField)(JNIEnv*, jobject, jfieldID, jint);
+ void (*SetLongField)(JNIEnv*, jobject, jfieldID, jlong);
+ void (*SetFloatField)(JNIEnv*, jobject, jfieldID, jfloat);
+ void (*SetDoubleField)(JNIEnv*, jobject, jfieldID, jdouble);
+
+ jmethodID (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*);
+
+ jobject (*CallStaticObjectMethod)(JNIEnv*, jclass, jmethodID, ...);
+ jobject (*CallStaticObjectMethodV)(JNIEnv*, jclass, jmethodID, va_list);
+ jobject (*CallStaticObjectMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
+ jboolean (*CallStaticBooleanMethod)(JNIEnv*, jclass, jmethodID, ...);
+ jboolean (*CallStaticBooleanMethodV)(JNIEnv*, jclass, jmethodID,
+ va_list);
+ jboolean (*CallStaticBooleanMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
+ jbyte (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...);
+ jbyte (*CallStaticByteMethodV)(JNIEnv*, jclass, jmethodID, va_list);
+ jbyte (*CallStaticByteMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
+ jchar (*CallStaticCharMethod)(JNIEnv*, jclass, jmethodID, ...);
+ jchar (*CallStaticCharMethodV)(JNIEnv*, jclass, jmethodID, va_list);
+ jchar (*CallStaticCharMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
+ jshort (*CallStaticShortMethod)(JNIEnv*, jclass, jmethodID, ...);
+ jshort (*CallStaticShortMethodV)(JNIEnv*, jclass, jmethodID, va_list);
+ jshort (*CallStaticShortMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
+ jint (*CallStaticIntMethod)(JNIEnv*, jclass, jmethodID, ...);
+ jint (*CallStaticIntMethodV)(JNIEnv*, jclass, jmethodID, va_list);
+ jint (*CallStaticIntMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
+ jlong (*CallStaticLongMethod)(JNIEnv*, jclass, jmethodID, ...);
+ jlong (*CallStaticLongMethodV)(JNIEnv*, jclass, jmethodID, va_list);
+ jlong (*CallStaticLongMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
+ jfloat (*CallStaticFloatMethod)(JNIEnv*, jclass, jmethodID, ...);
+ jfloat (*CallStaticFloatMethodV)(JNIEnv*, jclass, jmethodID, va_list);
+ jfloat (*CallStaticFloatMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
+ jdouble (*CallStaticDoubleMethod)(JNIEnv*, jclass, jmethodID, ...);
+ jdouble (*CallStaticDoubleMethodV)(JNIEnv*, jclass, jmethodID, va_list);
+ jdouble (*CallStaticDoubleMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
+ void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);
+ void (*CallStaticVoidMethodV)(JNIEnv*, jclass, jmethodID, va_list);
+ void (*CallStaticVoidMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
+
+ jfieldID (*GetStaticFieldID)(JNIEnv*, jclass, const char*,
+ const char*);
+
+ jobject (*GetStaticObjectField)(JNIEnv*, jclass, jfieldID);
+ jboolean (*GetStaticBooleanField)(JNIEnv*, jclass, jfieldID);
+ jbyte (*GetStaticByteField)(JNIEnv*, jclass, jfieldID);
+ jchar (*GetStaticCharField)(JNIEnv*, jclass, jfieldID);
+ jshort (*GetStaticShortField)(JNIEnv*, jclass, jfieldID);
+ jint (*GetStaticIntField)(JNIEnv*, jclass, jfieldID);
+ jlong (*GetStaticLongField)(JNIEnv*, jclass, jfieldID);
+ jfloat (*GetStaticFloatField)(JNIEnv*, jclass, jfieldID);
+ jdouble (*GetStaticDoubleField)(JNIEnv*, jclass, jfieldID);
+
+ void (*SetStaticObjectField)(JNIEnv*, jclass, jfieldID, jobject);
+ void (*SetStaticBooleanField)(JNIEnv*, jclass, jfieldID, jboolean);
+ void (*SetStaticByteField)(JNIEnv*, jclass, jfieldID, jbyte);
+ void (*SetStaticCharField)(JNIEnv*, jclass, jfieldID, jchar);
+ void (*SetStaticShortField)(JNIEnv*, jclass, jfieldID, jshort);
+ void (*SetStaticIntField)(JNIEnv*, jclass, jfieldID, jint);
+ void (*SetStaticLongField)(JNIEnv*, jclass, jfieldID, jlong);
+ void (*SetStaticFloatField)(JNIEnv*, jclass, jfieldID, jfloat);
+ void (*SetStaticDoubleField)(JNIEnv*, jclass, jfieldID, jdouble);
+
+ jstring (*NewString)(JNIEnv*, const jchar*, jsize);
+ jsize (*GetStringLength)(JNIEnv*, jstring);
+ const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*);
+ void (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*);
+ jstring (*NewStringUTF)(JNIEnv*, const char*);
+ jsize (*GetStringUTFLength)(JNIEnv*, jstring);
+ /* JNI spec says this returns const jbyte*, but that's inconsistent */
+ const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*);
+ void (*ReleaseStringUTFChars)(JNIEnv*, jstring, const char*);
+ jsize (*GetArrayLength)(JNIEnv*, jarray);
+ jobjectArray (*NewObjectArray)(JNIEnv*, jsize, jclass, jobject);
+ jobject (*GetObjectArrayElement)(JNIEnv*, jobjectArray, jsize);
+ void (*SetObjectArrayElement)(JNIEnv*, jobjectArray, jsize, jobject);
+
+ jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize);
+ jbyteArray (*NewByteArray)(JNIEnv*, jsize);
+ jcharArray (*NewCharArray)(JNIEnv*, jsize);
+ jshortArray (*NewShortArray)(JNIEnv*, jsize);
+ jintArray (*NewIntArray)(JNIEnv*, jsize);
+ jlongArray (*NewLongArray)(JNIEnv*, jsize);
+ jfloatArray (*NewFloatArray)(JNIEnv*, jsize);
+ jdoubleArray (*NewDoubleArray)(JNIEnv*, jsize);
+
+ jboolean* (*GetBooleanArrayElements)(JNIEnv*, jbooleanArray, jboolean*);
+ jbyte* (*GetByteArrayElements)(JNIEnv*, jbyteArray, jboolean*);
+ jchar* (*GetCharArrayElements)(JNIEnv*, jcharArray, jboolean*);
+ jshort* (*GetShortArrayElements)(JNIEnv*, jshortArray, jboolean*);
+ jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);
+ jlong* (*GetLongArrayElements)(JNIEnv*, jlongArray, jboolean*);
+ jfloat* (*GetFloatArrayElements)(JNIEnv*, jfloatArray, jboolean*);
+ jdouble* (*GetDoubleArrayElements)(JNIEnv*, jdoubleArray, jboolean*);
+
+ void (*ReleaseBooleanArrayElements)(JNIEnv*, jbooleanArray,
+ jboolean*, jint);
+ void (*ReleaseByteArrayElements)(JNIEnv*, jbyteArray,
+ jbyte*, jint);
+ void (*ReleaseCharArrayElements)(JNIEnv*, jcharArray,
+ jchar*, jint);
+ void (*ReleaseShortArrayElements)(JNIEnv*, jshortArray,
+ jshort*, jint);
+ void (*ReleaseIntArrayElements)(JNIEnv*, jintArray,
+ jint*, jint);
+ void (*ReleaseLongArrayElements)(JNIEnv*, jlongArray,
+ jlong*, jint);
+ void (*ReleaseFloatArrayElements)(JNIEnv*, jfloatArray,
+ jfloat*, jint);
+ void (*ReleaseDoubleArrayElements)(JNIEnv*, jdoubleArray,
+ jdouble*, jint);
+
+ void (*GetBooleanArrayRegion)(JNIEnv*, jbooleanArray,
+ jsize, jsize, jboolean*);
+ void (*GetByteArrayRegion)(JNIEnv*, jbyteArray,
+ jsize, jsize, jbyte*);
+ void (*GetCharArrayRegion)(JNIEnv*, jcharArray,
+ jsize, jsize, jchar*);
+ void (*GetShortArrayRegion)(JNIEnv*, jshortArray,
+ jsize, jsize, jshort*);
+ void (*GetIntArrayRegion)(JNIEnv*, jintArray,
+ jsize, jsize, jint*);
+ void (*GetLongArrayRegion)(JNIEnv*, jlongArray,
+ jsize, jsize, jlong*);
+ void (*GetFloatArrayRegion)(JNIEnv*, jfloatArray,
+ jsize, jsize, jfloat*);
+ void (*GetDoubleArrayRegion)(JNIEnv*, jdoubleArray,
+ jsize, jsize, jdouble*);
+
+ /* spec shows these without const; some jni.h do, some don't */
+ void (*SetBooleanArrayRegion)(JNIEnv*, jbooleanArray,
+ jsize, jsize, const jboolean*);
+ void (*SetByteArrayRegion)(JNIEnv*, jbyteArray,
+ jsize, jsize, const jbyte*);
+ void (*SetCharArrayRegion)(JNIEnv*, jcharArray,
+ jsize, jsize, const jchar*);
+ void (*SetShortArrayRegion)(JNIEnv*, jshortArray,
+ jsize, jsize, const jshort*);
+ void (*SetIntArrayRegion)(JNIEnv*, jintArray,
+ jsize, jsize, const jint*);
+ void (*SetLongArrayRegion)(JNIEnv*, jlongArray,
+ jsize, jsize, const jlong*);
+ void (*SetFloatArrayRegion)(JNIEnv*, jfloatArray,
+ jsize, jsize, const jfloat*);
+ void (*SetDoubleArrayRegion)(JNIEnv*, jdoubleArray,
+ jsize, jsize, const jdouble*);
+
+ jint (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*,
+ jint);
+ jint (*UnregisterNatives)(JNIEnv*, jclass);
+ jint (*MonitorEnter)(JNIEnv*, jobject);
+ jint (*MonitorExit)(JNIEnv*, jobject);
+ jint (*GetJavaVM)(JNIEnv*, JavaVM**);
+
+ void (*GetStringRegion)(JNIEnv*, jstring, jsize, jsize, jchar*);
+ void (*GetStringUTFRegion)(JNIEnv*, jstring, jsize, jsize, char*);
+
+ void* (*GetPrimitiveArrayCritical)(JNIEnv*, jarray, jboolean*);
+ void (*ReleasePrimitiveArrayCritical)(JNIEnv*, jarray, void*, jint);
+
+ const jchar* (*GetStringCritical)(JNIEnv*, jstring, jboolean*);
+ void (*ReleaseStringCritical)(JNIEnv*, jstring, const jchar*);
+
+ jweak (*NewWeakGlobalRef)(JNIEnv*, jobject);
+ void (*DeleteWeakGlobalRef)(JNIEnv*, jweak);
+
+ jboolean (*ExceptionCheck)(JNIEnv*);
+
+ jobject (*NewDirectByteBuffer)(JNIEnv*, void*, jlong);
+ void* (*GetDirectBufferAddress)(JNIEnv*, jobject);
+ jlong (*GetDirectBufferCapacity)(JNIEnv*, jobject);
+
+ /* added in JNI 1.6 */
+ jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject);
+};
+
+/*
+ * C++ object wrapper.
+ *
+ * This is usually overlaid on a C struct whose first element is a
+ * JNINativeInterface*. We rely somewhat on compiler behavior.
+ */
+struct _JNIEnv {
+ /* do not rename this; it does not seem to be entirely opaque */
+ const struct JNINativeInterface* functions;
+
+#if defined(__cplusplus)
+
+ jint GetVersion()
+ { return functions->GetVersion(this); }
+
+ jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
+ jsize bufLen)
+ { return functions->DefineClass(this, name, loader, buf, bufLen); }
+
+ jclass FindClass(const char* name)
+ { return functions->FindClass(this, name); }
+
+ jmethodID FromReflectedMethod(jobject method)
+ { return functions->FromReflectedMethod(this, method); }
+
+ jfieldID FromReflectedField(jobject field)
+ { return functions->FromReflectedField(this, field); }
+
+ jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic)
+ { return functions->ToReflectedMethod(this, cls, methodID, isStatic); }
+
+ jclass GetSuperclass(jclass clazz)
+ { return functions->GetSuperclass(this, clazz); }
+
+ jboolean IsAssignableFrom(jclass clazz1, jclass clazz2)
+ { return functions->IsAssignableFrom(this, clazz1, clazz2); }
+
+ jobject ToReflectedField(jclass cls, jfieldID fieldID, jboolean isStatic)
+ { return functions->ToReflectedField(this, cls, fieldID, isStatic); }
+
+ jint Throw(jthrowable obj)
+ { return functions->Throw(this, obj); }
+
+ jint ThrowNew(jclass clazz, const char* message)
+ { return functions->ThrowNew(this, clazz, message); }
+
+ jthrowable ExceptionOccurred()
+ { return functions->ExceptionOccurred(this); }
+
+ void ExceptionDescribe()
+ { functions->ExceptionDescribe(this); }
+
+ void ExceptionClear()
+ { functions->ExceptionClear(this); }
+
+ void FatalError(const char* msg)
+ { functions->FatalError(this, msg); }
+
+ jint PushLocalFrame(jint capacity)
+ { return functions->PushLocalFrame(this, capacity); }
+
+ jobject PopLocalFrame(jobject result)
+ { return functions->PopLocalFrame(this, result); }
+
+ jobject NewGlobalRef(jobject obj)
+ { return functions->NewGlobalRef(this, obj); }
+
+ void DeleteGlobalRef(jobject globalRef)
+ { functions->DeleteGlobalRef(this, globalRef); }
+
+ void DeleteLocalRef(jobject localRef)
+ { functions->DeleteLocalRef(this, localRef); }
+
+ jboolean IsSameObject(jobject ref1, jobject ref2)
+ { return functions->IsSameObject(this, ref1, ref2); }
+
+ jobject NewLocalRef(jobject ref)
+ { return functions->NewLocalRef(this, ref); }
+
+ jint EnsureLocalCapacity(jint capacity)
+ { return functions->EnsureLocalCapacity(this, capacity); }
+
+ jobject AllocObject(jclass clazz)
+ { return functions->AllocObject(this, clazz); }
+
+ jobject NewObject(jclass clazz, jmethodID methodID, ...)
+ {
+ va_list args;
+ va_start(args, methodID);
+ jobject result = functions->NewObjectV(this, clazz, methodID, args);
+ va_end(args);
+ return result;
+ }
+
+ jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args)
+ { return functions->NewObjectV(this, clazz, methodID, args); }
+
+ jobject NewObjectA(jclass clazz, jmethodID methodID, const jvalue* args)
+ { return functions->NewObjectA(this, clazz, methodID, args); }
+
+ jclass GetObjectClass(jobject obj)
+ { return functions->GetObjectClass(this, obj); }
+
+ jboolean IsInstanceOf(jobject obj, jclass clazz)
+ { return functions->IsInstanceOf(this, obj, clazz); }
+
+ jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
+ { return functions->GetMethodID(this, clazz, name, sig); }
+
+#define CALL_TYPE_METHOD(_jtype, _jname) \
+ _jtype Call##_jname##Method(jobject obj, jmethodID methodID, ...) \
+ { \
+ _jtype result; \
+ va_list args; \
+ va_start(args, methodID); \
+ result = functions->Call##_jname##MethodV(this, obj, methodID, \
+ args); \
+ va_end(args); \
+ return result; \
+ }
+#define CALL_TYPE_METHODV(_jtype, _jname) \
+ _jtype Call##_jname##MethodV(jobject obj, jmethodID methodID, \
+ va_list args) \
+ { return functions->Call##_jname##MethodV(this, obj, methodID, args); }
+#define CALL_TYPE_METHODA(_jtype, _jname) \
+ _jtype Call##_jname##MethodA(jobject obj, jmethodID methodID, \
+ const jvalue* args) \
+ { return functions->Call##_jname##MethodA(this, obj, methodID, args); }
+
+#define CALL_TYPE(_jtype, _jname) \
+ CALL_TYPE_METHOD(_jtype, _jname) \
+ CALL_TYPE_METHODV(_jtype, _jname) \
+ CALL_TYPE_METHODA(_jtype, _jname)
+
+ CALL_TYPE(jobject, Object)
+ CALL_TYPE(jboolean, Boolean)
+ CALL_TYPE(jbyte, Byte)
+ CALL_TYPE(jchar, Char)
+ CALL_TYPE(jshort, Short)
+ CALL_TYPE(jint, Int)
+ CALL_TYPE(jlong, Long)
+ CALL_TYPE(jfloat, Float)
+ CALL_TYPE(jdouble, Double)
+
+ void CallVoidMethod(jobject obj, jmethodID methodID, ...)
+ {
+ va_list args;
+ va_start(args, methodID);
+ functions->CallVoidMethodV(this, obj, methodID, args);
+ va_end(args);
+ }
+ void CallVoidMethodV(jobject obj, jmethodID methodID, va_list args)
+ { functions->CallVoidMethodV(this, obj, methodID, args); }
+ void CallVoidMethodA(jobject obj, jmethodID methodID, const jvalue* args)
+ { functions->CallVoidMethodA(this, obj, methodID, args); }
+
+#define CALL_NONVIRT_TYPE_METHOD(_jtype, _jname) \
+ _jtype CallNonvirtual##_jname##Method(jobject obj, jclass clazz, \
+ jmethodID methodID, ...) \
+ { \
+ _jtype result; \
+ va_list args; \
+ va_start(args, methodID); \
+ result = functions->CallNonvirtual##_jname##MethodV(this, obj, \
+ clazz, methodID, args); \
+ va_end(args); \
+ return result; \
+ }
+#define CALL_NONVIRT_TYPE_METHODV(_jtype, _jname) \
+ _jtype CallNonvirtual##_jname##MethodV(jobject obj, jclass clazz, \
+ jmethodID methodID, va_list args) \
+ { return functions->CallNonvirtual##_jname##MethodV(this, obj, clazz, \
+ methodID, args); }
+#define CALL_NONVIRT_TYPE_METHODA(_jtype, _jname) \
+ _jtype CallNonvirtual##_jname##MethodA(jobject obj, jclass clazz, \
+ jmethodID methodID, const jvalue* args) \
+ { return functions->CallNonvirtual##_jname##MethodA(this, obj, clazz, \
+ methodID, args); }
+
+#define CALL_NONVIRT_TYPE(_jtype, _jname) \
+ CALL_NONVIRT_TYPE_METHOD(_jtype, _jname) \
+ CALL_NONVIRT_TYPE_METHODV(_jtype, _jname) \
+ CALL_NONVIRT_TYPE_METHODA(_jtype, _jname)
+
+ CALL_NONVIRT_TYPE(jobject, Object)
+ CALL_NONVIRT_TYPE(jboolean, Boolean)
+ CALL_NONVIRT_TYPE(jbyte, Byte)
+ CALL_NONVIRT_TYPE(jchar, Char)
+ CALL_NONVIRT_TYPE(jshort, Short)
+ CALL_NONVIRT_TYPE(jint, Int)
+ CALL_NONVIRT_TYPE(jlong, Long)
+ CALL_NONVIRT_TYPE(jfloat, Float)
+ CALL_NONVIRT_TYPE(jdouble, Double)
+
+ void CallNonvirtualVoidMethod(jobject obj, jclass clazz,
+ jmethodID methodID, ...)
+ {
+ va_list args;
+ va_start(args, methodID);
+ functions->CallNonvirtualVoidMethodV(this, obj, clazz, methodID, args);
+ va_end(args);
+ }
+ void CallNonvirtualVoidMethodV(jobject obj, jclass clazz,
+ jmethodID methodID, va_list args)
+ { functions->CallNonvirtualVoidMethodV(this, obj, clazz, methodID, args); }
+ void CallNonvirtualVoidMethodA(jobject obj, jclass clazz,
+ jmethodID methodID, const jvalue* args)
+ { functions->CallNonvirtualVoidMethodA(this, obj, clazz, methodID, args); }
+
+ jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
+ { return functions->GetFieldID(this, clazz, name, sig); }
+
+ jobject GetObjectField(jobject obj, jfieldID fieldID)
+ { return functions->GetObjectField(this, obj, fieldID); }
+ jboolean GetBooleanField(jobject obj, jfieldID fieldID)
+ { return functions->GetBooleanField(this, obj, fieldID); }
+ jbyte GetByteField(jobject obj, jfieldID fieldID)
+ { return functions->GetByteField(this, obj, fieldID); }
+ jchar GetCharField(jobject obj, jfieldID fieldID)
+ { return functions->GetCharField(this, obj, fieldID); }
+ jshort GetShortField(jobject obj, jfieldID fieldID)
+ { return functions->GetShortField(this, obj, fieldID); }
+ jint GetIntField(jobject obj, jfieldID fieldID)
+ { return functions->GetIntField(this, obj, fieldID); }
+ jlong GetLongField(jobject obj, jfieldID fieldID)
+ { return functions->GetLongField(this, obj, fieldID); }
+ jfloat GetFloatField(jobject obj, jfieldID fieldID)
+ { return functions->GetFloatField(this, obj, fieldID); }
+ jdouble GetDoubleField(jobject obj, jfieldID fieldID)
+ { return functions->GetDoubleField(this, obj, fieldID); }
+
+ void SetObjectField(jobject obj, jfieldID fieldID, jobject value)
+ { functions->SetObjectField(this, obj, fieldID, value); }
+ void SetBooleanField(jobject obj, jfieldID fieldID, jboolean value)
+ { functions->SetBooleanField(this, obj, fieldID, value); }
+ void SetByteField(jobject obj, jfieldID fieldID, jbyte value)
+ { functions->SetByteField(this, obj, fieldID, value); }
+ void SetCharField(jobject obj, jfieldID fieldID, jchar value)
+ { functions->SetCharField(this, obj, fieldID, value); }
+ void SetShortField(jobject obj, jfieldID fieldID, jshort value)
+ { functions->SetShortField(this, obj, fieldID, value); }
+ void SetIntField(jobject obj, jfieldID fieldID, jint value)
+ { functions->SetIntField(this, obj, fieldID, value); }
+ void SetLongField(jobject obj, jfieldID fieldID, jlong value)
+ { functions->SetLongField(this, obj, fieldID, value); }
+ void SetFloatField(jobject obj, jfieldID fieldID, jfloat value)
+ { functions->SetFloatField(this, obj, fieldID, value); }
+ void SetDoubleField(jobject obj, jfieldID fieldID, jdouble value)
+ { functions->SetDoubleField(this, obj, fieldID, value); }
+
+ jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)
+ { return functions->GetStaticMethodID(this, clazz, name, sig); }
+
+#define CALL_STATIC_TYPE_METHOD(_jtype, _jname) \
+ _jtype CallStatic##_jname##Method(jclass clazz, jmethodID methodID, \
+ ...) \
+ { \
+ _jtype result; \
+ va_list args; \
+ va_start(args, methodID); \
+ result = functions->CallStatic##_jname##MethodV(this, clazz, \
+ methodID, args); \
+ va_end(args); \
+ return result; \
+ }
+#define CALL_STATIC_TYPE_METHODV(_jtype, _jname) \
+ _jtype CallStatic##_jname##MethodV(jclass clazz, jmethodID methodID, \
+ va_list args) \
+ { return functions->CallStatic##_jname##MethodV(this, clazz, methodID, \
+ args); }
+#define CALL_STATIC_TYPE_METHODA(_jtype, _jname) \
+ _jtype CallStatic##_jname##MethodA(jclass clazz, jmethodID methodID, \
+ const jvalue* args) \
+ { return functions->CallStatic##_jname##MethodA(this, clazz, methodID, \
+ args); }
+
+#define CALL_STATIC_TYPE(_jtype, _jname) \
+ CALL_STATIC_TYPE_METHOD(_jtype, _jname) \
+ CALL_STATIC_TYPE_METHODV(_jtype, _jname) \
+ CALL_STATIC_TYPE_METHODA(_jtype, _jname)
+
+ CALL_STATIC_TYPE(jobject, Object)
+ CALL_STATIC_TYPE(jboolean, Boolean)
+ CALL_STATIC_TYPE(jbyte, Byte)
+ CALL_STATIC_TYPE(jchar, Char)
+ CALL_STATIC_TYPE(jshort, Short)
+ CALL_STATIC_TYPE(jint, Int)
+ CALL_STATIC_TYPE(jlong, Long)
+ CALL_STATIC_TYPE(jfloat, Float)
+ CALL_STATIC_TYPE(jdouble, Double)
+
+ void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...)
+ {
+ va_list args;
+ va_start(args, methodID);
+ functions->CallStaticVoidMethodV(this, clazz, methodID, args);
+ va_end(args);
+ }
+ void CallStaticVoidMethodV(jclass clazz, jmethodID methodID, va_list args)
+ { functions->CallStaticVoidMethodV(this, clazz, methodID, args); }
+ void CallStaticVoidMethodA(jclass clazz, jmethodID methodID, const jvalue* args)
+ { functions->CallStaticVoidMethodA(this, clazz, methodID, args); }
+
+ jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)
+ { return functions->GetStaticFieldID(this, clazz, name, sig); }
+
+ jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)
+ { return functions->GetStaticObjectField(this, clazz, fieldID); }
+ jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID)
+ { return functions->GetStaticBooleanField(this, clazz, fieldID); }
+ jbyte GetStaticByteField(jclass clazz, jfieldID fieldID)
+ { return functions->GetStaticByteField(this, clazz, fieldID); }
+ jchar GetStaticCharField(jclass clazz, jfieldID fieldID)
+ { return functions->GetStaticCharField(this, clazz, fieldID); }
+ jshort GetStaticShortField(jclass clazz, jfieldID fieldID)
+ { return functions->GetStaticShortField(this, clazz, fieldID); }
+ jint GetStaticIntField(jclass clazz, jfieldID fieldID)
+ { return functions->GetStaticIntField(this, clazz, fieldID); }
+ jlong GetStaticLongField(jclass clazz, jfieldID fieldID)
+ { return functions->GetStaticLongField(this, clazz, fieldID); }
+ jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID)
+ { return functions->GetStaticFloatField(this, clazz, fieldID); }
+ jdouble GetStaticDoubleField(jclass clazz, jfieldID fieldID)
+ { return functions->GetStaticDoubleField(this, clazz, fieldID); }
+
+ void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value)
+ { functions->SetStaticObjectField(this, clazz, fieldID, value); }
+ void SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value)
+ { functions->SetStaticBooleanField(this, clazz, fieldID, value); }
+ void SetStaticByteField(jclass clazz, jfieldID fieldID, jbyte value)
+ { functions->SetStaticByteField(this, clazz, fieldID, value); }
+ void SetStaticCharField(jclass clazz, jfieldID fieldID, jchar value)
+ { functions->SetStaticCharField(this, clazz, fieldID, value); }
+ void SetStaticShortField(jclass clazz, jfieldID fieldID, jshort value)
+ { functions->SetStaticShortField(this, clazz, fieldID, value); }
+ void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value)
+ { functions->SetStaticIntField(this, clazz, fieldID, value); }
+ void SetStaticLongField(jclass clazz, jfieldID fieldID, jlong value)
+ { functions->SetStaticLongField(this, clazz, fieldID, value); }
+ void SetStaticFloatField(jclass clazz, jfieldID fieldID, jfloat value)
+ { functions->SetStaticFloatField(this, clazz, fieldID, value); }
+ void SetStaticDoubleField(jclass clazz, jfieldID fieldID, jdouble value)
+ { functions->SetStaticDoubleField(this, clazz, fieldID, value); }
+
+ jstring NewString(const jchar* unicodeChars, jsize len)
+ { return functions->NewString(this, unicodeChars, len); }
+
+ jsize GetStringLength(jstring string)
+ { return functions->GetStringLength(this, string); }
+
+ const jchar* GetStringChars(jstring string, jboolean* isCopy)
+ { return functions->GetStringChars(this, string, isCopy); }
+
+ void ReleaseStringChars(jstring string, const jchar* chars)
+ { functions->ReleaseStringChars(this, string, chars); }
+
+ jstring NewStringUTF(const char* bytes)
+ { return functions->NewStringUTF(this, bytes); }
+
+ jsize GetStringUTFLength(jstring string)
+ { return functions->GetStringUTFLength(this, string); }
+
+ const char* GetStringUTFChars(jstring string, jboolean* isCopy)
+ { return functions->GetStringUTFChars(this, string, isCopy); }
+
+ void ReleaseStringUTFChars(jstring string, const char* utf)
+ { functions->ReleaseStringUTFChars(this, string, utf); }
+
+ jsize GetArrayLength(jarray array)
+ { return functions->GetArrayLength(this, array); }
+
+ jobjectArray NewObjectArray(jsize length, jclass elementClass,
+ jobject initialElement)
+ { return functions->NewObjectArray(this, length, elementClass,
+ initialElement); }
+
+ jobject GetObjectArrayElement(jobjectArray array, jsize index)
+ { return functions->GetObjectArrayElement(this, array, index); }
+
+ void SetObjectArrayElement(jobjectArray array, jsize index, jobject value)
+ { functions->SetObjectArrayElement(this, array, index, value); }
+
+ jbooleanArray NewBooleanArray(jsize length)
+ { return functions->NewBooleanArray(this, length); }
+ jbyteArray NewByteArray(jsize length)
+ { return functions->NewByteArray(this, length); }
+ jcharArray NewCharArray(jsize length)
+ { return functions->NewCharArray(this, length); }
+ jshortArray NewShortArray(jsize length)
+ { return functions->NewShortArray(this, length); }
+ jintArray NewIntArray(jsize length)
+ { return functions->NewIntArray(this, length); }
+ jlongArray NewLongArray(jsize length)
+ { return functions->NewLongArray(this, length); }
+ jfloatArray NewFloatArray(jsize length)
+ { return functions->NewFloatArray(this, length); }
+ jdoubleArray NewDoubleArray(jsize length)
+ { return functions->NewDoubleArray(this, length); }
+
+ jboolean* GetBooleanArrayElements(jbooleanArray array, jboolean* isCopy)
+ { return functions->GetBooleanArrayElements(this, array, isCopy); }
+ jbyte* GetByteArrayElements(jbyteArray array, jboolean* isCopy)
+ { return functions->GetByteArrayElements(this, array, isCopy); }
+ jchar* GetCharArrayElements(jcharArray array, jboolean* isCopy)
+ { return functions->GetCharArrayElements(this, array, isCopy); }
+ jshort* GetShortArrayElements(jshortArray array, jboolean* isCopy)
+ { return functions->GetShortArrayElements(this, array, isCopy); }
+ jint* GetIntArrayElements(jintArray array, jboolean* isCopy)
+ { return functions->GetIntArrayElements(this, array, isCopy); }
+ jlong* GetLongArrayElements(jlongArray array, jboolean* isCopy)
+ { return functions->GetLongArrayElements(this, array, isCopy); }
+ jfloat* GetFloatArrayElements(jfloatArray array, jboolean* isCopy)
+ { return functions->GetFloatArrayElements(this, array, isCopy); }
+ jdouble* GetDoubleArrayElements(jdoubleArray array, jboolean* isCopy)
+ { return functions->GetDoubleArrayElements(this, array, isCopy); }
+
+ void ReleaseBooleanArrayElements(jbooleanArray array, jboolean* elems,
+ jint mode)
+ { functions->ReleaseBooleanArrayElements(this, array, elems, mode); }
+ void ReleaseByteArrayElements(jbyteArray array, jbyte* elems,
+ jint mode)
+ { functions->ReleaseByteArrayElements(this, array, elems, mode); }
+ void ReleaseCharArrayElements(jcharArray array, jchar* elems,
+ jint mode)
+ { functions->ReleaseCharArrayElements(this, array, elems, mode); }
+ void ReleaseShortArrayElements(jshortArray array, jshort* elems,
+ jint mode)
+ { functions->ReleaseShortArrayElements(this, array, elems, mode); }
+ void ReleaseIntArrayElements(jintArray array, jint* elems,
+ jint mode)
+ { functions->ReleaseIntArrayElements(this, array, elems, mode); }
+ void ReleaseLongArrayElements(jlongArray array, jlong* elems,
+ jint mode)
+ { functions->ReleaseLongArrayElements(this, array, elems, mode); }
+ void ReleaseFloatArrayElements(jfloatArray array, jfloat* elems,
+ jint mode)
+ { functions->ReleaseFloatArrayElements(this, array, elems, mode); }
+ void ReleaseDoubleArrayElements(jdoubleArray array, jdouble* elems,
+ jint mode)
+ { functions->ReleaseDoubleArrayElements(this, array, elems, mode); }
+
+ void GetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len,
+ jboolean* buf)
+ { functions->GetBooleanArrayRegion(this, array, start, len, buf); }
+ void GetByteArrayRegion(jbyteArray array, jsize start, jsize len,
+ jbyte* buf)
+ { functions->GetByteArrayRegion(this, array, start, len, buf); }
+ void GetCharArrayRegion(jcharArray array, jsize start, jsize len,
+ jchar* buf)
+ { functions->GetCharArrayRegion(this, array, start, len, buf); }
+ void GetShortArrayRegion(jshortArray array, jsize start, jsize len,
+ jshort* buf)
+ { functions->GetShortArrayRegion(this, array, start, len, buf); }
+ void GetIntArrayRegion(jintArray array, jsize start, jsize len,
+ jint* buf)
+ { functions->GetIntArrayRegion(this, array, start, len, buf); }
+ void GetLongArrayRegion(jlongArray array, jsize start, jsize len,
+ jlong* buf)
+ { functions->GetLongArrayRegion(this, array, start, len, buf); }
+ void GetFloatArrayRegion(jfloatArray array, jsize start, jsize len,
+ jfloat* buf)
+ { functions->GetFloatArrayRegion(this, array, start, len, buf); }
+ void GetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len,
+ jdouble* buf)
+ { functions->GetDoubleArrayRegion(this, array, start, len, buf); }
+
+ void SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len,
+ const jboolean* buf)
+ { functions->SetBooleanArrayRegion(this, array, start, len, buf); }
+ void SetByteArrayRegion(jbyteArray array, jsize start, jsize len,
+ const jbyte* buf)
+ { functions->SetByteArrayRegion(this, array, start, len, buf); }
+ void SetCharArrayRegion(jcharArray array, jsize start, jsize len,
+ const jchar* buf)
+ { functions->SetCharArrayRegion(this, array, start, len, buf); }
+ void SetShortArrayRegion(jshortArray array, jsize start, jsize len,
+ const jshort* buf)
+ { functions->SetShortArrayRegion(this, array, start, len, buf); }
+ void SetIntArrayRegion(jintArray array, jsize start, jsize len,
+ const jint* buf)
+ { functions->SetIntArrayRegion(this, array, start, len, buf); }
+ void SetLongArrayRegion(jlongArray array, jsize start, jsize len,
+ const jlong* buf)
+ { functions->SetLongArrayRegion(this, array, start, len, buf); }
+ void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len,
+ const jfloat* buf)
+ { functions->SetFloatArrayRegion(this, array, start, len, buf); }
+ void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len,
+ const jdouble* buf)
+ { functions->SetDoubleArrayRegion(this, array, start, len, buf); }
+
+ jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,
+ jint nMethods)
+ { return functions->RegisterNatives(this, clazz, methods, nMethods); }
+
+ jint UnregisterNatives(jclass clazz)
+ { return functions->UnregisterNatives(this, clazz); }
+
+ jint MonitorEnter(jobject obj)
+ { return functions->MonitorEnter(this, obj); }
+
+ jint MonitorExit(jobject obj)
+ { return functions->MonitorExit(this, obj); }
+
+ jint GetJavaVM(JavaVM** vm)
+ { return functions->GetJavaVM(this, vm); }
+
+ void GetStringRegion(jstring str, jsize start, jsize len, jchar* buf)
+ { functions->GetStringRegion(this, str, start, len, buf); }
+
+ void GetStringUTFRegion(jstring str, jsize start, jsize len, char* buf)
+ { return functions->GetStringUTFRegion(this, str, start, len, buf); }
+
+ void* GetPrimitiveArrayCritical(jarray array, jboolean* isCopy)
+ { return functions->GetPrimitiveArrayCritical(this, array, isCopy); }
+
+ void ReleasePrimitiveArrayCritical(jarray array, void* carray, jint mode)
+ { functions->ReleasePrimitiveArrayCritical(this, array, carray, mode); }
+
+ const jchar* GetStringCritical(jstring string, jboolean* isCopy)
+ { return functions->GetStringCritical(this, string, isCopy); }
+
+ void ReleaseStringCritical(jstring string, const jchar* carray)
+ { functions->ReleaseStringCritical(this, string, carray); }
+
+ jweak NewWeakGlobalRef(jobject obj)
+ { return functions->NewWeakGlobalRef(this, obj); }
+
+ void DeleteWeakGlobalRef(jweak obj)
+ { functions->DeleteWeakGlobalRef(this, obj); }
+
+ jboolean ExceptionCheck()
+ { return functions->ExceptionCheck(this); }
+
+ jobject NewDirectByteBuffer(void* address, jlong capacity)
+ { return functions->NewDirectByteBuffer(this, address, capacity); }
+
+ void* GetDirectBufferAddress(jobject buf)
+ { return functions->GetDirectBufferAddress(this, buf); }
+
+ jlong GetDirectBufferCapacity(jobject buf)
+ { return functions->GetDirectBufferCapacity(this, buf); }
+
+ /* added in JNI 1.6 */
+ jobjectRefType GetObjectRefType(jobject obj)
+ { return functions->GetObjectRefType(this, obj); }
+#endif /*__cplusplus*/
+};
+
+
+/*
+ * JNI invocation interface.
+ */
+struct JNIInvokeInterface {
+ void* reserved0;
+ void* reserved1;
+ void* reserved2;
+
+ jint (*DestroyJavaVM)(JavaVM*);
+ jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);
+ jint (*DetachCurrentThread)(JavaVM*);
+ jint (*GetEnv)(JavaVM*, void**, jint);
+ jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);
+};
+
+/*
+ * C++ version.
+ */
+struct _JavaVM {
+ const struct JNIInvokeInterface* functions;
+
+#if defined(__cplusplus)
+ jint DestroyJavaVM()
+ { return functions->DestroyJavaVM(this); }
+ jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
+ { return functions->AttachCurrentThread(this, p_env, thr_args); }
+ jint DetachCurrentThread()
+ { return functions->DetachCurrentThread(this); }
+ jint GetEnv(void** env, jint version)
+ { return functions->GetEnv(this, env, version); }
+ jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
+ { return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
+#endif /*__cplusplus*/
+};
+
+struct JavaVMAttachArgs {
+ jint version; /* must be >= JNI_VERSION_1_2 */
+ const char* name; /* NULL or name of thread as modified UTF-8 str */
+ jobject group; /* global ref of a ThreadGroup object, or NULL */
+};
+typedef struct JavaVMAttachArgs JavaVMAttachArgs;
+
+/*
+ * JNI 1.2+ initialization. (As of 1.6, the pre-1.2 structures are no
+ * longer supported.)
+ */
+typedef struct JavaVMOption {
+ const char* optionString;
+ void* extraInfo;
+} JavaVMOption;
+
+typedef struct JavaVMInitArgs {
+ jint version; /* use JNI_VERSION_1_2 or later */
+
+ jint nOptions;
+ JavaVMOption* options;
+ jboolean ignoreUnrecognized;
+} JavaVMInitArgs;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * VM initialization functions.
+ *
+ * Note these are the only symbols exported for JNI by the VM.
+ */
+jint JNI_GetDefaultJavaVMInitArgs(void*);
+jint JNI_CreateJavaVM(JavaVM**, JNIEnv**, void*);
+jint JNI_GetCreatedJavaVMs(JavaVM**, jsize, jsize*);
+
+#define JNIIMPORT
+#define JNIEXPORT __attribute__ ((visibility ("default")))
+#define JNICALL
+
+/*
+ * Prototypes for functions exported by loadable shared libs. These are
+ * called by JNI, not provided by JNI.
+ */
+JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved);
+JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+/*
+ * Manifest constants.
+ */
+#define JNI_FALSE 0
+#define JNI_TRUE 1
+
+#define JNI_VERSION_1_1 0x00010001
+#define JNI_VERSION_1_2 0x00010002
+#define JNI_VERSION_1_4 0x00010004
+#define JNI_VERSION_1_6 0x00010006
+
+#define JNI_OK (0) /* no error */
+#define JNI_ERR (-1) /* generic error */
+#define JNI_EDETACHED (-2) /* thread detached from the VM */
+#define JNI_EVERSION (-3) /* JNI version error */
+#define JNI_ENOMEM (-4) /* Out of memory */
+#define JNI_EEXIST (-5) /* VM already created */
+#define JNI_EINVAL (-6) /* Invalid argument */
+
+#define JNI_COMMIT 1 /* copy content, do not free buffer */
+#define JNI_ABORT 2 /* free buffer w/o copying back */
+
diff --git a/module_ndk_libs/libnativehelper/libnativehelper.map.txt b/module_ndk_libs/libnativehelper/libnativehelper.map.txt
new file mode 100644
index 00000000..46769dd2
--- /dev/null
+++ b/module_ndk_libs/libnativehelper/libnativehelper.map.txt
@@ -0,0 +1,30 @@
+LIBNATIVEHELPER_S { # introduced=S
+ global:
+ # NDK API for libnativehelper.
+ AFileDescriptor_create;
+ AFileDescriptor_getFd;
+ AFileDescriptor_setFd;
+
+ # JNI Invocation methods available to platform and apps.
+ JNI_CreateJavaVM;
+ JNI_GetDefaultJavaVMInitArgs;
+ JNI_GetCreatedJavaVMs;
+
+ local:
+ *;
+};
+
+LIBNATIVEHELPER_PLATFORM { # platform-only
+ global:
+ JniInvocationCreate;
+ JniInvocationDestroy;
+ JniInvocationInit;
+ JniInvocationGetLibrary;
+
+ jniGetNioBufferBaseArray;
+ jniGetNioBufferBaseArrayOffset;
+ jniGetNioBufferPointer;
+ jniGetNioBufferFields;
+
+ jniUninitializeConstants;
+};
diff --git a/multinetwork/Android.bp b/multinetwork/Android.bp
index e2b4be0e..06c4b346 100644
--- a/multinetwork/Android.bp
+++ b/multinetwork/Android.bp
@@ -1,3 +1,7 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_defaults {
name: "multinetwork_defaults",
diff --git a/multinetwork/common.cpp b/multinetwork/common.cpp
index 7a5e7be4..44298191 100644
--- a/multinetwork/common.cpp
+++ b/multinetwork/common.cpp
@@ -54,16 +54,14 @@ bool parseNetworkHandle(const char *arg, net_handle_t *nethandle) {
void printUsage(const char *progname) {
- std::cerr << "Usage: " << progname
- << " [--nethandle <nethandle>]"
+ std::cerr << "Usage: " << progname << " [--nethandle <nethandle>]"
<< " [--mode explicit|process]"
<< " [--family unspec|ipv4|ipv6]"
- << " <argument>"
- << std::endl;
- std::cerr << std::endl;
- std::cerr << "Learn nethandle values from 'dumpsys connectivity --short' "
- << "or 'dumpsys connectivity --diag'"
- << std::endl;
+ << " [--attempts <N>]"
+ << " --randomname | name" << std::endl
+ << std::endl
+ << "Learn nethandle values from 'dumpsys connectivity --short' "
+ << "or 'dumpsys connectivity --diag'" << std::endl;
}
Arguments::~Arguments() {}
@@ -102,7 +100,21 @@ bool Arguments::parseArguments(int argc, const char* argv[]) {
} else {
break;
}
- } else if (arg1 == nullptr) {
+ } else if (strEqual(argv[i], "--attempts")) {
+ i++;
+ if (argc == i) break;
+ char* endptr;
+ attempts = strtoul(argv[i], &endptr, 10);
+ if (*endptr != '\0') {
+ std::cerr << "Failed to parse arguments: '" << argv[i] << "'" << std::endl;
+ break;
+ }
+ } else if (strEqual(argv[i], "--randomname")) {
+ time_t t;
+ time(&t);
+ srand((unsigned long)time);
+ random_name = true;
+ } else if (arg1 == nullptr && !random_name) {
arg1 = argv[i];
} else {
arg1 = nullptr;
@@ -110,7 +122,7 @@ bool Arguments::parseArguments(int argc, const char* argv[]) {
}
}
- if (arg1 != nullptr) {
+ if (random_name || arg1 != nullptr) {
return true;
}
diff --git a/multinetwork/common.h b/multinetwork/common.h
index f431ea99..a180fb55 100644
--- a/multinetwork/common.h
+++ b/multinetwork/common.h
@@ -31,10 +31,13 @@ enum class ApiMode {
struct Arguments {
- Arguments() : nethandle(NETWORK_UNSPECIFIED),
- api_mode(ApiMode::EXPLICIT),
- family(AF_UNSPEC),
- arg1(nullptr) {}
+ Arguments()
+ : nethandle(NETWORK_UNSPECIFIED),
+ api_mode(ApiMode::EXPLICIT),
+ family(AF_UNSPEC),
+ attempts(1),
+ random_name(false),
+ arg1(nullptr) {}
~Arguments();
bool parseArguments(int argc, const char* argv[]);
@@ -42,6 +45,8 @@ struct Arguments {
net_handle_t nethandle;
ApiMode api_mode;
sa_family_t family;
+ unsigned attempts;
+ bool random_name;
const char* arg1;
};
diff --git a/multinetwork/dnschk.cpp b/multinetwork/dnschk.cpp
index a2c42d4d..07338538 100644
--- a/multinetwork/dnschk.cpp
+++ b/multinetwork/dnschk.cpp
@@ -25,6 +25,7 @@
#include <iostream>
#include <string>
+#include <android-base/format.h>
#include <android/multinetwork.h>
#include "common.h"
@@ -41,43 +42,54 @@ int main(int argc, const char* argv[]) {
};
struct addrinfo *result = nullptr;
- std::cout << "# " << args.arg1
- << " (via nethandle " << args.nethandle << "):"
- << std::endl;
-
- switch (args.api_mode) {
- case ApiMode::EXPLICIT:
- rval = android_getaddrinfofornetwork(args.nethandle,
- args.arg1, nullptr, &hints, &result);
- break;
- case ApiMode::PROCESS:
- if (args.nethandle != NETWORK_UNSPECIFIED) {
- rval = android_setprocnetwork(args.nethandle);
- if (rval != 0) {
- std::cerr << "android_setprocnetwork returned " << rval
- << std::endl;
- return rval;
+ time_t t;
+ time(&t);
+ srand((unsigned long)time);
+
+ for (int i = 0; i < args.attempts; i++) {
+ std::string name;
+
+ if (args.random_name) {
+ name = fmt::format("{}-{}-ds.metric.gstatic.com", rand(), rand());
+ } else {
+ name = args.arg1;
+ }
+
+ std::cout << "# " << name << " (via nethandle " << args.nethandle << "):" << std::endl;
+
+ switch (args.api_mode) {
+ case ApiMode::EXPLICIT:
+ rval = android_getaddrinfofornetwork(args.nethandle, name.c_str(), nullptr, &hints,
+ &result);
+ break;
+ case ApiMode::PROCESS:
+ if (args.nethandle != NETWORK_UNSPECIFIED) {
+ rval = android_setprocnetwork(args.nethandle);
+ if (rval != 0) {
+ std::cerr << "android_setprocnetwork returned " << rval << std::endl;
+ return rval;
+ }
}
- }
- rval = getaddrinfo(args.arg1, nullptr, &hints, &result);
- break;
- default:
- // Unreachable.
- std::cerr << "Unknown api mode." << std::endl;
- return -1;
- }
+ rval = getaddrinfo(name.c_str(), nullptr, &hints, &result);
+ break;
+ default:
+ // Unreachable.
+ std::cerr << "Unknown api mode." << std::endl;
+ return -1;
+ }
- if (rval != 0) {
- std::cerr << "DNS resolution failure; gaierror=" << rval
- << " [" << gai_strerror(rval) << "]"
- << std::endl;
- return rval;
- }
+ if (rval != 0) {
+ std::cerr << "DNS resolution failure; gaierror=" << rval << " [" << gai_strerror(rval)
+ << "]" << std::endl;
+ return rval;
+ }
+
+ for (struct addrinfo* rp = result; rp != nullptr; rp = rp->ai_next) {
+ std::cout << inetSockaddrToString(rp->ai_addr) << std::endl;
+ }
- for (struct addrinfo* rp = result; rp != nullptr; rp = rp->ai_next) {
- std::cout << inetSockaddrToString(rp->ai_addr) << std::endl;
+ freeaddrinfo(result);
}
- freeaddrinfo(result);
return 0;
}
diff --git a/multinetwork/httpurl.cpp b/multinetwork/httpurl.cpp
index d78c0c93..a562ae2c 100644
--- a/multinetwork/httpurl.cpp
+++ b/multinetwork/httpurl.cpp
@@ -29,7 +29,7 @@
#include <android-base/stringprintf.h>
#include "common.h"
-
+using android::base::StringPrintf;
struct Parameters {
Parameters() : ss({}), port("80"), path("/") {}
@@ -41,10 +41,18 @@ struct Parameters {
std::string path;
};
+bool resolveHostname(const struct Arguments& args, struct Parameters* parameters);
bool parseUrl(const struct Arguments& args, struct Parameters* parameters) {
if (parameters == nullptr) { return false; }
+ if (args.random_name) {
+ parameters->host = StringPrintf("%d-%d-ipv6test.ds.metric.gstatic.com", rand(), rand());
+ parameters->hostname = parameters->host;
+ parameters->path = "/ip.js?fmt=text";
+ return resolveHostname(args, parameters);
+ }
+
static const char HTTP_PREFIX[] = "http://";
if (strncmp(args.arg1, HTTP_PREFIX, strlen(HTTP_PREFIX)) != 0) {
std::cerr << "Only " << HTTP_PREFIX << " URLs supported." << std::endl;
@@ -91,6 +99,10 @@ bool parseUrl(const struct Arguments& args, struct Parameters* parameters) {
// TODO: find the request portion to send (before '#...').
+ return resolveHostname(args, parameters);
+}
+
+bool resolveHostname(const struct Arguments& args, struct Parameters* parameters) {
std::cerr << "Resolving hostname=" << parameters->hostname
<< ", port=" << parameters->port
<< std::endl;
@@ -136,7 +148,6 @@ bool parseUrl(const struct Arguments& args, struct Parameters* parameters) {
return true;
}
-
int makeTcpSocket(sa_family_t address_family, net_handle_t nethandle) {
int fd = socket(address_family, SOCK_STREAM, IPPROTO_TCP);
if (fd < 0) {
@@ -233,13 +244,20 @@ int main(int argc, const char* argv[]) {
struct Parameters parameters;
if (!parseUrl(args, &parameters)) { return -1; }
- // TODO: Fall back from IPv6 to IPv4 if ss.ss_family is AF_UNSPEC.
- // This will involve changes to parseUrl() as well.
- struct FdAutoCloser closer = makeTcpSocket(
- parameters.ss.ss_family,
- (args.api_mode == ApiMode::EXPLICIT) ? args.nethandle
- : NETWORK_UNSPECIFIED);
- if (closer.fd < 0) { return closer.fd; }
+ int ret = 0;
+
+ for (int i = 0; i < args.attempts; i++) {
+ // TODO: Fall back from IPv6 to IPv4 if ss.ss_family is AF_UNSPEC.
+ // This will involve changes to parseUrl() as well.
+ struct FdAutoCloser closer = makeTcpSocket(
+ parameters.ss.ss_family,
+ (args.api_mode == ApiMode::EXPLICIT) ? args.nethandle : NETWORK_UNSPECIFIED);
+ if (closer.fd < 0) {
+ return closer.fd;
+ }
+
+ ret |= doHttpQuery(closer.fd, parameters);
+ }
- return doHttpQuery(closer.fd, parameters);
+ return ret;
}
diff --git a/pagecache/Android.bp b/pagecache/Android.bp
index 40fc4449..c53f75bb 100644
--- a/pagecache/Android.bp
+++ b/pagecache/Android.bp
@@ -1,5 +1,22 @@
// Copyright 2015 The Android Open Source Project
+package {
+ default_applicable_licenses: ["system_extras_pagecache_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_pagecache_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_binary {
name: "dumpcache",
diff --git a/partition_tools/Android.bp b/partition_tools/Android.bp
index a05f5e4e..eb886327 100644
--- a/partition_tools/Android.bp
+++ b/partition_tools/Android.bp
@@ -14,6 +14,10 @@
// limitations under the License.
//
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_defaults {
name: "lp_defaults",
cflags: [
diff --git a/partition_tools/aidl/Android.bp b/partition_tools/aidl/Android.bp
index f031150f..e1a17769 100644
--- a/partition_tools/aidl/Android.bp
+++ b/partition_tools/aidl/Android.bp
@@ -14,6 +14,10 @@
// limitations under the License.
//
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
aidl_interface {
name: "liblpdump_interface",
unstable: true,
diff --git a/partition_tools/lpmake.cc b/partition_tools/lpmake.cc
index 1b879636..ca9e62f9 100644
--- a/partition_tools/lpmake.cc
+++ b/partition_tools/lpmake.cc
@@ -19,9 +19,11 @@
#include <stdio.h>
#include <sysexits.h>
+#include <algorithm>
#include <memory>
#include <android-base/parseint.h>
+#include <android-base/result.h>
#include <android-base/strings.h>
#include <liblp/builder.h>
#include <liblp/liblp.h>
@@ -29,6 +31,9 @@
using namespace android;
using namespace android::fs_mgr;
+using android::base::Error;
+using android::base::Result;
+
/* Prints program usage to |where|. */
static int usage(int /* argc */, char* argv[]) {
fprintf(stderr,
@@ -38,7 +43,10 @@ static int usage(int /* argc */, char* argv[]) {
" %s [options]\n"
"\n"
"Required options:\n"
- " -d,--device-size=SIZE Size of the block device for logical partitions.\n"
+ " -d,--device-size=[SIZE|auto] Size of the block device for logical partitions.\n"
+ " Can be set to auto to automatically calculate the\n"
+ " minimum size, the sum of partition sizes plus\n"
+ " metadata-size times the number of partitions.\n"
" -m,--metadata-size=SIZE Maximum size to reserve for partition metadata.\n"
" -s,--metadata-slots=COUNT Number of slots to store metadata copies.\n"
" -p,--partition=DATA Add a partition given the data, see below.\n"
@@ -108,6 +116,56 @@ enum class Option : int {
kForceFullImage = 'F',
};
+struct PartitionInfo {
+ std::string name;
+ uint64_t size;
+ uint32_t attribute_flags;
+ std::string group_name;
+
+ static Result<PartitionInfo> Parse(const char* arg) {
+ std::vector<std::string> parts = android::base::Split(arg, ":");
+ if (parts.size() > 4) {
+ return Error() << "Partition info has invalid formatting.";
+ }
+
+ std::string name = parts[0];
+ if (name.empty()) {
+ return Error() << "Partition must have a valid name.";
+ }
+
+ uint64_t size;
+ if (!android::base::ParseUint(parts[2].c_str(), &size)) {
+ return Error() << "Partition must have a valid size.";
+ }
+
+ uint32_t attribute_flags = 0;
+ std::string attributes = parts[1];
+ if (attributes == "readonly") {
+ attribute_flags |= LP_PARTITION_ATTR_READONLY;
+ } else if (attributes != "none") {
+ return Error() << "Attribute not recognized: " << attributes;
+ }
+
+ std::string group_name = "default";
+ if (parts.size() >= 4) {
+ group_name = parts[3];
+ }
+
+ return PartitionInfo{name, size, attribute_flags, group_name};
+ }
+};
+
+static uint64_t CalculateBlockDeviceSize(uint32_t alignment, uint32_t metadata_size,
+ const std::vector<PartitionInfo>& partitions) {
+ uint64_t ret = std::max(alignment, LP_PARTITION_RESERVED_BYTES +
+ (LP_METADATA_GEOMETRY_SIZE + metadata_size) * 2) +
+ partitions.size() * alignment;
+ for (const auto& partition_info : partitions) {
+ ret += partition_info.size;
+ }
+ return ret;
+}
+
int main(int argc, char* argv[]) {
struct option options[] = {
{ "device-size", required_argument, nullptr, (int)Option::kDeviceSize },
@@ -138,7 +196,7 @@ int main(int argc, char* argv[]) {
uint32_t block_size = 4096;
std::string super_name = "super";
std::string output_path;
- std::vector<std::string> partitions;
+ std::vector<PartitionInfo> partitions;
std::vector<std::string> groups;
std::vector<BlockDeviceInfo> block_devices;
std::map<std::string, std::string> images;
@@ -147,6 +205,7 @@ int main(int argc, char* argv[]) {
bool auto_slot_suffixing = false;
bool force_full_image = false;
bool virtual_ab = false;
+ bool auto_blockdevice_size = false;
int rv;
int index;
@@ -155,7 +214,10 @@ int main(int argc, char* argv[]) {
case Option::kHelp:
return usage(argc, argv);
case Option::kDeviceSize:
- if (!android::base::ParseUint(optarg, &blockdevice_size) || !blockdevice_size) {
+ if (strcmp(optarg, "auto") == 0) {
+ auto_blockdevice_size = true;
+ } else if (!android::base::ParseUint(optarg, &blockdevice_size) ||
+ !blockdevice_size) {
fprintf(stderr, "Invalid argument to --device-size.\n");
return EX_USAGE;
}
@@ -174,7 +236,12 @@ int main(int argc, char* argv[]) {
}
break;
case Option::kPartition:
- partitions.push_back(optarg);
+ if (auto res = PartitionInfo::Parse(optarg); !res.ok()) {
+ fprintf(stderr, "%s\n", res.error().message().c_str());
+ return EX_USAGE;
+ } else {
+ partitions.push_back(std::move(*res));
+ }
break;
case Option::kGroup:
groups.push_back(optarg);
@@ -270,6 +337,10 @@ int main(int argc, char* argv[]) {
return usage(argc, argv);
}
+ if (auto_blockdevice_size) {
+ blockdevice_size = CalculateBlockDeviceSize(alignment, metadata_size, partitions);
+ }
+
// Must specify a block device via the old method (--device-size etc) or
// via --device, but not both.
if ((has_implied_super && (!block_devices.empty() || !blockdevice_size)) ||
@@ -346,46 +417,15 @@ int main(int argc, char* argv[]) {
}
for (const auto& partition_info : partitions) {
- std::vector<std::string> parts = android::base::Split(partition_info, ":");
- if (parts.size() > 4) {
- fprintf(stderr, "Partition info has invalid formatting.\n");
- return EX_USAGE;
- }
-
- std::string name = parts[0];
- if (name.empty()) {
- fprintf(stderr, "Partition must have a valid name.\n");
- return EX_USAGE;
- }
-
- uint64_t size;
- if (!android::base::ParseUint(parts[2].c_str(), &size)) {
- fprintf(stderr, "Partition must have a valid size.\n");
- return EX_USAGE;
- }
-
- uint32_t attribute_flags = 0;
- std::string attributes = parts[1];
- if (attributes == "readonly") {
- attribute_flags |= LP_PARTITION_ATTR_READONLY;
- } else if (attributes != "none") {
- fprintf(stderr, "Attribute not recognized: %s\n", attributes.c_str());
- return EX_USAGE;
- }
-
- std::string group_name = "default";
- if (parts.size() >= 4) {
- group_name = parts[3];
- }
-
- Partition* partition = builder->AddPartition(name, group_name, attribute_flags);
+ Partition* partition = builder->AddPartition(partition_info.name, partition_info.group_name,
+ partition_info.attribute_flags);
if (!partition) {
- fprintf(stderr, "Could not add partition: %s\n", name.c_str());
+ fprintf(stderr, "Could not add partition: %s\n", partition_info.name.c_str());
return EX_SOFTWARE;
}
- if (!builder->ResizePartition(partition, size)) {
+ if (!builder->ResizePartition(partition, partition_info.size)) {
fprintf(stderr, "Not enough space on device for partition %s with size %" PRIu64 "\n",
- name.c_str(), size);
+ partition_info.name.c_str(), partition_info.size);
return EX_SOFTWARE;
}
}
diff --git a/perf2cfg/.style.yapf b/perf2cfg/.style.yapf
new file mode 100644
index 00000000..0e9640c2
--- /dev/null
+++ b/perf2cfg/.style.yapf
@@ -0,0 +1,2 @@
+[style]
+based_on_style = google
diff --git a/perf2cfg/Android.bp b/perf2cfg/Android.bp
new file mode 100644
index 00000000..e6b5505e
--- /dev/null
+++ b/perf2cfg/Android.bp
@@ -0,0 +1,51 @@
+// 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.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+python_library_host {
+ name: "perf2cfg_library",
+ srcs: [
+ "perf2cfg/*.py",
+ ],
+ libs: [
+ "simpleperf_report_lib",
+ ],
+}
+
+python_binary_host {
+ name: "perf2cfg",
+ srcs: [
+ "perf2cfg.py",
+ ],
+ libs: [
+ "perf2cfg_library",
+ ],
+}
+
+python_test_host {
+ name: "perf2cfg_test",
+ srcs: [
+ "perf2cfg_test.py",
+ "tests/*.py",
+ ],
+ libs: [
+ "perf2cfg_library",
+ ],
+ test_options: {
+ unit_test: true,
+ },
+}
diff --git a/perf2cfg/OWNERS b/perf2cfg/OWNERS
new file mode 100644
index 00000000..ce20bb8e
--- /dev/null
+++ b/perf2cfg/OWNERS
@@ -0,0 +1,5 @@
+mast@google.com
+ngeoffray@google.com
+rpl@google.com
+skvadrik@google.com
+vmarko@google.com
diff --git a/perf2cfg/README.md b/perf2cfg/README.md
new file mode 100644
index 00000000..85cbc455
--- /dev/null
+++ b/perf2cfg/README.md
@@ -0,0 +1,121 @@
+# perf2cfg
+
+perf2cfg annotates a control-flow graph (CFG) file with profiling information
+from simpleperf data files. A CFG file can be generated by the Android Runtime
+compiler using the `--dump-cfg=<cfg-file>` option. The tool outputs an
+annotated CFG file with the following added information:
+- Methods are annotated with their contribution relative to the total profile.
+- Basic blocks and assembly instructions are annotated with their contribution
+ relative to the method profile.
+- Basic blocks are colored according to their contribution to the method
+ profile.
+
+The tool does not modify any input files and assumes the input CFG file can be
+parsed by c1visualizer. The input files must have all been generated for the
+same architecture.
+
+## Usage
+
+```
+usage: perf2cfg [-h|--help] --cfg CFG --perf-data PERF_DATA [PERF_DATA ...]
+ [--output-file OUTPUT_FILE] [-e|--events EVENTS]
+ [--primary-event PRIMARY_EVENT]
+
+Annotates a CFG file with profiling information from simpleperf data files.
+
+optional arguments:
+ -h, --help Show this help message and exit.
+ --output-file OUTPUT_FILE
+ A path to the output CFG file.
+ -e EVENTS, --events EVENTS
+ A comma-separated list of events only to use for
+ annotating a CFG (default: use all events found in
+ perf data). An error is reported if the events are not
+ present in perf data.
+ --primary-event PRIMARY_EVENT
+ The event to be used for basic blocks hotness analysis
+ (default: cpu-cycles). Basic blocks are color
+ highlighted according to their hotness. An error is
+ reported if the primary event is not present in perf
+ data.
+
+required arguments:
+ --cfg CFG The CFG file to annotate.
+ --perf-data PERF_DATA [PERF_DATA ...]
+ The perf data files to extract information from.
+```
+
+### Examples
+
+Annotate a CFG file:
+```
+perf2cfg --cfg art.cfg --perf-data perf.data
+```
+
+Annotate a CFG file with multiple simpleperf data files:
+```
+perf2cfg --cfg art.cfg \
+ --perf-data perf_event1.data perf_event2.data perf_event3.data
+```
+
+Color basic blocks according to cache-misses events:
+```
+perf2cfg --cfg art.cfg --perf-data perf.data \
+ --primary-event cache-misses
+```
+
+Display a subset of events from the simpleperf data file:
+```
+perf2cfg --cfg art.cfg --perf-data perf.data \
+ --events cpu-cycles,cache-misses
+```
+
+## Method annotations
+
+Once the annotated CFG file has been opened in c1visualizer, method annotations
+can be seen by enabling the "Show Package Names" and "Sort List of
+Compilations" options in the top-left "Compiled Methods" panel.
+
+## Basic block coloring
+
+perf2cfg implements basic block coloring by adding specific flags to the output
+CFG file. These flags have the following names and meanings:
+- `LO` (low): the basic block is responsible for 1 to 10% of its method primary
+ event.
+- `MO` (moderate): for 10 to 30%.
+- `CO` (considerable): for 30 to 50%
+- `HI` (high): for 50 to 100%.
+
+To use this feature, custom flags have to be defined in c1visualizer:
+1. Open c1visualizer.
+2. Click on the "Tools" menu entry and "Options" to open the options window.
+3. Click on the "Control Flow Graph" button if it isn't already selected.
+4. On the right of the "Flags" list, click on the "New" button.
+5. Enter "LO" in the text field and press "OK".
+6. Select the newly created flag in the list and click on the color picker
+ button.
+7. Select an appropriate color and press "OK".
+8. Repeat steps 4 to 7 for the remaining flags (MO, CO, and HI).
+
+Alternatively, flags can be defined by editing a properties file located at:
+`~/.c1visualizer/dev/config/Preferences/at/ssw/visualizer/cfg/options/CfgPreferences.properties`.
+The directory hierarchy and the file itself might have to be created.
+
+Replace the file contents with the following line to use a yellow to red
+gradient:
+```
+flagsPreference=LO(255,210,0);MO(253,155,5);CO(253,100,5);HI(245,40,5)
+```
+
+For colorblind people, this green gradient can be used as an alternative:
+```
+flagsPreference=LO(235,235,50);MO(210,210,40);CO(185,185,25);HI(155,155,15)
+```
+
+## Hacking
+
+A diagram of the finite state machine used to parse the input CFG file can be
+generated with the following command (requires Graphviz):
+```
+dot -Tpng doc/FSM.dot -o doc/FSM.png
+```
diff --git a/perf2cfg/doc/FSM.dot b/perf2cfg/doc/FSM.dot
new file mode 100644
index 00000000..43702b0b
--- /dev/null
+++ b/perf2cfg/doc/FSM.dot
@@ -0,0 +1,71 @@
+digraph finite_state_machine {
+ rankdir = "LR";
+
+ node [ shape = "doublecircle" ];
+ "End";
+
+ node [ shape = "point" ];
+ "Init";
+
+ node [ shape = "circle" ];
+ "Init" -> "Start";
+
+ "Start" -> "End" [ label = "EOF" ];
+ "Start" -> "Parse Method Name" [ label = "'begin_compilation'" ];
+ "Start" -> "Error" [ label = "NOT('begin_compilation')" ];
+
+ "Parse Method Name" -> "Skip to CFG"
+ [ label = "method_name IN analyzer.methods" ];
+ "Parse Method Name" -> "Skip Method"
+ [ label = "method_name NOT IN analyzer.methods" ];
+ "Parse Method Name" -> "Error" [ label = "EOF OR NOT('name')" ];
+
+ "Skip Method" -> "End" [ label = "EOF" ];
+ "Skip Method" -> "Parse Method Name" [ label = "'begin_compilation'" ];
+ "Skip Method" -> "Skip Method";
+
+ "Skip to CFG" -> "Start CFG" [ label = "'end_compilation'" ];
+ "Skip to CFG" -> "Skip to CFG";
+ "Skip to CFG" -> "Error" [ label = "EOF" ];
+
+ "Start CFG" -> "Is Disassembly Pass" [ label = "'begin_cfg'" ];
+ "Start CFG" -> "Error" [ label = "EOF OR NOT('begin_cfg')" ];
+
+ "Is Disassembly Pass" -> "Parse Flags"
+ [ label = "'name \"disassembly (after)\"'" ];
+ "Is Disassembly Pass" -> "Skip Pass"
+ [ label = "NOT('name \"disassembly (after)\"')" ];
+ "Is Disassembly Pass" -> "Error" [ label = "EOF OR NOT('name')" ];
+
+ "Skip Pass" -> "End CFG" [ label = "'end_cfg'" ];
+ "Skip Pass" -> "Skip Pass";
+ "Skip Pass" -> "Error" [ label = "EOF" ];
+
+ "Parse Flags" -> "Skip to HIR" [ label = "'flags'" ];
+ "Parse Flags" -> "Parse Flags";
+ "Parse Flags" -> "Error" [ label = "EOF" ];
+
+ "Skip to HIR" -> "HIR Instruction" [ label = "'begin_HIR'" ];
+ "Skip to HIR" -> "Skip to HIR";
+ "Skip to HIR" -> "Error" [ label = "EOF" ];
+
+ "HIR Instruction" -> "HIR Instruction" [ label = "'<|@'" ];
+ "HIR Instruction" -> "End HIR" [ label = "'end_HIR'" ];
+ "HIR Instruction" -> "Disassembly";
+ "HIR Instruction" -> "Error" [ label = "EOF" ];
+
+ "Disassembly" -> "HIR Instruction" [ label = "'<|@'" ];
+ "Disassembly" -> "Disassembly";
+ "Disassembly" -> "Error" [ label = "EOF" ];
+
+ "End HIR" -> "End Block" [ label = "'end_block'" ];
+ "End HIR" -> "Error" [ label = "EOF OR NOT('end_block')" ];
+
+ "End Block" -> "Parse Flags" [ label = "'begin_block'" ];
+ "End Block" -> "End CFG" [ label = "'end_cfg'" ];
+ "End Block" -> "Error" [ label = "EOF OR NOT('begin_block' OR 'end_cfg')" ];
+
+ "End CFG" -> "Is Disassembly Pass" [ label = "'begin_cfg'" ];
+ "End CFG" -> "Parse Method Name" [ label = "'begin_compilation'" ];
+ "End CFG" -> "End" [ label = "EOF" ];
+}
diff --git a/perf2cfg/perf2cfg.py b/perf2cfg/perf2cfg.py
new file mode 100755
index 00000000..b010d1a6
--- /dev/null
+++ b/perf2cfg/perf2cfg.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python3
+# 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.
+"""This script annotates a CFG file with profiling information from simpleperf
+record files.
+
+Example:
+ perf2cfg --cfg bench.cfg --perf-data perf.data
+"""
+
+import argparse
+import logging
+import os
+import sys
+import textwrap
+
+from perf2cfg import analyze
+from perf2cfg import edit
+
+
+def parse_arguments() -> argparse.Namespace:
+ """Parses program arguments.
+
+ Returns:
+ argparse.Namespace: A populated argument namespace.
+ """
+ parser = argparse.ArgumentParser(
+ # Hardcode the usage string as argparse does not display long options
+ # if short ones are specified
+ usage=textwrap.dedent("""\
+ perf2cfg [-h|--help] --cfg CFG --perf-data PERF_DATA [PERF_DATA ...]
+ [--output-file OUTPUT_FILE] [-e|--events EVENTS]
+ [--primary-event PRIMARY_EVENT]"""),
+ description='Annotates a CFG file with profiling information from '
+ 'simpleperf data files.',
+ add_help=False)
+ required = parser.add_argument_group('required arguments')
+ required.add_argument('--cfg',
+ required=True,
+ help='The CFG file to annotate.')
+ required.add_argument(
+ '--perf-data',
+ nargs='+',
+ required=True,
+ help='The perf data files to extract information from.')
+ parser.add_argument('-h',
+ '--help',
+ action='help',
+ default=argparse.SUPPRESS,
+ help='Show this help message and exit.')
+ parser.add_argument('--output-file', help='A path to the output CFG file.')
+ parser.add_argument(
+ '-e',
+ '--events',
+ type=lambda events: events.split(',') if events else [],
+ help='A comma-separated list of events only to use for annotating a '
+ 'CFG (default: use all events found in perf data). An error is '
+ 'reported if the events are not present in perf data.')
+ parser.add_argument(
+ '--primary-event',
+ default='cpu-cycles',
+ help='The event to be used for basic blocks hotness analysis '
+ '(default: %(default)s). Basic blocks are color highlighted according '
+ 'to their hotness. An error is reported if the primary event is not '
+ 'present in perf data.')
+ args = parser.parse_args()
+
+ if not args.output_file:
+ root, ext = os.path.splitext(args.cfg)
+ args.output_file = f'{root}-annotated{ext}'
+
+ return args
+
+
+def analyze_record_files(args: argparse.Namespace) -> analyze.RecordAnalyzer:
+ """Analyzes simpleperf record files.
+
+ Args:
+ args (argparse.Namespace): An argument namespace.
+
+ Returns:
+ analyze.RecordAnalyzer: A RecordAnalyzer object.
+ """
+ analyzer = analyze.RecordAnalyzer(args.events)
+ for record_file in args.perf_data:
+ analyzer.analyze(record_file)
+
+ return analyzer
+
+
+def validate_events(analyzer: analyze.RecordAnalyzer,
+ args: argparse.Namespace) -> None:
+ """Validates event names given on the command line.
+
+ Args:
+ analyzer (analyze.RecordAnalyzer): A RecordAnalyzer object.
+ args (argparse.Namespace): An argument namespace.
+ """
+ if not analyzer.event_counts:
+ logging.error('The selected events are not present in perf data')
+ sys.exit(1)
+
+ if args.primary_event not in analyzer.event_counts:
+ logging.error(
+ 'The selected primary event %s is not present in perf data',
+ args.primary_event)
+ sys.exit(1)
+
+
+def annotate_cfg_file(analyzer: analyze.RecordAnalyzer,
+ args: argparse.Namespace) -> None:
+ """Annotates a CFG file.
+
+ Args:
+ analyzer (analyze.RecordAnalyzer): A RecordAnalyzer object.
+ args (argparse.Namespace): An argument namespace.
+ """
+ input_stream = open(args.cfg, 'r')
+ output_stream = open(args.output_file, 'w')
+
+ editor = edit.CfgEditor(analyzer, input_stream, output_stream,
+ args.primary_event)
+ editor.edit()
+
+ input_stream.close()
+ output_stream.close()
+
+
+def main() -> None:
+ """Annotates a CFG file with information from simpleperf record files."""
+ args = parse_arguments()
+ analyzer = analyze_record_files(args)
+ validate_events(analyzer, args)
+ annotate_cfg_file(analyzer, args)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/perf2cfg/perf2cfg/__init__.py b/perf2cfg/perf2cfg/__init__.py
new file mode 100644
index 00000000..c1b565d3
--- /dev/null
+++ b/perf2cfg/perf2cfg/__init__.py
@@ -0,0 +1,13 @@
+# 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.
diff --git a/perf2cfg/perf2cfg/analyze.py b/perf2cfg/perf2cfg/analyze.py
new file mode 100644
index 00000000..90a4e7b7
--- /dev/null
+++ b/perf2cfg/perf2cfg/analyze.py
@@ -0,0 +1,210 @@
+# 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.
+"""Classes for extracting profiling information from simpleperf record files.
+
+Example:
+ analyzer = RecordAnalyzer()
+ analyzer.analyze('perf.data')
+
+ for event_name, event_count in analyzer.event_counts.items():
+ print(f'Number of {event_name} events: {event_count}')
+"""
+
+import collections
+import logging
+import sys
+
+from typing import DefaultDict, Dict, Iterable, Iterator, Optional
+
+# Disable import-error as simpleperf_report_lib is not in pylint's `sys.path`
+# pylint: disable=import-error
+import simpleperf_report_lib # type: ignore
+
+
+class Instruction:
+ """Instruction records profiling information for an assembly instruction.
+
+ Attributes:
+ relative_addr (int): The address of an instruction relative to the
+ start of its method. For arm64, the first instruction of a method
+ will be at the relative address 0, the second at the relative
+ address 4, and so on.
+ event_counts (DefaultDict[str, int]): A mapping of event names to their
+ total number of events for this instruction.
+ """
+
+ def __init__(self, relative_addr: int) -> None:
+ """Instantiates an Instruction.
+
+ Args:
+ relative_addr (int): A relative address.
+ """
+ self.relative_addr = relative_addr
+
+ self.event_counts: DefaultDict[str, int] = collections.defaultdict(int)
+
+ def record_sample(self, event_name: str, event_count: int) -> None:
+ """Records profiling information given by a sample.
+
+ Args:
+ event_name (str): An event name.
+ event_count (int): An event count.
+ """
+ self.event_counts[event_name] += event_count
+
+
+class Method:
+ """Method records profiling information for a compiled method.
+
+ Attributes:
+ name (str): A method name.
+ event_counts (DefaultDict[str, int]): A mapping of event names to their
+ total number of events for this method.
+ instructions (Dict[int, Instruction]): A mapping of relative
+ instruction addresses to their Instruction object.
+ """
+
+ def __init__(self, name: str) -> None:
+ """Instantiates a Method.
+
+ Args:
+ name (str): A method name.
+ """
+ self.name = name
+
+ self.event_counts: DefaultDict[str, int] = collections.defaultdict(int)
+ self.instructions: Dict[int, Instruction] = {}
+
+ def record_sample(self, relative_addr: int, event_name: str,
+ event_count: int) -> None:
+ """Records profiling information given by a sample.
+
+ Args:
+ relative_addr (int): The relative address of an instruction hit.
+ event_name (str): An event name.
+ event_count (int): An event count.
+ """
+ self.event_counts[event_name] += event_count
+
+ if relative_addr not in self.instructions:
+ self.instructions[relative_addr] = Instruction(relative_addr)
+
+ instruction = self.instructions[relative_addr]
+ instruction.record_sample(event_name, event_count)
+
+
+class RecordAnalyzer:
+ """RecordAnalyzer extracts profiling information from simpleperf record
+ files.
+
+ Multiple record files can be analyzed successively, each containing one or
+ more event types. Samples from odex files are the only ones analyzed, as
+ we're interested by the performance of methods generated by the optimizing
+ compiler.
+
+ Attributes:
+ event_names (Set[str]): A set of event names to analyze. If empty, all
+ events are analyzed.
+ event_counts (DefaultDict[str, int]): A mapping of event names to their
+ total number of events for the analyzed samples.
+ methods (Dict[str, Method]): A mapping of method names to their Method
+ object.
+ report (simpleperf_report_lib.ReportLib): A ReportLib object.
+ target_arch (str): A target architecture determined from the first
+ record file analyzed.
+ """
+
+ def __init__(self, event_names: Optional[Iterable[str]] = None) -> None:
+ """Instantiates a RecordAnalyzer.
+
+ Args:
+ event_names (Optional[Iterable[str]]): An optional iterable of
+ event names to analyze. If empty or falsy, all events are
+ analyzed.
+ """
+ if not event_names:
+ event_names = []
+
+ self.event_names = set(event_names)
+
+ self.event_counts: DefaultDict[str, int] = collections.defaultdict(int)
+ self.methods: Dict[str, Method] = {}
+ self.report: simpleperf_report_lib.ReportLib
+ self.target_arch = ''
+
+ def analyze(self, filename: str) -> None:
+ """Analyzes a perf record file.
+
+ Args:
+ filename (str): The path to a perf record file.
+ """
+ # One ReportLib object needs to be instantiated per record file
+ self.report = simpleperf_report_lib.ReportLib()
+ self.report.SetRecordFile(filename)
+
+ arch = self.report.GetArch()
+ if not self.target_arch:
+ self.target_arch = arch
+ elif self.target_arch != arch:
+ logging.error(
+ 'Record file %s is for the architecture %s, expected %s',
+ filename, arch, self.target_arch)
+ self.report.Close()
+ sys.exit(1)
+
+ for sample in self.samples():
+ event = self.report.GetEventOfCurrentSample()
+ if self.event_names and event.name not in self.event_names:
+ continue
+
+ symbol = self.report.GetSymbolOfCurrentSample()
+ relative_addr = symbol.vaddr_in_file - symbol.symbol_addr
+ self.record_sample(symbol.symbol_name, relative_addr, event.name,
+ sample.period)
+
+ self.report.Close()
+ logging.info('Analyzed %d event(s) for %d method(s)',
+ len(self.event_counts), len(self.methods))
+
+ def samples(self) -> Iterator[simpleperf_report_lib.SampleStruct]:
+ """Iterates over samples for compiled methods located in odex files.
+
+ Yields:
+ simpleperf_report_lib.SampleStruct: A sample for a compiled method.
+ """
+ sample = self.report.GetNextSample()
+ while sample:
+ symbol = self.report.GetSymbolOfCurrentSample()
+ if symbol.dso_name.endswith('.odex'):
+ yield sample
+
+ sample = self.report.GetNextSample()
+
+ def record_sample(self, method_name: str, relative_addr: int,
+ event_name: str, event_count: int) -> None:
+ """Records profiling information given by a sample.
+
+ Args:
+ method_name (str): A method name.
+ relative_addr (int): The relative address of an instruction hit.
+ event_name (str): An event name.
+ event_count (int): An event count.
+ """
+ self.event_counts[event_name] += event_count
+
+ if method_name not in self.methods:
+ self.methods[method_name] = Method(method_name)
+
+ method = self.methods[method_name]
+ method.record_sample(relative_addr, event_name, event_count)
diff --git a/perf2cfg/perf2cfg/edit.py b/perf2cfg/perf2cfg/edit.py
new file mode 100644
index 00000000..ae5c581e
--- /dev/null
+++ b/perf2cfg/perf2cfg/edit.py
@@ -0,0 +1,549 @@
+# 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.
+"""Classes for annotating a CFG file with profiling information.
+
+Attributes:
+ END_INSTRUCTION_MARKER (str): The marker used to indicate the end of a HIR
+ instruction.
+ EOF_MARKER (str): The marker used to indicate that the end-of-file has been
+ reached.
+"""
+
+import collections
+import enum
+import logging
+import os
+import re
+
+from typing import DefaultDict, Iterator, List, TextIO, Tuple
+
+from perf2cfg import analyze
+from perf2cfg import events
+from perf2cfg import exceptions
+from perf2cfg import parse
+
+END_INSTRUCTION_MARKER = '<|@'
+EOF_MARKER = '<EOF>'
+
+
+class State(enum.Enum):
+ """State represents the internal state of a CfgEditor object."""
+ START = 1
+ PARSE_METHOD_NAME = 2
+ SKIP_METHOD = 3
+ SKIP_TO_CFG = 4
+ START_CFG = 5
+ IS_DISASSEMBLY_PASS = 6
+ SKIP_PASS = 7
+ PARSE_FLAGS = 8
+ SKIP_TO_HIR = 9
+ HIR_INSTRUCTION = 10
+ DISASSEMBLY = 11
+ END_HIR = 12
+ END_BLOCK = 13
+ END_CFG = 14
+ END = 15
+
+
+class CfgEditor:
+ """CfgEditor annotates a CFG file with profiling information.
+
+ CfgEditor does *not* edit the input CFG file in place. Instead, it reads
+ the input file line by line, generates annotations from profiling
+ information, and writes an annotated CFG file to a given path.
+
+ CfgEditor includes a CFG file parser based on a finite state machine. This
+ parser supports CFG files in the c1visualizer format dumped by the ART
+ optimizing compiler:
+ - The CFG file must be valid (correctly parsed by c1visualizer).
+ - Each line must contain only one directive.
+ - Disassembly of an IR instruction must end with the `<|@` marker on a
+ newline.
+
+ Attributes:
+ analyzer (analyzer.RecordAnalyzer): A RecordAnalyzer object.
+ input_stream (TextIO): An input CFG text stream.
+ output_stream (TextIO): An output CFG text stream.
+ primary_event (str): An event used to color basic blocks.
+ basic_block_event_counts (DefaultDict[str, int]): A mapping of event
+ names to their total number of events for the current basic block.
+ buffer (List[str]): A list of strings to be written to the output CFG
+ file instead of the current line from the input CFG file.
+ current_method (analyze.Method): A Method object representing the
+ current method being annotated.
+ event_names (List[str]): A list of sorted event names from the
+ analysis.
+ flags_offset (int): An output file offset pointing to the last flags
+ directive seen.
+ isa (str): The instruction set architecture as defined in the input CFG
+ file metadata, or the string "unknown" if no metadata was found.
+ padding (str): A string used to pad assembly instructions with no
+ profiling information.
+ saved_flags (List[str]): A list of strings representing the flags of
+ the current basic block being parsed.
+ state (State): A State value representing the internal state of the
+ parser.
+ """
+
+ def __init__(self,
+ analyzer: analyze.RecordAnalyzer,
+ input_stream: TextIO,
+ output_stream: TextIO,
+ primary_event: str = 'cpu-cycles') -> None:
+ """Instantiates a CfgEditor.
+
+ Args:
+ analyzer (analyze.RecordAnalyzer): A RecordAnalyzer object. An
+ analysis must have been completed before passing this object to
+ CfgEditor.
+ input_stream (TextIO): An input CFG text stream.
+ output_stream (TextIO): An output CFG text stream.
+ primary_event (str): An event used to color basic blocks.
+ """
+ self.analyzer = analyzer
+ self.input_stream = input_stream
+ self.output_stream = output_stream
+ self.primary_event = primary_event
+
+ self.basic_block_event_counts: DefaultDict[
+ str, int] = collections.defaultdict(int)
+ self.buffer: List[str] = []
+ self.current_method: analyze.Method
+ self.event_names = events.sort_event_names(self.analyzer.event_counts)
+ self.flags_offset = 0
+ self.isa = ''
+ self.padding = ''
+ self.saved_flags: List[str] = []
+ self.state = State.START
+
+ def edit(self) -> None:
+ """Annotates a CFG file with profiling information."""
+ for lineno, raw_line in self.lines():
+ line = raw_line.strip()
+ try:
+ self.parse_line(line)
+ except exceptions.ArchitectureError as ex:
+ logging.error(ex)
+ return
+ except exceptions.ParseError as ex:
+ logging.error('Line %d: %s', lineno, ex)
+ return
+
+ if self.buffer:
+ self.output_stream.write(''.join(self.buffer))
+ self.buffer = []
+ else:
+ self.output_stream.write(raw_line)
+
+ self.parse_line(EOF_MARKER)
+ if self.state != State.END:
+ logging.error('Unexpected end-of-file while parsing the CFG file')
+
+ def lines(self) -> Iterator[Tuple[int, str]]:
+ """Iterates over lines from the input CFG stream.
+
+ Yields:
+ Tuple[int, str]: A line number and a non-empty line.
+ """
+ for lineno, line in enumerate(self.input_stream, 1):
+ if line:
+ yield lineno, line
+
+ def parse_line(self, line: str) -> None:
+ """Parses a line from the input CFG file.
+
+ Args:
+ line (str): A line to parse.
+
+ Raises:
+ exceptions.ParseError: An error occurred during parsing.
+ """
+ if self.state == State.START:
+ if line == EOF_MARKER:
+ self.state = State.END
+ elif line == 'begin_compilation':
+ self.state = State.PARSE_METHOD_NAME
+ else:
+ raise exceptions.ParseError(
+ 'Expected a `begin_compilation` directive')
+
+ elif self.state == State.PARSE_METHOD_NAME:
+ method_name = parse.parse_name(line)
+ if not self.isa:
+ self.set_isa(method_name)
+
+ if method_name in self.analyzer.methods:
+ self.update_current_method(method_name)
+ self.state = State.SKIP_TO_CFG
+ else:
+ # If no profiling information has been recorded for this
+ # method, skip it
+ self.state = State.SKIP_METHOD
+
+ elif self.state == State.SKIP_METHOD:
+ if line == EOF_MARKER:
+ self.state = State.END
+ elif line == 'begin_compilation':
+ self.state = State.PARSE_METHOD_NAME
+
+ elif self.state == State.SKIP_TO_CFG:
+ if line == 'end_compilation':
+ self.state = State.START_CFG
+
+ elif self.state == State.START_CFG:
+ if line == 'begin_cfg':
+ self.state = State.IS_DISASSEMBLY_PASS
+ else:
+ raise exceptions.ParseError('Expected a `begin_cfg` directive')
+
+ elif self.state == State.IS_DISASSEMBLY_PASS:
+ pass_name = parse.parse_name(line)
+ if pass_name == 'disassembly (after)':
+ self.state = State.PARSE_FLAGS
+ else:
+ self.state = State.SKIP_PASS
+
+ elif self.state == State.SKIP_PASS:
+ if line == 'end_cfg':
+ self.state = State.END_CFG
+
+ elif self.state == State.PARSE_FLAGS:
+ if line.startswith('flags'):
+ self.update_saved_flags(line)
+ self.state = State.SKIP_TO_HIR
+
+ elif self.state == State.SKIP_TO_HIR:
+ if line == 'begin_HIR':
+ self.state = State.HIR_INSTRUCTION
+
+ elif self.state == State.HIR_INSTRUCTION:
+ if line.endswith(END_INSTRUCTION_MARKER):
+ # If no disassembly is available for this HIR instruction, skip
+ # it
+ pass
+ elif line == 'end_HIR':
+ self.state = State.END_HIR
+ else:
+ self.state = State.DISASSEMBLY
+
+ elif self.state == State.DISASSEMBLY:
+ if line == END_INSTRUCTION_MARKER:
+ self.state = State.HIR_INSTRUCTION
+ else:
+ self.annotate_instruction(line)
+
+ elif self.state == State.END_HIR:
+ if line == 'end_block':
+ self.annotate_block()
+ self.state = State.END_BLOCK
+ else:
+ raise exceptions.ParseError('Expected a `end_block` directive')
+
+ elif self.state == State.END_BLOCK:
+ if line == 'begin_block':
+ self.state = State.PARSE_FLAGS
+ elif line == 'end_cfg':
+ logging.info('Annotated %s', self.current_method.name)
+ self.state = State.END_CFG
+ else:
+ raise exceptions.ParseError(
+ 'Expected a `begin_block` or `end_cfg` directive')
+
+ elif self.state == State.END_CFG:
+ if line == EOF_MARKER:
+ self.state = State.END
+ elif line == 'begin_cfg':
+ self.state = State.IS_DISASSEMBLY_PASS
+ elif line == 'begin_compilation':
+ self.state = State.PARSE_METHOD_NAME
+
+ def set_isa(self, metadata: str) -> None:
+ """Sets the instruction set architecture.
+
+ Args:
+ metadata (str): The input CFG file metadata.
+
+ Raises:
+ exceptions.ArchitectureError: An error occurred when the input CFG
+ file ISA is incompatible with the target architecture.
+ """
+ match = re.search(r'isa:(\w+)', metadata)
+ if not match:
+ logging.warning(
+ 'Could not deduce the CFG file ISA, assuming it is compatible '
+ 'with the target architecture %s', self.analyzer.target_arch)
+ self.isa = 'unknown'
+ return
+
+ self.isa = match.group(1)
+
+ # Map CFG file ISAs to compatible target architectures
+ target_archs = {
+ 'x86': [r'x86$', r'x86_64$'],
+ 'x86_64': [r'x86_64$'],
+ 'arm': [r'armv7', r'armv8'],
+ 'arm64': [r'aarch64$', r'armv8'],
+ }
+
+ if not any(
+ re.match(target_arch, self.analyzer.target_arch)
+ for target_arch in target_archs[self.isa]):
+ raise exceptions.ArchitectureError(
+ f'The CFG file ISA {self.isa} is incompatible with the target '
+ f'architecture {self.analyzer.target_arch}')
+
+ def update_current_method(self, method_name: str) -> None:
+ """Updates the current method and the padding string.
+
+ Args:
+ method_name (str): The name of a method being annotated.
+ """
+ self.current_method = self.analyzer.methods[method_name]
+
+ annotations = []
+ for event_name in self.event_names:
+ event_count = self.current_method.event_counts[event_name]
+ annotation = self.generate_method_annotation(
+ event_name, event_count)
+ annotations.append(annotation)
+
+ info = ', '.join(annotations)
+ # By default, c1visualizer displays short method names which are built
+ # by finding the first open parenthesis. To keep that behavior intact,
+ # the profiling information is enclosed in square brackets.
+ directive = parse.build_name(f'[{info}] {method_name}')
+ self.buffer.append(f'{directive}\n')
+
+ max_length = 0
+ for event_name in self.event_names:
+ max_event_count = max(
+ instruction.event_counts[event_name]
+ for instruction in self.current_method.instructions.values())
+ annotation = self.generate_instruction_annotation(
+ event_name, max_event_count)
+
+ if len(annotation) > max_length:
+ max_length = len(annotation)
+
+ self.padding = '_' + ' ' * max_length
+
+ def update_saved_flags(self, line: str) -> None:
+ """Updates the saved flags and saves space for a block annotation.
+
+ Args:
+ line (str): A line containing a flags directive.
+ """
+ self.saved_flags = parse.parse_flags(line)
+ self.flags_offset = self.output_stream.tell()
+
+ flags = self.saved_flags.copy()
+ for event_name in self.event_names:
+ # The current method could have only one basic block, making the
+ # maximum block event counts equal to the method ones
+ event_count = self.current_method.event_counts[event_name]
+ annotation = self.generate_block_annotation(event_name, event_count)
+ flags.append(annotation)
+
+ # Save space for a possible performance flag
+ flags.append('LO')
+
+ padding = ' ' * len(parse.build_flags(flags))
+ self.buffer.append(f'{padding}\n')
+
+ def annotate_block(self) -> None:
+ """Annotates a basic block."""
+ flags = []
+ for event_name in self.event_names:
+ event_count = self.basic_block_event_counts[event_name]
+ annotation = self.generate_block_annotation(event_name, event_count)
+ flags.append(annotation)
+
+ flag = self.generate_performance_flag()
+ if flag:
+ flags.append(flag)
+
+ flags.extend(self.saved_flags)
+
+ self.basic_block_event_counts.clear()
+
+ self.output_stream.seek(self.flags_offset)
+ self.output_stream.write(parse.build_flags(flags))
+ self.output_stream.seek(0, os.SEEK_END)
+
+ def annotate_instruction(self, line: str) -> None:
+ """Annotates an instruction.
+
+ Args:
+ line (str): A line containing an instruction to annotate.
+ """
+ addr = parse.parse_address(line)
+
+ instruction = self.current_method.instructions.get(addr)
+ if not instruction:
+ # If no profiling information has been recorded for this
+ # instruction, skip it
+ self.buffer.append(f'{self.padding}{line}\n')
+ return
+
+ for eventno, event_name in enumerate(self.event_names):
+ event_count = instruction.event_counts[event_name]
+ self.basic_block_event_counts[event_name] += event_count
+ annotation = self.generate_padded_instruction_annotation(
+ event_name, event_count)
+
+ if eventno:
+ self.buffer.append(f'{annotation}\n')
+ else:
+ self.buffer.append(f'{annotation} {line}\n')
+
+ def generate_performance_flag(self) -> str:
+ """Generates a performance flag for the current basic block.
+
+ For example, a `LO` (low) flag indicates the block is responsible for 1
+ to 10% of the current method primary event (cpu-cycles by default).
+
+ Returns:
+ str: A performance flag, or an empty string if the block
+ contribution is not high enough.
+ """
+ ranges = [
+ # Low
+ (1, 10, 'LO'),
+ # Moderate
+ (10, 30, 'MO'),
+ # Considerable
+ (30, 50, 'CO'),
+ # High
+ (50, 101, 'HI'),
+ ]
+
+ ratio = 0
+ method_event_count = self.current_method.event_counts[
+ self.primary_event]
+ if method_event_count:
+ ratio = int(self.basic_block_event_counts[self.primary_event] /
+ method_event_count * 100)
+
+ for start, end, name in ranges:
+ if start <= ratio < end:
+ return name
+
+ return ''
+
+ def generate_padded_instruction_annotation(self, event_name: str,
+ event_count: int) -> str:
+ """Generates a padded instruction annotation.
+
+ Args:
+ event_name (str): An event name.
+ event_count (int): An event count.
+
+ Returns:
+ str: A padded instruction annotation.
+ """
+ annotation = self.generate_instruction_annotation(
+ event_name, event_count)
+
+ # Remove one from the final length as a space may be added at the end
+ # of the annotation. The final length will always be positive as the
+ # length of the current padding is one more than the length of the
+ # longest annotation for the current method.
+ padding = ' ' * (len(self.padding) - len(annotation) - 1)
+ parts = annotation.split(':')
+
+ return f'{parts[0]}:{padding}{parts[1]}'
+
+ def generate_method_annotation(self, event_name: str,
+ event_count: int) -> str:
+ """Generates a method annotation.
+
+ Method annotations are relative to the whole analysis and exclude the
+ event count.
+
+ Args:
+ event_name (str): An event name.
+ event_count (int): An event count.
+
+ Returns:
+ str: A method annotation.
+ """
+ total_event_count = self.analyzer.event_counts[event_name]
+ return self.generate_annotation(event_name,
+ event_count,
+ total_event_count,
+ include_count=False)
+
+ def generate_block_annotation(self, event_name: str,
+ event_count: int) -> str:
+ """Generates a basic block annotation.
+
+ Basic block annotations are relative to the current method and exclude
+ the event count.
+
+ Args:
+ event_name (str): An event name.
+ event_count (int): An event count.
+
+ Returns:
+ str: A basic block annotation.
+ """
+ total_event_count = self.current_method.event_counts[event_name]
+ return self.generate_annotation(event_name,
+ event_count,
+ total_event_count,
+ include_count=False)
+
+ def generate_instruction_annotation(self, event_name: str,
+ event_count: int) -> str:
+ """Generates an instruction annotation.
+
+ Instruction annotations are relative to the current method and include
+ the event count.
+
+ Args:
+ event_name (str): An event name.
+ event_count (int): An event count.
+
+ Returns:
+ str: An instruction annotation.
+ """
+ total_event_count = self.current_method.event_counts[event_name]
+ return self.generate_annotation(event_name,
+ event_count,
+ total_event_count,
+ include_count=True)
+
+ # pylint: disable=no-self-use
+ def generate_annotation(self, event_name: str, event_count: int,
+ total_event_count: int, include_count: bool) -> str:
+ """Generates an annotation.
+
+ Args:
+ event_name (str): An event name.
+ event_count (int): An event count.
+ total_event_count (int): A total event count.
+ include_count (bool): If True, includes the event count alongside
+ the event name and ratio.
+
+ Returns:
+ str: An annotation.
+ """
+ ratio = 0.0
+ if total_event_count:
+ ratio = event_count / total_event_count
+
+ if include_count:
+ return f'{event_name}: {event_count} ({ratio:.2%})'
+
+ return f'{event_name}: {ratio:06.2%}'
diff --git a/perf2cfg/perf2cfg/events.py b/perf2cfg/perf2cfg/events.py
new file mode 100644
index 00000000..ecc5d90a
--- /dev/null
+++ b/perf2cfg/perf2cfg/events.py
@@ -0,0 +1,53 @@
+# 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.
+"""Sorts event names according to a predefined order.
+
+Attributes:
+ EVENT_SORT_ORDER (List[str]): A list of event names sorted as they should
+ appear in the output CFG file.
+ EVENT_SORT_MAP (Dict[str, int]): A mapping of event names to their index in
+ the event sort order list.
+"""
+
+from typing import Iterable, List
+
+EVENT_SORT_ORDER = [
+ 'cpu-cycles',
+ 'stalled-cycles-frontend',
+ 'stalled-cycles-backend',
+ 'instructions',
+ 'branch-instructions',
+ 'branch-misses',
+ 'cache-references',
+ 'cache-misses',
+ 'task-clock',
+ 'context-switches',
+ 'page-faults',
+]
+
+EVENT_SORT_MAP = {name: i for i, name in enumerate(EVENT_SORT_ORDER)}
+
+
+def sort_event_names(event_names: Iterable[str]) -> List[str]:
+ """Sorts event names according to a predefined order.
+
+ Args:
+ event_names (Iterable[str]): An iterable of event names.
+
+ Returns:
+ List[str]: A list of sorted event names.
+ """
+ default_index = len(EVENT_SORT_MAP)
+ return sorted(event_names,
+ key=lambda name: EVENT_SORT_MAP.get(name, default_index))
diff --git a/perf2cfg/perf2cfg/exceptions.py b/perf2cfg/perf2cfg/exceptions.py
new file mode 100644
index 00000000..94a48dc2
--- /dev/null
+++ b/perf2cfg/perf2cfg/exceptions.py
@@ -0,0 +1,24 @@
+# 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.
+"""Custom exception classes."""
+
+
+class ArchitectureError(Exception):
+ """ArchitectureError is raised when at least two input files were created
+ on systems with different architectures.
+ """
+
+
+class ParseError(Exception):
+ """ParseError is raised when a CFG parsing error occurs."""
diff --git a/perf2cfg/perf2cfg/parse.py b/perf2cfg/perf2cfg/parse.py
new file mode 100644
index 00000000..9e211bef
--- /dev/null
+++ b/perf2cfg/perf2cfg/parse.py
@@ -0,0 +1,131 @@
+# 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.
+"""Functions to build and parse directives from CFG files."""
+
+import re
+
+from typing import Iterable, List
+
+from perf2cfg import exceptions
+
+
+def build_flags(flags: Iterable[str]) -> str:
+ """Builds a flags directive from a list of arguments.
+
+ Args:
+ flags (Iterable[str]): An iterable of flags.
+
+ Returns:
+ str: A flags directive with the given arguments.
+
+ Examples:
+ >>> parse_flags(['catch_block', 'critical'])
+ ' flags "catch_block" "critical"'
+ """
+ if not flags:
+ return ' flags'
+
+ args = ' '.join(f'"{flag}"' for flag in flags)
+ return f' flags {args}'
+
+
+def build_name(name: str) -> str:
+ """Builds a name directive from an argument.
+
+ Args:
+ name (str): An argument.
+
+ Returns:
+ str: A name directive with the given argument.
+ """
+ return f' name "{name}"'
+
+
+def parse_address(line: str) -> int:
+ """Parses an address from a line.
+
+ Args:
+ line (str): A line to parse an address from.
+
+ Returns:
+ int: An instruction address.
+
+ Raises:
+ exceptions.ParseError: An error occurred during parsing.
+
+ Examples:
+ >>> parse_address('0x0000001c: d503201f nop')
+ 28
+ """
+ parts = line.split(':', 1)
+ addr = parts[0]
+
+ try:
+ return int(addr, 16)
+ except ValueError:
+ raise exceptions.ParseError('Expected an address')
+
+
+def parse_flags(line: str) -> List[str]:
+ """Parses a flags directive from a line.
+
+ Args:
+ line (str): A line to parse a flags directive from.
+
+ Returns:
+ List[str]: A list of unquoted arguments from a flags directive, or an
+ empty list if no arguments were found.
+
+ Raises:
+ exceptions.ParseError: An error occurred during parsing.
+
+ Example:
+ >>> parse_flags('flags "catch_block" "critical"')
+ ['catch_block', 'critical']
+ """
+ parts = line.split(None, 1)
+ if parts[0] != 'flags':
+ raise exceptions.ParseError('Expected a `flags` directive')
+
+ if len(parts) < 2:
+ return []
+
+ return re.findall(r'\"([^\"]+)\"', parts[1])
+
+
+def parse_name(line: str) -> str:
+ """Parses a name directive from a line.
+
+ Args:
+ line (str): A line to parse a name directive from.
+
+ Returns:
+ str: The unquoted argument of a name directive.
+
+ Raises:
+ exceptions.ParseError: An error occurred during parsing.
+
+ Examples:
+ >>> parse_name('name "disassembly (after)"')
+ 'disassembly (after)'
+ """
+ parts = line.split(None, 1)
+ if parts[0] != 'name':
+ raise exceptions.ParseError('Expected a `name` directive')
+
+ if len(parts) < 2:
+ raise exceptions.ParseError(
+ 'Expected an argument to the `name` directive')
+
+ return parts[1].strip('"')
diff --git a/perf2cfg/perf2cfg_test.py b/perf2cfg/perf2cfg_test.py
new file mode 100755
index 00000000..90d757f4
--- /dev/null
+++ b/perf2cfg/perf2cfg_test.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+# 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.
+
+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
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/perf2cfg/pylintrc b/perf2cfg/pylintrc
new file mode 100644
index 00000000..7c9f3f33
--- /dev/null
+++ b/perf2cfg/pylintrc
@@ -0,0 +1,17 @@
+# Reference: https://pylint.pycqa.org/en/latest/technical_reference/features.html
+[MASTER]
+
+load-plugins=pylint.extensions.docparams
+
+
+[MESSAGES CONTROL]
+
+disable=design
+
+
+[PARAMETER_DOCUMENTATION]
+
+accept-no-param-doc=no
+accept-no-raise-doc=no
+accept-no-return-doc=no
+accept-no-yields-doc=no
diff --git a/perf2cfg/tests/__init__.py b/perf2cfg/tests/__init__.py
new file mode 100644
index 00000000..c1b565d3
--- /dev/null
+++ b/perf2cfg/tests/__init__.py
@@ -0,0 +1,13 @@
+# 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.
diff --git a/perf2cfg/tests/test_edit.py b/perf2cfg/tests/test_edit.py
new file mode 100644
index 00000000..4602c02f
--- /dev/null
+++ b/perf2cfg/tests/test_edit.py
@@ -0,0 +1,144 @@
+# 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.
+import io
+import textwrap
+import unittest
+
+from perf2cfg import analyze
+from perf2cfg import edit
+
+
+def empty_analyzer():
+ return analyze.RecordAnalyzer()
+
+
+def populated_analyzer():
+ analyzer = analyze.RecordAnalyzer()
+ analyzer.target_arch = 'aarch64'
+ samples = [
+ ('void hcf()', 4, 'cpu-cycles', 90),
+ ('void hcf()', 8, 'cpu-cycles', 10),
+ ('void hcf()', 8, 'cache-misses', 100),
+ ]
+
+ for sample in samples:
+ analyzer.record_sample(*sample)
+
+ return analyzer
+
+
+def edit_string(analyzer, input_string):
+ input_stream = io.StringIO(input_string)
+ output_stream = io.StringIO()
+
+ editor = edit.CfgEditor(analyzer, input_stream, output_stream)
+ editor.edit()
+
+ return output_stream
+
+
+class TestEdit(unittest.TestCase):
+
+ def test_empty_file(self):
+ output_stream = edit_string(empty_analyzer(), '')
+ self.assertEqual(output_stream.getvalue(), '')
+
+ def test_wrong_filetype(self):
+ with self.assertLogs() as ctx:
+ edit_string(
+ empty_analyzer(), """<!DOCTYPE html>
+ <html>
+ <head>
+ <title>I'm not a CFG file</title>
+ </head>
+ </html>""")
+
+ self.assertEqual(
+ ctx.output,
+ ['ERROR:root:Line 1: Expected a `begin_compilation` directive'])
+
+ def test_no_architecture(self):
+ with self.assertLogs() as ctx:
+ edit_string(
+ populated_analyzer(), """begin_compilation
+ name "void noMetadata()"
+ end_compilation""")
+
+ self.assertEqual(ctx.output, [
+ 'WARNING:root:Could not deduce the CFG file ISA, assuming it is '
+ 'compatible with the target architecture aarch64'
+ ])
+
+ def test_wrong_architecture(self):
+ with self.assertLogs() as ctx:
+ edit_string(
+ populated_analyzer(), """begin_compilation
+ name "isa:x86_64"
+ end_compilation""")
+
+ self.assertEqual(ctx.output, [
+ 'ERROR:root:The CFG file ISA x86_64 is incompatible with the '
+ 'target architecture aarch64'
+ ])
+
+ def test_annotate_method(self):
+ with self.assertLogs() as ctx:
+ output_stream = edit_string(
+ populated_analyzer(),
+ textwrap.dedent("""\
+ begin_compilation
+ name "isa:arm64 isa_features:a53,crc,-lse,-fp16,-dotprod,-sve"
+ end_compilation
+ begin_compilation
+ name "void hcf()"
+ end_compilation
+ begin_cfg
+ name "disassembly (after)"
+ begin_block
+ flags
+ begin_HIR
+ 0 0 NOPSlide dex_pc:0 loop:none
+ 0x00000000: d503201f nop
+ 0x00000004: d503201f nop
+ 0x00000008: d503201f nop
+ <|@
+ end_HIR
+ end_block
+ end_cfg"""))
+
+ self.assertEqual(ctx.output, ['INFO:root:Annotated void hcf()'])
+ self.assertEqual(
+ output_stream.getvalue(),
+ textwrap.dedent("""\
+ begin_compilation
+ name "isa:arm64 isa_features:a53,crc,-lse,-fp16,-dotprod,-sve"
+ end_compilation
+ begin_compilation
+ name "[cpu-cycles: 100.00%, cache-misses: 100.00%] void hcf()"
+ end_compilation
+ begin_cfg
+ name "disassembly (after)"
+ begin_block
+ flags "cpu-cycles: 100.00%" "cache-misses: 100.00%" "HI"
+ begin_HIR
+ 0 0 NOPSlide dex_pc:0 loop:none
+ _ 0x00000000: d503201f nop
+ cpu-cycles: 90 (90.00%) 0x00000004: d503201f nop
+ cache-misses: 0 (0.00%)
+ cpu-cycles: 10 (10.00%) 0x00000008: d503201f nop
+ cache-misses: 100 (100.00%)
+ <|@
+ end_HIR
+ end_block
+ end_cfg"""))
diff --git a/perf2cfg/tests/test_events.py b/perf2cfg/tests/test_events.py
new file mode 100644
index 00000000..9cdcdf5a
--- /dev/null
+++ b/perf2cfg/tests/test_events.py
@@ -0,0 +1,27 @@
+# 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.
+import random
+import unittest
+
+from perf2cfg import events
+
+
+class TestEvents(unittest.TestCase):
+
+ def test_sort_event_names(self):
+ event_names = events.EVENT_SORT_ORDER.copy()
+ random.shuffle(event_names)
+ got = events.sort_event_names(event_names)
+
+ self.assertEqual(got, events.EVENT_SORT_ORDER)
diff --git a/perf2cfg/tests/test_parse.py b/perf2cfg/tests/test_parse.py
new file mode 100644
index 00000000..40ec243b
--- /dev/null
+++ b/perf2cfg/tests/test_parse.py
@@ -0,0 +1,73 @@
+# 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.
+import unittest
+
+from perf2cfg import exceptions
+from perf2cfg import parse
+
+
+class TestParse(unittest.TestCase):
+
+ def test_build_flags_without_arguments(self):
+ got = parse.build_flags([])
+ self.assertEqual(got.strip(), 'flags')
+
+ def test_build_flags_with_arguments(self):
+ got = parse.build_flags(['catch_block', 'critical'])
+ self.assertEqual(got.strip(), 'flags "catch_block" "critical"')
+
+ def test_build_name(self):
+ got = parse.build_name('void hcf()')
+ self.assertEqual(got.strip(), 'name "void hcf()"')
+
+ def test_parse_invalid_address_line(self):
+ with self.assertRaises(exceptions.ParseError) as ctx:
+ parse.parse_address(':)')
+
+ self.assertEqual(str(ctx.exception), 'Expected an address')
+
+ def test_parse_valid_address_line(self):
+ got = parse.parse_address('0x0000001c: d503201f nop')
+ self.assertEqual(got, 0x1c)
+
+ def test_parse_flags_wrong_directive(self):
+ with self.assertRaises(exceptions.ParseError) as ctx:
+ parse.parse_flags('name "void hcf()"')
+
+ self.assertEqual(str(ctx.exception), 'Expected a `flags` directive')
+
+ def test_parse_flags_without_arguments(self):
+ got = parse.parse_flags('flags')
+ self.assertEqual(got, [])
+
+ def test_parse_flags_with_arguments(self):
+ got = parse.parse_flags('flags "catch_block" "critical"')
+ self.assertEqual(got, ['catch_block', 'critical'])
+
+ def test_parse_name_wrong_directive(self):
+ with self.assertRaises(exceptions.ParseError) as ctx:
+ parse.parse_name('flags "catch_block" "critical"')
+
+ self.assertEqual(str(ctx.exception), 'Expected a `name` directive')
+
+ def test_parse_name_without_argument(self):
+ with self.assertRaises(exceptions.ParseError) as ctx:
+ parse.parse_name('name')
+
+ self.assertEqual(str(ctx.exception),
+ 'Expected an argument to the `name` directive')
+
+ def test_parse_name_with_argument(self):
+ got = parse.parse_name('name "void hcf()"')
+ self.assertEqual(got, 'void hcf()')
diff --git a/postinst/Android.bp b/postinst/Android.bp
index 25e9dfe4..89141893 100644
--- a/postinst/Android.bp
+++ b/postinst/Android.bp
@@ -14,6 +14,23 @@
// limitations under the License.
//
+package {
+ default_applicable_licenses: ["system_extras_postinst_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_postinst_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
sh_binary {
name: "postinst_example",
src: "postinst.sh",
diff --git a/power_profile/camera_avg/Application/src/main/AndroidManifest.xml b/power_profile/camera_avg/Application/src/main/AndroidManifest.xml
index 65aa029c..a4c73a33 100644
--- a/power_profile/camera_avg/Application/src/main/AndroidManifest.xml
+++ b/power_profile/camera_avg/Application/src/main/AndroidManifest.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="UTF-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2014 The Android Open Source Project
@@ -16,23 +16,24 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.android.powerprofile.cameraavg">
+ package="com.example.android.powerprofile.cameraavg">
- <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.CAMERA"/>
- <uses-feature android:name="android.hardware.camera" />
- <uses-feature android:name="android.hardware.camera.autofocus" />
+ <uses-feature android:name="android.hardware.camera"/>
+ <uses-feature android:name="android.hardware.camera.autofocus"/>
<application android:allowBackup="true"
- android:label="@string/app_name"
- android:icon="@drawable/ic_launcher"
- android:theme="@style/Theme.AppCompat.Light.NoActionBar">
+ android:label="@string/app_name"
+ android:icon="@drawable/ic_launcher"
+ android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<activity android:name=".CameraActivity"
- android:label="@string/app_name">
+ android:label="@string/app_name"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
diff --git a/power_profile/camera_flashlight/Application/src/main/AndroidManifest.xml b/power_profile/camera_flashlight/Application/src/main/AndroidManifest.xml
index 11b73c7b..02f0e831 100644
--- a/power_profile/camera_flashlight/Application/src/main/AndroidManifest.xml
+++ b/power_profile/camera_flashlight/Application/src/main/AndroidManifest.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="UTF-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2014 The Android Open Source Project
@@ -16,16 +16,17 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.android.powerprofile.cameraflashlight">
+ package="com.example.android.powerprofile.cameraflashlight">
<application android:allowBackup="true"
- android:label="@string/app_name">
+ android:label="@string/app_name">
<activity android:name=".FlashlightActivity"
- android:label="@string/app_name">
+ android:label="@string/app_name"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
diff --git a/power_profile/gps_on/Application/src/main/AndroidManifest.xml b/power_profile/gps_on/Application/src/main/AndroidManifest.xml
index 607fc200..83f40411 100644
--- a/power_profile/gps_on/Application/src/main/AndroidManifest.xml
+++ b/power_profile/gps_on/Application/src/main/AndroidManifest.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="UTF-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2014 The Android Open Source Project
@@ -16,20 +16,21 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.android.powerprofile.gpson">
+ package="com.example.android.powerprofile.gpson">
- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
- <uses-feature android:name="android.hardware.location.gps" />
+ <uses-feature android:name="android.hardware.location.gps"/>
<application android:allowBackup="true"
- android:label="@string/app_name">
+ android:label="@string/app_name">
<activity android:name=".GpsActivity"
- android:label="@string/app_name">
+ android:label="@string/app_name"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
diff --git a/preopt2cachename/Android.bp b/preopt2cachename/Android.bp
index 63fea15d..54e8e2e1 100644
--- a/preopt2cachename/Android.bp
+++ b/preopt2cachename/Android.bp
@@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_binary {
name: "preopt2cachename",
diff --git a/profcollectd/.clang-format b/profcollectd/.clang-format
new file mode 120000
index 00000000..f7e9891f
--- /dev/null
+++ b/profcollectd/.clang-format
@@ -0,0 +1 @@
+../../../system/core/.clang-format-2 \ No newline at end of file
diff --git a/profcollectd/Android.bp b/profcollectd/Android.bp
new file mode 100644
index 00000000..f1723006
--- /dev/null
+++ b/profcollectd/Android.bp
@@ -0,0 +1,64 @@
+//
+// 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.
+//
+
+package {
+ default_applicable_licenses: ["system_extras_profcollectd_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_profcollectd_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
+rust_binary {
+ name: "profcollectctl",
+
+ srcs: ["profcollectctl.rs"],
+
+ rustlibs: [
+ "libanyhow",
+ "libprofcollectd",
+ ],
+}
+
+rust_binary {
+ name: "profcollectd",
+
+ srcs: ["profcollectd.rs"],
+
+ rustlibs: [
+ "libanyhow",
+ "libprofcollectd",
+ ],
+
+ init_rc: ["profcollectd.rc"],
+}
+
+filegroup {
+ name: "profcollectd_aidl",
+ srcs: [
+ "binder/com/android/server/profcollect/IProfCollectd.aidl",
+ ],
+ path: "binder",
+}
diff --git a/profcollectd/MODULE_LICENSE_APACHE2 b/profcollectd/MODULE_LICENSE_APACHE2
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/profcollectd/MODULE_LICENSE_APACHE2
diff --git a/profcollectd/NOTICE b/profcollectd/NOTICE
new file mode 100644
index 00000000..f3e7764f
--- /dev/null
+++ b/profcollectd/NOTICE
@@ -0,0 +1,190 @@
+
+ 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.
+
+ 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/profcollectd/OWNERS b/profcollectd/OWNERS
new file mode 100644
index 00000000..b380e395
--- /dev/null
+++ b/profcollectd/OWNERS
@@ -0,0 +1,3 @@
+srhines@google.com
+yabinc@google.com
+yikong@google.com
diff --git a/profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl b/profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl
new file mode 100644
index 00000000..58503abc
--- /dev/null
+++ b/profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+package com.android.server.profcollect;
+
+/** {@hide} */
+interface IProfCollectd {
+ void schedule();
+ void terminate();
+ void trace_once(@utf8InCpp String tag);
+ void process(boolean blocking);
+ @utf8InCpp String report();
+ void copy_report_to_bb(int bb_profile_id, @utf8InCpp String report);
+ void delete_report(@utf8InCpp String report);
+ @utf8InCpp String get_supported_provider();
+}
diff --git a/profcollectd/libprofcollectd/Android.bp b/profcollectd/libprofcollectd/Android.bp
new file mode 100644
index 00000000..2e37af9e
--- /dev/null
+++ b/profcollectd/libprofcollectd/Android.bp
@@ -0,0 +1,63 @@
+//
+// 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.
+//
+
+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"],
+}
+
+aidl_interface {
+ name: "profcollectd_aidl_interface",
+ unstable: true,
+ srcs: [":profcollectd_aidl"],
+ backend: {
+ rust: {
+ enabled: true,
+ },
+ },
+}
+
+rust_library {
+ name: "libprofcollectd",
+ stem: "liblibprofcollectd",
+ crate_name: "libprofcollectd",
+ srcs: ["lib.rs"],
+ rustlibs: [
+ "profcollectd_aidl_interface-rust", // Move to rlib once b/179041242 is fixed.
+ "libandroid_logger",
+ "libanyhow",
+ "libbinder_rs", // Remove once b/179041241 is fixed.
+ "libchrono",
+ "liblazy_static",
+ "liblog_rust",
+ "libmacaddr",
+ "librand",
+ "libserde", // Remove once b/179041241 is fixed.
+ "libserde_json",
+ "libuuid",
+ "libzip",
+ ],
+ rlibs: [
+ "libprofcollect_libflags_rust",
+ "libprofcollect_libbase_rust",
+ "libsimpleperf_profcollect_rust",
+ ],
+ shared_libs: ["libsimpleperf_profcollect"],
+}
diff --git a/profcollectd/libprofcollectd/bindings/libbase/Android.bp b/profcollectd/libprofcollectd/bindings/libbase/Android.bp
new file mode 100644
index 00000000..8e5fb1e0
--- /dev/null
+++ b/profcollectd/libprofcollectd/bindings/libbase/Android.bp
@@ -0,0 +1,53 @@
+//
+// 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_libbase",
+ srcs: ["properties.cpp"],
+ generated_headers: ["cxx-bridge-header"],
+ generated_sources: ["libprofcollect_libbase_bridge_code"],
+}
+
+genrule {
+ name: "libprofcollect_libbase_bridge_code",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) >> $(out)",
+ srcs: ["lib.rs"],
+ out: ["libprofcollect_libbase_cxx_generated.cc"],
+}
+
+rust_library {
+ name: "libprofcollect_libbase_rust",
+ crate_name: "profcollect_libbase_rust",
+ srcs: ["lib.rs"],
+ rustlibs: [
+ "libcxx",
+ ],
+ static_libs: ["libprofcollect_libbase"],
+ shared_libs: [
+ "libc++",
+ "libbase",
+ ],
+}
diff --git a/profcollectd/libprofcollectd/bindings/libbase/lib.rs b/profcollectd/libprofcollectd/bindings/libbase/lib.rs
new file mode 100644
index 00000000..bdd99a02
--- /dev/null
+++ b/profcollectd/libprofcollectd/bindings/libbase/lib.rs
@@ -0,0 +1,34 @@
+//
+// 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 GetProperty and SetProperty from libbase.
+
+pub use ffi::{GetProperty, SetProperty};
+
+/// Safe wrappers for the GetProperty and SetProperty methods from libbase.
+#[cxx::bridge]
+mod ffi {
+ unsafe extern "C++" {
+ include!("properties.hpp");
+
+ /// Returns the current value of the system property `key`,
+ /// or `default_value` if the property is empty or doesn't exist.
+ fn GetProperty(key: &str, default_value: &str) -> String;
+
+ /// Sets the system property `key` to `value`.
+ fn SetProperty(key: &str, value: &str);
+ }
+}
diff --git a/profcollectd/libprofcollectd/bindings/libbase/properties.cpp b/profcollectd/libprofcollectd/bindings/libbase/properties.cpp
new file mode 100644
index 00000000..908f19d0
--- /dev/null
+++ b/profcollectd/libprofcollectd/bindings/libbase/properties.cpp
@@ -0,0 +1,26 @@
+/*
+ * 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 "../../../../../libbase/include/android-base/properties.h"
+#include "properties.hpp"
+
+rust::String GetProperty(rust::Str key, rust::Str default_value) {
+ return android::base::GetProperty(std::string(key), std::string(default_value));
+}
+
+void SetProperty(rust::Str key, rust::Str value) {
+ android::base::SetProperty(std::string(key), std::string(value));
+}
diff --git a/profcollectd/libprofcollectd/bindings/libbase/properties.hpp b/profcollectd/libprofcollectd/bindings/libbase/properties.hpp
new file mode 100644
index 00000000..c8ef1f64
--- /dev/null
+++ b/profcollectd/libprofcollectd/bindings/libbase/properties.hpp
@@ -0,0 +1,22 @@
+/*
+ * 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 GetProperty(rust::Str, rust::Str);
+void SetProperty(rust::Str, rust::Str);
diff --git a/profcollectd/libprofcollectd/bindings/libflags/Android.bp b/profcollectd/libprofcollectd/bindings/libflags/Android.bp
new file mode 100644
index 00000000..102a6c05
--- /dev/null
+++ b/profcollectd/libprofcollectd/bindings/libflags/Android.bp
@@ -0,0 +1,48 @@
+//
+// 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"],
+}
+
+rust_bindgen {
+ name: "libprofcollect_libflags_bindgen",
+ wrapper_src: "get_flags.hpp",
+ crate_name: "profcollect_libflags_bindgen",
+ source_stem: "bindings",
+}
+
+rust_library {
+ name: "libprofcollect_libflags_rust",
+ crate_name: "profcollect_libflags_rust",
+ srcs: ["lib.rs"],
+ rlibs: ["libprofcollect_libflags_bindgen"],
+ 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
new file mode 100644
index 00000000..e5ce65a1
--- /dev/null
+++ b/profcollectd/libprofcollectd/bindings/libflags/get_flags.cpp
@@ -0,0 +1,28 @@
+/*
+ * 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"
+
+const char* GetServerConfigurableFlag(const char* experiment_category_name,
+ const char* experiment_flag_name,
+ const char* default_value) {
+ auto v = server_configurable_flags::GetServerConfigurableFlag(
+ std::string(experiment_category_name),
+ std::string(experiment_flag_name),
+ std::string(default_value));
+ return strdup(v.c_str());
+}
diff --git a/profcollectd/libprofcollectd/bindings/libflags/get_flags.hpp b/profcollectd/libprofcollectd/bindings/libflags/get_flags.hpp
new file mode 100644
index 00000000..ab3be989
--- /dev/null
+++ b/profcollectd/libprofcollectd/bindings/libflags/get_flags.hpp
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+// C declaration for bindgen.
+const char* GetServerConfigurableFlag(const char*, const char*, const char*);
diff --git a/profcollectd/libprofcollectd/bindings/libflags/lib.rs b/profcollectd/libprofcollectd/bindings/libflags/lib.rs
new file mode 100644
index 00000000..c8875cb1
--- /dev/null
+++ b/profcollectd/libprofcollectd/bindings/libflags/lib.rs
@@ -0,0 +1,43 @@
+//
+// 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.
+
+use std::ffi::{CStr, CString};
+
+/// 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.
+pub fn get_server_configurable_flag<'a>(
+ experiment_category_name: &str,
+ experiment_flag_name: &str,
+ default_value: &'a str,
+) -> &'a str {
+ let experiment_category_name = CString::new(experiment_category_name).unwrap();
+ let experiment_flag_name = CString::new(experiment_flag_name).unwrap();
+ let default_value = CString::new(default_value).unwrap();
+ unsafe {
+ let cstr = profcollect_libflags_bindgen::GetServerConfigurableFlag(
+ experiment_category_name.as_ptr(),
+ experiment_flag_name.as_ptr(),
+ default_value.as_ptr(),
+ );
+ CStr::from_ptr(cstr).to_str().unwrap()
+ }
+}
diff --git a/profcollectd/libprofcollectd/config.rs b/profcollectd/libprofcollectd/config.rs
new file mode 100644
index 00000000..c3ab45cf
--- /dev/null
+++ b/profcollectd/libprofcollectd/config.rs
@@ -0,0 +1,138 @@
+//
+// 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.
+//
+
+//! ProfCollect configurations.
+
+use anyhow::Result;
+use lazy_static::lazy_static;
+use macaddr::MacAddr6;
+use rand::Rng;
+use serde::{Deserialize, Serialize};
+use std::error::Error;
+use std::path::Path;
+use std::str::FromStr;
+use std::time::Duration;
+
+const PROFCOLLECT_CONFIG_NAMESPACE: &str = "profcollect_native_boot";
+const PROFCOLLECT_NODE_ID_PROPERTY: &str = "persist.profcollectd.node_id";
+
+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 BETTERBUG_CACHE_DIR_PREFIX: &'static Path = Path::new("/data/user/");
+ pub static ref BETTERBUG_CACHE_DIR_SUFFIX: &'static Path =
+ Path::new("com.google.android.apps.internal.betterbug/cache/");
+ pub static ref CONFIG_FILE: &'static Path =
+ Path::new("/data/misc/profcollectd/output/config.json");
+}
+
+#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
+pub struct Config {
+ /// Version of config file scheme, always equals to 1.
+ version: u32,
+ /// Application specific node ID.
+ pub node_id: MacAddr6,
+ /// Device build fingerprint.
+ 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,
+}
+
+impl Config {
+ pub fn from_env() -> Result<Self> {
+ Ok(Config {
+ version: 1,
+ node_id: get_or_initialise_node_id()?,
+ build_fingerprint: get_build_fingerprint()?,
+ collection_interval: Duration::from_secs(get_device_config(
+ "collection_interval",
+ 600,
+ )?),
+ sampling_period: Duration::from_millis(get_device_config("sampling_period", 500)?),
+ binary_filter: get_device_config("binary_filter", "".to_string())?,
+ })
+ }
+}
+
+impl ToString for Config {
+ fn to_string(&self) -> String {
+ serde_json::to_string(self).expect("Failed to deserialise configuration.")
+ }
+}
+
+impl FromStr for Config {
+ type Err = serde_json::Error;
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ serde_json::from_str::<Config>(s)
+ }
+}
+
+fn get_or_initialise_node_id() -> Result<MacAddr6> {
+ let mut node_id = get_property(&PROFCOLLECT_NODE_ID_PROPERTY, MacAddr6::nil())?;
+ if node_id.is_nil() {
+ node_id = generate_random_node_id();
+ set_property(&PROFCOLLECT_NODE_ID_PROPERTY, node_id);
+ }
+
+ Ok(node_id)
+}
+
+fn get_build_fingerprint() -> Result<String> {
+ get_property("ro.build.fingerprint", "unknown".to_string())
+}
+
+fn get_device_config<T>(key: &str, default_value: T) -> Result<T>
+where
+ T: FromStr + ToString,
+ T::Err: Error + Send + Sync + 'static,
+{
+ let default_value = default_value.to_string();
+ let config = profcollect_libflags_rust::get_server_configurable_flag(
+ &PROFCOLLECT_CONFIG_NAMESPACE,
+ &key,
+ &default_value,
+ );
+ Ok(T::from_str(&config)?)
+}
+
+fn get_property<T>(key: &str, default_value: T) -> Result<T>
+where
+ T: FromStr + ToString,
+ T::Err: Error + Send + Sync + 'static,
+{
+ let default_value = default_value.to_string();
+ let value = profcollect_libbase_rust::GetProperty(&key, &default_value);
+ Ok(T::from_str(&value)?)
+}
+
+fn set_property<T>(key: &str, value: T)
+where
+ T: ToString,
+{
+ let value = value.to_string();
+ profcollect_libbase_rust::SetProperty(&key, &value);
+}
+
+fn generate_random_node_id() -> MacAddr6 {
+ let mut node_id = rand::thread_rng().gen::<[u8; 6]>();
+ node_id[0] |= 0x1;
+ MacAddr6::from(node_id)
+}
diff --git a/profcollectd/libprofcollectd/lib.rs b/profcollectd/libprofcollectd/lib.rs
new file mode 100644
index 00000000..aacbf8e3
--- /dev/null
+++ b/profcollectd/libprofcollectd/lib.rs
@@ -0,0 +1,98 @@
+//
+// 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.
+//
+
+//! ProfCollect Binder client interface.
+
+mod config;
+mod report;
+mod scheduler;
+mod service;
+mod simpleperf_etm_trace_provider;
+mod trace_provider;
+
+use anyhow::{Context, Result};
+use profcollectd_aidl_interface::aidl::com::android::server::profcollect::IProfCollectd::{
+ self, BnProfCollectd,
+};
+use profcollectd_aidl_interface::binder::{self, BinderFeatures};
+use service::ProfcollectdBinderService;
+
+const PROFCOLLECTD_SERVICE_NAME: &str = "profcollectd";
+
+/// Initialise profcollectd service.
+/// * `schedule_now` - Immediately schedule collection after service is initialised.
+pub fn init_service(schedule_now: bool) -> Result<()> {
+ binder::ProcessState::start_thread_pool();
+
+ let profcollect_binder_service = ProfcollectdBinderService::new()?;
+ binder::add_service(
+ &PROFCOLLECTD_SERVICE_NAME,
+ BnProfCollectd::new_binder(profcollect_binder_service, BinderFeatures::default())
+ .as_binder(),
+ )
+ .context("Failed to register service.")?;
+
+ if schedule_now {
+ trace_once("boot")?;
+ schedule()?;
+ }
+
+ binder::ProcessState::join_thread_pool();
+ Ok(())
+}
+
+fn get_profcollectd_service() -> Result<binder::Strong<dyn IProfCollectd::IProfCollectd>> {
+ binder::get_interface(&PROFCOLLECTD_SERVICE_NAME)
+ .context("Failed to get profcollectd binder service, is profcollectd running?")
+}
+
+/// Schedule periodic profile collection.
+pub fn schedule() -> Result<()> {
+ get_profcollectd_service()?.schedule()?;
+ Ok(())
+}
+
+/// Terminate periodic profile collection.
+pub fn terminate() -> Result<()> {
+ get_profcollectd_service()?.terminate()?;
+ Ok(())
+}
+
+/// Immediately schedule a one-off trace.
+pub fn trace_once(tag: &str) -> Result<()> {
+ get_profcollectd_service()?.trace_once(tag)?;
+ Ok(())
+}
+
+/// Process traces.
+pub fn process() -> Result<()> {
+ get_profcollectd_service()?.process(true)?;
+ Ok(())
+}
+
+/// Process traces and report profile.
+pub fn report() -> Result<String> {
+ Ok(get_profcollectd_service()?.report()?)
+}
+
+/// Inits logging for Android
+pub fn init_logging() {
+ android_logger::init_once(
+ android_logger::Config::default()
+ .with_tag("profcollectd")
+ .with_min_level(log::Level::Error),
+ );
+}
diff --git a/profcollectd/libprofcollectd/report.rs b/profcollectd/libprofcollectd/report.rs
new file mode 100644
index 00000000..c37993b7
--- /dev/null
+++ b/profcollectd/libprofcollectd/report.rs
@@ -0,0 +1,84 @@
+//
+// 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.
+//
+
+//! 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};
+use std::os::unix::fs::PermissionsExt;
+use std::path::{Path, PathBuf};
+use std::time::SystemTime;
+use uuid::v1::{Context, Timestamp};
+use uuid::Uuid;
+use zip::write::FileOptions;
+use zip::CompressionMethod::Deflated;
+use zip::ZipWriter;
+
+use crate::config::Config;
+
+lazy_static! {
+ pub static ref UUID_CONTEXT: Context = Context::new(0);
+}
+
+pub fn pack_report(profile: &Path, report: &Path, config: &Config) -> Result<String> {
+ let mut report = PathBuf::from(report);
+ let report_filename = get_report_filename(&config.node_id)?;
+ report.push(&report_filename);
+ report.set_extension("zip");
+
+ // Remove the current report file if exists.
+ fs::remove_file(&report).ok();
+
+ let report_file = fs::OpenOptions::new().create_new(true).write(true).open(&report)?;
+
+ // Set report file ACL bits to 644, so that this can be shared to uploaders.
+ // Who has permission to actually read the file is protected by SELinux policy.
+ fs::set_permissions(&report, Permissions::from_mode(0o644))?;
+
+ let options = FileOptions::default().compression_method(Deflated);
+ let mut zip = ZipWriter::new(report_file);
+
+ fs::read_dir(profile)?
+ .filter_map(|e| e.ok())
+ .map(|e| e.path())
+ .filter(|e| e.is_file())
+ .try_for_each(|e| -> Result<()> {
+ let filename = e
+ .file_name()
+ .and_then(|f| f.to_str())
+ .ok_or_else(|| anyhow!("Malformed profile path: {}", e.display()))?;
+ zip.start_file(filename, options)?;
+ let mut f = File::open(e)?;
+ let mut buffer = Vec::new();
+ f.read_to_end(&mut buffer)?;
+ zip.write_all(&*buffer)?;
+ Ok(())
+ })?;
+ zip.finish()?;
+
+ 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())?;
+ Ok(uuid.to_string())
+}
diff --git a/profcollectd/libprofcollectd/scheduler.rs b/profcollectd/libprofcollectd/scheduler.rs
new file mode 100644
index 00000000..cfd0381f
--- /dev/null
+++ b/profcollectd/libprofcollectd/scheduler.rs
@@ -0,0 +1,108 @@
+//
+// 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.
+//
+
+//! ProfCollect tracing scheduler.
+
+use std::sync::mpsc::{sync_channel, SyncSender};
+use std::sync::Arc;
+use std::sync::Mutex;
+use std::thread;
+
+use crate::config::{Config, PROFILE_OUTPUT_DIR, TRACE_OUTPUT_DIR};
+use crate::trace_provider::{self, TraceProvider};
+use anyhow::{anyhow, ensure, Context, Result};
+
+pub struct Scheduler {
+ /// Signal to terminate the periodic collection worker thread, None if periodic collection is
+ /// not scheduled.
+ termination_ch: Option<SyncSender<()>>,
+ /// The preferred trace provider for the system.
+ trace_provider: Arc<Mutex<dyn TraceProvider + Send>>,
+}
+
+impl Scheduler {
+ pub fn new() -> Result<Self> {
+ let p = trace_provider::get_trace_provider()?;
+ Ok(Scheduler { termination_ch: None, trace_provider: p })
+ }
+
+ fn is_scheduled(&self) -> bool {
+ self.termination_ch.is_some()
+ }
+
+ pub fn schedule_periodic(&mut self, config: &Config) -> Result<()> {
+ ensure!(!self.is_scheduled(), "Already scheduled.");
+
+ let (sender, receiver) = sync_channel(1);
+ self.termination_ch = Some(sender);
+
+ // Clone config and trace_provider ARC for the worker thread.
+ let config = config.clone();
+ let trace_provider = self.trace_provider.clone();
+
+ thread::spawn(move || {
+ loop {
+ match receiver.recv_timeout(config.collection_interval) {
+ Ok(_) => break,
+ Err(_) => {
+ // Did not receive a termination signal, initiate trace event.
+ trace_provider.lock().unwrap().trace(
+ &TRACE_OUTPUT_DIR,
+ "periodic",
+ &config.sampling_period,
+ );
+ }
+ }
+ }
+ });
+ Ok(())
+ }
+
+ pub fn terminate_periodic(&mut self) -> Result<()> {
+ self.termination_ch
+ .as_ref()
+ .ok_or_else(|| anyhow!("Not scheduled"))?
+ .send(())
+ .context("Scheduler worker disappeared.")?;
+ self.termination_ch = None;
+ Ok(())
+ }
+
+ pub fn one_shot(&self, config: &Config, tag: &str) -> Result<()> {
+ let trace_provider = self.trace_provider.clone();
+ trace_provider.lock().unwrap().trace(&TRACE_OUTPUT_DIR, tag, &config.sampling_period);
+ Ok(())
+ }
+
+ pub fn process(&self, blocking: bool) -> Result<()> {
+ let trace_provider = self.trace_provider.clone();
+ let handle = thread::spawn(move || {
+ trace_provider
+ .lock()
+ .unwrap()
+ .process(&TRACE_OUTPUT_DIR, &PROFILE_OUTPUT_DIR)
+ .expect("Failed to process profiles.");
+ });
+ if blocking {
+ handle.join().map_err(|_| anyhow!("Profile process thread panicked."))?;
+ }
+ Ok(())
+ }
+
+ pub fn get_trace_provider_name(&self) -> &'static str {
+ self.trace_provider.lock().unwrap().get_name()
+ }
+}
diff --git a/profcollectd/libprofcollectd/service.rs b/profcollectd/libprofcollectd/service.rs
new file mode 100644
index 00000000..0b9cca1e
--- /dev/null
+++ b/profcollectd/libprofcollectd/service.rs
@@ -0,0 +1,166 @@
+//
+// 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.
+//
+
+//! ProfCollect Binder service implementation.
+
+use anyhow::{anyhow, bail, Context, Error, Result};
+use binder::public_api::Result as BinderResult;
+use binder::Status;
+use profcollectd_aidl_interface::aidl::com::android::server::profcollect::IProfCollectd::IProfCollectd;
+use std::ffi::CString;
+use std::fs::{copy, create_dir, read_to_string, remove_dir_all, remove_file, write};
+use std::path::PathBuf;
+use std::str::FromStr;
+use std::sync::{Mutex, MutexGuard};
+
+use crate::config::{
+ Config, BETTERBUG_CACHE_DIR_PREFIX, BETTERBUG_CACHE_DIR_SUFFIX, CONFIG_FILE,
+ PROFILE_OUTPUT_DIR, REPORT_OUTPUT_DIR, TRACE_OUTPUT_DIR,
+};
+use crate::report::pack_report;
+use crate::scheduler::Scheduler;
+
+fn err_to_binder_status(msg: Error) -> Status {
+ let msg = format!("{:#?}", msg);
+ let msg = CString::new(msg).expect("Failed to convert to CString");
+ Status::new_service_specific_error(1, Some(&msg))
+}
+
+pub struct ProfcollectdBinderService {
+ lock: Mutex<Lock>,
+}
+
+struct Lock {
+ config: Config,
+ scheduler: Scheduler,
+}
+
+impl binder::Interface for ProfcollectdBinderService {}
+
+impl IProfCollectd for ProfcollectdBinderService {
+ fn schedule(&self) -> BinderResult<()> {
+ let lock = &mut *self.lock();
+ lock.scheduler
+ .schedule_periodic(&lock.config)
+ .context("Failed to schedule collection.")
+ .map_err(err_to_binder_status)
+ }
+ fn terminate(&self) -> BinderResult<()> {
+ self.lock()
+ .scheduler
+ .terminate_periodic()
+ .context("Failed to terminate collection.")
+ .map_err(err_to_binder_status)
+ }
+ fn trace_once(&self, tag: &str) -> BinderResult<()> {
+ let lock = &mut *self.lock();
+ lock.scheduler
+ .one_shot(&lock.config, tag)
+ .context("Failed to initiate an one-off trace.")
+ .map_err(err_to_binder_status)
+ }
+ fn process(&self, blocking: bool) -> BinderResult<()> {
+ let lock = &mut *self.lock();
+ lock.scheduler
+ .process(blocking)
+ .context("Failed to process profiles.")
+ .map_err(err_to_binder_status)
+ }
+ fn report(&self) -> BinderResult<String> {
+ self.process(true)?;
+
+ let lock = &mut *self.lock();
+ pack_report(&PROFILE_OUTPUT_DIR, &REPORT_OUTPUT_DIR, &lock.config)
+ .context("Failed to create profile report.")
+ .map_err(err_to_binder_status)
+ }
+ fn delete_report(&self, report_name: &str) -> BinderResult<()> {
+ verify_report_name(&report_name).map_err(err_to_binder_status)?;
+
+ let mut report = PathBuf::from(&*REPORT_OUTPUT_DIR);
+ report.push(report_name);
+ report.set_extension("zip");
+ remove_file(&report).ok();
+ Ok(())
+ }
+ fn copy_report_to_bb(&self, bb_profile_id: i32, report_name: &str) -> BinderResult<()> {
+ if bb_profile_id < 0 {
+ return Err(err_to_binder_status(anyhow!("Invalid profile ID")));
+ }
+ verify_report_name(&report_name).map_err(err_to_binder_status)?;
+
+ let mut report = PathBuf::from(&*REPORT_OUTPUT_DIR);
+ report.push(report_name);
+ report.set_extension("zip");
+
+ let mut dest = PathBuf::from(&*BETTERBUG_CACHE_DIR_PREFIX);
+ dest.push(bb_profile_id.to_string());
+ dest.push(&*BETTERBUG_CACHE_DIR_SUFFIX);
+ if !dest.is_dir() {
+ return Err(err_to_binder_status(anyhow!("Cannot open BetterBug cache dir")));
+ }
+ dest.push(report_name);
+ dest.set_extension("zip");
+
+ copy(report, dest)
+ .map(|_| ())
+ .context("Failed to copy report to bb storage.")
+ .map_err(err_to_binder_status)
+ }
+ fn get_supported_provider(&self) -> BinderResult<String> {
+ Ok(self.lock().scheduler.get_trace_provider_name().to_string())
+ }
+}
+
+/// Verify that the report name is valid, i.e. not a relative path component, to prevent potential
+/// attack.
+fn verify_report_name(report_name: &str) -> Result<()> {
+ match report_name.chars().all(|c| c.is_ascii_hexdigit() || c == '-') {
+ true => Ok(()),
+ false => bail!("Invalid report name: {}", report_name),
+ }
+}
+
+impl ProfcollectdBinderService {
+ pub fn new() -> Result<Self> {
+ let new_scheduler = Scheduler::new()?;
+ let new_config = Config::from_env()?;
+
+ let config_changed = read_to_string(*CONFIG_FILE)
+ .ok()
+ .and_then(|s| Config::from_str(&s).ok())
+ .filter(|c| new_config == *c)
+ .is_none();
+
+ if config_changed {
+ log::info!("Config change detected, clearing traces.");
+ remove_dir_all(*PROFILE_OUTPUT_DIR)?;
+ remove_dir_all(*TRACE_OUTPUT_DIR)?;
+ create_dir(*PROFILE_OUTPUT_DIR)?;
+ create_dir(*TRACE_OUTPUT_DIR)?;
+
+ write(*CONFIG_FILE, &new_config.to_string())?;
+ }
+
+ Ok(ProfcollectdBinderService {
+ lock: Mutex::new(Lock { scheduler: new_scheduler, config: new_config }),
+ })
+ }
+
+ fn lock(&self) -> MutexGuard<Lock> {
+ self.lock.lock().unwrap()
+ }
+}
diff --git a/profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs b/profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs
new file mode 100644
index 00000000..2038a5be
--- /dev/null
+++ b/profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs
@@ -0,0 +1,79 @@
+//
+// 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.
+//
+
+//! Trace provider backed by ARM Coresight ETM, 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 ETM_TRACEFILE_EXTENSION: &str = "etmtrace";
+static ETM_PROFILE_EXTENSION: &str = "data";
+
+pub struct SimpleperfEtmTraceProvider {}
+
+impl TraceProvider for SimpleperfEtmTraceProvider {
+ fn get_name(&self) -> &'static str {
+ "simpleperf_etm"
+ }
+
+ fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration) {
+ let mut trace_file = PathBuf::from(trace_dir);
+ trace_file.push(trace_provider::construct_file_name(tag));
+ trace_file.set_extension(ETM_TRACEFILE_EXTENSION);
+
+ simpleperf_profcollect::record(
+ &trace_file,
+ sampling_period,
+ simpleperf_profcollect::RecordScope::BOTH,
+ );
+ }
+
+ fn process(&self, trace_dir: &Path, profile_dir: &Path) -> Result<()> {
+ read_dir(trace_dir)?
+ .filter_map(|e| e.ok())
+ .map(|e| e.path())
+ .filter(|e| {
+ e.is_file()
+ && e.extension()
+ .and_then(|f| f.to_str())
+ .filter(|ext| ext == &ETM_TRACEFILE_EXTENSION)
+ .is_some()
+ })
+ .try_for_each(|trace_file| -> Result<()> {
+ 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(ETM_PROFILE_EXTENSION);
+ simpleperf_profcollect::process(&trace_file, &profile_file);
+ remove_file(&trace_file)?;
+ Ok(())
+ })
+ }
+}
+
+impl SimpleperfEtmTraceProvider {
+ pub fn supported() -> bool {
+ simpleperf_profcollect::has_support()
+ }
+}
diff --git a/profcollectd/libprofcollectd/trace_provider.rs b/profcollectd/libprofcollectd/trace_provider.rs
new file mode 100644
index 00000000..ed0d56f5
--- /dev/null
+++ b/profcollectd/libprofcollectd/trace_provider.rs
@@ -0,0 +1,44 @@
+//
+// 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.
+//
+
+//! ProfCollect trace provider trait and helper functions.
+
+use anyhow::{anyhow, Result};
+use chrono::Utc;
+use std::path::Path;
+use std::sync::{Arc, Mutex};
+use std::time::Duration;
+
+use crate::simpleperf_etm_trace_provider::SimpleperfEtmTraceProvider;
+
+pub trait TraceProvider {
+ fn get_name(&self) -> &'static str;
+ fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration);
+ fn process(&self, trace_dir: &Path, profile_dir: &Path) -> Result<()>;
+}
+
+pub fn get_trace_provider() -> Result<Arc<Mutex<dyn TraceProvider + Send>>> {
+ if SimpleperfEtmTraceProvider::supported() {
+ log::info!("simpleperf_etm trace provider registered.");
+ return Ok(Arc::new(Mutex::new(SimpleperfEtmTraceProvider {})));
+ }
+
+ Err(anyhow!("No trace provider found for this device."))
+}
+
+pub fn construct_file_name(tag: &str) -> String {
+ format!("{}_{}", Utc::now().format("%Y%m%d-%H%M%S"), tag)
+}
diff --git a/profcollectd/profcollectctl.rs b/profcollectd/profcollectctl.rs
new file mode 100644
index 00000000..c825f550
--- /dev/null
+++ b/profcollectd/profcollectctl.rs
@@ -0,0 +1,72 @@
+//
+// 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.
+//
+
+//! Command to control profcollectd behaviour.
+
+use anyhow::{bail, Context, Result};
+use std::env;
+
+const HELP_MSG: &str = r#"
+usage: profcollectctl [command]
+
+Command to control profcollectd behaviour.
+
+command:
+ start Schedule periodic collection.
+ stop Terminate periodic collection.
+ once Request an one-off trace.
+ process Convert traces to perf profiles.
+ report Create a report containing all profiles.
+ reconfig Refresh configuration.
+ help Print this message.
+"#;
+
+fn main() -> Result<()> {
+ libprofcollectd::init_logging();
+
+ let args: Vec<String> = env::args().collect();
+ if args.len() != 2 {
+ bail!("This program only takes one argument{}", &HELP_MSG);
+ }
+
+ let action = &args[1];
+ match action.as_str() {
+ "start" => {
+ println!("Scheduling profile collection");
+ libprofcollectd::schedule().context("Failed to schedule collection.")?;
+ }
+ "stop" => {
+ println!("Terminating profile collection");
+ libprofcollectd::terminate().context("Failed to terminate collection.")?;
+ }
+ "once" => {
+ println!("Trace once");
+ libprofcollectd::trace_once("manual").context("Failed to trace.")?;
+ }
+ "process" => {
+ println!("Processing traces");
+ libprofcollectd::process().context("Failed to process traces.")?;
+ }
+ "report" => {
+ println!("Creating profile report");
+ let path = libprofcollectd::report().context("Failed to create profile report.")?;
+ println!("Report created at: {}", &path);
+ }
+ "help" => println!("{}", &HELP_MSG),
+ arg => bail!("Unknown argument: {}\n{}", &arg, &HELP_MSG),
+ }
+ Ok(())
+}
diff --git a/profcollectd/profcollectd.rc b/profcollectd/profcollectd.rc
new file mode 100644
index 00000000..d1dbd9ef
--- /dev/null
+++ b/profcollectd/profcollectd.rc
@@ -0,0 +1,20 @@
+service profcollectd /system/bin/profcollectd
+ class late_start
+ disabled
+ oneshot
+ user root
+ group root wakelock
+ writepid /dev/cpuset/system-background/tasks
+
+on property:persist.device_config.profcollect_native_boot.enabled=true
+ start profcollectd
+
+on property:persist.profcollectd.enabled_override=true
+ start profcollectd
+
+on post-fs-data
+ # Create directory for profcollectd.
+ mkdir /data/misc/profcollectd 0770 root system
+ mkdir /data/misc/profcollectd/trace 0770 root system
+ mkdir /data/misc/profcollectd/output 0770 root system
+ mkdir /data/misc/profcollectd/report 0770 root system
diff --git a/profcollectd/profcollectd.rs b/profcollectd/profcollectd.rs
new file mode 100644
index 00000000..0c2e87d9
--- /dev/null
+++ b/profcollectd/profcollectd.rs
@@ -0,0 +1,46 @@
+//
+// 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.
+//
+
+//! Daemon program to collect system traces.
+
+use anyhow::{bail, Result};
+use std::env;
+
+const HELP_MSG: &str = r#"
+profcollectd background daemon.
+usage: profcollectd [command]
+ nostart Start daemon but do not schedule profile collection.
+"#;
+
+fn main() -> Result<()> {
+ libprofcollectd::init_logging();
+
+ let args: Vec<String> = env::args().collect();
+ if args.len() > 2 {
+ bail!("This program only takes one or no argument{}", &HELP_MSG);
+ }
+ if args.len() == 1 {
+ libprofcollectd::init_service(true)?;
+ }
+
+ let action = &args[1];
+ match action.as_str() {
+ "nostart" => libprofcollectd::init_service(false)?,
+ "help" => println!("{}", &HELP_MSG),
+ arg => bail!("Unknown argument: {}\n{}", &arg, &HELP_MSG),
+ }
+ Ok(())
+}
diff --git a/pssbench/Android.mk b/pssbench/Android.mk
index 45bab557..2c7761d3 100644
--- a/pssbench/Android.mk
+++ b/pssbench/Android.mk
@@ -3,6 +3,8 @@ include $(CLEAR_VARS)
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/local/tmp
LOCAL_MODULE:= pssbench
+LOCAL_LICENSE_KINDS:= SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS:= notice
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := EXECUTABLES
diff --git a/puncture_fs/Android.bp b/puncture_fs/Android.bp
index 5c163372..aef7ba43 100644
--- a/puncture_fs/Android.bp
+++ b/puncture_fs/Android.bp
@@ -12,6 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_applicable_licenses: ["system_extras_puncture_fs_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_puncture_fs_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_binary {
name: "puncture_fs",
diff --git a/runconuid/Android.bp b/runconuid/Android.bp
index 59fb9ca1..ceea3ff3 100644
--- a/runconuid/Android.bp
+++ b/runconuid/Android.bp
@@ -1,3 +1,20 @@
+package {
+ default_applicable_licenses: ["system_extras_runconuid_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_runconuid_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_binary {
name: "runconuid",
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 120000
index 00000000..ee92d9ef
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1 @@
+../../build/soong/scripts/rustfmt.toml \ No newline at end of file
diff --git a/sane_schedstat/Android.bp b/sane_schedstat/Android.bp
index 68adc743..9be0d4e3 100644
--- a/sane_schedstat/Android.bp
+++ b/sane_schedstat/Android.bp
@@ -1,3 +1,20 @@
+package {
+ default_applicable_licenses: ["system_extras_sane_schedstat_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_sane_schedstat_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_binary {
name: "sane_schedstat",
diff --git a/showslab/Android.bp b/showslab/Android.bp
index d84004e5..27a71c43 100644
--- a/showslab/Android.bp
+++ b/showslab/Android.bp
@@ -1,5 +1,22 @@
// Copyright 2007 The Android Open Source Project
+package {
+ default_applicable_licenses: ["system_extras_showslab_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_showslab_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_binary {
name: "showslab",
diff --git a/simpleperf/.clang-format b/simpleperf/.clang-format
new file mode 120000
index 00000000..fd0645fd
--- /dev/null
+++ b/simpleperf/.clang-format
@@ -0,0 +1 @@
+../.clang-format-2 \ No newline at end of file
diff --git a/simpleperf/Android.bp b/simpleperf/Android.bp
index f1f2d6bc..5f9b0de2 100644
--- a/simpleperf/Android.bp
+++ b/simpleperf/Android.bp
@@ -14,30 +14,20 @@
// limitations under the License.
//
-cc_defaults {
- name: "simpleperf_defaults",
-
- cflags: [
- "-Wall",
- "-Wextra",
- "-Werror",
- "-Wimplicit-fallthrough",
+package {
+ default_applicable_licenses: ["system_extras_simpleperf_license"],
+}
- // Try some more extreme warnings.
- "-Wpedantic",
- "-Wunreachable-code-aggressive",
- // And disable some dumb things.
- "-Wno-zero-length-array",
- "-Wno-c99-extensions",
- "-Wno-language-extension-token",
- "-Wno-gnu-zero-variadic-macro-arguments",
- "-Wno-nested-anon-types",
- "-Wno-gnu-statement-expression",
- "-Wno-vla-extension",
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_simpleperf_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
],
- cppflags: [
- "-Wno-sign-compare",
- "-Wno-unused-parameter",
+ license_text: [
+ "NOTICE",
],
}
@@ -54,45 +44,6 @@ cc_defaults {
"libz",
"libziparchive",
],
- target: {
- // Required for LLVM.
- darwin: {
- host_ldlibs: [
- "-lncurses",
- ],
- },
- linux_glibc: {
- host_ldlibs: [
- "-lncurses",
- ],
- },
- },
-}
-
-cc_library_static {
- name: "libsimpleperf_elf_read",
- defaults: [
- "simpleperf_defaults",
- "libsimpleperf_elf_read_static_reqs_defaults",
- ],
- host_supported: true,
-
- export_include_dirs: [
- ".",
- ],
-
- static_libs: [
- "libbase",
- ],
-
- srcs: [
- "read_apk.cpp",
- "read_elf.cpp",
- "utils.cpp",
- ],
-
- group_static_libs: true,
- use_version_lib: true,
}
cc_defaults {
@@ -101,7 +52,7 @@ cc_defaults {
host: {
cflags: [
"-DUSE_BIONIC_UAPI_HEADERS",
- "-fvisibility=hidden"
+ "-fvisibility=hidden",
],
include_dirs: ["bionic/libc/kernel"],
},
@@ -112,7 +63,7 @@ cc_defaults {
windows: {
cflags: ["-DNO_LIBDEXFILE_SUPPORT"],
local_include_dirs: ["nonlinux_support/include"],
- }
+ },
},
}
@@ -143,8 +94,8 @@ cc_library_static {
target: {
windows: {
enabled: true,
- }
- }
+ },
+ },
}
cc_defaults {
@@ -169,6 +120,7 @@ cc_defaults {
"libcutils",
"libprocinfo",
"libevent",
+ "libc++fs",
],
},
android: {
@@ -206,20 +158,14 @@ cc_defaults {
linux: {
shared_libs: [
"libcutils",
- "libdexfile_support",
"libevent",
+ "liblog",
"libprocinfo",
"libunwindstack",
],
- },
- darwin: {
- host_ldlibs: [
- "-lncurses",
- ],
- },
- linux_glibc: {
- host_ldlibs: [
- "-lncurses",
+ static_libs: [
+ "libc++fs",
+ "libdexfile_support",
],
},
host: {
@@ -264,18 +210,25 @@ cc_defaults {
"cmd_help.cpp",
"cmd_inject.cpp",
"cmd_kmem.cpp",
+ "cmd_merge.cpp",
"cmd_report.cpp",
"cmd_report_sample.cpp",
+ "cmd_report_sample.proto",
"command.cpp",
"dso.cpp",
+ "etm_branch_list.proto",
"event_attr.cpp",
"event_type.cpp",
+ "kallsyms.cpp",
"perf_regs.cpp",
"read_apk.cpp",
"read_elf.cpp",
+ "read_symbol_map.cpp",
"record.cpp",
+ "record_file.proto",
"record_file_reader.cpp",
- "report_sample.proto",
+ "record_file_writer.cpp",
+ "report_utils.cpp",
"thread_tree.cpp",
"tracing.cpp",
"utils.cpp",
@@ -287,6 +240,7 @@ cc_defaults {
"cmd_api.cpp",
"cmd_debug_unwind.cpp",
"cmd_list.cpp",
+ "cmd_monitor.cpp",
"cmd_record.cpp",
"cmd_stat.cpp",
"cmd_trace_sched.cpp",
@@ -296,9 +250,11 @@ cc_defaults {
"event_selection_set.cpp",
"IOEventLoop.cpp",
"JITDebugReader.cpp",
+ "MapRecordReader.cpp",
"OfflineUnwinder.cpp",
+ "ProbeEvents.cpp",
"read_dex_file.cpp",
- "record_file_writer.cpp",
+ "RecordFilter.cpp",
"RecordReadThread.cpp",
"workload.cpp",
],
@@ -329,7 +285,7 @@ cc_library_static {
target: {
linux: {
- // See note for libdexfile_support_static in simpleperf_ndk.
+ // See note for libdexfile_static in simpleperf_ndk.
static_libs: ["libdexfile_support"],
},
},
@@ -355,6 +311,31 @@ cc_binary {
},
}
+cc_library {
+ name: "libsimpleperf_profcollect",
+ defaults: ["simpleperf_shared_libs"],
+ srcs: ["profcollect.cpp"],
+ host_supported: false,
+ static_libs: ["libsimpleperf"],
+ shared_libs: ["libLLVM_android"],
+}
+
+rust_bindgen {
+ name: "libsimpleperf_profcollect_bindgen",
+ wrapper_src: "include/simpleperf_profcollect.hpp",
+ crate_name: "simpleperf_profcollect_bindgen",
+ source_stem: "bindings",
+}
+
+rust_library {
+ name: "libsimpleperf_profcollect_rust",
+ crate_name: "simpleperf_profcollect",
+ srcs: ["rust/lib.rs"],
+ rlibs: ["libsimpleperf_profcollect_bindgen"],
+ shared_libs: ["libsimpleperf_profcollect"],
+ visibility: ["//system/extras/profcollectd:__subpackages__"],
+}
+
// simpleperf released in ndk
cc_binary {
name: "simpleperf_ndk",
@@ -413,12 +394,11 @@ cc_binary {
},
},
linux: {
- // In the NDK we need libdexfile_support_static which links
- // libdexfile_external and its ART dependencies statically. However
- // in other libraries we must use libdexfile_support, which dlopen's
- // libdexfile_external.so from the ART APEX, to avoid getting ART
- // internals in the system image.
- static_libs: ["libdexfile_support_static"],
+ // In the NDK we need libdexfile_static which links libdexfile and
+ // its ART dependencies statically. However in other libraries we
+ // must use libdexfile_support, which dlopen's libdexfile.so from
+ // the ART APEX, to avoid getting ART internals in the system image.
+ static_libs: ["libdexfile_static"],
},
linux_glibc_x86: {
dist: {
@@ -455,7 +435,7 @@ cc_library {
"record_lib_interface.cpp",
],
static_libs: [
- "libsimpleperf"
+ "libsimpleperf",
],
target: {
darwin: {
@@ -465,8 +445,8 @@ cc_library {
enabled: false,
},
linux: {
- // See note for libdexfile_support_static in simpleperf_ndk.
- static_libs: ["libdexfile_support"],
+ // See note for libdexfile_static in simpleperf_ndk.
+ static_libs: ["libdexfile_static"],
},
},
}
@@ -491,9 +471,8 @@ cc_library_shared {
},
linux: {
ldflags: ["-Wl,--exclude-libs,ALL"],
- // See note for libdexfile_support_static in simpleperf_ndk. This is
- // part of the NDK, so use libdexfile_support_static.
- static_libs: ["libdexfile_support_static"],
+ // See note for libdexfile_static in simpleperf_ndk.
+ static_libs: ["libdexfile_static"],
},
darwin: {
dist: {
@@ -533,16 +512,24 @@ cc_defaults {
srcs: [
"cmd_inject_test.cpp",
"cmd_kmem_test.cpp",
+ "cmd_merge_test.cpp",
"cmd_report_test.cpp",
"cmd_report_sample_test.cpp",
"command_test.cpp",
"dso_test.cpp",
"gtest_main.cpp",
+ "kallsyms_test.cpp",
+ "perf_regs_test.cpp",
"read_apk_test.cpp",
"read_elf_test.cpp",
+ "read_symbol_map_test.cpp",
+ "record_file_test.cpp",
"record_test.cpp",
+ "report_utils_test.cpp",
"sample_tree_test.cpp",
"thread_tree_test.cpp",
+ "test_util.cpp",
+ "tracing_test.cpp",
"utils_test.cpp",
],
target: {
@@ -554,13 +541,16 @@ cc_defaults {
"cmd_dumprecord_test.cpp",
"cmd_list_test.cpp",
"cmd_record_test.cpp",
+ "cmd_monitor_test.cpp",
"cmd_stat_test.cpp",
"cmd_trace_sched_test.cpp",
"environment_test.cpp",
"IOEventLoop_test.cpp",
+ "MapRecordReader_test.cpp",
"OfflineUnwinder_test.cpp",
+ "ProbeEvents_test.cpp",
"read_dex_file_test.cpp",
- "record_file_test.cpp",
+ "RecordFilter_test.cpp",
"RecordReadThread_test.cpp",
"workload_test.cpp",
],
@@ -576,7 +566,7 @@ cc_test {
],
static_libs: [
"libgmock",
- "libsimpleperf"
+ "libsimpleperf",
],
target: {
android: {
@@ -593,6 +583,9 @@ cc_test {
defaults: [
"simpleperf_libs_for_tests",
],
+ test_options: {
+ unit_test: true,
+ },
srcs: [
"cpu_hotplug_test.cpp",
],
@@ -650,3 +643,16 @@ cc_test {
},
},
}
+
+python_library_host {
+ name: "simpleperf_report_lib",
+ srcs: [
+ "scripts/simpleperf_report_lib.py",
+ "scripts/simpleperf_utils.py",
+ ],
+ data: [
+ "scripts/bin/darwin/x86_64/libsimpleperf_report.dylib",
+ "scripts/bin/linux/x86_64/libsimpleperf_report.so",
+ "scripts/bin/windows/**/*.dll",
+ ],
+}
diff --git a/simpleperf/Android.mk b/simpleperf/Android.mk
index a2202159..df0009f4 100644
--- a/simpleperf/Android.mk
+++ b/simpleperf/Android.mk
@@ -18,26 +18,14 @@ LOCAL_PATH := $(call my-dir)
# simpleperf_script.zip (for release in ndk)
# ============================================================
SIMPLEPERF_SCRIPT_LIST := \
- $(filter-out scripts/update.py,$(call all-named-files-under,*.py,scripts)) \
- scripts/inferno.sh \
- scripts/inferno.bat \
- scripts/inferno/inferno.b64 \
- $(call all-named-files-under,*,scripts/script_testdata) \
- $(call all-named-files-under,*.js,scripts) \
- $(call all-named-files-under,*.css,scripts) \
- $(call all-named-files-under,*,doc) \
- $(call all-named-files-under,app-profiling.apk,demo) \
- $(call all-named-files-under,*.so,demo) \
- $(call all-cpp-files-under,demo) \
- $(call all-java-files-under,demo) \
- $(call all-named-files-under,*.kt,demo) \
- testdata/perf_with_symbols.data \
- testdata/perf_with_trace_offcpu.data \
- testdata/perf_with_tracepoint_event.data \
- testdata/perf_with_interpreter_frames.data \
- $(call all-named-files-under,*,app_api)
+ app_api \
+ doc \
+ demo \
+ runtest \
+ scripts \
+ testdata
-SIMPLEPERF_SCRIPT_LIST := $(addprefix -f $(LOCAL_PATH)/,$(SIMPLEPERF_SCRIPT_LIST))
+SIMPLEPERF_SCRIPT_LIST := $(addprefix -D $(LOCAL_PATH)/,$(SIMPLEPERF_SCRIPT_LIST))
SIMPLEPERF_SCRIPT_PATH := \
$(call intermediates-dir-for,PACKAGING,simplerperf_script,HOST)/simpleperf_script.zip
diff --git a/simpleperf/AndroidTest.xml b/simpleperf/AndroidTest.xml
deleted file mode 100644
index 310cd2a5..00000000
--- a/simpleperf/AndroidTest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
- 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.
--->
-<configuration description="Config for simpleperf_unit_test and simpleperf_cpu_hotplug_test">
- <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
- <option name="cleanup" value="true" />
- <option name="push" value="simpleperf_unit_test->/data/local/tmp/simpleperf_unit_test" />
- <option name="push" value="simpleperf_cpu_hotplug_test->/data/local/tmp/simpleperf_cpu_hotplug_test" />
- </target_preparer>
- <option name="test-suite-tag" value="apct" />
- <test class="com.android.tradefed.testtype.GTest" >
- <option name="native-test-device-path" value="/data/local/tmp" />
- <option name="module-name" value="simpleperf_unit_test" />
- </test>
- <test class="com.android.tradefed.testtype.GTest" >
- <option name="native-test-device-path" value="/data/local/tmp" />
- <option name="module-name" value="simpleperf_cpu_hotplug_test" />
- </test>
-</configuration>
diff --git a/simpleperf/CallChainJoiner.cpp b/simpleperf/CallChainJoiner.cpp
index f6198844..5d02cd5e 100644
--- a/simpleperf/CallChainJoiner.cpp
+++ b/simpleperf/CallChainJoiner.cpp
@@ -31,7 +31,7 @@ LRUCache::LRUCache(size_t cache_size, size_t matched_node_count_to_extend_callch
CHECK_GE(cache_stat_.max_node_count, 2u);
CHECK_GE(matched_node_count_to_extend_callchain, 1u);
cache_stat_.matched_node_count_to_extend_callchain = matched_node_count_to_extend_callchain;
- nodes_ = new CacheNode[cache_stat_.max_node_count + 1]; // with 1 sentinel node
+ nodes_ = new CacheNode[cache_stat_.max_node_count + 1]; // with 1 sentinel node
// nodes_[0] is the sentinel node of the LRU linked list.
nodes_[0].is_leaf = 1;
nodes_[0].parent_index = 0;
@@ -170,13 +170,12 @@ void LRUCache::UnlinkParent(CacheNode* child) {
child->parent_index = 0;
}
-} // call_chain_joiner_impl
+} // namespace call_chain_joiner_impl
using namespace call_chain_joiner_impl;
static bool WriteCallChain(FILE* fp, pid_t pid, pid_t tid, CallChainJoiner::ChainType type,
- const std::vector<uint64_t>& ips,
- const std::vector<uint64_t>& sps,
+ const std::vector<uint64_t>& ips, const std::vector<uint64_t>& sps,
size_t ip_count) {
// Below is the content of a call chain stored in file.
// uint32_t pid;
@@ -231,8 +230,7 @@ static bool ReadCallChain(FILE* fp, pid_t& pid, pid_t& tid, CallChainJoiner::Cha
static bool ReadCallChainInReverseOrder(FILE* fp, pid_t& pid, pid_t& tid,
CallChainJoiner::ChainType& type,
- std::vector<uint64_t>& ips,
- std::vector<uint64_t>& sps) {
+ std::vector<uint64_t>& ips, std::vector<uint64_t>& sps) {
uint32_t size;
if (fseek(fp, -4, SEEK_CUR) != 0 || fread(&size, sizeof(size), 1, fp) != 1) {
PLOG(ERROR) << "fread";
@@ -348,8 +346,7 @@ bool CallChainJoiner::JoinCallChains() {
}
std::vector<std::pair<FILE*, FILE*>> file_pairs = {
std::make_pair(original_chains_fp_, tmp_fp.get()),
- std::make_pair(tmp_fp.get(), joined_chains_fp_)
- };
+ std::make_pair(tmp_fp.get(), joined_chains_fp_)};
for (size_t pass = 0; pass < 2u; ++pass) {
auto& pair = file_pairs[pass];
for (size_t i = 0; i < stat_.chain_count; ++i) {
@@ -382,8 +379,7 @@ bool CallChainJoiner::JoinCallChains() {
}
bool CallChainJoiner::GetNextCallChain(pid_t& pid, pid_t& tid, ChainType& type,
- std::vector<uint64_t>& ips,
- std::vector<uint64_t>& sps) {
+ std::vector<uint64_t>& ips, std::vector<uint64_t>& sps) {
if (next_chain_index_ == stat_.chain_count * 2) {
// No more chains.
return false;
diff --git a/simpleperf/CallChainJoiner.h b/simpleperf/CallChainJoiner.h
index 29370ee8..ecb0b8b5 100644
--- a/simpleperf/CallChainJoiner.h
+++ b/simpleperf/CallChainJoiner.h
@@ -72,9 +72,7 @@ class LRUCache {
// Add a call chain in the cache, and extend it if possible.
void AddCallChain(pid_t tid, std::vector<uint64_t>& ips, std::vector<uint64_t>& sps);
- const LRUCacheStat& Stat() {
- return cache_stat_;
- }
+ const LRUCacheStat& Stat() { return cache_stat_; }
CacheNode* FindNode(uint32_t tid, uint64_t ip, uint64_t sp) {
CacheNode key;
@@ -96,9 +94,7 @@ class LRUCache {
return node->parent_index == 0u ? nullptr : nodes_ + node->parent_index;
}
- int GetNodeIndex(CacheNode* node) {
- return node - nodes_;
- }
+ int GetNodeIndex(CacheNode* node) { return node - nodes_; }
void RemoveNodeFromLRUList(CacheNode* node) {
CacheNode* prev = &nodes_[node->leaf_link_prev];
@@ -168,15 +164,10 @@ class CallChainJoiner {
size_t after_join_max_chain_length = 0u;
};
void DumpStat();
- const Stat& GetStat() {
- return stat_;
- }
- const call_chain_joiner_impl::LRUCacheStat& GetCacheStat() {
- return cache_stat_;
- }
+ const Stat& GetStat() { return stat_; }
+ const call_chain_joiner_impl::LRUCacheStat& GetCacheStat() { return cache_stat_; }
private:
-
bool keep_original_callchains_;
FILE* original_chains_fp_;
FILE* joined_chains_fp_;
diff --git a/simpleperf/CallChainJoiner_test.cpp b/simpleperf/CallChainJoiner_test.cpp
index f33b97d5..90460e03 100644
--- a/simpleperf/CallChainJoiner_test.cpp
+++ b/simpleperf/CallChainJoiner_test.cpp
@@ -23,8 +23,7 @@
using namespace simpleperf;
using namespace simpleperf::call_chain_joiner_impl;
-static bool JoinCallChain(LRUCache& cache, uint32_t tid,
- const std::vector<uint64_t>& input_ip,
+static bool JoinCallChain(LRUCache& cache, uint32_t tid, const std::vector<uint64_t>& input_ip,
const std::vector<uint64_t>& input_sp,
const std::vector<uint64_t>& expected_output_ip,
const std::vector<uint64_t>& expected_output_sp) {
@@ -87,10 +86,10 @@ TEST(LRUCache, extend_chains) {
// d -> c -> b
// c -> b -> a => d -> c -> b -> a
LRUCache cache3(sizeof(CacheNode) * 4, 2);
- ASSERT_TRUE(JoinCallChain(cache3, 0, {0xb, 0xc, 0xd}, {0xb, 0xc, 0xd},
- {0xb, 0xc, 0xd}, {0xb, 0xc, 0xd}));
- ASSERT_TRUE(JoinCallChain(cache3, 0, {0xa, 0xb, 0xc}, {0xa, 0xb, 0xc},
- {0xa, 0xb, 0xc, 0xd}, {0xa, 0xb, 0xc, 0xd}));
+ ASSERT_TRUE(
+ JoinCallChain(cache3, 0, {0xb, 0xc, 0xd}, {0xb, 0xc, 0xd}, {0xb, 0xc, 0xd}, {0xb, 0xc, 0xd}));
+ ASSERT_TRUE(JoinCallChain(cache3, 0, {0xa, 0xb, 0xc}, {0xa, 0xb, 0xc}, {0xa, 0xb, 0xc, 0xd},
+ {0xa, 0xb, 0xc, 0xd}));
ASSERT_EQ(cache3.Stat().used_node_count, 4u);
}
@@ -158,7 +157,7 @@ class CallChainJoinerTest : public ::testing::Test {
#else
std::string tmpdir = "/tmp";
#endif
- scoped_temp_files_.reset(new ScopedTempFiles(tmpdir));
+ scoped_temp_files_ = ScopedTempFiles::Create(tmpdir);
}
private:
@@ -168,12 +167,11 @@ class CallChainJoinerTest : public ::testing::Test {
TEST_F(CallChainJoinerTest, smoke) {
CallChainJoiner joiner(sizeof(CacheNode) * 1024, 1, true);
for (pid_t pid = 0; pid < 10; ++pid) {
- ASSERT_TRUE(joiner.AddCallChain(pid, pid, CallChainJoiner::ORIGINAL_OFFLINE,
- {1, 2, 3}, {1, 2, 3}));
- ASSERT_TRUE(joiner.AddCallChain(pid, pid, CallChainJoiner::ORIGINAL_REMOTE,
- {3, 4, 5}, {3, 4, 5}));
- ASSERT_TRUE(joiner.AddCallChain(pid, pid, CallChainJoiner::ORIGINAL_OFFLINE,
- {1, 4}, {1, 4}));
+ ASSERT_TRUE(
+ joiner.AddCallChain(pid, pid, CallChainJoiner::ORIGINAL_OFFLINE, {1, 2, 3}, {1, 2, 3}));
+ ASSERT_TRUE(
+ joiner.AddCallChain(pid, pid, CallChainJoiner::ORIGINAL_REMOTE, {3, 4, 5}, {3, 4, 5}));
+ ASSERT_TRUE(joiner.AddCallChain(pid, pid, CallChainJoiner::ORIGINAL_OFFLINE, {1, 4}, {1, 4}));
}
ASSERT_TRUE(joiner.JoinCallChains());
pid_t pid;
@@ -200,8 +198,7 @@ TEST_F(CallChainJoinerTest, smoke) {
ASSERT_TRUE(joiner.GetNextCallChain(pid, tid, type, ips, sps));
ASSERT_EQ(pid, expected_pid);
ASSERT_EQ(tid, expected_pid);
- ASSERT_EQ(type, i == 0u ? CallChainJoiner::ORIGINAL_REMOTE
- : CallChainJoiner::JOINED_REMOTE);
+ ASSERT_EQ(type, i == 0u ? CallChainJoiner::ORIGINAL_REMOTE : CallChainJoiner::JOINED_REMOTE);
ASSERT_EQ(ips, std::vector<uint64_t>({3, 4, 5}));
ASSERT_EQ(sps, std::vector<uint64_t>({3, 4, 5}));
}
diff --git a/simpleperf/ETMDecoder.cpp b/simpleperf/ETMDecoder.cpp
index 136bbcdf..546f2988 100644
--- a/simpleperf/ETMDecoder.cpp
+++ b/simpleperf/ETMDecoder.cpp
@@ -16,13 +16,15 @@
#include "ETMDecoder.h"
+#include <sstream>
+
+#include <android-base/expected.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
#include <llvm/Support/MemoryBuffer.h>
#include <opencsd.h>
-using namespace simpleperf;
-
+namespace simpleperf {
namespace {
class DecoderLogStr : public ocsdMsgLogStrOutI {
@@ -53,7 +55,9 @@ class DecodeErrorLogger : public ocsdDefaultErrorLogger {
ocsdMsgLogger msg_logger_;
};
-static bool IsRespError(ocsd_datapath_resp_t resp) { return resp >= OCSD_RESP_ERR_CONT; }
+static bool IsRespError(ocsd_datapath_resp_t resp) {
+ return resp >= OCSD_RESP_ERR_CONT;
+}
// Used instead of DecodeTree in OpenCSD to avoid linking decoders not for ETMV4 instruction tracing
// in OpenCSD.
@@ -122,6 +126,7 @@ struct PacketCallback {
// packet callbacks are called in priority order.
enum Priority {
MAP_LOCATOR,
+ BRANCH_LIST_PARSER,
PACKET_TO_ELEMENT,
};
@@ -170,6 +175,9 @@ class MapLocator : public PacketCallback {
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; }
+
ocsd_datapath_resp_t ProcessPacket(uint8_t trace_id, ocsd_datapath_op_t op,
ocsd_trc_index_t index_sop,
const EtmV4ITrcPacket* pkt) override {
@@ -250,18 +258,25 @@ class MemAccess : public ITargetMemAccess {
if (map != nullptr) {
llvm::MemoryBuffer* memory = GetMemoryBuffer(map->dso);
if (memory != nullptr) {
- uint64_t offset = address - map->start_addr + map->pgoff;
- size_t file_size = memory->getBufferSize();
- copy_size = file_size > offset ? std::min<size_t>(file_size - offset, *num_bytes) : 0;
- if (copy_size > 0) {
- memcpy(p_buffer, memory->getBufferStart() + offset, copy_size);
+ if (auto opt_offset = map->dso->IpToFileOffset(address, map->start_addr, map->pgoff);
+ opt_offset) {
+ uint64_t offset = opt_offset.value();
+ size_t file_size = memory->getBufferSize();
+ copy_size = file_size > offset ? std::min<size_t>(file_size - offset, *num_bytes) : 0;
+ if (copy_size > 0) {
+ memcpy(p_buffer, memory->getBufferStart() + offset, copy_size);
+ }
}
}
// Update the last buffer cache.
- data.buffer_map = map;
- data.buffer = memory == nullptr ? nullptr : (memory->getBufferStart() + map->pgoff);
- data.buffer_start = map->start_addr;
- data.buffer_end = map->get_end_addr();
+ // Don't cache for the kernel map. Because simpleperf doesn't record an accurate kernel end
+ // addr.
+ if (!map->in_kernel) {
+ data.buffer_map = map;
+ data.buffer = memory == nullptr ? nullptr : (memory->getBufferStart() + map->pgoff);
+ data.buffer_start = map->start_addr;
+ data.buffer_end = map->get_end_addr();
+ }
}
*num_bytes = copy_size;
return OCSD_OK;
@@ -403,7 +418,7 @@ class InstrRangeParser : public ElementCallback {
};
public:
- InstrRangeParser(MapLocator& map_locator, const ETMDecoder::CallbackFn& callback)
+ InstrRangeParser(MapLocator& map_locator, const ETMDecoder::InstrRangeCallbackFn& callback)
: map_locator_(map_locator), callback_(callback) {}
ocsd_datapath_resp_t ProcessElement(const ocsd_trc_index_t, uint8_t trace_id,
@@ -468,7 +483,132 @@ class InstrRangeParser : public ElementCallback {
MapLocator& map_locator_;
std::unordered_map<uint8_t, TraceData> trace_data_;
- ETMDecoder::CallbackFn callback_;
+ ETMDecoder::InstrRangeCallbackFn callback_;
+};
+
+// It parses ETMBranchLists from ETMV4IPackets.
+// It doesn't do element decoding and instruction decoding, thus is about 5 timers faster than
+// InstrRangeParser. But some data will be lost when converting ETMBranchLists to InstrRanges:
+// 1. InstrRanges described by Except packets (the last instructions executed before exeception,
+// about 2%?).
+// 2. Branch to addresses of direct branch instructions across binaries.
+class BranchListParser : public PacketCallback {
+ private:
+ struct TraceData {
+ uint64_t addr = 0;
+ uint8_t addr_valid_bits = 0;
+ uint8_t isa = 0;
+ bool invalid_branch = false;
+ ETMBranchList branch;
+ };
+
+ public:
+ BranchListParser(MapLocator& map_locator, const ETMDecoder::BranchListCallbackFn& callback)
+ : PacketCallback(BRANCH_LIST_PARSER), map_locator_(map_locator), callback_(callback) {}
+
+ void CheckConfigs(std::unordered_map<uint8_t, EtmV4Config>& configs) {
+ // TODO: Current implementation doesn't support non-zero speculation length and return stack.
+ for (auto& p : configs) {
+ if (p.second.MaxSpecDepth() > 0) {
+ LOG(WARNING) << "branch list collection isn't accurate with non-zero speculation length";
+ break;
+ }
+ }
+ for (auto& p : configs) {
+ if (p.second.enabledRetStack()) {
+ LOG(WARNING) << "branch list collection will lose some data with return stack enabled";
+ break;
+ }
+ }
+ }
+
+ bool IsAddrPacket(const EtmV4ITrcPacket* pkt) {
+ return pkt->getType() >= ETM4_PKT_I_ADDR_CTXT_L_32IS0 &&
+ pkt->getType() <= ETM4_PKT_I_ADDR_L_64IS1;
+ }
+
+ bool IsAtomPacket(const EtmV4ITrcPacket* pkt) { return pkt->getAtom().num > 0; }
+
+ ocsd_datapath_resp_t ProcessPacket(uint8_t trace_id, ocsd_datapath_op_t op,
+ ocsd_trc_index_t /*index_sop */,
+ const EtmV4ITrcPacket* pkt) override {
+ TraceData& data = trace_data_[trace_id];
+ if (op == OCSD_OP_DATA) {
+ if (IsAddrPacket(pkt)) {
+ // Flush branch when seeing an Addr packet. Because it isn't correct to concatenate
+ // branches before and after an Addr packet.
+ FlushBranch(data);
+ data.addr = pkt->getAddrVal();
+ data.addr_valid_bits = pkt->v_addr.valid_bits;
+ data.isa = pkt->getAddrIS();
+ }
+
+ if (IsAtomPacket(pkt)) {
+ // An atom packet contains a branch list. We may receive one or more atom packets in a row,
+ // and need to concatenate them.
+ ProcessAtomPacket(trace_id, data, pkt);
+ }
+
+ } else {
+ // Flush branch when seeing a flush or reset operation.
+ FlushBranch(data);
+ if (op == OCSD_OP_RESET) {
+ data.addr = 0;
+ data.addr_valid_bits = 0;
+ data.isa = 0;
+ data.invalid_branch = false;
+ }
+ }
+ return OCSD_RESP_CONT;
+ }
+
+ void FinishData() {
+ for (auto& pair : trace_data_) {
+ FlushBranch(pair.second);
+ }
+ }
+
+ private:
+ void ProcessAtomPacket(uint8_t trace_id, TraceData& data, const EtmV4ITrcPacket* pkt) {
+ if (data.invalid_branch) {
+ return; // Skip atom packets when we think a branch list is invalid.
+ }
+ if (data.branch.branch.empty()) {
+ // This is the first atom packet in a branch list. Check if we have tid and addr info to
+ // parse it and the following atom packets. If not, mark the branch list as invalid.
+ if (map_locator_.GetTid(trace_id) == -1 || data.addr_valid_bits == 0) {
+ data.invalid_branch = true;
+ return;
+ }
+ const MapEntry* map = map_locator_.FindMap(trace_id, data.addr);
+ if (map == nullptr) {
+ data.invalid_branch = true;
+ return;
+ }
+ data.branch.dso = map->dso;
+ data.branch.addr = map->GetVaddrInFile(data.addr);
+ if (data.isa == 1) { // thumb instruction, mark it in bit 0.
+ data.branch.addr |= 1;
+ }
+ }
+ uint32_t bits = pkt->atom.En_bits;
+ for (size_t i = 0; i < pkt->atom.num; i++) {
+ data.branch.branch.push_back((bits & 1) == 1);
+ bits >>= 1;
+ }
+ }
+
+ void FlushBranch(TraceData& data) {
+ if (!data.branch.branch.empty()) {
+ callback_(data.branch);
+ data.branch.branch.clear();
+ }
+ data.invalid_branch = false;
+ }
+
+ MapLocator& map_locator_;
+ ETMDecoder::BranchListCallbackFn callback_;
+ std::unordered_map<uint8_t, TraceData> trace_data_;
};
// Etm data decoding in OpenCSD library has two steps:
@@ -523,12 +663,19 @@ class ETMDecoderImpl : public ETMDecoder {
}
}
- void RegisterCallback(const CallbackFn& callback) {
+ void RegisterCallback(const InstrRangeCallbackFn& callback) {
InstallMapLocator();
instr_range_parser_.reset(new InstrRangeParser(*map_locator_, callback));
InstallElementCallback(instr_range_parser_.get());
}
+ void RegisterCallback(const BranchListCallbackFn& callback) {
+ InstallMapLocator();
+ branch_list_parser_.reset(new BranchListParser(*map_locator_, callback));
+ branch_list_parser_->CheckConfigs(configs_);
+ InstallPacketCallback(branch_list_parser_.get());
+ }
+
bool ProcessData(const uint8_t* data, size_t size) override {
// Reset decoders before processing each data block. Because:
// 1. Data blocks are not continuous. So decoders shouldn't keep previous states when
@@ -563,6 +710,9 @@ class ETMDecoderImpl : public ETMDecoder {
if (instr_range_parser_) {
instr_range_parser_->FinishData();
}
+ if (branch_list_parser_) {
+ branch_list_parser_->FinishData();
+ }
return true;
}
@@ -604,12 +754,11 @@ class ETMDecoderImpl : public ETMDecoder {
size_t data_index_ = 0;
std::unique_ptr<InstrRangeParser> instr_range_parser_;
std::unique_ptr<MapLocator> map_locator_;
+ std::unique_ptr<BranchListParser> branch_list_parser_;
};
} // namespace
-namespace simpleperf {
-
bool ParseEtmDumpOption(const std::string& s, ETMDumpOption* option) {
for (auto& value : android::base::Split(s, ",")) {
if (value == "raw") {
@@ -633,4 +782,135 @@ std::unique_ptr<ETMDecoder> ETMDecoder::Create(const AuxTraceInfoRecord& auxtrac
return std::unique_ptr<ETMDecoder>(decoder.release());
}
+// Use OpenCSD instruction decoder to convert branches to instruction addresses.
+class BranchDecoder {
+ public:
+ android::base::expected<void, std::string> Init(Dso* dso) {
+ ElfStatus status;
+ elf_ = ElfFile::Open(dso->GetDebugFilePath(), &status);
+ if (!elf_) {
+ std::stringstream ss;
+ ss << status;
+ return android::base::unexpected(ss.str());
+ }
+ if (dso->type() == DSO_KERNEL_MODULE) {
+ // Kernel module doesn't have program header. So create a fake one mapping to .text section.
+ for (const auto& section : elf_->GetSectionHeader()) {
+ if (section.name == ".text") {
+ segments_.resize(1);
+ segments_[0].is_executable = true;
+ segments_[0].is_load = true;
+ segments_[0].file_offset = section.file_offset;
+ segments_[0].file_size = section.size;
+ segments_[0].vaddr = section.vaddr;
+ break;
+ }
+ }
+ } else {
+ segments_ = elf_->GetProgramHeader();
+ auto it = std::remove_if(segments_.begin(), segments_.end(),
+ [](const ElfSegment& s) { return !s.is_executable; });
+ segments_.resize(it - segments_.begin());
+ }
+ if (segments_.empty()) {
+ return android::base::unexpected("no segments");
+ }
+ buffer_ = elf_->GetMemoryBuffer();
+ return {};
+ }
+
+ void SetAddr(uint64_t addr, bool is_thumb) {
+ memset(&instr_info_, 0, sizeof(instr_info_));
+ instr_info_.pe_type.arch = ARCH_V8;
+ instr_info_.pe_type.profile = profile_CortexA;
+ instr_info_.isa =
+ elf_->Is64Bit() ? ocsd_isa_aarch64 : (is_thumb ? ocsd_isa_thumb2 : ocsd_isa_arm);
+ instr_info_.instr_addr = addr;
+ }
+
+ bool FindNextBranch() {
+ // Loop until we find a branch instruction.
+ while (ReadMem(instr_info_.instr_addr, 4, &instr_info_.opcode)) {
+ ocsd_err_t err = instruction_decoder_.DecodeInstruction(&instr_info_);
+ if (err != OCSD_OK) {
+ break;
+ }
+ if (instr_info_.type != OCSD_INSTR_OTHER) {
+ return true;
+ }
+ instr_info_.instr_addr += instr_info_.instr_size;
+ }
+ return false;
+ };
+
+ ocsd_instr_info& InstrInfo() { return instr_info_; }
+
+ private:
+ bool ReadMem(uint64_t vaddr, size_t size, void* data) {
+ for (auto& segment : segments_) {
+ if (vaddr >= segment.vaddr && vaddr + size <= segment.vaddr + segment.file_size) {
+ uint64_t offset = vaddr - segment.vaddr + segment.file_offset;
+ memcpy(data, buffer_->getBufferStart() + offset, size);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ std::unique_ptr<ElfFile> elf_;
+ std::vector<ElfSegment> segments_;
+ llvm::MemoryBuffer* buffer_ = nullptr;
+ ocsd_instr_info instr_info_;
+ InstructionDecoder instruction_decoder_;
+};
+
+android::base::expected<void, std::string> ConvertBranchMapToInstrRanges(
+ Dso* dso, const BranchMap& branch_map, const ETMDecoder::InstrRangeCallbackFn& callback) {
+ ETMInstrRange instr_range;
+ instr_range.dso = dso;
+
+ BranchDecoder decoder;
+ if (auto result = decoder.Init(dso); !result.ok()) {
+ return result;
+ }
+
+ for (const auto& addr_p : branch_map) {
+ uint64_t start_addr = addr_p.first & ~1ULL;
+ bool is_thumb = addr_p.first & 1;
+ for (const auto& branch_p : addr_p.second) {
+ const std::vector<bool>& branch = branch_p.first;
+ uint64_t count = branch_p.second;
+ decoder.SetAddr(start_addr, is_thumb);
+
+ for (bool b : branch) {
+ ocsd_instr_info& instr = decoder.InstrInfo();
+ uint64_t from_addr = instr.instr_addr;
+ if (!decoder.FindNextBranch()) {
+ break;
+ }
+ bool end_with_branch = instr.type == OCSD_INSTR_BR || instr.type == OCSD_INSTR_BR_INDIRECT;
+ bool branch_taken = end_with_branch && b;
+ instr_range.start_addr = from_addr;
+ instr_range.end_addr = instr.instr_addr;
+ if (instr.type == OCSD_INSTR_BR) {
+ instr_range.branch_to_addr = instr.branch_addr;
+ } else {
+ instr_range.branch_to_addr = 0;
+ }
+ instr_range.branch_taken_count = branch_taken ? count : 0;
+ instr_range.branch_not_taken_count = branch_taken ? 0 : count;
+
+ callback(instr_range);
+
+ if (b) {
+ instr.instr_addr = instr.branch_addr;
+ } else {
+ instr.instr_addr += instr.instr_size;
+ }
+ }
+ }
+ }
+ return {};
+}
+
} // namespace simpleperf \ No newline at end of file
diff --git a/simpleperf/ETMDecoder.h b/simpleperf/ETMDecoder.h
index b9493acd..f8cec366 100644
--- a/simpleperf/ETMDecoder.h
+++ b/simpleperf/ETMDecoder.h
@@ -20,6 +20,8 @@
#include <memory>
#include <string>
+#include <android-base/expected.h>
+
#include "record.h"
#include "thread_tree.h"
@@ -50,6 +52,15 @@ struct ETMInstrRange {
uint64_t branch_not_taken_count = 0;
};
+struct ETMBranchList {
+ // the binary containing the branch list
+ Dso* dso = nullptr;
+ // the instruction address before the first branch. Bit 0 is set for thumb instructions.
+ uint64_t addr = 0;
+ // the branch list (one bit for each branch, true for branch taken, false for not taken)
+ std::vector<bool> branch;
+};
+
class ETMDecoder {
public:
static std::unique_ptr<ETMDecoder> Create(const AuxTraceInfoRecord& auxtrace_info,
@@ -57,11 +68,22 @@ class ETMDecoder {
virtual ~ETMDecoder() {}
virtual void EnableDump(const ETMDumpOption& option) = 0;
- using CallbackFn = std::function<void(const ETMInstrRange&)>;
- virtual void RegisterCallback(const CallbackFn& callback) = 0;
+ using InstrRangeCallbackFn = std::function<void(const ETMInstrRange&)>;
+ virtual void RegisterCallback(const InstrRangeCallbackFn& callback) = 0;
+
+ using BranchListCallbackFn = std::function<void(const ETMBranchList&)>;
+ virtual void RegisterCallback(const BranchListCallbackFn& callback) = 0;
virtual bool ProcessData(const uint8_t* data, size_t size) = 0;
virtual bool FinishData() = 0;
};
+// 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>>;
+
+android::base::expected<void, std::string> ConvertBranchMapToInstrRanges(
+ Dso* dso, const BranchMap& 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 b561c483..c9119d0e 100644
--- a/simpleperf/ETMRecorder.cpp
+++ b/simpleperf/ETMRecorder.cpp
@@ -19,8 +19,8 @@
#include <stdio.h>
#include <sys/sysinfo.h>
-#include <memory>
#include <limits>
+#include <memory>
#include <string>
#include <android-base/file.h>
@@ -28,6 +28,7 @@
#include <android-base/parseint.h>
#include <android-base/strings.h>
+#include "environment.h"
#include "utils.h"
namespace simpleperf {
@@ -80,6 +81,10 @@ bool ETMPerCpu::IsTimestampSupported() const {
return GetBits(trcidr0, 24, 28) > 0;
}
+bool ETMPerCpu::IsEnabled() const {
+ return GetBits(trcauthstatus, 0, 3) == 0xc;
+}
+
ETMRecorder& ETMRecorder::GetInstance() {
static ETMRecorder etm;
return etm;
@@ -99,8 +104,8 @@ std::unique_ptr<EventType> ETMRecorder::BuildEventType() {
if (etm_event_type == -1) {
return nullptr;
}
- return std::make_unique<EventType>(
- "cs-etm", etm_event_type, 0, "CoreSight ETM instruction tracing", "arm");
+ return std::make_unique<EventType>("cs-etm", etm_event_type, 0,
+ "CoreSight ETM instruction tracing", "arm");
}
bool ETMRecorder::CheckEtmSupport() {
@@ -121,6 +126,10 @@ bool ETMRecorder::CheckEtmSupport() {
LOG(ERROR) << "etm device doesn't support contextID";
return false;
}
+ if (!p.second.IsEnabled()) {
+ LOG(ERROR) << "etm device isn't enabled by the bootloader";
+ return false;
+ }
}
if (!FindSinkConfig()) {
LOG(ERROR) << "can't find etr device, which moves etm data to memory";
@@ -131,35 +140,49 @@ bool ETMRecorder::CheckEtmSupport() {
}
bool ETMRecorder::ReadEtmInfo() {
- int cpu_count = get_nprocs_conf();
- for (const auto &name : GetEntriesInDir(ETM_DIR)) {
+ std::vector<int> online_cpus = GetOnlineCpus();
+ for (const auto& name : GetEntriesInDir(ETM_DIR)) {
int cpu;
if (sscanf(name.c_str(), "cpu%d", &cpu) == 1) {
- ETMPerCpu &cpu_info = etm_info_[cpu];
- bool success =
- ReadValueInEtmDir(name + "/trcidr/trcidr0", &cpu_info.trcidr0) &&
- ReadValueInEtmDir(name + "/trcidr/trcidr1", &cpu_info.trcidr1) &&
- ReadValueInEtmDir(name + "/trcidr/trcidr2", &cpu_info.trcidr2) &&
- ReadValueInEtmDir(name + "/trcidr/trcidr4", &cpu_info.trcidr4) &&
- ReadValueInEtmDir(name + "/trcidr/trcidr8", &cpu_info.trcidr8) &&
- ReadValueInEtmDir(name + "/mgmt/trcauthstatus", &cpu_info.trcauthstatus);
+ // We can't read ETM registers for offline cpus. So skip them.
+ if (std::find(online_cpus.begin(), online_cpus.end(), cpu) == online_cpus.end()) {
+ continue;
+ }
+ ETMPerCpu& cpu_info = etm_info_[cpu];
+ bool success = ReadValueInEtmDir(name + "/trcidr/trcidr0", &cpu_info.trcidr0) &&
+ ReadValueInEtmDir(name + "/trcidr/trcidr1", &cpu_info.trcidr1) &&
+ ReadValueInEtmDir(name + "/trcidr/trcidr2", &cpu_info.trcidr2) &&
+ ReadValueInEtmDir(name + "/trcidr/trcidr4", &cpu_info.trcidr4) &&
+ ReadValueInEtmDir(name + "/trcidr/trcidr8", &cpu_info.trcidr8) &&
+ ReadValueInEtmDir(name + "/mgmt/trcauthstatus", &cpu_info.trcauthstatus);
if (!success) {
return false;
}
}
}
- return (etm_info_.size() == cpu_count);
+ return (etm_info_.size() == online_cpus.size());
}
bool ETMRecorder::FindSinkConfig() {
- for (const auto &name : GetEntriesInDir(ETM_DIR + "sinks")) {
- if (name.find("etr") != -1) {
+ bool has_etr = false;
+ bool has_trbe = false;
+ for (const auto& name : GetEntriesInDir(ETM_DIR + "sinks")) {
+ if (!has_etr && name.find("etr") != -1) {
if (ReadValueInEtmDir("sinks/" + name, &sink_config_)) {
- return true;
+ has_etr = true;
}
}
+ if (name.find("trbe") != -1) {
+ has_trbe = true;
+ break;
+ }
+ }
+ if (has_trbe) {
+ // When TRBE is present, let the driver choose the most suitable
+ // configuration.
+ sink_config_ = 0;
}
- return false;
+ return has_trbe || has_etr;
}
void ETMRecorder::SetEtmPerfEventAttr(perf_event_attr* attr) {
diff --git a/simpleperf/ETMRecorder.h b/simpleperf/ETMRecorder.h
index 219741fb..33c0f726 100644
--- a/simpleperf/ETMRecorder.h
+++ b/simpleperf/ETMRecorder.h
@@ -22,8 +22,8 @@
#include <memory>
#include "event_type.h"
-#include "record.h"
#include "perf_event.h"
+#include "record.h"
namespace simpleperf {
@@ -38,6 +38,7 @@ struct ETMPerCpu {
int GetMajorVersion() const;
bool IsContextIDSupported() const;
bool IsTimestampSupported() const;
+ bool IsEnabled() const;
};
// Help recording Coresight ETM data on ARM devices.
@@ -74,4 +75,4 @@ class ETMRecorder {
std::map<int, ETMPerCpu> etm_info_;
};
-} // namespace simpleperf \ No newline at end of file
+} // namespace simpleperf \ No newline at end of file
diff --git a/simpleperf/IOEventLoop.cpp b/simpleperf/IOEventLoop.cpp
index 102bc6d2..06bdd713 100644
--- a/simpleperf/IOEventLoop.cpp
+++ b/simpleperf/IOEventLoop.cpp
@@ -21,6 +21,8 @@
#include <android-base/logging.h>
+namespace simpleperf {
+
struct IOEvent {
IOEventLoop* loop;
event* e;
@@ -29,8 +31,7 @@ struct IOEvent {
bool enabled;
IOEvent(IOEventLoop* loop, const std::function<bool()>& callback)
- : loop(loop), e(nullptr), timeout({}), callback(callback), enabled(false) {
- }
+ : loop(loop), e(nullptr), timeout({}), callback(callback), enabled(false) {}
~IOEvent() {
if (e != nullptr) {
@@ -109,29 +110,25 @@ static bool MakeFdNonBlocking(int fd) {
return true;
}
-IOEventRef IOEventLoop::AddReadEvent(int fd,
- const std::function<bool()>& callback) {
+IOEventRef IOEventLoop::AddReadEvent(int fd, const std::function<bool()>& callback) {
if (!MakeFdNonBlocking(fd)) {
return nullptr;
}
return AddEvent(fd, EV_READ | EV_PERSIST, nullptr, callback);
}
-IOEventRef IOEventLoop::AddWriteEvent(int fd,
- const std::function<bool()>& callback) {
+IOEventRef IOEventLoop::AddWriteEvent(int fd, const std::function<bool()>& callback) {
if (!MakeFdNonBlocking(fd)) {
return nullptr;
}
return AddEvent(fd, EV_WRITE | EV_PERSIST, nullptr, callback);
}
-bool IOEventLoop::AddSignalEvent(int sig,
- const std::function<bool()>& callback) {
+bool IOEventLoop::AddSignalEvent(int sig, const std::function<bool()>& callback) {
return AddEvent(sig, EV_SIGNAL | EV_PERSIST, nullptr, callback) != nullptr;
}
-bool IOEventLoop::AddSignalEvents(std::vector<int> sigs,
- const std::function<bool()>& callback) {
+bool IOEventLoop::AddSignalEvents(std::vector<int> sigs, const std::function<bool()>& callback) {
for (auto sig : sigs) {
if (!AddSignalEvent(sig, callback)) {
return false;
@@ -204,8 +201,8 @@ bool IOEventLoop::DisableEvent(IOEventRef ref) {
bool IOEventLoop::EnableEvent(IOEventRef ref) {
if (!ref->enabled) {
- timeval* timeout = (ref->timeout.tv_sec != 0 || ref->timeout.tv_usec != 0) ?
- &ref->timeout : nullptr;
+ timeval* timeout =
+ (ref->timeout.tv_sec != 0 || ref->timeout.tv_usec != 0) ? &ref->timeout : nullptr;
if (event_add(ref->e, timeout) != 0) {
LOG(ERROR) << "event_add() failed";
return false;
@@ -226,3 +223,5 @@ bool IOEventLoop::DelEvent(IOEventRef ref) {
}
return true;
}
+
+} // namespace simpleperf
diff --git a/simpleperf/IOEventLoop.h b/simpleperf/IOEventLoop.h
index ee9014ae..f0192969 100644
--- a/simpleperf/IOEventLoop.h
+++ b/simpleperf/IOEventLoop.h
@@ -24,9 +24,12 @@
#include <memory>
#include <vector>
+struct event_base;
+
+namespace simpleperf {
+
struct IOEvent;
typedef IOEvent* IOEventRef;
-struct event_base;
// IOEventLoop is a class wrapper of libevent, it monitors events happened,
// and calls the corresponding callbacks. Possible events are: file ready to
@@ -53,8 +56,7 @@ class IOEventLoop {
bool AddSignalEvent(int sig, const std::function<bool()>& callback);
// Register a vector of signal Events.
- bool AddSignalEvents(std::vector<int> sigs,
- const std::function<bool()>& callback);
+ bool AddSignalEvents(std::vector<int> sigs, const std::function<bool()>& callback);
// Register a periodic Event, so [callback] is called periodically every
// [duration].
@@ -88,4 +90,6 @@ class IOEventLoop {
bool in_loop_;
};
+} // namespace simpleperf
+
#endif // SIMPLE_PERF_IOEVENT_LOOP_H_
diff --git a/simpleperf/IOEventLoop_test.cpp b/simpleperf/IOEventLoop_test.cpp
index 9363bbc0..09f64522 100644
--- a/simpleperf/IOEventLoop_test.cpp
+++ b/simpleperf/IOEventLoop_test.cpp
@@ -24,6 +24,8 @@
#include <android-base/logging.h>
+using namespace simpleperf;
+
TEST(IOEventLoop, read) {
int fd[2];
ASSERT_EQ(0, pipe(fd));
@@ -140,10 +142,10 @@ void TestPeriodicEvents(int period_in_us, int iterations, bool precise) {
ASSERT_TRUE(loop.RunLoop());
auto end_time = std::chrono::steady_clock::now();
ASSERT_EQ(iterations, count);
- double time_used = std::chrono::duration_cast<std::chrono::duration<double>>(
- end_time - start_time).count();
+ 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.1 : 1);
+ double max_time_in_sec = min_time_in_sec + (precise ? 0.3 : 1);
ASSERT_GE(time_used, min_time_in_sec);
ASSERT_LT(time_used, max_time_in_sec);
}
diff --git a/simpleperf/JITDebugReader.cpp b/simpleperf/JITDebugReader.cpp
index 2897a06a..3b58407d 100644
--- a/simpleperf/JITDebugReader.cpp
+++ b/simpleperf/JITDebugReader.cpp
@@ -17,9 +17,11 @@
#include "JITDebugReader.h"
#include <inttypes.h>
+#include <stdio.h>
#include <sys/mman.h>
#include <sys/uio.h>
#include <sys/user.h>
+#include <unistd.h>
#include <algorithm>
#include <unordered_map>
@@ -28,6 +30,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include "dso.h"
@@ -38,6 +41,9 @@
namespace simpleperf {
+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;
@@ -50,6 +56,9 @@ static constexpr size_t MAX_JIT_SYMFILE_SIZE = 1024 * 1024u;
// avoid spending all time checking, wait 100 ms between any two checks.
static constexpr size_t kUpdateJITDebugInfoIntervalInMs = 100;
+// map name used for jit zygote cache
+static const char* kJITZygoteCacheMmapPrefix = "/memfd:jit-zygote-cache";
+
// Match the format of JITDescriptor in art/runtime/jit/debugger_interface.cc.
template <typename ADDRT>
struct JITDescriptor {
@@ -61,14 +70,12 @@ struct JITDescriptor {
uint32_t flags;
uint32_t sizeof_descriptor;
uint32_t sizeof_entry;
- uint32_t action_seqlock; // incremented before and after any modification
+ uint32_t action_seqlock; // incremented before and after any modification
uint64_t action_timestamp; // CLOCK_MONOTONIC time of last action
bool Valid() const;
- int AndroidVersion() const {
- return magic[7] - '0';
- }
+ int AndroidVersion() const { return magic[7] - '0'; }
};
// Match the format of JITCodeEntry in art/runtime/jit/debugger_interface.cc
@@ -81,9 +88,7 @@ struct JITCodeEntry {
uint64_t symfile_size;
uint64_t register_timestamp; // CLOCK_MONOTONIC time of entry registration
- bool Valid() const {
- return symfile_addr > 0u && symfile_size > 0u;
- }
+ bool Valid() const { return symfile_addr > 0u && symfile_size > 0u; }
};
// Match the format of JITCodeEntry in art/runtime/jit/debugger_interface.cc
@@ -96,9 +101,7 @@ struct __attribute__((packed)) PackedJITCodeEntry {
uint64_t symfile_size;
uint64_t register_timestamp;
- bool Valid() const {
- return symfile_addr > 0u && symfile_size > 0u;
- }
+ bool Valid() const { return symfile_addr > 0u && symfile_size > 0u; }
};
// Match the format of JITCodeEntry in art/runtime/jit/debugger_interface.cc
@@ -110,11 +113,9 @@ struct JITCodeEntryV2 {
ADDRT symfile_addr;
uint64_t symfile_size;
uint64_t register_timestamp; // CLOCK_MONOTONIC time of entry registration
- uint32_t seqlock; // even value if valid
+ uint32_t seqlock; // even value if valid
- bool Valid() const {
- return (seqlock & 1) == 0;
- }
+ bool Valid() const { return (seqlock & 1) == 0; }
};
// Match the format of JITCodeEntry in art/runtime/jit/debugger_interface.cc
@@ -128,9 +129,7 @@ struct __attribute__((packed)) PackedJITCodeEntryV2 {
uint64_t register_timestamp;
uint32_t seqlock;
- bool Valid() const {
- return (seqlock & 1) == 0;
- }
+ bool Valid() const { return (seqlock & 1) == 0; }
};
// Match the format of JITCodeEntry in art/runtime/jit/debugger_interface.cc
@@ -145,9 +144,7 @@ struct __attribute__((packed)) PaddedJITCodeEntryV2 {
uint32_t seqlock;
uint32_t pad;
- bool Valid() const {
- return (seqlock & 1) == 0;
- }
+ bool Valid() const { return (seqlock & 1) == 0; }
};
using JITDescriptor32 = JITDescriptor<uint32_t>;
@@ -206,8 +203,61 @@ static_assert(sizeof(JITCodeEntry64) == 40, "");
static_assert(sizeof(JITCodeEntry64V2) == 48, "");
#endif
+class TempSymFile {
+ public:
+ static std::unique_ptr<TempSymFile> Create(std::string&& path, bool remove_in_destructor) {
+ FILE* fp = fopen(path.data(), "web");
+ if (fp == nullptr) {
+ PLOG(ERROR) << "failed to create " << path;
+ return nullptr;
+ }
+ if (remove_in_destructor) {
+ ScopedTempFiles::RegisterTempFile(path);
+ }
+ return std::unique_ptr<TempSymFile>(new TempSymFile(std::move(path), fp));
+ }
+
+ bool WriteEntry(const char* data, size_t size) {
+ if (fwrite(data, size, 1, fp_.get()) != 1) {
+ PLOG(ERROR) << "failed to write to " << path_;
+ return false;
+ }
+ file_offset_ += size;
+ need_flush_ = true;
+ return true;
+ }
+
+ bool Flush() {
+ if (need_flush_) {
+ if (fflush(fp_.get()) != 0) {
+ PLOG(ERROR) << "failed to flush " << path_;
+ return false;
+ }
+ need_flush_ = false;
+ }
+ return true;
+ }
+
+ const std::string& GetPath() const { return path_; }
+ uint64_t GetOffset() const { return file_offset_; }
+
+ private:
+ TempSymFile(std::string&& path, FILE* fp) : path_(std::move(path)), fp_(fp, fclose) {}
+
+ const std::string path_;
+ std::unique_ptr<FILE, decltype(&fclose)> fp_;
+ uint64_t file_offset_ = 0;
+ bool need_flush_ = false;
+};
+
+JITDebugReader::JITDebugReader(const std::string& symfile_prefix, SymFileOption symfile_option,
+ SyncOption sync_option)
+ : symfile_prefix_(symfile_prefix), symfile_option_(symfile_option), sync_option_(sync_option) {}
+
+JITDebugReader::~JITDebugReader() {}
+
bool JITDebugReader::RegisterDebugInfoCallback(IOEventLoop* loop,
- const debug_info_callback_t& callback) {
+ const debug_info_callback_t& callback) {
debug_info_callback_ = callback;
read_event_ = loop->AddPeriodicEvent(SecondToTimeval(kUpdateJITDebugInfoIntervalInMs / 1000.0),
[this]() { return ReadAllProcesses(); });
@@ -229,7 +279,7 @@ bool JITDebugReader::MonitorProcess(pid_t pid) {
static bool IsArtLib(const std::string& filename) {
return android::base::EndsWith(filename, "libart.so") ||
- android::base::EndsWith(filename, "libartd.so");
+ android::base::EndsWith(filename, "libartd.so");
}
bool JITDebugReader::UpdateRecord(const Record* record) {
@@ -264,7 +314,7 @@ bool JITDebugReader::UpdateRecord(const Record* record) {
}
bool JITDebugReader::FlushDebugInfo(uint64_t timestamp) {
- if (sync_with_records_) {
+ if (sync_option_ == SyncOption::kSyncWithRecords) {
if (!debug_info_q_.empty() && debug_info_q_.top().timestamp < timestamp) {
std::vector<JITDebugInfo> debug_info;
while (!debug_info_q_.empty() && debug_info_q_.top().timestamp < timestamp) {
@@ -284,7 +334,9 @@ bool JITDebugReader::ReadAllProcesses() {
std::vector<JITDebugInfo> debug_info;
for (auto it = processes_.begin(); it != processes_.end();) {
Process& process = it->second;
- ReadProcess(process, &debug_info);
+ if (!ReadProcess(process, &debug_info)) {
+ return false;
+ }
if (process.died) {
LOG(DEBUG) << "Stop monitoring process " << process.pid;
it = processes_.erase(it);
@@ -305,80 +357,85 @@ bool JITDebugReader::ReadProcess(pid_t pid) {
auto it = processes_.find(pid);
if (it != processes_.end()) {
std::vector<JITDebugInfo> debug_info;
- ReadProcess(it->second, &debug_info);
- return AddDebugInfo(debug_info, false);
+ return ReadProcess(it->second, &debug_info) && AddDebugInfo(debug_info, false);
}
return true;
}
-void JITDebugReader::ReadProcess(Process& process, std::vector<JITDebugInfo>* debug_info) {
+bool JITDebugReader::ReadProcess(Process& process, std::vector<JITDebugInfo>* debug_info) {
if (process.died || (!process.initialized && !InitializeProcess(process))) {
- return;
+ return true;
}
// 1. Read descriptors.
Descriptor jit_descriptor;
Descriptor dex_descriptor;
if (!ReadDescriptors(process, &jit_descriptor, &dex_descriptor)) {
- return;
+ return true;
}
// 2. Return if descriptors are not changed.
if (jit_descriptor.action_seqlock == process.last_jit_descriptor.action_seqlock &&
dex_descriptor.action_seqlock == process.last_dex_descriptor.action_seqlock) {
- return;
+ return true;
}
// 3. Read new symfiles.
- auto check_descriptor = [&](Descriptor& descriptor, bool is_jit) {
- Descriptor tmp_jit_descriptor;
- Descriptor tmp_dex_descriptor;
- if (!ReadDescriptors(process, &tmp_jit_descriptor, &tmp_dex_descriptor)) {
+ return ReadDebugInfo(process, jit_descriptor, debug_info) &&
+ ReadDebugInfo(process, dex_descriptor, debug_info);
+}
+
+bool JITDebugReader::ReadDebugInfo(Process& process, Descriptor& new_descriptor,
+ std::vector<JITDebugInfo>* debug_info) {
+ DescriptorType type = new_descriptor.type;
+ Descriptor* old_descriptor =
+ (type == DescriptorType::kJIT) ? &process.last_jit_descriptor : &process.last_dex_descriptor;
+
+ bool has_update = new_descriptor.action_seqlock != old_descriptor->action_seqlock &&
+ (new_descriptor.action_seqlock & 1) == 0;
+ LOG(DEBUG) << (type == DescriptorType::kJIT ? "JIT" : "Dex") << " symfiles of pid " << process.pid
+ << ": old seqlock " << old_descriptor->action_seqlock << ", new seqlock "
+ << new_descriptor.action_seqlock;
+ if (!has_update) {
+ return true;
+ }
+ std::vector<CodeEntry> new_entries;
+ // Adding or removing one code entry will make two increments of action_seqlock. So we should
+ // not read more than (seqlock_diff / 2) new entries.
+ uint32_t read_entry_limit = (new_descriptor.action_seqlock - old_descriptor->action_seqlock) / 2;
+ if (!ReadNewCodeEntries(process, new_descriptor, old_descriptor->action_timestamp,
+ read_entry_limit, &new_entries)) {
+ return true;
+ }
+ // If the descriptor was changed while we were reading new entries, skip reading debug info this
+ // time.
+ if (IsDescriptorChanged(process, new_descriptor)) {
+ return true;
+ }
+ LOG(DEBUG) << (type == DescriptorType::kJIT ? "JIT" : "Dex") << " symfiles of pid " << process.pid
+ << ": read " << new_entries.size() << " new entries";
+
+ if (!new_entries.empty()) {
+ if (type == DescriptorType::kJIT) {
+ if (!ReadJITCodeDebugInfo(process, new_entries, debug_info)) {
return false;
}
- if (is_jit) {
- return descriptor.action_seqlock == tmp_jit_descriptor.action_seqlock;
- }
- return descriptor.action_seqlock == tmp_dex_descriptor.action_seqlock;
- };
-
- auto read_debug_info = [&](Descriptor& new_descriptor, Descriptor& old_descriptor, bool is_jit) {
- bool has_update = new_descriptor.action_seqlock != old_descriptor.action_seqlock &&
- (new_descriptor.action_seqlock & 1) == 0;
- LOG(DEBUG) << (is_jit ? "JIT" : "Dex") << " symfiles of pid " << process.pid
- << ": old seqlock " << old_descriptor.action_seqlock
- << ", new seqlock " << new_descriptor.action_seqlock;
- if (!has_update) {
- return false;
- }
- std::vector<CodeEntry> new_entries;
- // Adding or removing one code entry will make two increments of action_seqlock. So we should
- // not read more than (seqlock_diff / 2) new entries.
- uint32_t read_entry_limit = (new_descriptor.action_seqlock - old_descriptor.action_seqlock) / 2;
- if (!ReadNewCodeEntries(process, new_descriptor, old_descriptor.action_timestamp,
- read_entry_limit, &new_entries)) {
- return false;
- }
- // Check if the descriptor was changed while we were reading new entries.
- if (!check_descriptor(new_descriptor, is_jit)) {
- return false;
- }
- LOG(DEBUG) << (is_jit ? "JIT" : "Dex") << " symfiles of pid " << process.pid
- << ": read " << new_entries.size() << " new entries";
- if (new_entries.empty()) {
- return true;
- }
- if (is_jit) {
- ReadJITCodeDebugInfo(process, new_entries, debug_info);
} else {
ReadDexFileDebugInfo(process, new_entries, debug_info);
}
+ }
+ *old_descriptor = new_descriptor;
+ return true;
+}
+
+bool JITDebugReader::IsDescriptorChanged(Process& process, Descriptor& prev_descriptor) {
+ Descriptor tmp_jit_descriptor;
+ Descriptor tmp_dex_descriptor;
+ if (!ReadDescriptors(process, &tmp_jit_descriptor, &tmp_dex_descriptor)) {
return true;
- };
- if (read_debug_info(jit_descriptor, process.last_jit_descriptor, true)) {
- process.last_jit_descriptor = jit_descriptor;
}
- if (read_debug_info(dex_descriptor, process.last_dex_descriptor, false)) {
- process.last_dex_descriptor = dex_descriptor;
+ if (prev_descriptor.type == DescriptorType::kJIT) {
+ return prev_descriptor.action_seqlock != tmp_jit_descriptor.action_seqlock;
}
+ return prev_descriptor.action_seqlock != tmp_dex_descriptor.action_seqlock;
}
bool JITDebugReader::InitializeProcess(Process& process) {
@@ -411,6 +468,13 @@ bool JITDebugReader::InitializeProcess(Process& process) {
process.descriptors_size = location->size;
process.jit_descriptor_offset = location->jit_descriptor_offset;
process.dex_descriptor_offset = location->dex_descriptor_offset;
+
+ for (auto& map : thread_mmaps) {
+ if (StartsWith(map.name, kJITZygoteCacheMmapPrefix)) {
+ process.jit_zygote_cache_ranges_.emplace_back(map.start_addr, map.start_addr + map.len);
+ }
+ }
+
process.initialized = true;
return true;
}
@@ -424,15 +488,14 @@ const JITDebugReader::DescriptorsLocation* JITDebugReader::GetDescriptorsLocatio
DescriptorsLocation& location = descriptors_location_cache_[art_lib_path];
// Read libart.so to find the addresses of __jit_debug_descriptor and __dex_debug_descriptor.
- uint64_t min_vaddr_in_file;
- uint64_t file_offset;
- ElfStatus status = ReadMinExecutableVirtualAddressFromElfFile(art_lib_path, BuildId(),
- &min_vaddr_in_file,
- &file_offset);
- if (status != ElfStatus::NO_ERROR) {
- LOG(ERROR) << "ReadMinExecutableVirtualAddress failed, status = " << status;
+ ElfStatus status;
+ auto elf = ElfFile::Open(art_lib_path, &status);
+ if (!elf) {
+ LOG(ERROR) << "failed to read min_exec_vaddr from " << art_lib_path << ": " << status;
return nullptr;
}
+ 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;
@@ -448,15 +511,14 @@ const JITDebugReader::DescriptorsLocation* JITDebugReader::GetDescriptorsLocatio
dex_addr = symbol.vaddr - aligned_segment_vaddr;
}
};
- if (ParseDynamicSymbolsFromElfFile(art_lib_path, callback) != ElfStatus::NO_ERROR) {
- return nullptr;
- }
+ elf->ParseDynamicSymbols(callback);
if (jit_addr == 0u || dex_addr == 0u) {
return nullptr;
}
location.relative_addr = std::min(jit_addr, dex_addr);
location.size = std::max(jit_addr, dex_addr) +
- (is_64bit ? sizeof(JITDescriptor64) : sizeof(JITDescriptor32)) - location.relative_addr;
+ (is_64bit ? sizeof(JITDescriptor64) : sizeof(JITDescriptor32)) -
+ location.relative_addr;
if (location.size >= 4096u) {
PLOG(WARNING) << "The descriptors_size is unexpected large: " << location.size;
}
@@ -478,8 +540,9 @@ bool JITDebugReader::ReadRemoteMem(Process& process, uint64_t remote_addr, uint6
remote_iov.iov_len = size;
ssize_t result = process_vm_readv(process.pid, &local_iov, 1, &remote_iov, 1, 0);
if (static_cast<size_t>(result) != size) {
- PLOG(DEBUG) << "ReadRemoteMem(" << " pid " << process.pid << ", addr " << std::hex
- << remote_addr << ", size " << size << ") failed";
+ PLOG(DEBUG) << "ReadRemoteMem("
+ << " pid " << process.pid << ", addr " << std::hex << remote_addr << ", size "
+ << size << ") failed";
process.died = true;
return false;
}
@@ -492,10 +555,15 @@ bool JITDebugReader::ReadDescriptors(Process& process, Descriptor* jit_descripto
descriptors_buf_.data())) {
return false;
}
- return LoadDescriptor(process.is_64bit, &descriptors_buf_[process.jit_descriptor_offset],
- jit_descriptor) &&
- LoadDescriptor(process.is_64bit, &descriptors_buf_[process.dex_descriptor_offset],
- dex_descriptor);
+ if (!LoadDescriptor(process.is_64bit, &descriptors_buf_[process.jit_descriptor_offset],
+ jit_descriptor) ||
+ !LoadDescriptor(process.is_64bit, &descriptors_buf_[process.dex_descriptor_offset],
+ dex_descriptor)) {
+ return false;
+ }
+ jit_descriptor->type = DescriptorType::kJIT;
+ dex_descriptor->type = DescriptorType::kDEX;
+ return true;
}
bool JITDebugReader::LoadDescriptor(bool is_64bit, const char* data, Descriptor* descriptor) {
@@ -527,19 +595,19 @@ bool JITDebugReader::ReadNewCodeEntries(Process& process, const Descriptor& desc
std::vector<CodeEntry>* new_code_entries) {
if (descriptor.version == 1) {
if (process.is_64bit) {
- return ReadNewCodeEntriesImpl<JITCodeEntry64>(
- process, descriptor, last_action_timestamp, read_entry_limit, new_code_entries);
+ return ReadNewCodeEntriesImpl<JITCodeEntry64>(process, descriptor, last_action_timestamp,
+ read_entry_limit, new_code_entries);
}
- return ReadNewCodeEntriesImpl<JITCodeEntry32>(
- process, descriptor, last_action_timestamp, read_entry_limit, new_code_entries);
+ return ReadNewCodeEntriesImpl<JITCodeEntry32>(process, descriptor, last_action_timestamp,
+ read_entry_limit, new_code_entries);
}
if (descriptor.version == 2) {
if (process.is_64bit) {
- return ReadNewCodeEntriesImplV2<JITCodeEntry64V2>(
- process, descriptor, last_action_timestamp, read_entry_limit, new_code_entries);
+ return ReadNewCodeEntriesImpl<JITCodeEntry64V2>(process, descriptor, last_action_timestamp,
+ read_entry_limit, new_code_entries);
}
- return ReadNewCodeEntriesImplV2<JITCodeEntry32V2>(
- process, descriptor, last_action_timestamp, read_entry_limit, new_code_entries);
+ return ReadNewCodeEntriesImpl<JITCodeEntry32V2>(process, descriptor, last_action_timestamp,
+ read_entry_limit, new_code_entries);
}
return false;
}
@@ -552,7 +620,6 @@ bool JITDebugReader::ReadNewCodeEntriesImpl(Process& process, const Descriptor&
uint64_t current_entry_addr = descriptor.first_entry_addr;
uint64_t prev_entry_addr = 0u;
std::unordered_set<uint64_t> entry_addr_set;
-
for (size_t i = 0u; i < read_entry_limit && current_entry_addr != 0u; ++i) {
if (entry_addr_set.find(current_entry_addr) != entry_addr_set.end()) {
// We enter a loop, which means a broken linked list.
@@ -586,50 +653,11 @@ bool JITDebugReader::ReadNewCodeEntriesImpl(Process& process, const Descriptor&
return true;
}
-// Temporary work around for patch "JIT mini-debug-info: Append packed entries towards end.", which
-// adds new entries at the end of the list and forces simpleperf to read the whole list.
-template <typename CodeEntryT>
-bool JITDebugReader::ReadNewCodeEntriesImplV2(Process& process, const Descriptor& descriptor,
- uint64_t last_action_timestamp,
- uint32_t /* read_entry_limit */,
- std::vector<CodeEntry>* new_code_entries) {
- uint64_t current_entry_addr = descriptor.first_entry_addr;
- uint64_t prev_entry_addr = 0u;
- std::unordered_set<uint64_t> entry_addr_set;
- const size_t READ_ENTRY_LIMIT = 10000; // to avoid endless loop
-
- for (size_t i = 0u; i < READ_ENTRY_LIMIT && current_entry_addr != 0u; ++i) {
- if (entry_addr_set.find(current_entry_addr) != entry_addr_set.end()) {
- // We enter a loop, which means a broken linked list.
- return false;
- }
- CodeEntryT entry;
- if (!ReadRemoteMem(process, current_entry_addr, sizeof(entry), &entry)) {
- return false;
- }
- if (entry.prev_addr != prev_entry_addr || !entry.Valid()) {
- // A broken linked list
- return false;
- }
- if (entry.symfile_size > 0 && entry.register_timestamp > last_action_timestamp) {
- CodeEntry code_entry;
- code_entry.addr = current_entry_addr;
- code_entry.symfile_addr = entry.symfile_addr;
- code_entry.symfile_size = entry.symfile_size;
- code_entry.timestamp = entry.register_timestamp;
- new_code_entries->push_back(code_entry);
- }
- entry_addr_set.insert(current_entry_addr);
- prev_entry_addr = current_entry_addr;
- current_entry_addr = entry.next_addr;
- }
- return true;
-}
-
-void JITDebugReader::ReadJITCodeDebugInfo(Process& process,
+bool JITDebugReader::ReadJITCodeDebugInfo(Process& process,
const std::vector<CodeEntry>& jit_entries,
std::vector<JITDebugInfo>* debug_info) {
std::vector<char> data;
+
for (auto& jit_entry : jit_entries) {
if (jit_entry.symfile_size > MAX_JIT_SYMFILE_SIZE) {
continue;
@@ -643,25 +671,67 @@ void JITDebugReader::ReadJITCodeDebugInfo(Process& process,
if (!IsValidElfFileMagic(data.data(), jit_entry.symfile_size)) {
continue;
}
- std::unique_ptr<TemporaryFile> tmp_file = ScopedTempFiles::CreateTempFile(!keep_symfiles_);
- if (tmp_file == nullptr || !android::base::WriteFully(tmp_file->fd, data.data(),
- jit_entry.symfile_size)) {
- continue;
+ TempSymFile* symfile = GetTempSymFile(process, jit_entry);
+ if (symfile == nullptr) {
+ return false;
}
- if (keep_symfiles_) {
- tmp_file->DoNotRemove();
+ uint64_t file_offset = symfile->GetOffset();
+ if (!symfile->WriteEntry(data.data(), jit_entry.symfile_size)) {
+ return false;
}
+
auto callback = [&](const ElfFileSymbol& symbol) {
if (symbol.len == 0) { // Some arm labels can have zero length.
return;
}
- LOG(VERBOSE) << "JITSymbol " << symbol.name << " at [" << std::hex << symbol.vaddr
- << " - " << (symbol.vaddr + symbol.len) << " with size " << symbol.len;
+ // Pass out the location of the symfile for unwinding and symbolization.
+ std::string location_in_file =
+ StringPrintf(":%" PRIu64 "-%" PRIu64, file_offset, file_offset + jit_entry.symfile_size);
debug_info->emplace_back(process.pid, jit_entry.timestamp, symbol.vaddr, symbol.len,
- tmp_file->path);
+ symfile->GetPath() + location_in_file, file_offset);
+
+ LOG(VERBOSE) << "JITSymbol " << symbol.name << " at [" << std::hex << symbol.vaddr << " - "
+ << (symbol.vaddr + symbol.len) << " with size " << symbol.len << " in "
+ << symfile->GetPath() << location_in_file;
};
- ParseSymbolsFromElfFileInMemory(data.data(), jit_entry.symfile_size, callback);
+ ElfStatus status;
+ auto elf = ElfFile::Open(data.data(), jit_entry.symfile_size, &status);
+ if (elf) {
+ elf->ParseSymbols(callback);
+ }
+ }
+
+ if (app_symfile_) {
+ app_symfile_->Flush();
}
+ if (zygote_symfile_) {
+ zygote_symfile_->Flush();
+ }
+ return true;
+}
+
+TempSymFile* JITDebugReader::GetTempSymFile(Process& process, const CodeEntry& jit_entry) {
+ bool is_zygote = false;
+ for (const auto& range : process.jit_zygote_cache_ranges_) {
+ if (jit_entry.symfile_addr >= range.first && jit_entry.symfile_addr < range.second) {
+ is_zygote = true;
+ break;
+ }
+ }
+ if (is_zygote) {
+ if (!zygote_symfile_) {
+ std::string path = symfile_prefix_ + "_" + kJITZygoteCacheFile;
+ zygote_symfile_ =
+ TempSymFile::Create(std::move(path), symfile_option_ == SymFileOption::kDropSymFiles);
+ }
+ return zygote_symfile_.get();
+ }
+ if (!app_symfile_) {
+ std::string path = symfile_prefix_ + "_" + kJITAppCacheFile;
+ app_symfile_ =
+ TempSymFile::Create(std::move(path), symfile_option_ == SymFileOption::kDropSymFiles);
+ }
+ return app_symfile_.get();
}
void JITDebugReader::ReadDexFileDebugInfo(Process& process,
@@ -672,12 +742,10 @@ void JITDebugReader::ReadDexFileDebugInfo(Process& process,
process.died = true;
return;
}
- auto comp = [](const ThreadMmap& map, uint64_t addr) {
- return map.start_addr <= addr;
- };
+ auto comp = [](const ThreadMmap& map, uint64_t addr) { return map.start_addr <= addr; };
for (auto& dex_entry : dex_entries) {
- auto it = std::lower_bound(thread_mmaps.begin(), thread_mmaps.end(),
- dex_entry.symfile_addr, comp);
+ auto it =
+ std::lower_bound(thread_mmaps.begin(), thread_mmaps.end(), dex_entry.symfile_addr, comp);
if (it == thread_mmaps.begin()) {
continue;
}
@@ -703,16 +771,16 @@ void JITDebugReader::ReadDexFileDebugInfo(Process& process,
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);
- 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;
+ 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) {
+ bool sync_kernel_records) {
if (!debug_info.empty()) {
- if (sync_with_records_) {
+ if (sync_option_ == SyncOption::kSyncWithRecords) {
for (auto& info : debug_info) {
debug_info_q_.push(std::move(info));
}
diff --git a/simpleperf/JITDebugReader.h b/simpleperf/JITDebugReader.h
index 1a3b5e69..72f3790b 100644
--- a/simpleperf/JITDebugReader.h
+++ b/simpleperf/JITDebugReader.h
@@ -30,12 +30,15 @@
#include <android-base/file.h>
#include <android-base/logging.h>
-#include "environment.h"
#include "IOEventLoop.h"
+#include "environment.h"
#include "record.h"
namespace simpleperf {
+inline constexpr const char* kJITAppCacheFile = "jit_app_cache";
+inline constexpr const char* kJITZygoteCacheFile = "jit_zygote_cache";
+
// JITDebugInfo represents the debug info of a JITed Java method or a dex file.
struct JITDebugInfo {
enum {
@@ -54,6 +57,7 @@ struct JITDebugInfo {
// For JITed code, it is the path of a temporary ELF file storing its debug info.
// For dex file, it is the path of the file containing the dex file.
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
@@ -61,35 +65,53 @@ struct JITDebugInfo {
std::shared_ptr<ThreadMmap> extracted_dex_file_map;
JITDebugInfo(pid_t pid, uint64_t timestamp, uint64_t jit_code_addr, uint64_t jit_code_len,
- const std::string& file_path)
- : type(JIT_DEBUG_JIT_CODE), pid(pid), timestamp(timestamp), jit_code_addr(jit_code_addr),
- jit_code_len(jit_code_len), file_path(file_path) {}
+ const std::string& file_path, uint64_t file_offset)
+ : type(JIT_DEBUG_JIT_CODE),
+ pid(pid),
+ timestamp(timestamp),
+ jit_code_addr(jit_code_addr),
+ jit_code_len(jit_code_len),
+ file_path(file_path),
+ 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)
- : type(JIT_DEBUG_DEX_FILE), pid(pid), timestamp(timestamp), dex_file_offset(dex_file_offset),
- file_path(file_path), extracted_dex_file_map(extracted_dex_file_map) {}
-
- bool operator>(const JITDebugInfo& other) const {
- return timestamp > other.timestamp;
- }
+ : 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) {}
+
+ bool operator>(const JITDebugInfo& other) const { return timestamp > other.timestamp; }
};
+class TempSymFile;
+
// JITDebugReader reads debug info of JIT code and dex files of processes using ART. The
// corresponding debug interface in ART is at art/runtime/jit/debugger_interface.cc.
class JITDebugReader {
public:
- // keep_symfiles: whether to keep dumped JIT debug info files after recording. Usually they
- // are only kept for debug unwinding.
- // sync_with_records: If true, sync debug info with records based on monotonic timestamp.
- // Otherwise, save debug info whenever they are added.
- JITDebugReader(bool keep_symfiles, bool sync_with_records)
- : keep_symfiles_(keep_symfiles), sync_with_records_(sync_with_records) {}
-
- bool SyncWithRecords() const {
- return sync_with_records_;
- }
+ enum class SymFileOption {
+ kDropSymFiles, // JIT symfiles are dropped after recording.
+ kKeepSymFiles, // JIT symfiles are kept after recording, usually for debug unwinding.
+ };
+
+ enum class SyncOption {
+ kNoSync, // Don't sync debug info with records.
+ kSyncWithRecords, // Sync debug info with records based on monotonic timestamp.
+ };
+
+ // symfile_prefix: JITDebugReader creates temporary file to store symfiles for JIT code. Add this
+ // prefix to avoid conflicts.
+ JITDebugReader(const std::string& symfile_prefix, SymFileOption symfile_option,
+ SyncOption sync_option);
+
+ ~JITDebugReader();
+
+ bool SyncWithRecords() const { return sync_option_ == SyncOption::kSyncWithRecords; }
typedef std::function<bool(const std::vector<JITDebugInfo>&, bool)> debug_info_callback_t;
bool RegisterDebugInfoCallback(IOEventLoop* loop, const debug_info_callback_t& callback);
@@ -107,12 +129,22 @@ class JITDebugReader {
// Flush all debug info registered before timestamp.
bool FlushDebugInfo(uint64_t timestamp);
+ static bool IsPathInJITSymFile(const std::string& path) {
+ return path.find(std::string("_") + kJITAppCacheFile + ":") != path.npos ||
+ 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
+ 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;
};
@@ -142,6 +174,9 @@ class JITDebugReader {
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_;
};
// The location of descriptors in libart.so.
@@ -152,10 +187,12 @@ class JITDebugReader {
uint64_t dex_descriptor_offset = 0;
};
- void ReadProcess(Process& process, std::vector<JITDebugInfo>* debug_info);
+ bool ReadProcess(Process& process, std::vector<JITDebugInfo>* debug_info);
+ bool ReadDebugInfo(Process& process, Descriptor& new_descriptor,
+ std::vector<JITDebugInfo>* debug_info);
+ bool IsDescriptorChanged(Process& process, Descriptor& old_descriptor);
bool InitializeProcess(Process& process);
- const DescriptorsLocation* GetDescriptorsLocation(const std::string& art_lib_path,
- bool is_64bit);
+ const DescriptorsLocation* GetDescriptorsLocation(const std::string& art_lib_path, bool is_64bit);
bool ReadRemoteMem(Process& process, uint64_t remote_addr, uint64_t size, void* data);
bool ReadDescriptors(Process& process, Descriptor* jit_descriptor, Descriptor* dex_descriptor);
bool LoadDescriptor(bool is_64bit, const char* data, Descriptor* descriptor);
@@ -169,19 +206,17 @@ class JITDebugReader {
bool ReadNewCodeEntriesImpl(Process& process, const Descriptor& descriptor,
uint64_t last_action_timestamp, uint32_t read_entry_limit,
std::vector<CodeEntry>* new_code_entries);
- template <typename CodeEntryT>
- bool ReadNewCodeEntriesImplV2(Process& process, const Descriptor& descriptor,
- uint64_t last_action_timestamp, uint32_t read_entry_limit,
- std::vector<CodeEntry>* new_code_entries);
- void ReadJITCodeDebugInfo(Process& process, const std::vector<CodeEntry>& jit_entries,
- std::vector<JITDebugInfo>* debug_info);
+ 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);
+ std::vector<JITDebugInfo>* debug_info);
bool AddDebugInfo(const std::vector<JITDebugInfo>& debug_info, bool sync_kernel_records);
- bool keep_symfiles_ = false;
- bool sync_with_records_ = false;
+ const std::string symfile_prefix_;
+ SymFileOption symfile_option_;
+ SyncOption sync_option_;
IOEventRef read_event_ = nullptr;
debug_info_callback_t debug_info_callback_;
@@ -195,8 +230,12 @@ class JITDebugReader {
std::priority_queue<JITDebugInfo, std::vector<JITDebugInfo>, std::greater<JITDebugInfo>>
debug_info_q_;
+
+ // temporary files used to store jit symfiles created by the app process and the zygote process.
+ std::unique_ptr<TempSymFile> app_symfile_;
+ std::unique_ptr<TempSymFile> zygote_symfile_;
};
-} //namespace simpleperf
+} // namespace simpleperf
-#endif // SIMPLE_PERF_JIT_DEBUG_READER_H_
+#endif // SIMPLE_PERF_JIT_DEBUG_READER_H_
diff --git a/simpleperf/MapRecordReader.cpp b/simpleperf/MapRecordReader.cpp
new file mode 100644
index 00000000..e141e033
--- /dev/null
+++ b/simpleperf/MapRecordReader.cpp
@@ -0,0 +1,186 @@
+/*
+ * 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.
+ */
+
+#include "MapRecordReader.h"
+
+#include <stdint.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <vector>
+
+#include <android-base/strings.h>
+
+#include "environment.h"
+
+namespace simpleperf {
+
+bool MapRecordReader::ReadKernelMaps() {
+ KernelMmap kernel_mmap;
+ std::vector<KernelMmap> module_mmaps;
+ GetKernelAndModuleMmaps(&kernel_mmap, &module_mmaps);
+
+ MmapRecord mmap_record(attr_, true, UINT_MAX, 0, kernel_mmap.start_addr, kernel_mmap.len, 0,
+ kernel_mmap.filepath, event_id_);
+ if (!callback_(&mmap_record)) {
+ return false;
+ }
+ for (const auto& module_mmap : module_mmaps) {
+ MmapRecord mmap_record(attr_, true, UINT_MAX, 0, module_mmap.start_addr, module_mmap.len, 0,
+ module_mmap.filepath, event_id_);
+ if (!callback_(&mmap_record)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool MapRecordReader::ReadProcessMaps(pid_t pid, uint64_t timestamp) {
+ std::vector<pid_t> tids = GetThreadsInProcess(pid);
+ return ReadProcessMaps(pid, std::unordered_set<pid_t>(tids.begin(), tids.end()), timestamp);
+}
+
+bool MapRecordReader::ReadProcessMaps(pid_t pid, const std::unordered_set<pid_t>& tids,
+ uint64_t timestamp) {
+ // Dump mmap records.
+ std::vector<ThreadMmap> thread_mmaps;
+ if (!GetThreadMmapsInProcess(pid, &thread_mmaps)) {
+ // The process may exit before we get its info.
+ return true;
+ }
+ for (const auto& map : thread_mmaps) {
+ if (!(map.prot & PROT_EXEC) && !keep_non_executable_maps_) {
+ continue;
+ }
+ Mmap2Record record(attr_, false, pid, pid, map.start_addr, map.len, map.pgoff, map.prot,
+ map.name, event_id_, timestamp);
+ if (!callback_(&record)) {
+ return false;
+ }
+ }
+ // Dump process name.
+ std::string process_name = GetCompleteProcessName(pid);
+ if (!process_name.empty()) {
+ CommRecord record(attr_, pid, pid, process_name, event_id_, timestamp);
+ if (!callback_(&record)) {
+ return false;
+ }
+ }
+ // Dump thread info.
+ for (const auto& tid : tids) {
+ std::string name;
+ if (tid != pid && GetThreadName(tid, &name)) {
+ // If a thread name matches the suffix of its process name, probably the thread name
+ // is stripped by TASK_COMM_LEN.
+ if (android::base::EndsWith(process_name, name)) {
+ name = process_name;
+ }
+ CommRecord comm_record(attr_, pid, tid, name, event_id_, timestamp);
+ if (!callback_(&comm_record)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+MapRecordThread::MapRecordThread(const MapRecordReader& map_record_reader)
+ : map_record_reader_(map_record_reader), fp_(nullptr, fclose) {
+ map_record_reader_.SetCallback([this](Record* r) { return WriteRecordToFile(r); });
+ tmpfile_ = ScopedTempFiles::CreateTempFile();
+ fp_.reset(fdopen(tmpfile_->release(), "r+"));
+ thread_ = std::thread([this]() { thread_result_ = RunThread(); });
+}
+
+MapRecordThread::~MapRecordThread() {
+ if (thread_.joinable()) {
+ early_stop_ = true;
+ thread_.join();
+ }
+}
+
+bool MapRecordThread::RunThread() {
+ if (!fp_) {
+ return false;
+ }
+ if (!map_record_reader_.ReadKernelMaps()) {
+ return false;
+ }
+ for (auto pid : GetAllProcesses()) {
+ if (early_stop_) {
+ return false;
+ }
+ if (!map_record_reader_.ReadProcessMaps(pid, 0)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool MapRecordThread::WriteRecordToFile(Record* record) {
+ if (fwrite(record->Binary(), record->size(), 1, fp_.get()) != 1) {
+ PLOG(ERROR) << "failed to write map records to file";
+ return false;
+ }
+ return true;
+}
+
+bool MapRecordThread::Join() {
+ thread_.join();
+ if (!thread_result_) {
+ LOG(ERROR) << "map record thread failed";
+ }
+ return thread_result_;
+}
+
+bool MapRecordThread::ReadMapRecords(const std::function<bool(Record*)>& callback) {
+ off_t offset = ftello(fp_.get());
+ if (offset == -1) {
+ PLOG(ERROR) << "ftello() failed";
+ return false;
+ }
+ uint64_t file_size = static_cast<uint64_t>(offset);
+ if (fseek(fp_.get(), 0, SEEK_SET) != 0) {
+ PLOG(ERROR) << "fseek() failed";
+ return false;
+ }
+ uint64_t nread = 0;
+ std::vector<char> buffer(1024);
+ while (nread < file_size) {
+ if (fread(buffer.data(), Record::header_size(), 1, fp_.get()) != 1) {
+ PLOG(ERROR) << "fread() failed";
+ return false;
+ }
+ RecordHeader header(buffer.data());
+ if (buffer.size() < header.size) {
+ buffer.resize(header.size);
+ }
+ if (fread(buffer.data() + Record::header_size(), header.size - Record::header_size(), 1,
+ fp_.get()) != 1) {
+ PLOG(ERROR) << "fread() failed";
+ return false;
+ }
+ auto r = ReadRecordFromBuffer(map_record_reader_.Attr(), header.type, buffer.data());
+ CHECK(r);
+ if (!callback(r.get())) {
+ return false;
+ }
+ nread += header.size;
+ }
+ return true;
+}
+
+} // namespace simpleperf
diff --git a/simpleperf/MapRecordReader.h b/simpleperf/MapRecordReader.h
new file mode 100644
index 00000000..84269d0f
--- /dev/null
+++ b/simpleperf/MapRecordReader.h
@@ -0,0 +1,77 @@
+/*
+ * 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 <inttypes.h>
+#include <stdio.h>
+
+#include <atomic>
+#include <functional>
+#include <memory>
+#include <thread>
+#include <unordered_set>
+#include <vector>
+
+#include "event_attr.h"
+#include "record.h"
+
+namespace simpleperf {
+
+class MapRecordReader {
+ public:
+ MapRecordReader(const perf_event_attr& attr, uint64_t event_id, bool keep_non_executable_maps)
+ : attr_(attr), event_id_(event_id), keep_non_executable_maps_(keep_non_executable_maps) {}
+
+ const perf_event_attr& Attr() { return attr_; }
+ void SetCallback(const std::function<bool(Record*)>& callback) { callback_ = callback; }
+ bool ReadKernelMaps();
+ // Read process maps and all thread names in a process.
+ bool ReadProcessMaps(pid_t pid, uint64_t timestamp);
+ // Read process maps and selected thread names in a process.
+ bool ReadProcessMaps(pid_t pid, const std::unordered_set<pid_t>& tids, uint64_t timestamp);
+
+ private:
+ const perf_event_attr& attr_;
+ const uint64_t event_id_;
+ const bool keep_non_executable_maps_;
+ std::function<bool(Record*)> callback_;
+};
+
+// Create a thread for reading maps while recording. The maps are stored in a temporary file, and
+// read back after recording.
+class MapRecordThread {
+ public:
+ MapRecordThread(const MapRecordReader& map_record_reader);
+ ~MapRecordThread();
+
+ bool Join();
+ bool ReadMapRecords(const std::function<bool(Record*)>& callback);
+
+ private:
+ // functions running in the map record thread
+ bool RunThread();
+ bool WriteRecordToFile(Record* record);
+
+ MapRecordReader map_record_reader_;
+ std::unique_ptr<TemporaryFile> tmpfile_;
+ std::unique_ptr<FILE, decltype(&fclose)> fp_;
+ std::thread thread_;
+ std::atomic<bool> early_stop_ = false;
+ std::atomic<bool> thread_result_ = false;
+};
+
+} // namespace simpleperf
diff --git a/simpleperf/MapRecordReader_test.cpp b/simpleperf/MapRecordReader_test.cpp
new file mode 100644
index 00000000..669b31fb
--- /dev/null
+++ b/simpleperf/MapRecordReader_test.cpp
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+#include "MapRecordReader.h"
+
+#include <optional>
+
+#include <gtest/gtest.h>
+
+#include "environment.h"
+#include "event_attr.h"
+#include "event_type.h"
+
+using namespace simpleperf;
+
+class MapRecordReaderTest : public ::testing::Test {
+ protected:
+ bool CreateMapRecordReader() {
+ const EventType* event_type = FindEventTypeByName("cpu-clock");
+ if (event_type == nullptr) {
+ return false;
+ }
+ attr_ = CreateDefaultPerfEventAttr(*event_type);
+ reader_.emplace(attr_, 0, true);
+ reader_->SetCallback([this](Record* r) { return CountRecord(r); });
+ return true;
+ }
+
+ bool CountRecord(Record* r) {
+ if (r->type() == PERF_RECORD_MMAP || r->type() == PERF_RECORD_MMAP2) {
+ map_record_count_++;
+ } else if (r->type() == PERF_RECORD_COMM) {
+ comm_record_count_++;
+ }
+ return true;
+ }
+
+ perf_event_attr attr_;
+ std::optional<MapRecordReader> reader_;
+ size_t map_record_count_ = 0;
+ size_t comm_record_count_ = 0;
+};
+
+TEST_F(MapRecordReaderTest, ReadKernelMaps) {
+ ASSERT_TRUE(CreateMapRecordReader());
+ ASSERT_TRUE(reader_->ReadKernelMaps());
+ ASSERT_GT(map_record_count_, 0);
+}
+
+TEST_F(MapRecordReaderTest, ReadProcessMaps) {
+ ASSERT_TRUE(CreateMapRecordReader());
+ ASSERT_TRUE(reader_->ReadProcessMaps(getpid(), 0));
+ ASSERT_GT(map_record_count_, 0);
+ ASSERT_GT(comm_record_count_, 0);
+}
+
+TEST_F(MapRecordReaderTest, MapRecordThread) {
+#ifdef __ANDROID__
+ std::string tmpdir = "/data/local/tmp";
+#else
+ std::string tmpdir = "/tmp";
+#endif
+ auto scoped_temp_files = ScopedTempFiles::Create(tmpdir);
+ ASSERT_TRUE(scoped_temp_files);
+ ASSERT_TRUE(CreateMapRecordReader());
+ MapRecordThread thread(*reader_);
+ ASSERT_TRUE(thread.Join());
+ ASSERT_TRUE(thread.ReadMapRecords([this](Record* r) { return CountRecord(r); }));
+ ASSERT_GT(map_record_count_, 0);
+ ASSERT_GT(comm_record_count_, 0);
+}
diff --git a/simpleperf/OWNERS b/simpleperf/OWNERS
index 860538d4..32f8fc65 100644
--- a/simpleperf/OWNERS
+++ b/simpleperf/OWNERS
@@ -1,2 +1,3 @@
enh@google.com
yabinc@google.com
+include platform/prebuilts/clang/host/linux-x86:/OWNERS
diff --git a/simpleperf/OfflineUnwinder.cpp b/simpleperf/OfflineUnwinder.cpp
index e166d846..1593fb64 100644
--- a/simpleperf/OfflineUnwinder.cpp
+++ b/simpleperf/OfflineUnwinder.cpp
@@ -16,17 +16,18 @@
#include "OfflineUnwinder.h"
+#include <inttypes.h>
#include <sys/mman.h>
#include <unordered_map>
#include <android-base/logging.h>
+#include <android-base/parseint.h>
#include <unwindstack/MachineArm.h>
#include <unwindstack/MachineArm64.h>
#include <unwindstack/MachineX86.h>
#include <unwindstack/MachineX86_64.h>
#include <unwindstack/Maps.h>
-#include <unwindstack/Regs.h>
#include <unwindstack/RegsArm.h>
#include <unwindstack/RegsArm64.h>
#include <unwindstack/RegsX86.h>
@@ -37,27 +38,45 @@
#include <unwindstack/UserX86.h>
#include <unwindstack/UserX86_64.h>
-#include "environment.h"
+#include "JITDebugReader.h"
#include "OfflineUnwinder_impl.h"
+#include "environment.h"
#include "perf_regs.h"
#include "read_apk.h"
#include "thread_tree.h"
-static_assert(simpleperf::map_flags::PROT_JIT_SYMFILE_MAP ==
- unwindstack::MAPS_FLAGS_JIT_SYMFILE_MAP, "");
-
namespace simpleperf {
+// unwindstack only builds on linux. So simpleperf redefines flags in unwindstack, to use them on
+// darwin/windows. Use static_assert to make sure they are on the same page.
+static_assert(map_flags::PROT_JIT_SYMFILE_MAP == unwindstack::MAPS_FLAGS_JIT_SYMFILE_MAP);
+
+#define CHECK_ERROR_CODE(error_code_name) \
+ static_assert(UnwindStackErrorCode::error_code_name == unwindstack::ErrorCode::error_code_name)
+
+CHECK_ERROR_CODE(ERROR_NONE);
+CHECK_ERROR_CODE(ERROR_MEMORY_INVALID);
+CHECK_ERROR_CODE(ERROR_UNWIND_INFO);
+CHECK_ERROR_CODE(ERROR_UNSUPPORTED);
+CHECK_ERROR_CODE(ERROR_INVALID_MAP);
+CHECK_ERROR_CODE(ERROR_MAX_FRAMES_EXCEEDED);
+CHECK_ERROR_CODE(ERROR_REPEATED_FRAME);
+CHECK_ERROR_CODE(ERROR_INVALID_ELF);
+CHECK_ERROR_CODE(ERROR_THREAD_DOES_NOT_EXIST);
+CHECK_ERROR_CODE(ERROR_THREAD_TIMEOUT);
+CHECK_ERROR_CODE(ERROR_SYSTEM_CALL);
+CHECK_ERROR_CODE(ERROR_MAX);
+
// Max frames seen so far is 463, in http://b/110923759.
static constexpr size_t MAX_UNWINDING_FRAMES = 512;
-static unwindstack::Regs* GetBacktraceRegs(const RegSet& regs) {
+unwindstack::Regs* OfflineUnwinderImpl::GetBacktraceRegs(const RegSet& regs) {
switch (regs.arch) {
case ARCH_ARM: {
unwindstack::arm_user_regs arm_user_regs;
memset(&arm_user_regs, 0, sizeof(arm_user_regs));
- static_assert(
- static_cast<int>(unwindstack::ARM_REG_R0) == static_cast<int>(PERF_REG_ARM_R0), "");
+ static_assert(static_cast<int>(unwindstack::ARM_REG_R0) == static_cast<int>(PERF_REG_ARM_R0),
+ "");
static_assert(
static_cast<int>(unwindstack::ARM_REG_LAST) == static_cast<int>(PERF_REG_ARM_MAX), "");
for (size_t i = unwindstack::ARM_REG_R0; i < unwindstack::ARM_REG_LAST; ++i) {
@@ -76,7 +95,10 @@ static unwindstack::Regs* GetBacktraceRegs(const RegSet& regs) {
sizeof(uint64_t) * (PERF_REG_ARM64_LR - PERF_REG_ARM64_X0 + 1));
arm64_user_regs.sp = regs.data[PERF_REG_ARM64_SP];
arm64_user_regs.pc = regs.data[PERF_REG_ARM64_PC];
- return unwindstack::RegsArm64::Read(&arm64_user_regs);
+ auto regs =
+ static_cast<unwindstack::RegsArm64*>(unwindstack::RegsArm64::Read(&arm64_user_regs));
+ regs->SetPACMask(arm64_pac_mask_);
+ return regs;
}
case ARCH_X86_32: {
unwindstack::x86_user_regs x86_user_regs;
@@ -120,7 +142,8 @@ static unwindstack::Regs* GetBacktraceRegs(const RegSet& regs) {
}
static unwindstack::MapInfo* CreateMapInfo(const MapEntry* entry) {
- const char* name = entry->dso->GetDebugFilePath().c_str();
+ std::string name_holder;
+ const char* name = entry->dso->GetDebugFilePath().data();
uint64_t pgoff = entry->pgoff;
auto tuple = SplitUrlInApk(entry->dso->GetDebugFilePath());
if (std::get<0>(tuple)) {
@@ -128,9 +151,18 @@ static unwindstack::MapInfo* CreateMapInfo(const MapEntry* entry) {
// the previous format (apk, offset).
EmbeddedElf* elf = ApkInspector::FindElfInApkByName(std::get<1>(tuple), std::get<2>(tuple));
if (elf != nullptr) {
- name = elf->filepath().c_str();
+ name = elf->filepath().data();
pgoff += elf->entry_offset();
}
+ } else if (entry->flags & map_flags::PROT_JIT_SYMFILE_MAP) {
+ // Remove location_in_file suffix, which isn't recognized by libunwindstack.
+ const std::string& path = entry->dso->GetDebugFilePath();
+ if (JITDebugReader::IsPathInJITSymFile(path)) {
+ size_t colon_pos = path.rfind(':');
+ CHECK_NE(colon_pos, std::string::npos);
+ name_holder = path.substr(0, colon_pos);
+ name = name_holder.data();
+ }
}
return new unwindstack::MapInfo(nullptr, nullptr, entry->start_addr, entry->get_end_addr(), pgoff,
PROT_READ | entry->flags, name);
@@ -173,29 +205,39 @@ void UnwindMaps::UpdateMaps(const MapSet& map_set) {
maps_.begin());
}
- std::sort(entries_.begin(), entries_.end(), [](const auto& e1, const auto& e2) {
- return e1->start_addr < e2->start_addr;
- });
+ std::sort(entries_.begin(), entries_.end(),
+ [](const auto& e1, const auto& e2) { return e1->start_addr < e2->start_addr; });
// Use Sort() to sort maps_ and create prev_real_map links.
// prev_real_map is needed by libunwindstack to find the start of an embedded lib in an apk.
// See http://b/120981155.
Sort();
}
-class OfflineUnwinderImpl : public OfflineUnwinder {
- public:
- OfflineUnwinderImpl(bool collect_stat) : collect_stat_(collect_stat) {
- unwindstack::Elf::SetCachingEnabled(true);
+void OfflineUnwinder::CollectMetaInfo(std::unordered_map<std::string, std::string>* info_map
+ __attribute__((unused))) {
+#if defined(__aarch64__)
+ // Find pac_mask for ARMv8.3-A Pointer Authentication by below steps:
+ // 1. Create a 64 bit value with every bit set, but clear bit 55. Because linux user space uses
+ // TTBR0.
+ // 2. Use XPACLRI to clear auth code bits.
+ // 3. Flip every bit to get pac_mask, excluding bit 55.
+ // We can also use ptrace(PTRACE_GETREGSET, pid, NT_ARM_PAC_MASK). But it needs a tracee.
+ register uint64_t x30 __asm("x30") = ~(1ULL << 55);
+ // This is XPACLRI on ARMv8.3-A, and nop on prev ARMv8.3-A.
+ asm("hint 0x7" : "+r"(x30));
+ uint64_t pac_mask = ~x30 & ~(1ULL << 55);
+ if (pac_mask != 0) {
+ (*info_map)[META_KEY_ARM64_PAC_MASK] = android::base::StringPrintf("0x%" PRIx64, pac_mask);
}
+#endif
+}
- bool UnwindCallChain(const ThreadEntry& thread, const RegSet& regs, const char* stack,
- size_t stack_size, std::vector<uint64_t>* ips,
- std::vector<uint64_t>* sps) override;
-
- private:
- bool collect_stat_;
- std::unordered_map<pid_t, UnwindMaps> cached_maps_;
-};
+void OfflineUnwinderImpl::LoadMetaInfo(
+ const std::unordered_map<std::string, std::string>& info_map) {
+ if (auto it = info_map.find(META_KEY_ARM64_PAC_MASK); it != info_map.end()) {
+ CHECK(android::base::ParseUint(it->second, &arm64_pac_mask_));
+ }
+}
bool OfflineUnwinderImpl::UnwindCallChain(const ThreadEntry& thread, const RegSet& regs,
const char* stack, size_t stack_size,
@@ -267,29 +309,8 @@ bool OfflineUnwinderImpl::UnwindCallChain(const ThreadEntry& thread, const RegSe
}
if (collect_stat_) {
unwinding_result_.used_time = GetSystemClock() - start_time;
- switch (unwinder.LastErrorCode()) {
- case unwindstack::ERROR_MAX_FRAMES_EXCEEDED:
- unwinding_result_.stop_reason = UnwindingResult::EXCEED_MAX_FRAMES_LIMIT;
- break;
- case unwindstack::ERROR_MEMORY_INVALID: {
- uint64_t addr = unwinder.LastErrorAddress();
- // Because we don't have precise stack range here, just guess an addr is in stack
- // if sp - 128K <= addr <= sp.
- if (addr <= stack_addr && addr >= stack_addr - 128 * 1024) {
- unwinding_result_.stop_reason = UnwindingResult::ACCESS_STACK_FAILED;
- } else {
- unwinding_result_.stop_reason = UnwindingResult::ACCESS_MEM_FAILED;
- }
- unwinding_result_.stop_info.addr = addr;
- break;
- }
- case unwindstack::ERROR_INVALID_MAP:
- unwinding_result_.stop_reason = UnwindingResult::MAP_MISSING;
- break;
- default:
- unwinding_result_.stop_reason = UnwindingResult::UNKNOWN_REASON;
- break;
- }
+ unwinding_result_.error_code = unwinder.LastErrorCode();
+ unwinding_result_.error_addr = unwinder.LastErrorAddress();
unwinding_result_.stack_start = stack_addr;
unwinding_result_.stack_end = stack_addr + stack_size;
}
diff --git a/simpleperf/OfflineUnwinder.h b/simpleperf/OfflineUnwinder.h
index b0ff7ac5..b6445577 100644
--- a/simpleperf/OfflineUnwinder.h
+++ b/simpleperf/OfflineUnwinder.h
@@ -26,32 +26,37 @@
namespace simpleperf {
struct ThreadEntry;
+enum UnwindStackErrorCode : uint8_t {
+ ERROR_NONE, // No error.
+ ERROR_MEMORY_INVALID, // Memory read failed.
+ ERROR_UNWIND_INFO, // Unable to use unwind information to unwind.
+ ERROR_UNSUPPORTED, // Encountered unsupported feature.
+ ERROR_INVALID_MAP, // Unwind in an invalid map.
+ ERROR_MAX_FRAMES_EXCEEDED, // The number of frames exceed the total allowed.
+ ERROR_REPEATED_FRAME, // The last frame has the same pc/sp as the next.
+ ERROR_INVALID_ELF, // Unwind in an invalid elf.
+ ERROR_THREAD_DOES_NOT_EXIST, // Attempt to unwind a local thread that does
+ // not exist.
+ ERROR_THREAD_TIMEOUT, // Timeout trying to unwind a local thread.
+ ERROR_SYSTEM_CALL, // System call failed while unwinding.
+ ERROR_MAX = ERROR_SYSTEM_CALL,
+};
+
struct UnwindingResult {
// time used for unwinding, in ns.
uint64_t used_time;
- enum {
- UNKNOWN_REASON,
- EXCEED_MAX_FRAMES_LIMIT,
- ACCESS_REG_FAILED,
- ACCESS_STACK_FAILED,
- ACCESS_MEM_FAILED,
- FIND_PROC_INFO_FAILED,
- EXECUTE_DWARF_INSTRUCTION_FAILED,
- DIFFERENT_ARCH,
- MAP_MISSING,
- } stop_reason;
- union {
- // for ACCESS_REG_FAILED
- uint64_t regno;
- // for ACCESS_MEM_FAILED and ACCESS_STACK_FAILED
- uint64_t addr;
- } stop_info;
+ // unwindstack::LastErrorCode()
+ uint64_t error_code;
+ // unwindstack::LastErrorAddress()
+ uint64_t error_addr;
uint64_t stack_start;
uint64_t stack_end;
};
class OfflineUnwinder {
public:
+ static constexpr const char* META_KEY_ARM64_PAC_MASK = "arm64_pac_mask";
+
static std::unique_ptr<OfflineUnwinder> Create(bool collect_stat);
virtual ~OfflineUnwinder() {}
@@ -59,14 +64,15 @@ class OfflineUnwinder {
size_t stack_size, std::vector<uint64_t>* ips,
std::vector<uint64_t>* sps) = 0;
- const UnwindingResult& GetUnwindingResult() const {
- return unwinding_result_;
- }
+ const UnwindingResult& GetUnwindingResult() const { return unwinding_result_; }
bool IsCallChainBrokenForIncompleteJITDebugInfo() {
return is_callchain_broken_for_incomplete_jit_debug_info_;
}
+ static void CollectMetaInfo(std::unordered_map<std::string, std::string>* info_map);
+ virtual void LoadMetaInfo(const std::unordered_map<std::string, std::string>&) {}
+
protected:
OfflineUnwinder() {}
@@ -74,6 +80,6 @@ class OfflineUnwinder {
bool is_callchain_broken_for_incomplete_jit_debug_info_ = false;
};
-} // namespace simpleperf
+} // namespace simpleperf
#endif // SIMPLE_PERF_OFFLINE_UNWINDER_H_
diff --git a/simpleperf/OfflineUnwinder_impl.h b/simpleperf/OfflineUnwinder_impl.h
index 01f03369..1a523547 100644
--- a/simpleperf/OfflineUnwinder_impl.h
+++ b/simpleperf/OfflineUnwinder_impl.h
@@ -17,6 +17,7 @@
#pragma once
#include <unwindstack/Maps.h>
+#include <unwindstack/Regs.h>
#include "thread_tree.h"
@@ -31,4 +32,23 @@ class UnwindMaps : public unwindstack::Maps {
std::vector<const MapEntry*> entries_;
};
+class OfflineUnwinderImpl : public OfflineUnwinder {
+ public:
+ OfflineUnwinderImpl(bool collect_stat) : collect_stat_(collect_stat) {
+ unwindstack::Elf::SetCachingEnabled(true);
+ }
+
+ bool UnwindCallChain(const ThreadEntry& thread, const RegSet& regs, const char* stack,
+ size_t stack_size, std::vector<uint64_t>* ips,
+ std::vector<uint64_t>* sps) override;
+
+ void LoadMetaInfo(const std::unordered_map<std::string, std::string>& info_map) override;
+ unwindstack::Regs* GetBacktraceRegs(const RegSet& regs);
+
+ private:
+ bool collect_stat_;
+ std::unordered_map<pid_t, UnwindMaps> cached_maps_;
+ uint64_t arm64_pac_mask_ = 0;
+};
+
} // namespace simpleperf
diff --git a/simpleperf/OfflineUnwinder_test.cpp b/simpleperf/OfflineUnwinder_test.cpp
index 79ba64c0..46e74fe0 100644
--- a/simpleperf/OfflineUnwinder_test.cpp
+++ b/simpleperf/OfflineUnwinder_test.cpp
@@ -14,8 +14,12 @@
* limitations under the License.
*/
+#include "OfflineUnwinder.h"
#include "OfflineUnwinder_impl.h"
+#include <android-base/parseint.h>
+#include <unwindstack/RegsArm64.h>
+
#include <gtest/gtest.h>
using namespace simpleperf;
@@ -27,10 +31,10 @@ bool CheckUnwindMaps(UnwindMaps& maps, const MapSet& map_set) {
unwindstack::MapInfo* prev_real_map = nullptr;
for (size_t i = 0; i < maps.Total(); i++) {
unwindstack::MapInfo* info = maps.Get(i);
- if (info == nullptr || map_set.maps.find(info->start) == map_set.maps.end()) {
+ if (info == nullptr || map_set.maps.find(info->start()) == map_set.maps.end()) {
return false;
}
- if (info->prev_real_map != prev_real_map) {
+ if (info->prev_real_map() != prev_real_map) {
return false;
}
if (!info->IsBlank()) {
@@ -85,3 +89,29 @@ TEST(OfflineUnwinder, UnwindMaps) {
maps.UpdateMaps(map_set);
ASSERT_TRUE(CheckUnwindMaps(maps, map_set));
}
+
+TEST(OfflineUnwinder, CollectMetaInfo) {
+ std::unordered_map<std::string, std::string> info_map;
+ OfflineUnwinder::CollectMetaInfo(&info_map);
+ if (auto it = info_map.find(OfflineUnwinder::META_KEY_ARM64_PAC_MASK); it != info_map.end()) {
+ uint64_t arm64_pack_mask;
+ ASSERT_TRUE(android::base::ParseUint(it->second, &arm64_pack_mask));
+ ASSERT_NE(arm64_pack_mask, 0);
+ }
+}
+
+TEST(OfflineUnwinder, ARM64PackMask) {
+ std::unordered_map<std::string, std::string> info_map;
+ info_map[OfflineUnwinder::META_KEY_ARM64_PAC_MASK] = "0xff00000000";
+ std::unique_ptr<OfflineUnwinderImpl> unwinder(new OfflineUnwinderImpl(false));
+ unwinder->LoadMetaInfo(info_map);
+
+ RegSet fake_regs(0, 0, nullptr);
+ fake_regs.arch = ARCH_ARM64;
+ unwindstack::Regs* regs = unwinder->GetBacktraceRegs(fake_regs);
+ ASSERT_TRUE(regs != nullptr);
+ auto& arm64 = *static_cast<unwindstack::RegsArm64*>(regs);
+ arm64.SetPseudoRegister(unwindstack::Arm64Reg::ARM64_PREG_RA_SIGN_STATE, 1);
+ arm64.set_pc(0xffccccccccULL);
+ ASSERT_EQ(arm64.pc(), 0xccccccccULL);
+}
diff --git a/simpleperf/ProbeEvents.cpp b/simpleperf/ProbeEvents.cpp
new file mode 100644
index 00000000..636923f9
--- /dev/null
+++ b/simpleperf/ProbeEvents.cpp
@@ -0,0 +1,163 @@
+/*
+ * 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.
+ */
+
+#include "ProbeEvents.h"
+
+#include <inttypes.h>
+
+#include <memory>
+#include <regex>
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+
+#include "environment.h"
+#include "event_type.h"
+#include "utils.h"
+
+namespace simpleperf {
+
+using android::base::ParseInt;
+using android::base::ParseUint;
+using android::base::Split;
+using android::base::StringPrintf;
+using android::base::unique_fd;
+using android::base::WriteStringToFd;
+
+static const std::string kKprobeEventPrefix = "kprobes:";
+
+bool ProbeEvents::ParseKprobeEventName(const std::string& kprobe_cmd, ProbeEvent* event) {
+ // kprobe_cmd is in formats described in <kernel>/Documentation/trace/kprobetrace.rst:
+ // p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS]
+ // r[MAXACTIVE][:[GRP/]EVENT] [MOD:]SYM[+offs] [FETCHARGS]
+ std::vector<std::string> args = Split(kprobe_cmd, " ");
+ if (args.size() < 2) {
+ return false;
+ }
+
+ // Parse given name.
+ event->group_name = "kprobes";
+ std::regex name_reg(R"(:([a-zA-Z_][\w_]*/)?([a-zA-Z_][\w_]*))");
+ std::smatch matches;
+ if (std::regex_search(args[0], matches, name_reg)) {
+ if (matches[1].length() > 0) {
+ event->group_name = matches[1].str();
+ event->group_name.pop_back();
+ }
+ event->event_name = matches[2].str();
+ return true;
+ }
+
+ // Generate name from MEMADDR.
+ char probe_type = args[0][0];
+ uint64_t kaddr;
+ if (ParseUint(args[1], &kaddr)) {
+ event->event_name = StringPrintf("%c_0x%" PRIx64, probe_type, kaddr);
+ return true;
+ }
+
+ // Generate name from [MOD:]SYM[+offs].
+ std::string symbol;
+ int64_t offset;
+ size_t split_pos = args[1].find_first_of("+-");
+ if (split_pos == std::string::npos) {
+ symbol = args[1];
+ offset = 0;
+ } else {
+ symbol = args[1].substr(0, split_pos);
+ if (!ParseInt(args[1].substr(split_pos), &offset) || offset < 0) {
+ return false;
+ }
+ }
+ std::string s = StringPrintf("%c_%s_%" PRId64, probe_type, symbol.c_str(), offset);
+ event->event_name = std::regex_replace(s, std::regex(R"(\.|:)"), "_");
+ return true;
+}
+
+bool ProbeEvents::IsKprobeSupported() {
+ if (!kprobe_control_path_.has_value()) {
+ kprobe_control_path_ = "";
+ if (const char* tracefs_dir = GetTraceFsDir(); tracefs_dir != nullptr) {
+ std::string path = std::string(tracefs_dir) + "/kprobe_events";
+ if (IsRegularFile(path)) {
+ kprobe_control_path_ = std::move(path);
+ }
+ }
+ }
+ return !kprobe_control_path_.value().empty();
+}
+
+bool ProbeEvents::AddKprobe(const std::string& kprobe_cmd) {
+ ProbeEvent event;
+ if (!ParseKprobeEventName(kprobe_cmd, &event)) {
+ LOG(ERROR) << "invalid kprobe cmd: " << kprobe_cmd;
+ return false;
+ }
+ if (!WriteKprobeCmd(kprobe_cmd)) {
+ return false;
+ }
+ kprobe_events_.emplace_back(std::move(event));
+ return true;
+}
+
+bool ProbeEvents::IsProbeEvent(const std::string& event_name) {
+ return android::base::StartsWith(event_name, kKprobeEventPrefix);
+}
+
+bool ProbeEvents::CreateProbeEventIfNotExist(const std::string& event_name) {
+ if (EventTypeManager::Instance().FindType(event_name) != nullptr) {
+ return true;
+ }
+ std::string function_name = event_name.substr(kKprobeEventPrefix.size());
+ return AddKprobe(StringPrintf("p:%s %s", function_name.c_str(), function_name.c_str()));
+}
+
+void ProbeEvents::Clear() {
+ for (const auto& kprobe_event : kprobe_events_) {
+ if (!WriteKprobeCmd("-:" + kprobe_event.group_name + "/" + kprobe_event.event_name)) {
+ LOG(WARNING) << "failed to delete kprobe event " << kprobe_event.group_name << ":"
+ << kprobe_event.event_name;
+ }
+ EventTypeManager::Instance().RemoveProbeType(kprobe_event.group_name + ":" +
+ kprobe_event.event_name);
+ }
+ kprobe_events_.clear();
+}
+
+bool ProbeEvents::WriteKprobeCmd(const std::string& kprobe_cmd) {
+ if (!IsKprobeSupported()) {
+ LOG(ERROR) << "kprobe events isn't supported by the kernel.";
+ return false;
+ }
+ const std::string& path = kprobe_control_path_.value();
+ unique_fd fd(open(path.c_str(), O_APPEND | O_WRONLY | O_CLOEXEC));
+ if (!fd.ok()) {
+ PLOG(ERROR) << "failed to open " << path;
+ return false;
+ }
+ if (!WriteStringToFd(kprobe_cmd, fd)) {
+ PLOG(ERROR) << "failed to write '" << kprobe_cmd << "' to " << path;
+ return false;
+ }
+ return true;
+}
+
+} // namespace simpleperf
diff --git a/simpleperf/ProbeEvents.h b/simpleperf/ProbeEvents.h
new file mode 100644
index 00000000..39fa65f2
--- /dev/null
+++ b/simpleperf/ProbeEvents.h
@@ -0,0 +1,54 @@
+/*
+ * 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 <optional>
+#include <string>
+#include <vector>
+
+namespace simpleperf {
+
+struct ProbeEvent {
+ std::string group_name;
+ std::string event_name;
+};
+
+// Add kprobe events in /sys/kernel/debug/tracing/kprobe_events, and
+// delete them in ProbeEvents::clear().
+class ProbeEvents {
+ public:
+ ~ProbeEvents() { Clear(); }
+
+ 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 WriteKprobeCmd(const std::string& kprobe_cmd);
+
+ std::vector<ProbeEvent> kprobe_events_;
+ std::optional<std::string> kprobe_control_path_;
+};
+
+} // namespace simpleperf
diff --git a/simpleperf/ProbeEvents_test.cpp b/simpleperf/ProbeEvents_test.cpp
new file mode 100644
index 00000000..be135386
--- /dev/null
+++ b/simpleperf/ProbeEvents_test.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 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 "ProbeEvents.h"
+
+#include <gtest/gtest.h>
+
+#include "get_test_data.h"
+#include "test_util.h"
+
+using namespace simpleperf;
+
+TEST(probe_events, ParseKprobeEventName) {
+ ProbeEvent event;
+ ASSERT_TRUE(ProbeEvents::ParseKprobeEventName("p:myprobe do_sys_open", &event));
+ ASSERT_EQ(event.group_name, "kprobes");
+ ASSERT_EQ(event.event_name, "myprobe");
+
+ ASSERT_TRUE(ProbeEvents::ParseKprobeEventName("p:mygroup/myprobe do_sys_open", &event));
+ ASSERT_EQ(event.group_name, "mygroup");
+ ASSERT_EQ(event.event_name, "myprobe");
+
+ ASSERT_TRUE(ProbeEvents::ParseKprobeEventName("p do_sys_open", &event));
+ ASSERT_EQ(event.group_name, "kprobes");
+ ASSERT_EQ(event.event_name, "p_do_sys_open_0");
+
+ ASSERT_TRUE(ProbeEvents::ParseKprobeEventName("r do_sys_open+138", &event));
+ ASSERT_EQ(event.group_name, "kprobes");
+ ASSERT_EQ(event.event_name, "r_do_sys_open_138");
+
+ ASSERT_TRUE(ProbeEvents::ParseKprobeEventName("r module:do_sys_open+138", &event));
+ ASSERT_EQ(event.group_name, "kprobes");
+ ASSERT_EQ(event.event_name, "r_module_do_sys_open_138");
+
+ ASSERT_TRUE(ProbeEvents::ParseKprobeEventName("p 0x12345678", &event));
+ ASSERT_EQ(event.group_name, "kprobes");
+ ASSERT_EQ(event.event_name, "p_0x12345678");
+}
diff --git a/simpleperf/README.md b/simpleperf/README.md
index 7ca61c54..ae30480a 100644
--- a/simpleperf/README.md
+++ b/simpleperf/README.md
@@ -5,7 +5,7 @@ There is also [user documentation](doc/README.md).
## Building new prebuilts
-To snap the aosp-simpleperf-release branch to ToT AOSP master and kick off a
+To snap the aosp-simpleperf-release branch to ToT AOSP main and kick off a
build, use [this coastguard
page](https://android-build.googleplex.com/coastguard/dashboard/5938649007521792/#/request/create)
and choose "aosp-simpleperf-release" from the "Branch" dropdown. Then click
diff --git a/simpleperf/RecordFilter.cpp b/simpleperf/RecordFilter.cpp
new file mode 100644
index 00000000..d85e9a63
--- /dev/null
+++ b/simpleperf/RecordFilter.cpp
@@ -0,0 +1,153 @@
+/*
+ * 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 "RecordFilter.h"
+
+#include "environment.h"
+#include "utils.h"
+
+namespace simpleperf {
+
+bool RecordFilter::ParseOptions(OptionValueMap& options) {
+ for (bool exclude : {true, false}) {
+ std::string prefix = exclude ? "--exclude-" : "--include-";
+ for (const OptionValue& value : options.PullValues(prefix + "pid")) {
+ if (auto pids = GetTidsFromString(*value.str_value, false); pids) {
+ AddPids(pids.value(), exclude);
+ } else {
+ return false;
+ }
+ }
+ for (const OptionValue& value : options.PullValues(prefix + "tid")) {
+ if (auto tids = GetTidsFromString(*value.str_value, false); tids) {
+ AddTids(tids.value(), exclude);
+ } else {
+ return false;
+ }
+ }
+ for (const OptionValue& value : options.PullValues(prefix + "process-name")) {
+ AddProcessNameRegex(*value.str_value, exclude);
+ }
+ for (const OptionValue& value : options.PullValues(prefix + "thread-name")) {
+ AddThreadNameRegex(*value.str_value, exclude);
+ }
+ for (const OptionValue& value : options.PullValues(prefix + "uid")) {
+ if (auto uids = ParseUintVector<uid_t>(*value.str_value); uids) {
+ AddUids(uids.value(), exclude);
+ } else {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+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());
+}
+
+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());
+}
+
+void RecordFilter::AddProcessNameRegex(const std::string& process_name, bool exclude) {
+ RecordFilterCondition& cond = GetCondition(exclude);
+ cond.used = true;
+ cond.process_name_regs.emplace_back(process_name, std::regex::optimize);
+}
+
+void RecordFilter::AddThreadNameRegex(const std::string& thread_name, bool exclude) {
+ RecordFilterCondition& cond = GetCondition(exclude);
+ cond.used = true;
+ cond.thread_name_regs.emplace_back(thread_name, std::regex::optimize);
+}
+
+void RecordFilter::AddUids(const std::set<uid_t>& uids, bool exclude) {
+ RecordFilterCondition& cond = GetCondition(exclude);
+ cond.used = true;
+ cond.uids.insert(uids.begin(), uids.end());
+}
+
+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;
+ }
+ 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(const std::string& s, const std::vector<std::regex>& regs) {
+ for (auto& reg : regs) {
+ if (std::regex_search(s, reg)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+std::optional<uid_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;
+}
+
+} // namespace simpleperf
diff --git a/simpleperf/RecordFilter.h b/simpleperf/RecordFilter.h
new file mode 100644
index 00000000..f0c72fdb
--- /dev/null
+++ b/simpleperf/RecordFilter.h
@@ -0,0 +1,111 @@
+/*
+ * 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 <sys/types.h>
+
+#include <optional>
+#include <regex>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "command.h"
+#include "record.h"
+#include "thread_tree.h"
+
+namespace simpleperf {
+
+#define RECORD_FILTER_OPTION_HELP_MSG \
+ "--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" \
+ "--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" \
+ " 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"
+
+struct RecordFilterCondition {
+ bool used = false;
+ std::set<pid_t> pids;
+ std::set<pid_t> tids;
+ std::vector<std::regex> process_name_regs;
+ std::vector<std::regex> thread_name_regs;
+ std::set<uid_t> uids;
+};
+
+// 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.
+class RecordFilter {
+ public:
+ RecordFilter(const ThreadTree& thread_tree) : thread_tree_(thread_tree) {}
+ bool ParseOptions(OptionValueMap& options);
+ void AddPids(const std::set<pid_t>& pids, bool exclude);
+ void AddTids(const std::set<pid_t>& tids, bool exclude);
+ void AddProcessNameRegex(const std::string& process_name, bool exclude);
+ void AddThreadNameRegex(const std::string& thread_name, bool exclude);
+ void AddUids(const std::set<uid_t>& uids, bool exclude);
+
+ // Return true if the record passes filter.
+ bool Check(const SampleRecord* r);
+
+ RecordFilterCondition& GetCondition(bool exclude) {
+ return exclude ? exclude_condition_ : include_condition_;
+ }
+ void Clear();
+
+ private:
+ bool CheckCondition(const SampleRecord* r, const RecordFilterCondition& condition);
+ bool SearchInRegs(const std::string& s, const std::vector<std::regex>& regs);
+ std::optional<uid_t> GetUidForProcess(pid_t pid);
+
+ const ThreadTree& thread_tree_;
+ RecordFilterCondition exclude_condition_;
+ RecordFilterCondition include_condition_;
+ std::unordered_map<pid_t, std::optional<uid_t>> pid_to_uid_map_;
+};
+
+inline const OptionFormatMap& GetRecordFilterOptionFormats() {
+ static const OptionFormatMap option_formats = {
+ {"--exclude-pid", {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}},
+ {"--exclude-tid", {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}},
+ {"--exclude-process-name",
+ {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}},
+ {"--exclude-thread-name",
+ {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}},
+ {"--exclude-uid", {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}},
+ {"--include-pid", {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}},
+ {"--include-tid", {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}},
+ {"--include-process-name",
+ {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}},
+ {"--include-thread-name",
+ {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}},
+ {"--include-uid", {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}},
+ };
+ return option_formats;
+}
+
+} // namespace simpleperf
diff --git a/simpleperf/RecordFilter_test.cpp b/simpleperf/RecordFilter_test.cpp
new file mode 100644
index 00000000..b131ed9b
--- /dev/null
+++ b/simpleperf/RecordFilter_test.cpp
@@ -0,0 +1,184 @@
+/*
+ * 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 "RecordFilter.h"
+
+#include <gtest/gtest.h>
+
+#include <memory>
+
+#include "event_attr.h"
+#include "event_type.h"
+#include "record.h"
+
+using namespace simpleperf;
+
+class RecordFilterTest : public ::testing::Test {
+ public:
+ RecordFilterTest() : filter(thread_tree) {}
+
+ protected:
+ void SetUp() override {
+ const EventType* event_type = FindEventTypeByName("cpu-clock");
+ attr = CreateDefaultPerfEventAttr(*event_type);
+ record.reset(new SampleRecord(attr, 0, 0, 0, 0, 0, 0, 0, {}, {}, 0));
+ }
+
+ SampleRecord* GetRecord(uint32_t pid, uint32_t tid) {
+ record->tid_data.pid = pid;
+ record->tid_data.tid = tid;
+ return record.get();
+ }
+
+ ThreadTree thread_tree;
+ perf_event_attr attr;
+ RecordFilter filter;
+ std::unique_ptr<SampleRecord> record;
+};
+
+TEST_F(RecordFilterTest, no_filter) {
+ ASSERT_TRUE(filter.Check(GetRecord(0, 0)));
+}
+
+TEST_F(RecordFilterTest, exclude_pid) {
+ filter.AddPids({1}, true);
+ ASSERT_FALSE(filter.Check(GetRecord(1, 1)));
+ ASSERT_TRUE(filter.Check(GetRecord(2, 2)));
+}
+
+TEST_F(RecordFilterTest, exclude_tid) {
+ filter.AddTids({1}, true);
+ ASSERT_FALSE(filter.Check(GetRecord(1, 1)));
+ ASSERT_TRUE(filter.Check(GetRecord(1, 2)));
+}
+
+TEST_F(RecordFilterTest, exclude_process_name_regex) {
+ filter.AddProcessNameRegex("processA", true);
+ thread_tree.SetThreadName(1, 1, "processA1");
+ thread_tree.SetThreadName(2, 2, "processB1");
+ ASSERT_FALSE(filter.Check(GetRecord(1, 1)));
+ ASSERT_TRUE(filter.Check(GetRecord(2, 2)));
+}
+
+TEST_F(RecordFilterTest, exclude_thread_name_regex) {
+ filter.AddThreadNameRegex("threadA", true);
+ thread_tree.SetThreadName(1, 1, "processA_threadA");
+ thread_tree.SetThreadName(1, 2, "processA_threadB");
+ ASSERT_FALSE(filter.Check(GetRecord(1, 1)));
+ ASSERT_TRUE(filter.Check(GetRecord(1, 2)));
+}
+
+TEST_F(RecordFilterTest, exclude_uid) {
+ pid_t pid = getpid();
+ std::optional<uid_t> uid = GetProcessUid(pid);
+ ASSERT_TRUE(uid.has_value());
+ filter.AddUids({uid.value()}, true);
+ ASSERT_FALSE(filter.Check(GetRecord(pid, pid)));
+ uint32_t pid_not_exist = UINT32_MAX;
+ ASSERT_TRUE(filter.Check(GetRecord(pid_not_exist, pid_not_exist)));
+}
+
+TEST_F(RecordFilterTest, include_pid) {
+ filter.AddPids({1}, false);
+ ASSERT_TRUE(filter.Check(GetRecord(1, 1)));
+ ASSERT_FALSE(filter.Check(GetRecord(2, 2)));
+}
+
+TEST_F(RecordFilterTest, include_tid) {
+ filter.AddTids({1}, false);
+ ASSERT_TRUE(filter.Check(GetRecord(1, 1)));
+ ASSERT_FALSE(filter.Check(GetRecord(1, 2)));
+}
+
+TEST_F(RecordFilterTest, include_process_name_regex) {
+ filter.AddProcessNameRegex("processA", false);
+ thread_tree.SetThreadName(1, 1, "processA1");
+ thread_tree.SetThreadName(2, 2, "processB1");
+ ASSERT_TRUE(filter.Check(GetRecord(1, 1)));
+ ASSERT_FALSE(filter.Check(GetRecord(2, 2)));
+}
+
+TEST_F(RecordFilterTest, include_thread_name_regex) {
+ filter.AddThreadNameRegex("threadA", false);
+ thread_tree.SetThreadName(1, 1, "processA_threadA");
+ thread_tree.SetThreadName(1, 2, "processA_threadB");
+ ASSERT_TRUE(filter.Check(GetRecord(1, 1)));
+ ASSERT_FALSE(filter.Check(GetRecord(1, 2)));
+}
+
+TEST_F(RecordFilterTest, include_uid) {
+ pid_t pid = getpid();
+ std::optional<uid_t> uid = GetProcessUid(pid);
+ ASSERT_TRUE(uid.has_value());
+ filter.AddUids({uid.value()}, false);
+ ASSERT_TRUE(filter.Check(GetRecord(pid, pid)));
+ uint32_t pid_not_exist = UINT32_MAX;
+ ASSERT_FALSE(filter.Check(GetRecord(pid_not_exist, pid_not_exist)));
+}
+
+namespace {
+
+class ParseRecordFilterCommand : public Command {
+ public:
+ ParseRecordFilterCommand(RecordFilter& filter) : Command("", "", ""), filter_(filter) {}
+
+ bool Run(const std::vector<std::string>& args) override {
+ const auto& option_formats = GetRecordFilterOptionFormats();
+ OptionValueMap options;
+ std::vector<std::pair<OptionName, OptionValue>> ordered_options;
+
+ if (!PreprocessOptions(args, option_formats, &options, &ordered_options, nullptr)) {
+ return false;
+ }
+ filter_.Clear();
+ return filter_.ParseOptions(options);
+ }
+
+ private:
+ RecordFilter& filter_;
+};
+
+} // namespace
+
+TEST_F(RecordFilterTest, parse_options) {
+ ParseRecordFilterCommand filter_cmd(filter);
+
+ for (bool exclude : {true, false}) {
+ 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_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_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(std::regex_match("processA", process_regs[0]));
+ ASSERT_TRUE(std::regex_match("processB", process_regs[1]));
+
+ 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(std::regex_match("threadA", thread_regs[0]));
+ ASSERT_TRUE(std::regex_match("threadB", thread_regs[1]));
+
+ ASSERT_TRUE(filter_cmd.Run({prefix + "uid", "1,2", prefix + "uid", "3"}));
+ ASSERT_EQ(filter.GetCondition(exclude).uids, std::set<uid_t>({1, 2, 3}));
+ }
+}
diff --git a/simpleperf/RecordReadThread.cpp b/simpleperf/RecordReadThread.cpp
index dfcc1a9e..16af9292 100644
--- a/simpleperf/RecordReadThread.cpp
+++ b/simpleperf/RecordReadThread.cpp
@@ -33,17 +33,16 @@ static constexpr size_t kDefaultLowBufferLevel = 10 * 1024 * 1024u;
static constexpr size_t kDefaultCriticalBufferLevel = 5 * 1024 * 1024u;
RecordBuffer::RecordBuffer(size_t buffer_size)
- : read_head_(0), write_head_(0), buffer_size_(buffer_size), buffer_(new char[buffer_size]) {
-}
+ : read_head_(0), write_head_(0), buffer_size_(buffer_size), buffer_(new char[buffer_size]) {}
size_t RecordBuffer::GetFreeSize() const {
- size_t write_head = write_head_.load(std::memory_order_relaxed);
- size_t read_head = read_head_.load(std::memory_order_relaxed);
- size_t write_tail = read_head > 0 ? read_head - 1 : buffer_size_ - 1;
- if (write_head <= write_tail) {
- return write_tail - write_head;
- }
- return buffer_size_ - write_head + write_tail;
+ size_t write_head = write_head_.load(std::memory_order_relaxed);
+ size_t read_head = read_head_.load(std::memory_order_relaxed);
+ size_t write_tail = read_head > 0 ? read_head - 1 : buffer_size_ - 1;
+ if (write_head <= write_tail) {
+ return write_tail - write_head;
+ }
+ return buffer_size_ - write_head + write_tail;
}
char* RecordBuffer::AllocWriteSpace(size_t record_size) {
@@ -121,13 +120,13 @@ RecordParser::RecordParser(const perf_event_attr& attr)
pos += sizeof(uint64_t);
}
mask = PERF_SAMPLE_ADDR | PERF_SAMPLE_ID | PERF_SAMPLE_STREAM_ID | PERF_SAMPLE_CPU |
- PERF_SAMPLE_PERIOD;
+ PERF_SAMPLE_PERIOD;
pos += __builtin_popcountll(sample_type_ & mask) * sizeof(uint64_t);
callchain_pos_in_sample_records_ = pos;
if ((sample_type_ & PERF_SAMPLE_TIME) && attr.sample_id_all) {
mask = PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_CPU | PERF_SAMPLE_STREAM_ID | PERF_SAMPLE_ID;
- time_rpos_in_non_sample_records_ = (__builtin_popcountll(sample_type_ & mask) + 1) *
- sizeof(uint64_t);
+ time_rpos_in_non_sample_records_ =
+ (__builtin_popcountll(sample_type_ & mask) + 1) * sizeof(uint64_t);
}
}
@@ -143,7 +142,7 @@ size_t RecordParser::GetTimePos(const perf_event_header& header) const {
}
size_t RecordParser::GetStackSizePos(
- const std::function<void(size_t,size_t,void*)>& read_record_fn) const{
+ const std::function<void(size_t, size_t, void*)>& read_record_fn) const {
size_t pos = callchain_pos_in_sample_records_;
if (sample_type_ & PERF_SAMPLE_CALLCHAIN) {
uint64_t ip_nr;
@@ -272,10 +271,13 @@ bool RecordReadThread::SyncKernelBuffer() {
}
bool RecordReadThread::StopReadThread() {
- bool result = SendCmdToReadThread(CMD_STOP_THREAD, nullptr);
- if (result) {
- read_thread_->join();
- read_thread_ = nullptr;
+ bool result = true;
+ if (read_thread_ != nullptr) {
+ result = SendCmdToReadThread(CMD_STOP_THREAD, nullptr);
+ if (result) {
+ read_thread_->join();
+ read_thread_ = nullptr;
+ }
}
return result;
}
@@ -286,8 +288,8 @@ bool RecordReadThread::SendCmdToReadThread(Cmd cmd, void* cmd_arg) {
cmd_ = cmd;
cmd_arg_ = cmd_arg;
}
- char dummy = 0;
- if (TEMP_FAILURE_RETRY(write(write_cmd_fd_, &dummy, 1)) != 1) {
+ char unused = 0;
+ if (TEMP_FAILURE_RETRY(write(write_cmd_fd_, &unused, 1)) != 1) {
return false;
}
std::unique_lock<std::mutex> lock(cmd_mutex_);
@@ -310,8 +312,8 @@ std::unique_ptr<Record> RecordReadThread::GetRecord() {
return r;
}
if (has_data_notification_) {
- char dummy;
- TEMP_FAILURE_RETRY(read(read_data_fd_, &dummy, 1));
+ char unused;
+ TEMP_FAILURE_RETRY(read(read_data_fd_, &unused, 1));
has_data_notification_ = false;
}
return nullptr;
@@ -342,8 +344,8 @@ RecordReadThread::Cmd RecordReadThread::GetCmd() {
}
bool RecordReadThread::HandleCmd(IOEventLoop& loop) {
- char dummy;
- TEMP_FAILURE_RETRY(read(read_cmd_fd_, &dummy, 1));
+ char unused;
+ TEMP_FAILURE_RETRY(read(read_cmd_fd_, &unused, 1));
bool result = true;
switch (GetCmd()) {
case CMD_ADD_EVENT_FDS:
@@ -423,10 +425,9 @@ bool RecordReadThread::HandleAddEventFds(IOEventLoop& loop,
bool RecordReadThread::HandleRemoveEventFds(const std::vector<EventFd*>& event_fds) {
for (auto& event_fd : event_fds) {
if (event_fd->HasMappedBuffer()) {
- auto it = std::find_if(kernel_record_readers_.begin(), kernel_record_readers_.end(),
- [&](const KernelRecordReader& reader) {
- return reader.GetEventFd() == event_fd;
- });
+ auto it = std::find_if(
+ kernel_record_readers_.begin(), kernel_record_readers_.end(),
+ [&](const KernelRecordReader& reader) { return reader.GetEventFd() == event_fd; });
if (it != kernel_record_readers_.end()) {
kernel_record_readers_.erase(it);
event_fd->StopPolling();
@@ -518,10 +519,10 @@ void RecordReadThread::PushRecordToRecordBuffer(KernelRecordReader* kernel_recor
// the call chain joiner can complete the callchains.
stack_size_limit = 1024;
}
- size_t stack_size_pos = record_parser_.GetStackSizePos(
- [&](size_t pos, size_t size, void* dest) {
+ size_t stack_size_pos =
+ record_parser_.GetStackSizePos([&](size_t pos, size_t size, void* dest) {
return kernel_record_reader->ReadRecord(pos, size, dest);
- });
+ });
uint64_t stack_size;
kernel_record_reader->ReadRecord(stack_size_pos, sizeof(stack_size), &stack_size);
if (stack_size > 0) {
@@ -618,8 +619,8 @@ void RecordReadThread::ReadAuxDataFromKernelBuffer(bool* has_data) {
bool RecordReadThread::SendDataNotificationToMainThread() {
if (!has_data_notification_.load(std::memory_order_relaxed)) {
has_data_notification_ = true;
- char dummy = 0;
- if (TEMP_FAILURE_RETRY(write(write_data_fd_, &dummy, 1)) != 1) {
+ char unused = 0;
+ if (TEMP_FAILURE_RETRY(write(write_data_fd_, &unused, 1)) != 1) {
PLOG(ERROR) << "write";
return false;
}
diff --git a/simpleperf/RecordReadThread.h b/simpleperf/RecordReadThread.h
index e991fff0..321dc8c5 100644
--- a/simpleperf/RecordReadThread.h
+++ b/simpleperf/RecordReadThread.h
@@ -75,7 +75,7 @@ class RecordParser {
// Return pos of the time field in the record. If not available, return 0.
size_t GetTimePos(const perf_event_header& header) const;
// Return pos of the user stack size field in the sample record. If not available, return 0.
- size_t GetStackSizePos(const std::function<void(size_t,size_t,void*)>& read_record_fn) const;
+ size_t GetStackSizePos(const std::function<void(size_t, size_t, void*)>& read_record_fn) const;
private:
uint64_t sample_type_;
@@ -127,8 +127,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_cutting_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;
diff --git a/simpleperf/RecordReadThread_test.cpp b/simpleperf/RecordReadThread_test.cpp
index 8d7f6ac4..71c8e287 100644
--- a/simpleperf/RecordReadThread_test.cpp
+++ b/simpleperf/RecordReadThread_test.cpp
@@ -74,8 +74,8 @@ TEST_F(RecordBufferTest, fifo) {
}
TEST(RecordParser, smoke) {
- std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(
- GetTestData(PERF_DATA_NO_UNWIND));
+ std::unique_ptr<RecordFileReader> reader =
+ RecordFileReader::CreateInstance(GetTestData(PERF_DATA_NO_UNWIND));
ASSERT_TRUE(reader);
RecordParser parser(*reader->AttrSection()[0].attr);
auto process_record = [&](std::unique_ptr<Record> record) {
@@ -143,8 +143,10 @@ static perf_event_attr CreateFakeEventAttr() {
return CreateDefaultPerfEventAttr(*type);
}
-static std::vector<std::unique_ptr<Record>> CreateFakeRecords(
- const perf_event_attr& attr, size_t record_count, size_t stack_size, size_t dyn_stack_size) {
+static std::vector<std::unique_ptr<Record>> CreateFakeRecords(const perf_event_attr& attr,
+ size_t record_count,
+ size_t stack_size,
+ size_t dyn_stack_size) {
std::vector<std::unique_ptr<Record>> records;
for (size_t i = 0; i < record_count; ++i) {
SampleRecord* r = new SampleRecord(attr, i, i + 1, i + 2, i + 3, i + 4, i + 5, i + 6, {},
@@ -164,8 +166,8 @@ static size_t AlignToPowerOfTwo(size_t value) {
static inline std::function<bool(size_t&)> SetArg(size_t value) {
return [value](size_t& arg) {
- arg = value;
- return true;
+ arg = value;
+ return true;
};
}
@@ -190,7 +192,8 @@ TEST(KernelRecordReader, smoke) {
MockEventFd event_fd(attr, 0, buffer.data(), buffer.size(), false);
EXPECT_CALL(event_fd, GetAvailableMmapDataSize(Truly(SetArg(data_pos))))
- .Times(1).WillOnce(Return(data_size));
+ .Times(1)
+ .WillOnce(Return(data_size));
EXPECT_CALL(event_fd, DiscardMmapData(Eq(data_size))).Times(1);
KernelRecordReader reader(&event_fd);
RecordParser parser(attr);
@@ -228,7 +231,8 @@ class RecordReadThreadTest : public ::testing::Test {
event_fds_[i].reset(new MockEventFd(attr, i, buffers_[i].data(), buffer_size, false));
EXPECT_CALL(*event_fds_[i], CreateMappedBuffer(_, _)).Times(1).WillOnce(Return(true));
EXPECT_CALL(*event_fds_[i], StartPolling(_, _)).Times(1).WillOnce(Return(true));
- EXPECT_CALL(*event_fds_[i], GetAvailableMmapDataSize(Truly(SetArg(0)))).Times(1)
+ EXPECT_CALL(*event_fds_[i], GetAvailableMmapDataSize(Truly(SetArg(0))))
+ .Times(1)
.WillOnce(Return(data_size));
EXPECT_CALL(*event_fds_[i], DiscardMmapData(Eq(data_size))).Times(1);
EXPECT_CALL(*event_fds_[i], StopPolling()).Times(1).WillOnce(Return(true));
diff --git a/simpleperf/SampleComparator.h b/simpleperf/SampleComparator.h
index 465370a1..e13f2346 100644
--- a/simpleperf/SampleComparator.h
+++ b/simpleperf/SampleComparator.h
@@ -21,6 +21,8 @@
#include <vector>
+namespace simpleperf {
+
// The compare functions below are used to compare two samples by their item
// content.
@@ -54,12 +56,10 @@ BUILD_COMPARE_VALUE_FUNCTION(ComparePid, pid);
BUILD_COMPARE_VALUE_FUNCTION(CompareTid, tid);
BUILD_COMPARE_VALUE_FUNCTION_REVERSE(CompareSampleCount, sample_count);
BUILD_COMPARE_STRING_FUNCTION(CompareComm, thread_comm);
-BUILD_COMPARE_STRING_FUNCTION(CompareDso, map->dso->Path().c_str());
+BUILD_COMPARE_STRING_FUNCTION(CompareDso, map->dso->GetReportPath().data());
BUILD_COMPARE_STRING_FUNCTION(CompareSymbol, symbol->DemangledName());
-BUILD_COMPARE_STRING_FUNCTION(CompareDsoFrom,
- branch_from.map->dso->Path().c_str());
-BUILD_COMPARE_STRING_FUNCTION(CompareSymbolFrom,
- branch_from.symbol->DemangledName());
+BUILD_COMPARE_STRING_FUNCTION(CompareDsoFrom, branch_from.map->dso->GetReportPath().data());
+BUILD_COMPARE_STRING_FUNCTION(CompareSymbolFrom, branch_from.symbol->DemangledName());
BUILD_COMPARE_VALUE_FUNCTION(CompareCallGraphDuplicated, callchain.duplicated);
template <typename EntryT>
@@ -82,13 +82,10 @@ class SampleComparator {
public:
typedef int (*compare_sample_func_t)(const EntryT*, const EntryT*);
- void AddCompareFunction(compare_sample_func_t func) {
- compare_v_.push_back(func);
- }
+ void AddCompareFunction(compare_sample_func_t func) { compare_v_.push_back(func); }
void AddComparator(const SampleComparator<EntryT>& other) {
- compare_v_.insert(compare_v_.end(), other.compare_v_.begin(),
- other.compare_v_.end());
+ compare_v_.insert(compare_v_.end(), other.compare_v_.begin(), other.compare_v_.end());
}
bool operator()(const EntryT* sample1, const EntryT* sample2) const {
@@ -101,6 +98,16 @@ class SampleComparator {
return false;
}
+ bool operator()(const EntryT& sample1, const EntryT& sample2) const {
+ for (const auto& func : compare_v_) {
+ int ret = func(&sample1, &sample2);
+ if (ret != 0) {
+ return ret < 0;
+ }
+ }
+ return false;
+ }
+
bool IsSameSample(const EntryT* sample1, const EntryT* sample2) const {
for (const auto& func : compare_v_) {
if (func(sample1, sample2) != 0) {
@@ -116,4 +123,6 @@ class SampleComparator {
std::vector<compare_sample_func_t> compare_v_;
};
+} // namespace simpleperf
+
#endif // SIMPLE_PERF_SAMPLE_COMPARATOR_H_
diff --git a/simpleperf/SampleDisplayer.h b/simpleperf/SampleDisplayer.h
index 3a59abe5..cc27a46c 100644
--- a/simpleperf/SampleDisplayer.h
+++ b/simpleperf/SampleDisplayer.h
@@ -25,11 +25,12 @@
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
+namespace simpleperf {
+
// The display functions below are used to show items in a sample.
template <typename EntryT, typename InfoT>
-std::string DisplayAccumulatedOverhead(const EntryT* sample,
- const InfoT* info) {
+std::string DisplayAccumulatedOverhead(const EntryT* sample, const InfoT* info) {
uint64_t period = sample->period + sample->accumulated_period;
uint64_t total_period = info->total_period;
double percentage = (total_period != 0) ? 100.0 * period / total_period : 0.0;
@@ -81,7 +82,7 @@ std::string DisplayComm(const EntryT* sample) {
template <typename EntryT>
std::string DisplayDso(const EntryT* sample) {
- return sample->map->dso->Path();
+ return std::string{sample->map->dso->GetReportPath()};
}
template <typename EntryT>
@@ -91,7 +92,7 @@ std::string DisplaySymbol(const EntryT* sample) {
template <typename EntryT>
std::string DisplayDsoFrom(const EntryT* sample) {
- return sample->branch_from.map->dso->Path();
+ return std::string{sample->branch_from.map->dso->GetReportPath()};
}
template <typename EntryT>
@@ -105,8 +106,7 @@ class CallgraphDisplayer {
static constexpr int SPACES_BETWEEN_CALLGRAPH_ENTRIES = 4;
public:
- CallgraphDisplayer(uint32_t max_stack = UINT32_MAX,
- double percent_limit = 0.0,
+ CallgraphDisplayer(uint32_t max_stack = UINT32_MAX, double percent_limit = 0.0,
bool brief_callgraph = false)
: max_stack_(max_stack), percent_limit_(percent_limit), brief_callgraph_(brief_callgraph) {}
@@ -132,15 +132,14 @@ class CallgraphDisplayer {
}
void DisplayCallGraphEntry(FILE* fp, size_t depth, std::string prefix,
- const std::unique_ptr<CallChainNodeT>& node,
- uint64_t parent_period, bool last) {
+ const std::unique_ptr<CallChainNodeT>& node, uint64_t parent_period,
+ bool last) {
if (depth > max_stack_) {
return;
}
std::string percentage_s = "-- ";
if (node->period + node->children_period != parent_period) {
- double percentage =
- 100.0 * (node->period + node->children_period) / parent_period;
+ double percentage = 100.0 * (node->period + node->children_period) / parent_period;
if (percentage < percent_limit_) {
return;
}
@@ -164,8 +163,7 @@ class CallgraphDisplayer {
}
for (size_t i = 0; i < node->children.size(); ++i) {
DisplayCallGraphEntry(fp, depth + 1, prefix, node->children[i],
- node->children_period + node->period,
- (i + 1 == node->children.size()));
+ node->children_period + node->period, (i + 1 == node->children.size()));
}
}
@@ -187,10 +185,8 @@ template <typename EntryT, typename InfoT>
class SampleDisplayer {
public:
typedef std::string (*display_sample_func_t)(const EntryT*);
- typedef std::string (*display_sample_with_info_func_t)(const EntryT*,
- const InfoT*);
- using exclusive_display_sample_func_t =
- std::function<void(FILE*, const EntryT*)>;
+ typedef std::string (*display_sample_with_info_func_t)(const EntryT*, const InfoT*);
+ using exclusive_display_sample_func_t = std::function<void(FILE*, const EntryT*)>;
private:
struct Item {
@@ -202,6 +198,7 @@ class SampleDisplayer {
public:
void SetInfo(const InfoT* info) { info_ = info; }
+ void SetReportFormat(bool report_csv) { report_csv_ = report_csv; }
void AddDisplayFunction(const std::string& name, display_sample_func_t func) {
Item item;
@@ -212,8 +209,7 @@ class SampleDisplayer {
display_v_.push_back(item);
}
- void AddDisplayFunction(const std::string& name,
- display_sample_with_info_func_t func_with_info) {
+ void AddDisplayFunction(const std::string& name, display_sample_with_info_func_t func_with_info) {
Item item;
item.name = name;
item.width = name.size();
@@ -227,10 +223,12 @@ class SampleDisplayer {
}
void AdjustWidth(const EntryT* sample) {
+ if (report_csv_) {
+ return;
+ }
for (auto& item : display_v_) {
- std::string data = (item.func != nullptr)
- ? item.func(sample)
- : item.func_with_info(sample, info_);
+ std::string data =
+ (item.func != nullptr) ? item.func(sample) : item.func_with_info(sample, info_);
item.width = std::max(item.width, data.size());
}
}
@@ -238,10 +236,14 @@ class SampleDisplayer {
void PrintNames(FILE* fp) {
for (size_t i = 0; i < display_v_.size(); ++i) {
auto& item = display_v_[i];
- if (i != display_v_.size() - 1) {
- fprintf(fp, "%-*s ", static_cast<int>(item.width), item.name.c_str());
+ if (report_csv_) {
+ fprintf(fp, "%s%c", item.name.c_str(), (i + 1 == display_v_.size()) ? '\n' : ',');
} else {
- fprintf(fp, "%s\n", item.name.c_str());
+ if (i != display_v_.size() - 1) {
+ fprintf(fp, "%-*s ", static_cast<int>(item.width), item.name.c_str());
+ } else {
+ fprintf(fp, "%s\n", item.name.c_str());
+ }
}
}
}
@@ -249,13 +251,21 @@ class SampleDisplayer {
void PrintSample(FILE* fp, const EntryT* sample) {
for (size_t i = 0; i < display_v_.size(); ++i) {
auto& item = display_v_[i];
- std::string data = (item.func != nullptr)
- ? item.func(sample)
- : item.func_with_info(sample, info_);
- if (i != display_v_.size() - 1) {
- fprintf(fp, "%-*s ", static_cast<int>(item.width), data.c_str());
+ std::string data =
+ (item.func != nullptr) ? item.func(sample) : item.func_with_info(sample, info_);
+ if (report_csv_) {
+ if (data.find(',') == std::string::npos) {
+ fprintf(fp, "%s", data.c_str());
+ } else {
+ fprintf(fp, "\"%s\"", data.c_str());
+ }
+ fputc((i + 1 == display_v_.size()) ? '\n' : ',', fp);
} else {
- fprintf(fp, "%s\n", data.c_str());
+ if (i != display_v_.size() - 1) {
+ fprintf(fp, "%-*s ", static_cast<int>(item.width), data.c_str());
+ } else {
+ fprintf(fp, "%s\n", data.c_str());
+ }
}
}
for (auto& func : exclusive_display_v_) {
@@ -267,6 +277,9 @@ class SampleDisplayer {
const InfoT* info_;
std::vector<Item> display_v_;
std::vector<exclusive_display_sample_func_t> exclusive_display_v_;
+ bool report_csv_ = false;
};
+} // namespace simpleperf
+
#endif // SIMPLE_PERF_SAMPLE_DISPLAYER_H_
diff --git a/simpleperf/app_api/cpp/simpleperf.cpp b/simpleperf/app_api/cpp/simpleperf.cpp
index 71216fa0..0ccf7ae0 100644
--- a/simpleperf/app_api/cpp/simpleperf.cpp
+++ b/simpleperf/app_api/cpp/simpleperf.cpp
@@ -26,12 +26,14 @@
#include <time.h>
#include <unistd.h>
+#include <android/log.h>
#include <mutex>
#include <sstream>
-#include <android/log.h>
namespace simpleperf {
+constexpr int AID_USER_OFFSET = 100000;
+
enum RecordCmd {
CMD_PAUSE_RECORDING = 1,
CMD_RESUME_RECORDING,
@@ -49,19 +51,18 @@ class RecordOptionsImpl {
bool trace_offcpu = false;
};
-RecordOptions::RecordOptions() : impl_(new RecordOptionsImpl) {
-}
+RecordOptions::RecordOptions() : impl_(new RecordOptionsImpl) {}
RecordOptions::~RecordOptions() {
delete impl_;
}
-RecordOptions& RecordOptions::SetOutputFilename(const std::string &filename) {
+RecordOptions& RecordOptions::SetOutputFilename(const std::string& filename) {
impl_->output_filename = filename;
return *this;
}
-RecordOptions& RecordOptions::SetEvent(const std::string &event) {
+RecordOptions& RecordOptions::SetEvent(const std::string& event) {
impl_->event = event;
return *this;
}
@@ -76,7 +77,7 @@ RecordOptions& RecordOptions::SetDuration(double duration_in_second) {
return *this;
}
-RecordOptions& RecordOptions::SetSampleThreads(const std::vector<pid_t> &threads) {
+RecordOptions& RecordOptions::SetSampleThreads(const std::vector<pid_t>& threads) {
impl_->threads = threads;
return *this;
}
@@ -156,8 +157,7 @@ static void Abort(const char* fmt, ...) {
class ProfileSessionImpl {
public:
ProfileSessionImpl(const std::string& app_data_dir)
- : app_data_dir_(app_data_dir),
- simpleperf_data_dir_(app_data_dir + "/simpleperf_data") {}
+ : app_data_dir_(app_data_dir), simpleperf_data_dir_(app_data_dir + "/simpleperf_data") {}
~ProfileSessionImpl();
void StartRecording(const std::vector<std::string>& args);
void PauseRecording();
@@ -200,7 +200,7 @@ ProfileSessionImpl::~ProfileSessionImpl() {
}
}
-void ProfileSessionImpl::StartRecording(const std::vector<std::string> &args) {
+void ProfileSessionImpl::StartRecording(const std::vector<std::string>& args) {
std::lock_guard<std::mutex> guard(lock_);
if (state_ != NOT_YET_STARTED) {
Abort("startRecording: session in wrong state %d", state_);
@@ -261,7 +261,7 @@ void ProfileSessionImpl::StopRecording() {
void ProfileSessionImpl::SendCmd(const std::string& cmd) {
std::string data = cmd + "\n";
if (TEMP_FAILURE_RETRY(write(control_fd_, &data[0], data.size())) !=
- static_cast<ssize_t>(data.size())) {
+ static_cast<ssize_t>(data.size())) {
Abort("failed to send cmd to simpleperf: %s", strerror(errno));
}
if (ReadReply() != "ok") {
@@ -393,8 +393,8 @@ void ProfileSessionImpl::CreateSimpleperfDataDir() {
}
}
-void ProfileSessionImpl::CreateSimpleperfProcess(const std::string &simpleperf_path,
- const std::vector<std::string> &record_args) {
+void ProfileSessionImpl::CreateSimpleperfProcess(const std::string& simpleperf_path,
+ const std::vector<std::string>& record_args) {
// 1. Create control/reply pips.
int control_fd[2];
int reply_fd[2];
@@ -477,6 +477,11 @@ ProfileSession::ProfileSession() {
}
}
std::string app_data_dir = "/data/data/" + s;
+ int uid = getuid();
+ if (uid >= AID_USER_OFFSET) {
+ int user_id = uid / AID_USER_OFFSET;
+ app_data_dir = "/data/user/" + std::to_string(user_id) + "/" + s;
+ }
impl_ = new ProfileSessionImpl(app_data_dir);
}
@@ -487,12 +492,12 @@ ProfileSession::~ProfileSession() {
delete impl_;
}
-void ProfileSession::StartRecording(const RecordOptions &options) {
+void ProfileSession::StartRecording(const RecordOptions& options) {
StartRecording(options.ToRecordArgs());
}
-void ProfileSession::StartRecording(const std::vector<std::string> &record_args) {
- impl_->StartRecording(record_args);
+void ProfileSession::StartRecording(const std::vector<std::string>& record_args) {
+ impl_->StartRecording(record_args);
}
void ProfileSession::PauseRecording() {
diff --git a/simpleperf/app_api/cpp/simpleperf.h b/simpleperf/app_api/cpp/simpleperf.h
index 309b37b7..074b7ad0 100644
--- a/simpleperf/app_api/cpp/simpleperf.h
+++ b/simpleperf/app_api/cpp/simpleperf.h
@@ -153,6 +153,7 @@ class ProfileSession {
* Stop recording and generate a recording file under appDataDir/simpleperf_data/.
*/
void StopRecording();
+
private:
ProfileSessionImpl* impl_;
};
diff --git a/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java b/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java
index 1c512c63..09896638 100644
--- a/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java
+++ b/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java
@@ -17,8 +17,13 @@
package com.android.simpleperf;
import android.os.Build;
+import android.system.Os;
import android.system.OsConstants;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
+
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
@@ -55,6 +60,7 @@ import java.util.stream.Collectors;
* process, filter logcat with `simpleperf`.
* </p>
*/
+@RequiresApi(28)
public class ProfileSession {
private static final String SIMPLEPERF_PATH_IN_IMAGE = "/system/bin/simpleperf";
@@ -65,27 +71,27 @@ public class ProfileSession {
STOPPED,
}
- private State state = State.NOT_YET_STARTED;
- private String appDataDir;
- private String simpleperfPath;
- private String simpleperfDataDir;
- private Process simpleperfProcess;
- private boolean traceOffcpu = false;
+ private State mState = State.NOT_YET_STARTED;
+ private final String mAppDataDir;
+ private String mSimpleperfPath;
+ private final String mSimpleperfDataDir;
+ private Process mSimpleperfProcess;
+ private boolean mTraceOffCpu = false;
/**
* @param appDataDir the same as android.content.Context.getDataDir().
* ProfileSession stores profiling data in appDataDir/simpleperf_data/.
*/
- public ProfileSession(String appDataDir) {
- this.appDataDir = appDataDir;
- simpleperfDataDir = appDataDir + "/simpleperf_data";
+ public ProfileSession(@NonNull String appDataDir) {
+ mAppDataDir = appDataDir;
+ mSimpleperfDataDir = appDataDir + "/simpleperf_data";
}
/**
* ProfileSession assumes appDataDir as /data/data/app_package_name.
*/
public ProfileSession() {
- String packageName = "";
+ String packageName;
try {
String s = readInputStream(new FileInputStream("/proc/self/cmdline"));
for (int i = 0; i < s.length(); i++) {
@@ -101,15 +107,22 @@ public class ProfileSession {
if (packageName.isEmpty()) {
throw new Error("failed to find packageName");
}
- appDataDir = "/data/data/" + packageName;
- simpleperfDataDir = appDataDir + "/simpleperf_data";
+ final int AID_USER_OFFSET = 100000;
+ int uid = Os.getuid();
+ if (uid >= AID_USER_OFFSET) {
+ int user_id = uid / AID_USER_OFFSET;
+ mAppDataDir = "/data/user/" + user_id + "/" + packageName;
+ } else {
+ mAppDataDir = "/data/data/" + packageName;
+ }
+ mSimpleperfDataDir = mAppDataDir + "/simpleperf_data";
}
/**
* Start recording.
* @param options RecordOptions
*/
- public void startRecording(RecordOptions options) {
+ public void startRecording(@NonNull RecordOptions options) {
startRecording(options.toRecordArgs());
}
@@ -117,77 +130,77 @@ public class ProfileSession {
* Start recording.
* @param args arguments for `simpleperf record` cmd.
*/
- public synchronized void startRecording(List<String> args) {
- if (state != State.NOT_YET_STARTED) {
- throw new AssertionError("startRecording: session in wrong state " + state);
+ public synchronized void startRecording(@NonNull List<String> args) {
+ if (mState != State.NOT_YET_STARTED) {
+ throw new AssertionError("startRecording: session in wrong state " + mState);
}
for (String arg : args) {
if (arg.equals("--trace-offcpu")) {
- traceOffcpu = true;
+ mTraceOffCpu = true;
}
}
- simpleperfPath = findSimpleperf();
+ mSimpleperfPath = findSimpleperf();
checkIfPerfEnabled();
createSimpleperfDataDir();
- createSimpleperfProcess(simpleperfPath, args);
- state = State.STARTED;
+ createSimpleperfProcess(mSimpleperfPath, args);
+ mState = State.STARTED;
}
/**
* Pause recording. No samples are generated in paused state.
*/
public synchronized void pauseRecording() {
- if (state != State.STARTED) {
- throw new AssertionError("pauseRecording: session in wrong state " + state);
+ if (mState != State.STARTED) {
+ throw new AssertionError("pauseRecording: session in wrong state " + mState);
}
- if (traceOffcpu) {
+ if (mTraceOffCpu) {
throw new AssertionError(
"--trace-offcpu option doesn't work well with pause/resume recording");
}
sendCmd("pause");
- state = State.PAUSED;
+ mState = State.PAUSED;
}
/**
* Resume a paused session.
*/
public synchronized void resumeRecording() {
- if (state != State.PAUSED) {
- throw new AssertionError("resumeRecording: session in wrong state " + state);
+ if (mState != State.PAUSED) {
+ throw new AssertionError("resumeRecording: session in wrong state " + mState);
}
sendCmd("resume");
- state = State.STARTED;
+ mState = State.STARTED;
}
/**
* Stop recording and generate a recording file under appDataDir/simpleperf_data/.
*/
public synchronized void stopRecording() {
- if (state != State.STARTED && state != State.PAUSED) {
- throw new AssertionError("stopRecording: session in wrong state " + state);
+ if (mState != State.STARTED && mState != State.PAUSED) {
+ throw new AssertionError("stopRecording: session in wrong state " + mState);
}
- if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P + 1 &&
- simpleperfPath.equals(SIMPLEPERF_PATH_IN_IMAGE)) {
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P + 1
+ && mSimpleperfPath.equals(SIMPLEPERF_PATH_IN_IMAGE)) {
// The simpleperf shipped on Android Q contains a bug, which may make it abort if
// calling simpleperfProcess.destroy().
destroySimpleperfProcessWithoutClosingStdin();
} else {
- simpleperfProcess.destroy();
+ mSimpleperfProcess.destroy();
}
try {
- int exitCode = simpleperfProcess.waitFor();
+ int exitCode = mSimpleperfProcess.waitFor();
if (exitCode != 0) {
throw new AssertionError("simpleperf exited with error: " + exitCode);
}
} catch (InterruptedException e) {
}
- simpleperfProcess = null;
- state = State.STOPPED;
+ mSimpleperfProcess = null;
+ mState = State.STOPPED;
}
private void destroySimpleperfProcessWithoutClosingStdin() {
// In format "Process[pid=? ..."
- String s = simpleperfProcess.toString();
+ String s = mSimpleperfProcess.toString();
final String prefix = "Process[pid=";
if (s.startsWith(prefix)) {
int startIndex = prefix.length();
@@ -198,7 +211,7 @@ public class ProfileSession {
return;
}
}
- simpleperfProcess.destroy();
+ mSimpleperfProcess.destroy();
}
private String readInputStream(InputStream in) {
@@ -225,19 +238,20 @@ public class ProfileSession {
throw new Error("can't find simpleperf on device. Please run api_profiler.py.");
}
- private boolean isExecutableFile(String path) {
+ private boolean isExecutableFile(@NonNull String path) {
File file = new File(path);
return file.canExecute();
}
+ @Nullable
private String findSimpleperfInTempDir() {
String path = "/data/local/tmp/simpleperf";
File file = new File(path);
- if (!file.isFile()){
+ if (!file.isFile()) {
return null;
}
// Copy it to app dir to execute it.
- String toPath = appDataDir + "/simpleperf";
+ String toPath = mAppDataDir + "/simpleperf";
try {
Process process = new ProcessBuilder()
.command("cp", path, toPath).start();
@@ -252,10 +266,10 @@ public class ProfileSession {
// For android R, app context isn't allowed to use perf_event_open.
// So test executing downloaded simpleperf.
try {
- Process process = new ProcessBuilder().command(toPath + "list sw").start();
+ Process process = new ProcessBuilder().command(toPath, "list", "sw").start();
process.waitFor();
String data = readInputStream(process.getInputStream());
- if (data.indexOf("cpu-clock") == -1) {
+ if (!data.contains("cpu-clock")) {
return null;
}
} catch (Exception e) {
@@ -265,7 +279,7 @@ public class ProfileSession {
}
private void checkIfPerfEnabled() {
- String value = "";
+ String value;
Process process;
try {
process = new ProcessBuilder()
@@ -280,13 +294,13 @@ public class ProfileSession {
}
value = readInputStream(process.getInputStream());
if (value.startsWith("1")) {
- throw new Error("linux perf events aren't enabled on the device." +
- " Please run api_profiler.py.");
+ throw new Error("linux perf events aren't enabled on the device."
+ + " Please run api_profiler.py.");
}
}
private void createSimpleperfDataDir() {
- File file = new File(simpleperfDataDir);
+ File file = new File(mSimpleperfDataDir);
if (!file.isDirectory()) {
file.mkdir();
}
@@ -307,9 +321,9 @@ public class ProfileSession {
args.addAll(recordArgs);
// 2. Create the simpleperf process.
- ProcessBuilder pb = new ProcessBuilder(args).directory(new File(simpleperfDataDir));
+ ProcessBuilder pb = new ProcessBuilder(args).directory(new File(mSimpleperfDataDir));
try {
- simpleperfProcess = pb.start();
+ mSimpleperfProcess = pb.start();
} catch (IOException e) {
throw new Error("failed to create simpleperf process: " + e.getMessage());
}
@@ -321,11 +335,11 @@ public class ProfileSession {
}
}
- private void sendCmd(String cmd) {
+ private void sendCmd(@NonNull String cmd) {
cmd += "\n";
try {
- simpleperfProcess.getOutputStream().write(cmd.getBytes());
- simpleperfProcess.getOutputStream().flush();
+ mSimpleperfProcess.getOutputStream().write(cmd.getBytes());
+ mSimpleperfProcess.getOutputStream().flush();
} catch (IOException e) {
throw new Error("failed to send cmd to simpleperf: " + e.getMessage());
}
@@ -334,6 +348,7 @@ public class ProfileSession {
}
}
+ @NonNull
private String readReply() {
// Read one byte at a time to stop at line break or EOF. BufferedReader will try to read
// more than available and make us blocking, so don't use it.
@@ -341,13 +356,13 @@ public class ProfileSession {
while (true) {
int c = -1;
try {
- c = simpleperfProcess.getInputStream().read();
+ c = mSimpleperfProcess.getInputStream().read();
} catch (IOException e) {
}
if (c == -1 || c == '\n') {
break;
}
- s += (char)c;
+ s += (char) c;
}
return s;
}
diff --git a/simpleperf/app_api/java/com/android/simpleperf/RecordOptions.java b/simpleperf/app_api/java/com/android/simpleperf/RecordOptions.java
index 3ed39fb7..ae65b944 100644
--- a/simpleperf/app_api/java/com/android/simpleperf/RecordOptions.java
+++ b/simpleperf/app_api/java/com/android/simpleperf/RecordOptions.java
@@ -18,6 +18,10 @@ package com.android.simpleperf;
import android.system.Os;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
+
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
@@ -39,30 +43,34 @@ import java.util.List;
* session.startRecording(options);
* </p>
*/
+@RequiresApi(28)
public class RecordOptions {
/**
* Set output filename. Default is perf-<month>-<day>-<hour>-<minute>-<second>.data.
* The file will be generated under simpleperf_data/.
*/
- public RecordOptions setOutputFilename(String filename) {
- outputFilename = filename;
+ @NonNull
+ public RecordOptions setOutputFilename(@NonNull String filename) {
+ mOutputFilename = filename;
return this;
}
/**
* Set event to record. Default is cpu-cycles. See `simpleperf list` for all available events.
*/
- public RecordOptions setEvent(String event) {
- this.event = event;
+ @NonNull
+ public RecordOptions setEvent(@NonNull String event) {
+ mEvent = event;
return this;
}
/**
* Set how many samples to generate each second running. Default is 4000.
*/
+ @NonNull
public RecordOptions setSampleFrequency(int freq) {
- this.freq = freq;
+ mFreq = freq;
return this;
}
@@ -70,86 +78,92 @@ public class RecordOptions {
* Set record duration. The record stops after `durationInSecond` seconds. By default,
* record stops only when stopRecording() is called.
*/
+ @NonNull
public RecordOptions setDuration(double durationInSecond) {
- this.durationInSecond = durationInSecond;
+ mDurationInSeconds = durationInSecond;
return this;
}
/**
* Record some threads in the app process. By default, record all threads in the process.
*/
- public RecordOptions setSampleThreads(List<Integer> threads) {
- this.threads.addAll(threads);
+ @NonNull
+ public RecordOptions setSampleThreads(@NonNull List<Integer> threads) {
+ mThreads.addAll(threads);
return this;
}
/**
* Record dwarf based call graph. It is needed to get Java callstacks.
*/
+ @NonNull
public RecordOptions recordDwarfCallGraph() {
- this.dwarfCallGraph = true;
- this.fpCallGraph = false;
+ mDwarfCallGraph = true;
+ mFpCallGraph = false;
return this;
}
/**
* Record frame pointer based call graph. It is suitable to get C++ callstacks on 64bit devices.
*/
+ @NonNull
public RecordOptions recordFramePointerCallGraph() {
- this.fpCallGraph = true;
- this.dwarfCallGraph = false;
+ mFpCallGraph = true;
+ mDwarfCallGraph = false;
return this;
}
/**
* Trace context switch info to show where threads spend time off cpu.
*/
+ @NonNull
public RecordOptions traceOffCpu() {
- this.traceOffCpu = true;
+ mTraceOffCpu = true;
return this;
}
/**
* Translate record options into arguments for `simpleperf record` cmd.
*/
+ @NonNull
public List<String> toRecordArgs() {
ArrayList<String> args = new ArrayList<>();
- String filename = outputFilename;
+ String filename = mOutputFilename;
if (filename == null) {
filename = getDefaultOutputFilename();
}
args.add("-o");
args.add(filename);
args.add("-e");
- args.add(event);
+ args.add(mEvent);
args.add("-f");
- args.add(String.valueOf(freq));
- if (durationInSecond != 0.0) {
+ args.add(String.valueOf(mFreq));
+ if (mDurationInSeconds != 0.0) {
args.add("--duration");
- args.add(String.valueOf(durationInSecond));
+ args.add(String.valueOf(mDurationInSeconds));
}
- if (threads.isEmpty()) {
+ if (mThreads.isEmpty()) {
args.add("-p");
args.add(String.valueOf(Os.getpid()));
} else {
String s = "";
- for (int i = 0; i < threads.size(); i++) {
+ for (int i = 0; i < mThreads.size(); i++) {
if (i > 0) {
s += ",";
}
- s += threads.get(i).toString();
+ s += mThreads.get(i).toString();
}
args.add("-t");
args.add(s);
}
- if (dwarfCallGraph) {
+ if (mDwarfCallGraph) {
args.add("-g");
- } else if (fpCallGraph) {
+ } else if (mFpCallGraph) {
args.add("--call-graph");
args.add("fp");
}
- if (traceOffCpu) {
+ if (mTraceOffCpu) {
args.add("--trace-offcpu");
}
return args;
@@ -161,12 +175,22 @@ public class RecordOptions {
return time.format(formatter);
}
- private String outputFilename;
- private String event = "cpu-cycles";
- private int freq = 4000;
- private double durationInSecond = 0.0;
- private ArrayList<Integer> threads = new ArrayList<>();
- private boolean dwarfCallGraph = false;
- private boolean fpCallGraph = false;
- private boolean traceOffCpu = false;
+ @Nullable
+ private String mOutputFilename;
+
+ @NonNull
+ private String mEvent = "cpu-cycles";
+
+ private int mFreq = 4000;
+
+ private double mDurationInSeconds = 0.0;
+
+ @NonNull
+ private ArrayList<Integer> mThreads = new ArrayList<>();
+
+ private boolean mDwarfCallGraph = false;
+
+ private boolean mFpCallGraph = false;
+
+ private boolean mTraceOffCpu = false;
}
diff --git a/simpleperf/build_id.h b/simpleperf/build_id.h
index 9f360bdb..05acc266 100644
--- a/simpleperf/build_id.h
+++ b/simpleperf/build_id.h
@@ -17,9 +17,11 @@
#ifndef SIMPLE_PERF_BUILD_ID_H_
#define SIMPLE_PERF_BUILD_ID_H_
+#include <android-base/stringprintf.h>
#include <string.h>
#include <algorithm>
-#include <android-base/stringprintf.h>
+
+namespace simpleperf {
constexpr size_t BUILD_ID_SIZE = 20;
@@ -29,13 +31,9 @@ constexpr size_t BUILD_ID_SIZE = 20;
// memory.
class BuildId {
public:
- static size_t Size() {
- return BUILD_ID_SIZE;
- }
+ static size_t Size() { return BUILD_ID_SIZE; }
- BuildId() {
- memset(data_, '\0', BUILD_ID_SIZE);
- }
+ BuildId() { memset(data_, '\0', BUILD_ID_SIZE); }
// Copy build id from a byte array, like {0x76, 0x00, 0x32,...}.
BuildId(const void* data, size_t len) : BuildId() {
@@ -60,9 +58,7 @@ class BuildId {
}
}
- const unsigned char* Data() const {
- return data_;
- }
+ const unsigned char* Data() const { return data_; }
std::string ToString() const {
std::string s = "0x";
@@ -76,9 +72,7 @@ class BuildId {
return memcmp(data_, build_id.data_, BUILD_ID_SIZE) == 0;
}
- bool operator!=(const BuildId& build_id) const {
- return !(*this == build_id);
- }
+ bool operator!=(const BuildId& build_id) const { return !(*this == build_id); }
bool IsEmpty() const {
static BuildId empty_build_id;
@@ -89,4 +83,11 @@ class BuildId {
unsigned char data_[BUILD_ID_SIZE];
};
+inline std::ostream& operator<<(std::ostream& os, const BuildId& build_id) {
+ os << build_id.ToString();
+ return os;
+}
+
+} // namespace simpleperf
+
#endif // SIMPLE_PERF_BUILD_ID_H_
diff --git a/simpleperf/callchain.h b/simpleperf/callchain.h
index b2a0457e..0a9f173b 100644
--- a/simpleperf/callchain.h
+++ b/simpleperf/callchain.h
@@ -27,6 +27,8 @@
#include <android-base/logging.h>
+namespace simpleperf {
+
template <typename EntryT>
struct CallChainNode {
uint64_t period;
@@ -46,9 +48,8 @@ struct CallChainRoot {
CallChainRoot() : duplicated(false), children_period(0) {}
- void AddCallChain(
- const std::vector<EntryT*>& callchain, uint64_t period,
- std::function<bool(const EntryT*, const EntryT*)> is_same_sample) {
+ void AddCallChain(const std::vector<EntryT*>& callchain, uint64_t period,
+ std::function<bool(const EntryT*, const EntryT*)> is_same_sample) {
children_period += period;
NodeT* p = FindMatchingNode(children, callchain[0], is_same_sample);
if (p == nullptr) {
@@ -58,8 +59,7 @@ struct CallChainRoot {
}
size_t callchain_pos = 0;
while (true) {
- size_t match_length =
- GetMatchingLengthInNode(p, callchain, callchain_pos, is_same_sample);
+ size_t match_length = GetMatchingLengthInNode(p, callchain, callchain_pos, is_same_sample);
CHECK_GT(match_length, 0u);
callchain_pos += match_length;
bool find_child = true;
@@ -73,15 +73,13 @@ struct CallChainRoot {
}
p->children_period += period;
if (find_child) {
- NodeT* np = FindMatchingNode(p->children, callchain[callchain_pos],
- is_same_sample);
+ NodeT* np = FindMatchingNode(p->children, callchain[callchain_pos], is_same_sample);
if (np != nullptr) {
p = np;
continue;
}
}
- std::unique_ptr<NodeT> new_node =
- AllocateNode(callchain, callchain_pos, period, 0);
+ std::unique_ptr<NodeT> new_node = AllocateNode(callchain, callchain_pos, period, 0);
p->children.push_back(std::move(new_node));
break;
}
@@ -103,9 +101,8 @@ struct CallChainRoot {
}
private:
- NodeT* FindMatchingNode(
- const std::vector<std::unique_ptr<NodeT>>& nodes, const EntryT* sample,
- std::function<bool(const EntryT*, const EntryT*)> is_same_sample) {
+ NodeT* FindMatchingNode(const std::vector<std::unique_ptr<NodeT>>& nodes, const EntryT* sample,
+ std::function<bool(const EntryT*, const EntryT*)> is_same_sample) {
for (auto& node : nodes) {
if (is_same_sample(node->chain.front(), sample)) {
return node.get();
@@ -114,12 +111,10 @@ struct CallChainRoot {
return nullptr;
}
- size_t GetMatchingLengthInNode(
- NodeT* node, const std::vector<EntryT*>& chain, size_t chain_start,
- std::function<bool(const EntryT*, const EntryT*)> is_same_sample) {
+ size_t GetMatchingLengthInNode(NodeT* node, const std::vector<EntryT*>& chain, size_t chain_start,
+ std::function<bool(const EntryT*, const EntryT*)> is_same_sample) {
size_t i, j;
- for (i = 0, j = chain_start; i < node->chain.size() && j < chain.size();
- ++i, ++j) {
+ for (i = 0, j = chain_start; i < node->chain.size() && j < chain.size(); ++i, ++j) {
if (!is_same_sample(node->chain[i], chain[j])) {
break;
}
@@ -128,8 +123,8 @@ struct CallChainRoot {
}
void SplitNode(NodeT* parent, size_t parent_length) {
- std::unique_ptr<NodeT> child = AllocateNode(
- parent->chain, parent_length, parent->period, parent->children_period);
+ std::unique_ptr<NodeT> child =
+ AllocateNode(parent->chain, parent_length, parent->period, parent->children_period);
child->children = std::move(parent->children);
parent->period = 0;
parent->children_period = child->period + child->children_period;
@@ -138,9 +133,8 @@ struct CallChainRoot {
parent->children.push_back(std::move(child));
}
- std::unique_ptr<NodeT> AllocateNode(const std::vector<EntryT*>& chain,
- size_t chain_start, uint64_t period,
- uint64_t children_period) {
+ std::unique_ptr<NodeT> AllocateNode(const std::vector<EntryT*>& chain, size_t chain_start,
+ uint64_t period, uint64_t children_period) {
std::unique_ptr<NodeT> node(new NodeT);
for (size_t i = chain_start; i < chain.size(); ++i) {
node->chain.push_back(chain[i]);
@@ -158,4 +152,6 @@ struct CallChainRoot {
}
};
+} // namespace simpleperf
+
#endif // SIMPLE_PERF_CALLCHAIN_H_
diff --git a/simpleperf/cmd_api.cpp b/simpleperf/cmd_api.cpp
index 7f933736..736f239c 100644
--- a/simpleperf/cmd_api.cpp
+++ b/simpleperf/cmd_api.cpp
@@ -28,21 +28,23 @@
#include <android-base/unique_fd.h>
#include <ziparchive/zip_writer.h>
+#include "cmd_api_impl.h"
#include "command.h"
-#include "event_type.h"
#include "environment.h"
+#include "event_type.h"
#include "utils.h"
#include "workload.h"
+namespace simpleperf {
namespace {
+
const std::string SIMPLEPERF_DATA_DIR = "simpleperf_data";
class PrepareCommand : public Command {
public:
PrepareCommand()
- : Command("api-prepare", "Prepare recording via app api",
- "Usage: simpleperf api-prepare\n"
- ) {}
+ : Command("api-prepare", "Prepare recording via app api", "Usage: simpleperf api-prepare\n") {
+ }
bool Run(const std::vector<std::string>& args);
};
@@ -52,12 +54,7 @@ bool PrepareCommand::Run(const std::vector<std::string>&) {
return false;
}
// Create tracepoint_events file.
- if (!android::base::WriteStringToFile(GetTracepointEvents(),
- "/data/local/tmp/tracepoint_events")) {
- PLOG(ERROR) << "failed to write tracepoint_events file";
- return false;
- }
- return true;
+ return EventTypeManager::Instance().WriteTracepointsToFile("/data/local/tmp/tracepoint_events");
}
class CollectCommand : public Command {
@@ -76,7 +73,8 @@ class CollectCommand : public Command {
"--stop-signal-fd <fd> Stop recording when fd is readable.\n"
#endif
// clang-format on
- ) {}
+ ) {
+ }
bool Run(const std::vector<std::string>& args);
private:
@@ -104,36 +102,30 @@ bool CollectCommand::Run(const std::vector<std::string>& args) {
}
bool CollectCommand::ParseOptions(const std::vector<std::string>& args) {
- for (size_t i = 0; i < args.size(); ++i) {
- if (args[i] == "--app") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- app_name_ = args[i];
- } else if (args[i] == "--in-app") {
- in_app_context_ = true;
- } else if (args[i] == "-o") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- output_filepath_ = args[i];
- } else if (args[i] == "--out-fd") {
- int fd;
- if (!GetUintOption(args, &i, &fd)) {
- return false;
- }
- out_fd_.reset(fd);
- } else if (args[i] == "--stop-signal-fd") {
- int fd;
- if (!GetUintOption(args, &i, &fd)) {
- return false;
- }
- stop_signal_fd_.reset(fd);
- } else {
- ReportUnknownOption(args, i);
- return false;
- }
+ OptionValueMap options;
+ std::vector<std::pair<OptionName, OptionValue>> ordered_options;
+ if (!PreprocessOptions(args, GetApiCollectCmdOptionFormats(), &options, &ordered_options,
+ nullptr)) {
+ return false;
+ }
+
+ if (auto value = options.PullValue("--app"); value) {
+ app_name_ = *value->str_value;
}
+ in_app_context_ = options.PullBoolValue("--in-app");
+
+ if (auto value = options.PullValue("-o"); value) {
+ output_filepath_ = *value->str_value;
+ }
+ if (auto value = options.PullValue("--out-fd"); value) {
+ out_fd_.reset(static_cast<int>(value->uint_value));
+ }
+ if (auto value = options.PullValue("--stop-signal-fd"); value) {
+ stop_signal_fd_.reset(static_cast<int>(value->uint_value));
+ }
+
+ CHECK(options.values.empty());
+ CHECK(ordered_options.empty());
if (!in_app_context_) {
if (app_name_.empty()) {
LOG(ERROR) << "--app is missing";
@@ -154,8 +146,8 @@ void CollectCommand::HandleStopSignal() {
}
bool CollectCommand::CollectRecordingData() {
- std::unique_ptr<FILE, decltype(&fclose)> fp(android::base::Fdopen(std::move(out_fd_), "w"),
- fclose);
+ std::unique_ptr<FILE, decltype(&fclose)> fp(android::base::Fdopen(std::move(out_fd_), "w"),
+ fclose);
if (fp == nullptr) {
PLOG(ERROR) << "failed to call fdopen";
return false;
@@ -216,8 +208,8 @@ bool CollectCommand::RemoveRecordingData() {
} // namespace
void RegisterAPICommands() {
- RegisterCommand("api-prepare",
- []{ return std::unique_ptr<Command>(new PrepareCommand()); });
- RegisterCommand("api-collect",
- []{ return std::unique_ptr<Command>(new CollectCommand()); });
+ RegisterCommand("api-prepare", [] { return std::unique_ptr<Command>(new PrepareCommand()); });
+ RegisterCommand("api-collect", [] { return std::unique_ptr<Command>(new CollectCommand()); });
}
+
+} // namespace simpleperf
diff --git a/simpleperf/cmd_api_impl.h b/simpleperf/cmd_api_impl.h
new file mode 100644
index 00000000..cca6fad6
--- /dev/null
+++ b/simpleperf/cmd_api_impl.h
@@ -0,0 +1,34 @@
+/*
+ * 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 "command.h"
+
+namespace simpleperf {
+
+inline const OptionFormatMap& GetApiCollectCmdOptionFormats() {
+ static const OptionFormatMap option_formats = {
+ {"--app", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::NOT_ALLOWED}},
+ {"--in-app", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"-o", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::NOT_ALLOWED}},
+ {"--out-fd", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::CHECK_FD}},
+ {"--stop-signal-fd", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::CHECK_FD}},
+ };
+ return option_formats;
+}
+
+} // namespace simpleperf
diff --git a/simpleperf/cmd_api_test.cpp b/simpleperf/cmd_api_test.cpp
index 4157bdef..716b383b 100644
--- a/simpleperf/cmd_api_test.cpp
+++ b/simpleperf/cmd_api_test.cpp
@@ -24,6 +24,8 @@
#include "test_util.h"
#include "utils.h"
+using namespace simpleperf;
+
#if defined(__ANDROID__)
static bool WaitUntilAppExit(const std::string& package_name) {
diff --git a/simpleperf/cmd_debug_unwind.cpp b/simpleperf/cmd_debug_unwind.cpp
index b5df2da3..9370a35e 100644
--- a/simpleperf/cmd_debug_unwind.cpp
+++ b/simpleperf/cmd_debug_unwind.cpp
@@ -14,10 +14,13 @@
* limitations under the License.
*/
+#include <stdio.h>
+
#include <algorithm>
#include <memory>
#include <string>
#include <unordered_map>
+#include <unordered_set>
#include <vector>
#include <android-base/file.h>
@@ -26,20 +29,18 @@
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
-#include "CallChainJoiner.h"
+#include "JITDebugReader.h"
+#include "OfflineUnwinder.h"
#include "command.h"
#include "environment.h"
-#include "OfflineUnwinder.h"
#include "perf_regs.h"
#include "record_file.h"
+#include "report_utils.h"
#include "thread_tree.h"
#include "utils.h"
-#include "workload.h"
-using namespace simpleperf;
-
-// Cache size used by CallChainJoiner to cache call chains in memory.
-constexpr size_t DEFAULT_CALL_CHAIN_JOINER_CACHE_SIZE = 8 * 1024 * 1024;
+namespace simpleperf {
+namespace {
struct MemStat {
std::string vm_peak;
@@ -56,7 +57,7 @@ struct MemStat {
static bool GetMemStat(MemStat* stat) {
std::string s;
if (!android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/status", getpid()),
- &s)) {
+ &s)) {
PLOG(ERROR) << "Failed to read process status";
return false;
}
@@ -75,337 +76,639 @@ static bool GetMemStat(MemStat* stat) {
return true;
}
-class DebugUnwindCommand : public Command {
- public:
- DebugUnwindCommand()
- : Command("debug-unwind", "Debug/test offline unwinding.",
- // clang-format off
-"Usage: simpleperf debug-unwind [options]\n"
-" Given a perf.data generated with \"-g --no-unwind\", it converts\n"
-" regs/stack data of samples into callchains, and write result into\n"
-" a new perf.data. The new perf.data can be passed to\n"
-" unwind_result_reporter.py to generate a text report.\n"
-"-i <file> The path of record file generated with \"-g --no-unwind\".\n"
-" Default is perf.data.\n"
-"-o <file> The path ot write new perf.data. Default is perf.data.debug.\n"
-"--symfs <dir> Look for files with symbols relative to this directory.\n"
-"--time time Only unwind samples recorded at selected time.\n"
- // clang-format on
- ),
- input_filename_("perf.data"),
- output_filename_("perf.data.debug"),
- offline_unwinder_(OfflineUnwinder::Create(true)),
- callchain_joiner_(DEFAULT_CALL_CHAIN_JOINER_CACHE_SIZE, 1, true),
- selected_time_(0) {
- }
-
- bool Run(const std::vector<std::string>& args);
+struct UnwindingStat {
+ // For testing unwinding performance
+ uint64_t unwinding_sample_count = 0u;
+ uint64_t total_unwinding_time_in_ns = 0u;
+ uint64_t max_unwinding_time_in_ns = 0u;
- private:
- bool ParseOptions(const std::vector<std::string>& args);
- bool UnwindRecordFile();
- bool ProcessRecord(Record* record);
- void CollectHitFileInfo(const SampleRecord& r, const std::vector<uint64_t>& ips);
- bool JoinCallChains();
- bool WriteFeatureSections();
- void PrintStat();
-
- struct Stat {
- // For testing unwinding performance.
- uint64_t unwinding_sample_count = 0u;
- uint64_t total_unwinding_time_in_ns = 0u;
- uint64_t max_unwinding_time_in_ns = 0u;
-
- // For memory consumption.
- MemStat mem_before_unwinding;
- MemStat mem_after_unwinding;
- };
+ // For memory consumption
+ MemStat mem_before_unwinding;
+ MemStat mem_after_unwinding;
- std::string input_filename_;
- std::string output_filename_;
- std::unique_ptr<RecordFileReader> reader_;
- std::unique_ptr<RecordFileWriter> writer_;
- ThreadTree thread_tree_;
- std::unique_ptr<OfflineUnwinder> offline_unwinder_;
- CallChainJoiner callchain_joiner_;
- Stat stat_;
- uint64_t selected_time_;
-};
+ void AddUnwindingResult(const UnwindingResult& result) {
+ unwinding_sample_count++;
+ total_unwinding_time_in_ns += result.used_time;
+ max_unwinding_time_in_ns = std::max(max_unwinding_time_in_ns, result.used_time);
+ }
-bool DebugUnwindCommand::Run(const std::vector<std::string>& args) {
- // 1. Parse options.
- if (!ParseOptions(args)) {
- return false;
+ void Dump(FILE* fp) {
+ if (unwinding_sample_count == 0) {
+ return;
+ }
+ fprintf(fp, "unwinding_sample_count: %" PRIu64 "\n", unwinding_sample_count);
+ fprintf(fp, "average_unwinding_time: %.3f us\n",
+ total_unwinding_time_in_ns / 1e3 / unwinding_sample_count);
+ fprintf(fp, "max_unwinding_time: %.3f us\n", max_unwinding_time_in_ns / 1e3);
+
+ if (!mem_before_unwinding.vm_peak.empty()) {
+ fprintf(fp, "memory_change_VmPeak: %s -> %s\n", mem_before_unwinding.vm_peak.c_str(),
+ mem_after_unwinding.vm_peak.c_str());
+ fprintf(fp, "memory_change_VmSize: %s -> %s\n", mem_before_unwinding.vm_size.c_str(),
+ mem_after_unwinding.vm_size.c_str());
+ fprintf(fp, "memory_change_VmHwM: %s -> %s\n", mem_before_unwinding.vm_hwm.c_str(),
+ mem_after_unwinding.vm_hwm.c_str());
+ fprintf(fp, "memory_change_VmRSS: %s -> %s\n", mem_before_unwinding.vm_rss.c_str(),
+ mem_after_unwinding.vm_rss.c_str());
+ }
}
- ScopedTempFiles scoped_temp_files(android::base::Dirname(output_filename_));
+};
- // 2. Read input perf.data, and generate new perf.data.
- if (!UnwindRecordFile()) {
- return false;
+class RecordFileProcessor {
+ public:
+ RecordFileProcessor(const std::string& output_filename, bool output_binary_mode)
+ : output_filename_(output_filename),
+ output_binary_mode_(output_binary_mode),
+ unwinder_(OfflineUnwinder::Create(true)),
+ callchain_report_builder_(thread_tree_) {}
+
+ virtual ~RecordFileProcessor() {
+ if (out_fp_ != nullptr && out_fp_ != stdout) {
+ fclose(out_fp_);
+ }
}
- // 3. Show stat of unwinding.
- PrintStat();
- return true;
-}
+ bool ProcessFile(const std::string& input_filename) {
+ // 1. Check input file.
+ record_filename_ = input_filename;
+ reader_ = RecordFileReader::CreateInstance(record_filename_);
+ if (!reader_) {
+ return false;
+ }
+ std::string record_cmd = android::base::Join(reader_->ReadCmdlineFeature(), " ");
+ if (record_cmd.find("-g") == std::string::npos &&
+ record_cmd.find("--call-graph dwarf") == std::string::npos) {
+ LOG(ERROR) << "file isn't recorded with dwarf call graph: " << record_filename_;
+ return false;
+ }
+ if (!CheckRecordCmd(record_cmd)) {
+ return false;
+ }
-bool DebugUnwindCommand::ParseOptions(const std::vector<std::string>& args) {
- for (size_t i = 0; i < args.size(); ++i) {
- if (args[i] == "-i") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- input_filename_ = args[i];
- } else if (args[i] == "-o") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- output_filename_ = args[i];
- } else if (args[i] == "--symfs") {
- if (!NextArgumentOrError(args, &i)) {
+ // 2. Load feature sections.
+ reader_->LoadBuildIdAndFileFeatures(thread_tree_);
+ ScopedCurrentArch scoped_arch(
+ GetArchType(reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH)));
+ unwinder_->LoadMetaInfo(reader_->GetMetaInfoFeature());
+ if (reader_->HasFeature(PerfFileFormat::FEAT_DEBUG_UNWIND) &&
+ reader_->HasFeature(PerfFileFormat::FEAT_DEBUG_UNWIND_FILE)) {
+ auto debug_unwind_feature = reader_->ReadDebugUnwindFeature();
+ if (!debug_unwind_feature.has_value()) {
return false;
}
- if (!Dso::SetSymFsDir(args[i])) {
- return false;
+ uint64_t offset =
+ reader_->FeatureSectionDescriptors().at(PerfFileFormat::FEAT_DEBUG_UNWIND_FILE).offset;
+ for (DebugUnwindFile& file : debug_unwind_feature.value()) {
+ auto& loc = debug_unwind_files_[file.path];
+ loc.offset = offset;
+ loc.size = file.size;
+ offset += file.size;
}
- } else if (args[i] == "--time") {
- if (!GetUintOption(args, &i, &selected_time_)) {
+ }
+ callchain_report_builder_.SetRemoveArtFrame(false);
+ callchain_report_builder_.SetConvertJITFrame(false);
+
+ // 3. Open output file.
+ if (output_filename_.empty()) {
+ out_fp_ = stdout;
+ } else {
+ out_fp_ = fopen(output_filename_.c_str(), output_binary_mode_ ? "web+" : "we+");
+ if (out_fp_ == nullptr) {
+ PLOG(ERROR) << "failed to write to " << output_filename_;
return false;
}
- } else {
- ReportUnknownOption(args, i);
- return false;
}
+
+ // 4. Process records.
+ return Process();
}
- return true;
+
+ protected:
+ struct DebugUnwindFileLocation {
+ uint64_t offset;
+ uint64_t size;
+ };
+
+ virtual bool CheckRecordCmd(const std::string& record_cmd) = 0;
+ virtual bool Process() = 0;
+
+ std::string record_filename_;
+ std::unique_ptr<RecordFileReader> reader_;
+ std::string output_filename_;
+ bool output_binary_mode_;
+ FILE* out_fp_ = nullptr;
+ ThreadTree thread_tree_;
+ std::unique_ptr<OfflineUnwinder> unwinder_;
+ // Files stored in DEBUG_UNWIND_FILE feature section in the recording file.
+ // Map from file path to offset in the recording file.
+ std::unordered_map<std::string, DebugUnwindFileLocation> debug_unwind_files_;
+ CallChainReportBuilder callchain_report_builder_;
+};
+
+static void DumpUnwindingResult(const UnwindingResult& result, FILE* fp) {
+ fprintf(fp, "unwinding_used_time: %.3f us\n", result.used_time / 1e3);
+ fprintf(fp, "unwinding_error_code: %" PRIu64 "\n", result.error_code);
+ fprintf(fp, "unwinding_error_addr: 0x%" PRIx64 "\n", result.error_addr);
+ fprintf(fp, "stack_start: 0x%" PRIx64 "\n", result.stack_start);
+ fprintf(fp, "stack_end: 0x%" PRIx64 "\n", result.stack_end);
}
-bool DebugUnwindCommand::UnwindRecordFile() {
- // 1. Check input file.
- reader_ = RecordFileReader::CreateInstance(input_filename_);
- if (!reader_) {
- return false;
- }
- reader_->LoadBuildIdAndFileFeatures(thread_tree_);
- std::string record_cmd = android::base::Join(reader_->ReadCmdlineFeature(), " ");
- if (record_cmd.find("--no-unwind") == std::string::npos ||
- (record_cmd.find("-g") == std::string::npos &&
- record_cmd.find("--call-graph dwarf") == std::string::npos)) {
- LOG(ERROR) << input_filename_ << " isn't recorded with \"-g --no-unwind\"";
- return false;
+class SampleUnwinder : public RecordFileProcessor {
+ public:
+ SampleUnwinder(const std::string& output_filename,
+ const std::unordered_set<uint64_t>& sample_times)
+ : RecordFileProcessor(output_filename, false), sample_times_(sample_times) {}
+
+ protected:
+ bool CheckRecordCmd(const std::string& record_cmd) override {
+ if (record_cmd.find("--no-unwind") == std::string::npos &&
+ record_cmd.find("--keep-failed-unwinding-debug-info") == std::string::npos) {
+ LOG(ERROR) << "file isn't record with --no-unwind or --keep-failed-unwinding-debug-info: "
+ << record_filename_;
+ return false;
+ }
+ return true;
}
- ScopedCurrentArch scoped_arch(GetArchType(reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH)));
- // 2. Copy attr section.
- writer_ = RecordFileWriter::CreateInstance(output_filename_);
- if (!writer_ || !writer_->WriteAttrSection(reader_->AttrSection())) {
- return false;
- }
+ bool Process() override {
+ recording_file_dso_ = Dso::CreateDso(DSO_ELF_FILE, record_filename_);
- // 3. Process records in data section.
- if (!GetMemStat(&stat_.mem_before_unwinding)) {
- return false;
+ if (!GetMemStat(&stat_.mem_before_unwinding)) {
+ return false;
+ }
+ if (!reader_->ReadDataSection(
+ [&](std::unique_ptr<Record> r) { return ProcessRecord(std::move(r)); })) {
+ return false;
+ }
+ if (!GetMemStat(&stat_.mem_after_unwinding)) {
+ return false;
+ }
+ stat_.Dump(out_fp_);
+ return true;
}
- auto callback = [this](std::unique_ptr<Record> record) {
- return ProcessRecord(record.get());
- };
- if (!reader_->ReadDataSection(callback)) {
- return false;
+
+ bool ProcessRecord(std::unique_ptr<Record> r) {
+ thread_tree_.Update(*r);
+ if (r->type() == SIMPLE_PERF_RECORD_UNWINDING_RESULT) {
+ last_unwinding_result_.reset(static_cast<UnwindingResultRecord*>(r.release()));
+ } else if (r->type() == PERF_RECORD_SAMPLE) {
+ if (sample_times_.empty() || sample_times_.count(r->Timestamp())) {
+ auto& sr = *static_cast<SampleRecord*>(r.get());
+ const PerfSampleStackUserType* stack = &sr.stack_user_data;
+ const PerfSampleRegsUserType* regs = &sr.regs_user_data;
+ if (last_unwinding_result_ && last_unwinding_result_->Timestamp() == sr.Timestamp()) {
+ stack = &last_unwinding_result_->stack_user_data;
+ regs = &last_unwinding_result_->regs_user_data;
+ }
+ if (stack->size > 0 || regs->reg_mask > 0) {
+ if (!UnwindRecord(sr, *regs, *stack)) {
+ return false;
+ }
+ }
+ }
+ last_unwinding_result_.reset();
+ }
+ return true;
}
- if (!JoinCallChains()) {
- return false;
+
+ bool UnwindRecord(const SampleRecord& r, const PerfSampleRegsUserType& regs,
+ const PerfSampleStackUserType& stack) {
+ ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
+ ThreadEntry thread_with_new_maps = CreateThreadWithUpdatedMaps(*thread);
+
+ RegSet reg_set(regs.abi, regs.reg_mask, regs.regs);
+ std::vector<uint64_t> ips;
+ std::vector<uint64_t> sps;
+ if (!unwinder_->UnwindCallChain(thread_with_new_maps, reg_set, stack.data, stack.size, &ips,
+ &sps)) {
+ return false;
+ }
+ stat_.AddUnwindingResult(unwinder_->GetUnwindingResult());
+
+ // Print unwinding result.
+ fprintf(out_fp_, "sample_time: %" PRIu64 "\n", r.Timestamp());
+ DumpUnwindingResult(unwinder_->GetUnwindingResult(), out_fp_);
+ std::vector<CallChainReportEntry> entries = callchain_report_builder_.Build(thread, ips, 0);
+ for (size_t i = 0; i < entries.size(); i++) {
+ size_t id = i + 1;
+ const auto& entry = entries[i];
+ fprintf(out_fp_, "ip_%zu: 0x%" PRIx64 "\n", id, entry.ip);
+ fprintf(out_fp_, "sp_%zu: 0x%" PRIx64 "\n", id, sps[i]);
+ fprintf(out_fp_, "map_%zu: [0x%" PRIx64 "-0x%" PRIx64 "], pgoff 0x%" PRIx64 "\n", id,
+ entry.map->start_addr, entry.map->get_end_addr(), entry.map->pgoff);
+ fprintf(out_fp_, "dso_%zu: %s\n", id, entry.map->dso->Path().c_str());
+ fprintf(out_fp_, "vaddr_in_file_%zu: 0x%" PRIx64 "\n", id, entry.vaddr_in_file);
+ fprintf(out_fp_, "symbol_%zu: %s\n", id, entry.symbol->DemangledName());
+ }
+ fprintf(out_fp_, "\n");
+ return true;
}
- if (!GetMemStat(&stat_.mem_after_unwinding)) {
- return false;
+
+ // To use files stored in DEBUG_UNWIND_FILE feature section, create maps mapping to them.
+ ThreadEntry CreateThreadWithUpdatedMaps(const ThreadEntry& thread) {
+ ThreadEntry new_thread = thread;
+ new_thread.maps.reset(new MapSet);
+ new_thread.maps->version = thread.maps->version;
+ for (auto& p : thread.maps->maps) {
+ const MapEntry* old_map = p.second;
+ MapEntry* map = nullptr;
+ const std::string& path = old_map->dso->Path();
+ if (auto it = debug_unwind_files_.find(path); it != debug_unwind_files_.end()) {
+ map_storage_.emplace_back(new MapEntry);
+ map = map_storage_.back().get();
+ *map = *old_map;
+ map->dso = recording_file_dso_.get();
+ if (JITDebugReader::IsPathInJITSymFile(old_map->dso->Path())) {
+ map->pgoff = it->second.offset;
+ } else {
+ map->pgoff += it->second.offset;
+ }
+ } else {
+ map = const_cast<MapEntry*>(p.second);
+ }
+ new_thread.maps->maps[p.first] = map;
+ }
+ return new_thread;
}
- // 4. Write feature sections.
- return WriteFeatureSections();
-}
+ private:
+ const std::unordered_set<uint64_t> sample_times_;
+ std::unique_ptr<Dso> recording_file_dso_;
+ std::vector<std::unique_ptr<MapEntry>> map_storage_;
+ UnwindingStat stat_;
+ std::unique_ptr<UnwindingResultRecord> last_unwinding_result_;
+};
-bool DebugUnwindCommand::ProcessRecord(Record* record) {
- if (record->type() == PERF_RECORD_SAMPLE) {
- auto& r = *static_cast<SampleRecord*>(record);
- if (selected_time_ != 0u && r.Timestamp() != selected_time_) {
- return true;
- }
- uint64_t need_type = PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
- if ((r.sample_type & need_type) == need_type && r.regs_user_data.reg_mask != 0 &&
- 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;
- std::vector<uint64_t> sps;
- if (!offline_unwinder_->UnwindCallChain(*thread, regs, r.stack_user_data.data,
- r.GetValidStackSize(), &ips, &sps)) {
- return false;
- }
+class TestFileGenerator : public RecordFileProcessor {
+ public:
+ TestFileGenerator(const std::string& output_filename,
+ const std::unordered_set<uint64_t>& sample_times,
+ const std::unordered_set<std::string>& kept_binaries)
+ : RecordFileProcessor(output_filename, true),
+ sample_times_(sample_times),
+ kept_binaries_(kept_binaries) {}
+
+ protected:
+ bool CheckRecordCmd(const std::string&) override { return true; }
+
+ bool Process() override {
+ writer_.reset(new RecordFileWriter(output_filename_, out_fp_, false));
+ if (!writer_ || !writer_->WriteAttrSection(reader_->AttrSection())) {
+ return false;
+ }
+ if (!reader_->ReadDataSection(
+ [&](std::unique_ptr<Record> r) { return ProcessRecord(std::move(r)); })) {
+ return false;
+ }
+ return WriteFeatureSections();
+ }
- const UnwindingResult& unwinding_result = offline_unwinder_->GetUnwindingResult();
- stat_.unwinding_sample_count++;
- stat_.total_unwinding_time_in_ns += unwinding_result.used_time;
- stat_.max_unwinding_time_in_ns = std::max(stat_.max_unwinding_time_in_ns,
- unwinding_result.used_time);
- if (!writer_->WriteRecord(UnwindingResultRecord(r.time_data.time, unwinding_result))) {
- return false;
- }
- // We want to keep both reg/stack data and callchain of a sample. However, storing both
- // can exceed the size limit of a SampleRecord. So instead we store one sample with reg/stack
- // data and one sample with callchain.
- if (!writer_->WriteRecord(r)) {
- return false;
+ bool ProcessRecord(std::unique_ptr<Record> r) {
+ thread_tree_.Update(*r);
+ bool keep_record = false;
+ if (r->type() == SIMPLE_PERF_RECORD_UNWINDING_RESULT) {
+ keep_record = (sample_times_.count(r->Timestamp()) > 0);
+ } else if (r->type() == PERF_RECORD_SAMPLE) {
+ keep_record = (sample_times_.count(r->Timestamp()) > 0);
+ if (keep_record) {
+ // Dump maps needed to unwind this sample.
+ if (!WriteMapsForSample(*static_cast<SampleRecord*>(r.get()))) {
+ return false;
+ }
}
- r.ReplaceRegAndStackWithCallChain(ips);
- if (!callchain_joiner_.AddCallChain(r.tid_data.pid, r.tid_data.tid,
- CallChainJoiner::ORIGINAL_OFFLINE, ips, sps)) {
- return false;
+ }
+ if (keep_record) {
+ return writer_->WriteRecord(*r);
+ }
+ return true;
+ }
+
+ 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];
+
+ size_t kernel_ip_count;
+ std::vector<uint64_t> ips = r.GetCallChain(&kernel_ip_count);
+ for (size_t i = kernel_ip_count; i < ips.size(); i++) {
+ const MapEntry* map = thread_tree_.FindMap(thread, ips[i], false);
+ if (!thread_tree_.IsUnknownDso(map->dso)) {
+ 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)) {
+ return false;
+ }
+ }
}
- CollectHitFileInfo(r, ips);
}
- } else {
- thread_tree_.Update(*record);
+ return true;
}
- return writer_->WriteRecord(*record);
-}
-void DebugUnwindCommand::CollectHitFileInfo(const SampleRecord& r,
- const std::vector<uint64_t>& ips) {
- const ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
- for (auto ip : ips) {
- const MapEntry* map = thread_tree_.FindMap(thread, ip, false);
- Dso* dso = map->dso;
- if (!dso->HasDumpId() && dso->type() != DSO_UNKNOWN_FILE) {
- dso->CreateDumpId();
+ bool WriteFeatureSections() {
+ if (!writer_->BeginWriteFeatures(reader_->FeatureSectionDescriptors().size())) {
+ return false;
}
- const Symbol* symbol = thread_tree_.FindSymbol(map, ip, nullptr, &dso);
- if (!symbol->HasDumpId()) {
- dso->CreateSymbolDumpId(symbol);
+ 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;
+ std::string buffer(BUFFER_SIZE, '\0');
+ for (const auto& p : reader_->FeatureSectionDescriptors()) {
+ auto feat_type = p.first;
+ if (feat_type == PerfFileFormat::FEAT_DEBUG_UNWIND) {
+ DebugUnwindFeature feature;
+ buffer.resize(BUFFER_SIZE);
+ for (const auto& file_p : debug_unwind_files_) {
+ if (kept_binaries_.count(file_p.first)) {
+ feature.resize(feature.size() + 1);
+ feature.back().path = file_p.first;
+ feature.back().size = file_p.second.size;
+ if (!CopyDebugUnwindFile(file_p.second, buffer)) {
+ return false;
+ }
+ }
+ }
+ if (!writer_->WriteDebugUnwindFeature(feature)) {
+ return false;
+ }
+ } else if (feat_type == PerfFileFormat::FEAT_FILE) {
+ size_t read_pos = 0;
+ FileFeature file_feature;
+ while (reader_->ReadFileFeature(read_pos, &file_feature)) {
+ if (kept_binaries_.count(file_feature.path) && !writer_->WriteFileFeature(file_feature)) {
+ return false;
+ }
+ }
+ } else if (feat_type == PerfFileFormat::FEAT_BUILD_ID) {
+ std::vector<BuildIdRecord> build_ids = reader_->ReadBuildIdFeature();
+ std::vector<BuildIdRecord> write_build_ids;
+ for (auto& build_id : build_ids) {
+ if (kept_binaries_.count(build_id.filename)) {
+ write_build_ids.emplace_back(std::move(build_id));
+ }
+ }
+ if (!writer_->WriteBuildIdFeature(write_build_ids)) {
+ return false;
+ }
+ } else if (feature_types_to_copy.count(feat_type)) {
+ if (!reader_->ReadFeatureSection(feat_type, &buffer) ||
+ !writer_->WriteFeature(feat_type, buffer.data(), buffer.size())) {
+ return false;
+ }
+ }
}
+ return writer_->EndWriteFeatures() && writer_->Close();
}
-}
-bool DebugUnwindCommand::JoinCallChains() {
- // 1. Prepare joined callchains.
- if (!callchain_joiner_.JoinCallChains()) {
- return false;
- }
- // 2. Move records from record_filename_ to a temporary file.
- if (!writer_->Close()) {
- return false;
+ bool CopyDebugUnwindFile(const DebugUnwindFileLocation& loc, std::string& buffer) {
+ uint64_t offset = loc.offset;
+ uint64_t left_size = loc.size;
+ while (left_size > 0) {
+ size_t nread = std::min<size_t>(left_size, buffer.size());
+ if (!reader_->ReadAtOffset(offset, buffer.data(), nread) ||
+ !writer_->WriteFeature(PerfFileFormat::FEAT_DEBUG_UNWIND_FILE, buffer.data(), nread)) {
+ return false;
+ }
+ offset += nread;
+ left_size -= nread;
+ }
+ return true;
}
- writer_.reset();
- std::unique_ptr<TemporaryFile> tmp_file = ScopedTempFiles::CreateTempFile();
- if (!Workload::RunCmd({"mv", output_filename_, tmp_file->path})) {
- return false;
+
+ private:
+ const std::unordered_set<uint64_t> sample_times_;
+ const std::unordered_set<std::string> kept_binaries_;
+ std::unique_ptr<RecordFileWriter> writer_;
+};
+
+class ReportGenerator : public RecordFileProcessor {
+ public:
+ ReportGenerator(const std::string& output_filename)
+ : RecordFileProcessor(output_filename, false) {}
+
+ protected:
+ bool CheckRecordCmd(const std::string& record_cmd) override {
+ if (record_cmd.find("--keep-failed-unwinding-debug-info") == std::string::npos &&
+ record_cmd.find("--keep-failed-unwinding-result") == std::string::npos) {
+ LOG(ERROR) << "file isn't record with --keep-failed-unwinding-debug-info or "
+ << "--keep-failed-unwinding-result: " << record_filename_;
+ return false;
+ }
+ return true;
}
- // 3. Read records from the temporary file, and write records with joined call chains back
- // to record_filename_.
- std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmp_file->path);
- if (!reader) {
- return false;
+ bool Process() override {
+ if (!reader_->ReadDataSection(
+ [&](std::unique_ptr<Record> r) { return ProcessRecord(std::move(r)); })) {
+ return false;
+ }
+ return true;
}
- writer_ = RecordFileWriter::CreateInstance(output_filename_);
- if (!writer_ || !writer_->WriteAttrSection(reader->AttrSection())) {
- return false;
+
+ private:
+ bool ProcessRecord(std::unique_ptr<Record> r) {
+ thread_tree_.Update(*r);
+ if (r->type() == SIMPLE_PERF_RECORD_UNWINDING_RESULT) {
+ last_unwinding_result_.reset(static_cast<UnwindingResultRecord*>(r.release()));
+ } else if (r->type() == PERF_RECORD_SAMPLE) {
+ if (last_unwinding_result_) {
+ ReportUnwindingResult(*static_cast<SampleRecord*>(r.get()), *last_unwinding_result_);
+ last_unwinding_result_.reset();
+ }
+ }
+ return true;
}
- auto record_callback = [&](std::unique_ptr<Record> r) {
- if (r->type() != PERF_RECORD_SAMPLE) {
- return writer_->WriteRecord(*r);
+ void ReportUnwindingResult(const SampleRecord& sr, const UnwindingResultRecord& unwinding_r) {
+ ThreadEntry* thread = thread_tree_.FindThreadOrNew(sr.tid_data.pid, sr.tid_data.tid);
+ size_t kernel_ip_count;
+ std::vector<uint64_t> ips = sr.GetCallChain(&kernel_ip_count);
+ if (kernel_ip_count != 0) {
+ ips.erase(ips.begin(), ips.begin() + kernel_ip_count);
}
- SampleRecord& sr = *static_cast<SampleRecord*>(r.get());
- if (!sr.HasUserCallChain()) {
- return writer_->WriteRecord(sr);
+
+ fprintf(out_fp_, "sample_time: %" PRIu64 "\n", sr.Timestamp());
+ DumpUnwindingResult(unwinding_r.unwinding_result, out_fp_);
+ // Print callchain.
+ std::vector<CallChainReportEntry> entries = callchain_report_builder_.Build(thread, ips, 0);
+ for (size_t i = 0; i < entries.size(); i++) {
+ size_t id = i + 1;
+ const auto& entry = entries[i];
+ fprintf(out_fp_, "ip_%zu: 0x%" PRIx64 "\n", id, entry.ip);
+ if (i < unwinding_r.callchain.length) {
+ fprintf(out_fp_, "unwinding_ip_%zu: 0x%" PRIx64 "\n", id, unwinding_r.callchain.ips[i]);
+ fprintf(out_fp_, "unwinding_sp_%zu: 0x%" PRIx64 "\n", id, unwinding_r.callchain.sps[i]);
+ }
+ fprintf(out_fp_, "map_%zu: [0x%" PRIx64 "-0x%" PRIx64 "], pgoff 0x%" PRIx64 "\n", id,
+ entry.map->start_addr, entry.map->get_end_addr(), entry.map->pgoff);
+ fprintf(out_fp_, "dso_%zu: %s\n", id, entry.map->dso->Path().c_str());
+ fprintf(out_fp_, "vaddr_in_file_%zu: 0x%" PRIx64 "\n", id, entry.vaddr_in_file);
+ fprintf(out_fp_, "symbol_%zu: %s\n", id, entry.symbol->DemangledName());
}
- pid_t pid;
- pid_t tid;
- CallChainJoiner::ChainType type;
- std::vector<uint64_t> ips;
- std::vector<uint64_t> sps;
- do {
- if (!callchain_joiner_.GetNextCallChain(pid, tid, type, ips, sps)) {
- return false;
+ // Print regs.
+ uint64_t stack_addr = 0;
+ if (unwinding_r.regs_user_data.reg_nr > 0) {
+ auto& reg_data = unwinding_r.regs_user_data;
+ RegSet regs(reg_data.abi, reg_data.reg_mask, reg_data.regs);
+ uint64_t value;
+ if (regs.GetSpRegValue(&value)) {
+ stack_addr = value;
+ for (size_t i = 0; i < 64; i++) {
+ if (regs.GetRegValue(i, &value)) {
+ fprintf(out_fp_, "reg_%s: 0x%" PRIx64 "\n", GetRegName(i, regs.arch).c_str(), value);
+ }
+ }
}
- if (!writer_->WriteRecord(CallChainRecord(pid, tid, type, sr.Timestamp(), ips, sps))) {
- return false;
+ }
+ // Print stack.
+ if (unwinding_r.stack_user_data.size > 0) {
+ auto& stack = unwinding_r.stack_user_data;
+ const char* p = stack.data;
+ const char* end = stack.data + stack.size;
+ uint64_t value;
+ while (p + 8 <= end) {
+ fprintf(out_fp_, "stack_%" PRIx64 ":", stack_addr);
+ for (size_t i = 0; i < 4 && p + 8 <= end; ++i) {
+ MoveFromBinaryFormat(value, p);
+ fprintf(out_fp_, " %016" PRIx64, value);
+ }
+ fprintf(out_fp_, "\n");
+ stack_addr += 32;
}
- } while (type != CallChainJoiner::JOINED_OFFLINE);
- CHECK_EQ(pid, static_cast<pid_t>(sr.tid_data.pid));
- CHECK_EQ(tid, static_cast<pid_t>(sr.tid_data.tid));
- sr.UpdateUserCallChain(ips);
- return writer_->WriteRecord(sr);
- };
- return reader->ReadDataSection(record_callback);
-}
-
-bool DebugUnwindCommand::WriteFeatureSections() {
- // Add debug_unwind info in META_INFO section, and add symbol info in FILE section.
- const std::map<int, PerfFileFormat::SectionDesc>& features = reader_->FeatureSectionDescriptors();
- size_t new_feature_count = features.size();
- for (int feature : {PerfFileFormat::FEAT_FILE, PerfFileFormat::FEAT_META_INFO}) {
- if (features.find(feature) == features.end()) {
- new_feature_count++;
+ fprintf(out_fp_, "\n");
}
}
- if (!writer_->BeginWriteFeatures(new_feature_count)) {
+
+ std::unique_ptr<UnwindingResultRecord> last_unwinding_result_;
+};
+
+class DebugUnwindCommand : public Command {
+ public:
+ DebugUnwindCommand()
+ : Command(
+ "debug-unwind", "Debug/test offline unwinding.",
+ // clang-format off
+"Usage: simpleperf debug-unwind [options]\n"
+"--generate-report Generate a failed unwinding report.\n"
+"--generate-test-file Generate a test file with only one sample.\n"
+"-i <file> Input recording file. Default is perf.data.\n"
+"-o <file> Output file. Default is stdout.\n"
+"--keep-binaries-in-test-file binary1,binary2... Keep binaries in test file.\n"
+"--sample-time time1,time2... Only process samples recorded at selected times.\n"
+"--symfs <dir> Look for files with symbols relative to this directory.\n"
+"--unwind-sample Unwind samples.\n"
+"\n"
+"Examples:\n"
+"1. Unwind a sample.\n"
+"$ simpleperf debug-unwind -i perf.data --unwind-sample --sample-time 626970493946976\n"
+" perf.data should be generated with \"--no-unwind\" or \"--keep-failed-unwinding-debug-info\".\n"
+"2. Generate a test file.\n"
+"$ simpleperf debug-unwind -i perf.data --generate-test-file -o test.data --sample-time \\\n"
+" 626970493946976 --keep-binaries-in-test-file perf.data_jit_app_cache:255984-259968\n"
+"3. Generate a failed unwinding report.\n"
+"$ simpleperf debug-unwind -i perf.data --generate-report -o report.txt\n"
+" perf.data should be generated with \"--keep-failed-unwinding-debug-info\" or \\\n"
+" \"--keep-failed-unwinding-result\".\n"
+"\n"
+ // clang-format on
+ ) {}
+
+ bool Run(const std::vector<std::string>& args);
+
+ private:
+ bool ParseOptions(const std::vector<std::string>& args);
+
+ std::string input_filename_ = "perf.data";
+ std::string output_filename_;
+ bool unwind_sample_ = false;
+ bool generate_report_ = false;
+ bool generate_test_file_;
+ std::unordered_set<std::string> kept_binaries_in_test_file_;
+ std::unordered_set<uint64_t> sample_times_;
+};
+
+bool DebugUnwindCommand::Run(const std::vector<std::string>& args) {
+ // 1. Parse options.
+ if (!ParseOptions(args)) {
return false;
}
- auto it = features.begin();
- // Copy all feature sections except FEAT_FILE and FEAT_META_INFO, which require special handling.
- while (it != features.end() && it->first < PerfFileFormat::FEAT_FILE) {
- std::vector<char> data;
- if (!reader_->ReadFeatureSection(it->first, &data) || !writer_->WriteFeature(it->first, data)) {
- return false;
- }
- ++it;
+ // 2. Distribute sub commands.
+ if (unwind_sample_) {
+ SampleUnwinder sample_unwinder(output_filename_, sample_times_);
+ return sample_unwinder.ProcessFile(input_filename_);
}
- // Write a new file section.
- if (it != features.end() && it->first == PerfFileFormat::FEAT_FILE) {
- ++it;
+ if (generate_test_file_) {
+ TestFileGenerator test_file_generator(output_filename_, sample_times_,
+ kept_binaries_in_test_file_);
+ return test_file_generator.ProcessFile(input_filename_);
}
- if (!writer_->WriteFileFeatures(thread_tree_.GetAllDsos())) {
+ if (generate_report_) {
+ ReportGenerator report_generator(output_filename_);
+ return report_generator.ProcessFile(input_filename_);
+ }
+ return true;
+}
+
+bool DebugUnwindCommand::ParseOptions(const std::vector<std::string>& args) {
+ const OptionFormatMap option_formats = {
+ {"--generate-report", {OptionValueType::NONE, OptionType::SINGLE}},
+ {"--generate-test-file", {OptionValueType::NONE, OptionType::SINGLE}},
+ {"-i", {OptionValueType::STRING, OptionType::SINGLE}},
+ {"--keep-binaries-in-test-file", {OptionValueType::STRING, OptionType::MULTIPLE}},
+ {"-o", {OptionValueType::STRING, OptionType::SINGLE}},
+ {"--sample-time", {OptionValueType::STRING, OptionType::MULTIPLE}},
+ {"--symfs", {OptionValueType::STRING, OptionType::MULTIPLE}},
+ {"--unwind-sample", {OptionValueType::NONE, OptionType::SINGLE}},
+ };
+ OptionValueMap options;
+ std::vector<std::pair<OptionName, OptionValue>> ordered_options;
+ if (!PreprocessOptions(args, option_formats, &options, &ordered_options)) {
return false;
}
- // Write meta_info section.
- std::unordered_map<std::string, std::string> info_map;
- if (it != features.end() && it->first == PerfFileFormat::FEAT_META_INFO) {
- info_map = reader_->GetMetaInfoFeature();
- ++it;
+ generate_report_ = options.PullBoolValue("--generate-report");
+ generate_test_file_ = options.PullBoolValue("--generate-test-file");
+ options.PullStringValue("-i", &input_filename_);
+ for (auto& value : options.PullValues("--keep-binaries-in-test-file")) {
+ std::vector<std::string> binaries = android::base::Split(*value.str_value, ",");
+ kept_binaries_in_test_file_.insert(binaries.begin(), binaries.end());
}
- info_map["debug_unwind"] = "true";
- info_map["debug_unwind_mem_before"] = stat_.mem_before_unwinding.ToString();
- info_map["debug_unwind_mem_after"] = stat_.mem_after_unwinding.ToString();
- if (!writer_->WriteMetaInfoFeature(info_map)) {
- return false;
+ options.PullStringValue("-o", &output_filename_);
+ for (auto& value : options.PullValues("--sample-time")) {
+ auto times = ParseUintVector<uint64_t>(*value.str_value);
+ if (!times) {
+ return false;
+ }
+ sample_times_.insert(times.value().begin(), times.value().end());
}
- CHECK(it == features.end());
- return writer_->EndWriteFeatures() && writer_->Close();
-}
+ if (auto value = options.PullValue("--symfs"); value) {
+ if (!Dso::SetSymFsDir(*value->str_value)) {
+ return false;
+ }
+ }
+ unwind_sample_ = options.PullBoolValue("--unwind-sample");
+ CHECK(options.values.empty());
-void DebugUnwindCommand::PrintStat() {
- printf("Unwinding sample count: %" PRIu64 "\n", stat_.unwinding_sample_count);
- if (stat_.unwinding_sample_count > 0u) {
- printf("Average unwinding time: %f us\n", static_cast<double>(stat_.total_unwinding_time_in_ns)
- / 1000 / stat_.unwinding_sample_count);
- printf("Max unwinding time: %f us\n", static_cast<double>(stat_.max_unwinding_time_in_ns)
- / 1000);
+ if (generate_test_file_) {
+ if (output_filename_.empty()) {
+ LOG(ERROR) << "no output path for generated test file";
+ return false;
+ }
+ if (sample_times_.empty()) {
+ LOG(ERROR) << "no samples are selected via --sample-time";
+ return false;
+ }
}
- printf("Memory change:\n");
- PrintIndented(1, "VmPeak: %s -> %s\n", stat_.mem_before_unwinding.vm_peak.c_str(),
- stat_.mem_after_unwinding.vm_peak.c_str());
- PrintIndented(1, "VmSize: %s -> %s\n", stat_.mem_before_unwinding.vm_size.c_str(),
- stat_.mem_after_unwinding.vm_size.c_str());
- PrintIndented(1, "VmHWM: %s -> %s\n", stat_.mem_before_unwinding.vm_hwm.c_str(),
- stat_.mem_after_unwinding.vm_hwm.c_str());
- PrintIndented(1, "VmRSS: %s -> %s\n", stat_.mem_before_unwinding.vm_rss.c_str(),
- stat_.mem_after_unwinding.vm_rss.c_str());
- callchain_joiner_.DumpStat();
- printf("Please use debug_unwind_reporter.py to get a report in details.\n");
+
+ return true;
}
+} // namespace
+
void RegisterDebugUnwindCommand() {
RegisterCommand("debug-unwind",
- []{ return std::unique_ptr<Command>(new DebugUnwindCommand()); });
+ [] { return std::unique_ptr<Command>(new DebugUnwindCommand()); });
}
+
+} // namespace simpleperf
diff --git a/simpleperf/cmd_debug_unwind_test.cpp b/simpleperf/cmd_debug_unwind_test.cpp
index 20a441ec..85964a41 100644
--- a/simpleperf/cmd_debug_unwind_test.cpp
+++ b/simpleperf/cmd_debug_unwind_test.cpp
@@ -29,48 +29,63 @@
#include "record_file.h"
#include "test_util.h"
+using namespace simpleperf;
+
static std::unique_ptr<Command> DebugUnwindCmd() {
return CreateCommandInstance("debug-unwind");
}
-TEST(cmd_debug_unwind, smoke) {
+TEST(cmd_debug_unwind, unwind_sample_option) {
std::string input_data = GetTestData(PERF_DATA_NO_UNWIND);
CaptureStdout capture;
- TemporaryFile tmp_file;
+
ASSERT_TRUE(capture.Start());
- ASSERT_TRUE(DebugUnwindCmd()->Run({"-i", input_data, "-o", tmp_file.path}));
- ASSERT_NE(capture.Finish().find("Unwinding sample count: 8"), std::string::npos);
+ ASSERT_TRUE(DebugUnwindCmd()->Run({"-i", input_data, "--unwind-sample"}));
+ ASSERT_NE(capture.Finish().find("sample_time: 1516379654300997"), std::string::npos);
+}
+
+TEST(cmd_debug_unwind, sample_time_option) {
+ std::string input_data = GetTestData(PERF_DATA_NO_UNWIND);
+ CaptureStdout capture;
ASSERT_TRUE(capture.Start());
- ASSERT_TRUE(DebugUnwindCmd()->Run({"-i", input_data, "-o", tmp_file.path, "--time",
- "1516379654300997"}));
- ASSERT_NE(capture.Finish().find("Unwinding sample count: 1"), std::string::npos);
+ ASSERT_TRUE(DebugUnwindCmd()->Run({"-i", input_data, "--unwind-sample", "--sample-time",
+ "1516379654300997", "--sample-time",
+ "1516379654363914,1516379655959122"}));
+ std::string output = capture.Finish();
+ ASSERT_NE(output.find("sample_time: 1516379654300997"), std::string::npos);
+ ASSERT_NE(output.find("sample_time: 1516379654363914"), std::string::npos);
+ ASSERT_NE(output.find("sample_time: 1516379655959122"), std::string::npos);
+}
+
+TEST(cmd_debug_unwind, output_option) {
+ std::string input_data = GetTestData(PERF_DATA_NO_UNWIND);
+ TemporaryFile tmpfile;
+ close(tmpfile.release());
+ ASSERT_TRUE(DebugUnwindCmd()->Run({"-i", input_data, "--unwind-sample", "--sample-time",
+ "1516379654300997", "-o", tmpfile.path}));
+ std::string output;
+ ASSERT_TRUE(android::base::ReadFileToString(tmpfile.path, &output));
+ ASSERT_NE(output.find("sample_time: 1516379654300997"), std::string::npos);
}
TEST(cmd_debug_unwind, symfs_option) {
std::string input_data = GetTestData(NATIVELIB_IN_APK_PERF_DATA);
CaptureStdout capture;
- TemporaryFile tmp_file;
ASSERT_TRUE(capture.Start());
- ASSERT_TRUE(DebugUnwindCmd()->Run({"-i", input_data, "-o", tmp_file.path, "--symfs",
- GetTestDataDir()}));
- ASSERT_NE(capture.Finish().find("Unwinding sample count: 55"), std::string::npos);
- std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmp_file.path);
- ASSERT_TRUE(reader);
- const std::map<int, PerfFileFormat::SectionDesc>& features = reader->FeatureSectionDescriptors();
- ASSERT_NE(features.find(PerfFileFormat::FEAT_FILE), features.end());
- ASSERT_NE(features.find(PerfFileFormat::FEAT_META_INFO), features.end());
- auto meta_info = reader->GetMetaInfoFeature();
- ASSERT_EQ(meta_info["debug_unwind"], "true");
+ ASSERT_TRUE(DebugUnwindCmd()->Run({"-i", input_data, "--symfs", GetTestDataDir(),
+ "--unwind-sample", "--sample-time", "500329355223"}));
+ ASSERT_NE(capture.Finish().find(
+ "dso_4: /data/app/com.example.hellojni-1/base.apk!/lib/arm64-v8a/libhello-jni.so"),
+ std::string::npos);
}
TEST(cmd_debug_unwind, unwind_with_ip_zero_in_callchain) {
- TemporaryFile tmp_file;
CaptureStdout capture;
ASSERT_TRUE(capture.Start());
ASSERT_TRUE(DebugUnwindCmd()->Run({"-i", GetTestData(PERF_DATA_WITH_IP_ZERO_IN_CALLCHAIN),
- "-o", tmp_file.path}));
- ASSERT_NE(capture.Finish().find("Unwinding sample count: 1"), std::string::npos);
+ "--unwind-sample", "--sample-time", "152526249937103"}));
+ ASSERT_NE(capture.Finish().find("sample_time: 152526249937103"), std::string::npos);
}
TEST(cmd_debug_unwind, unwind_embedded_lib_in_apk) {
@@ -78,14 +93,66 @@ TEST(cmd_debug_unwind, unwind_embedded_lib_in_apk) {
// file, there is a sample with ip address pointing to
// /data/app/simpleperf.demo.cpp_api/base.apk!/lib/arm64-v8a/libnative-lib.so.
// If unwound successfully, it can reach a function in libc.so.
- TemporaryFile tmp_file;
+ CaptureStdout capture;
+ ASSERT_TRUE(capture.Start());
ASSERT_TRUE(DebugUnwindCmd()->Run({"-i", GetTestData("perf_unwind_embedded_lib_in_apk.data"),
- "--symfs", GetTestDataDir(), "-o", tmp_file.path}));
+ "--symfs", GetTestDataDir(), "--unwind-sample",
+ "--sample-time", "20345907755421"}));
+ std::string output = capture.Finish();
+ ASSERT_NE(
+ output.find(
+ "dso_1: /data/app/simpleperf.demo.cpp_api/base.apk!/lib/arm64-v8a/libnative-lib.so"),
+ std::string::npos)
+ << output;
+ ASSERT_NE(output.find("dso_2: /bionic/lib64/libc.so"), std::string::npos) << output;
+}
+
+TEST(cmd_debug_unwind, unwind_sample_in_unwinding_debug_info_file) {
CaptureStdout capture;
ASSERT_TRUE(capture.Start());
- ASSERT_TRUE(CreateCommandInstance("report-sample")->Run(
- {"--show-callchain", "-i", tmp_file.path}));
+ ASSERT_TRUE(DebugUnwindCmd()->Run(
+ {"-i", GetTestData("perf_with_failed_unwinding_debug_info.data"), "--unwind-sample"}));
std::string output = capture.Finish();
- ASSERT_NE(output.find("libnative-lib.so"), std::string::npos);
- ASSERT_NE(output.find("libc.so"), std::string::npos);
+ ASSERT_NE(output.find("symbol_5: android.os.Handler.post"), std::string::npos) << output;
+}
+
+TEST(cmd_debug_unwind, generate_test_file) {
+ TemporaryFile tmpfile;
+ close(tmpfile.release());
+ ASSERT_TRUE(DebugUnwindCmd()->Run(
+ {"-i", GetTestData("perf_with_failed_unwinding_debug_info.data"), "--generate-test-file",
+ "--sample-time", "626968783364202", "-o", tmpfile.path, "--keep-binaries-in-test-file",
+ "perf.data_jit_app_cache:255984-259968,perf.data_jit_app_cache:280144-283632"}));
+
+ CaptureStdout capture;
+ ASSERT_TRUE(capture.Start());
+ ASSERT_TRUE(DebugUnwindCmd()->Run({"-i", tmpfile.path, "--unwind-sample"}));
+ std::string output = capture.Finish();
+ ASSERT_NE(output.find("symbol_2: android.os.Handler.enqueueMessage"), std::string::npos);
+}
+
+TEST(cmd_debug_unwind, generate_test_file_with_build_id) {
+ TemporaryFile tmpfile;
+ close(tmpfile.release());
+ ASSERT_TRUE(DebugUnwindCmd()->Run({"-i", GetTestData("perf_display_bitmaps.data"),
+ "--generate-test-file", "--sample-time", "684943450156904",
+ "-o", tmpfile.path, "--keep-binaries-in-test-file",
+ "/apex/com.android.runtime/lib64/bionic/libc.so"}));
+ auto reader = RecordFileReader::CreateInstance(tmpfile.path);
+ ASSERT_TRUE(reader);
+ auto build_ids = reader->ReadBuildIdFeature();
+ ASSERT_EQ(build_ids.size(), 1);
+ ASSERT_STREQ(build_ids[0].filename, "/apex/com.android.runtime/lib64/bionic/libc.so");
+}
+
+TEST(cmd_debug_unwind, generate_report) {
+ TemporaryFile tmpfile;
+ close(tmpfile.release());
+ ASSERT_TRUE(
+ DebugUnwindCmd()->Run({"-i", GetTestData("perf_with_failed_unwinding_debug_info.data"),
+ "--generate-report", "-o", tmpfile.path}));
+ std::string output;
+ ASSERT_TRUE(android::base::ReadFileToString(tmpfile.path, &output));
+ ASSERT_NE(output.find("unwinding_error_code: 4"), std::string::npos);
+ ASSERT_NE(output.find("symbol_2: android.os.Handler.enqueueMessage"), std::string::npos);
}
diff --git a/simpleperf/cmd_dumprecord.cpp b/simpleperf/cmd_dumprecord.cpp
index 22a242e3..9d8cba90 100644
--- a/simpleperf/cmd_dumprecord.cpp
+++ b/simpleperf/cmd_dumprecord.cpp
@@ -18,24 +18,164 @@
#include <map>
#include <string>
+#include <type_traits>
#include <vector>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#include "ETMDecoder.h"
#include "command.h"
#include "dso.h"
-#include "ETMDecoder.h"
#include "event_attr.h"
#include "event_type.h"
#include "perf_regs.h"
#include "record.h"
#include "record_file.h"
+#include "tracing.h"
#include "utils.h"
+namespace simpleperf {
+namespace {
+
using namespace PerfFileFormat;
-using namespace simpleperf;
+
+struct SymbolInfo {
+ Dso* dso;
+ const Symbol* symbol;
+ uint64_t vaddr_in_file;
+};
+
+using ExtractFieldFn = std::function<std::string(const TracingField&, const PerfSampleRawType&)>;
+
+struct EventInfo {
+ size_t tp_data_size = 0;
+ std::vector<TracingField> tp_fields;
+ std::vector<ExtractFieldFn> extract_field_functions;
+};
+
+std::string ExtractStringField(const TracingField& field, const PerfSampleRawType& data) {
+ std::string s;
+ // data points to a char [field.elem_count] array. It is not guaranteed to be ended
+ // with '\0'. So need to copy from data like strncpy.
+ size_t max_len = std::min(data.size - field.offset, field.elem_count);
+ const char* p = data.data + field.offset;
+ for (size_t i = 0; i < max_len && *p != '\0'; i++) {
+ s.push_back(*p++);
+ }
+ return s;
+}
+
+std::string ExtractDynamicStringField(const TracingField& field, const PerfSampleRawType& data) {
+ std::string s;
+ const char* p = data.data + field.offset;
+ if (field.elem_size != 4 || field.offset + field.elem_size > data.size) {
+ return s;
+ }
+ uint32_t location;
+ MoveFromBinaryFormat(location, p);
+ // Parse location: (max_len << 16) | off.
+ uint32_t offset = location & 0xffff;
+ uint32_t max_len = location >> 16;
+ if (offset + max_len <= data.size) {
+ p = data.data + offset;
+ for (size_t i = 0; i < max_len && *p != '\0'; i++) {
+ s.push_back(*p++);
+ }
+ }
+ return s;
+}
+
+template <typename T, typename UT = typename std::make_unsigned<T>::type>
+std::string ExtractIntFieldFromPointer(const TracingField& field, const char* p) {
+ static_assert(std::is_signed<T>::value);
+ T value;
+ MoveFromBinaryFormat(value, p);
+
+ if (field.is_signed) {
+ return android::base::StringPrintf("%" PRId64, static_cast<int64_t>(value));
+ }
+ return android::base::StringPrintf("0x%" PRIx64, static_cast<uint64_t>(static_cast<UT>(value)));
+}
+
+template <typename T>
+std::string ExtractIntField(const TracingField& field, const PerfSampleRawType& data) {
+ if (field.offset + sizeof(T) > data.size) {
+ return "";
+ }
+ return ExtractIntFieldFromPointer<T>(field, data.data + field.offset);
+}
+
+template <typename T>
+std::string ExtractIntArrayField(const TracingField& field, const PerfSampleRawType& data) {
+ if (field.offset + field.elem_size * field.elem_count > data.size) {
+ return "";
+ }
+ std::string s;
+ const char* p = data.data + field.offset;
+ for (size_t i = 0; i < field.elem_count; i++) {
+ if (i != 0) {
+ s.push_back(' ');
+ }
+ ExtractIntFieldFromPointer<T>(field, p);
+ p += field.elem_size;
+ }
+ return s;
+}
+
+std::string ExtractUnknownField(const TracingField& field, const PerfSampleRawType& data) {
+ size_t total = field.elem_size * field.elem_count;
+ if (field.offset + total > data.size) {
+ return "";
+ }
+ uint32_t value;
+ std::string s;
+ const char* p = data.data + field.offset;
+ for (size_t i = 0; i + sizeof(value) <= total; i += sizeof(value)) {
+ if (i != 0) {
+ s.push_back(' ');
+ }
+ MoveFromBinaryFormat(value, p);
+ s += android::base::StringPrintf("0x%08x", value);
+ }
+ return s;
+}
+
+ExtractFieldFn GetExtractFieldFunction(const TracingField& field) {
+ if (field.is_dynamic) {
+ return ExtractDynamicStringField;
+ }
+ if (field.elem_count > 1 && field.elem_size == 1) {
+ // Probably the field is a string.
+ // Don't use field.is_signed, which has different values on x86 and arm.
+ return ExtractStringField;
+ }
+ if (field.elem_count == 1) {
+ switch (field.elem_size) {
+ case 1:
+ return ExtractIntField<int8_t>;
+ case 2:
+ return ExtractIntField<int16_t>;
+ case 4:
+ return ExtractIntField<int32_t>;
+ case 8:
+ return ExtractIntField<int64_t>;
+ }
+ } else {
+ switch (field.elem_size) {
+ case 1:
+ return ExtractIntArrayField<int8_t>;
+ case 2:
+ return ExtractIntArrayField<int16_t>;
+ case 4:
+ return ExtractIntArrayField<int32_t>;
+ case 8:
+ return ExtractIntArrayField<int64_t>;
+ }
+ }
+ return ExtractUnknownField;
+}
class DumpRecordCommand : public Command {
public:
@@ -45,9 +185,10 @@ class DumpRecordCommand : public Command {
"Usage: simpleperf dumprecord [options] [perf_record_file]\n"
" Dump different parts of a perf record file. Default file is perf.data.\n"
"--dump-etm type1,type2,... Dump etm data. A type is one of raw, packet and element.\n"
+"-i <record_file> Record file to dump. Default is perf.data.\n"
"--symdir <dir> Look for binaries in a directory recursively.\n"
// clang-format on
- ) {}
+ ) {}
bool Run(const std::vector<std::string>& args);
@@ -56,12 +197,23 @@ class DumpRecordCommand : public Command {
void DumpFileHeader();
void DumpAttrSection();
bool DumpDataSection();
- bool DumpAuxData(const AuxRecord& aux, ETMDecoder& etm_decoder);
+ 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);
+ void ProcessTracingData(const TracingDataRecord& r);
+ bool DumpAuxData(const AuxRecord& aux);
bool DumpFeatureSection();
+ // options
std::string record_filename_ = "perf.data";
- std::unique_ptr<RecordFileReader> record_file_reader_;
ETMDumpOption etm_dump_option_;
+
+ std::unique_ptr<RecordFileReader> record_file_reader_;
+ std::unique_ptr<ETMDecoder> etm_decoder_;
+ ThreadTree thread_tree_;
+
+ std::vector<EventInfo> events_;
};
bool DumpRecordCommand::Run(const std::vector<std::string>& args) {
@@ -81,30 +233,35 @@ bool DumpRecordCommand::Run(const std::vector<std::string>& args) {
}
bool DumpRecordCommand::ParseOptions(const std::vector<std::string>& args) {
- size_t i;
- for (i = 0; i < args.size() && !args[i].empty() && args[i][0] == '-'; ++i) {
- if (args[i] == "--dump-etm") {
- if (!NextArgumentOrError(args, &i) || !ParseEtmDumpOption(args[i], &etm_dump_option_)) {
- return false;
- }
- } else if (args[i] == "--symdir") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- if (!Dso::AddSymbolDir(args[i])) {
- return false;
- }
- } else {
- ReportUnknownOption(args, i);
+ const OptionFormatMap option_formats = {
+ {"--dump-etm", {OptionValueType::STRING, OptionType::SINGLE}},
+ {"-i", {OptionValueType::STRING, OptionType::SINGLE}},
+ {"--symdir", {OptionValueType::STRING, OptionType::MULTIPLE}},
+ };
+ OptionValueMap options;
+ std::vector<std::pair<OptionName, OptionValue>> ordered_options;
+ std::vector<std::string> non_option_args;
+ if (!PreprocessOptions(args, option_formats, &options, &ordered_options, &non_option_args)) {
+ return false;
+ }
+ if (auto value = options.PullValue("--dump-etm"); value) {
+ if (!ParseEtmDumpOption(*value->str_value, &etm_dump_option_)) {
return false;
}
}
- if (i + 1 < args.size()) {
+ options.PullStringValue("-i", &record_filename_);
+ for (const OptionValue& value : options.PullValues("--symdir")) {
+ if (!Dso::AddSymbolDir(*value.str_value)) {
+ return false;
+ }
+ }
+ CHECK(options.values.empty());
+ if (non_option_args.size() > 1) {
LOG(ERROR) << "too many record files";
return false;
}
- if (i + 1 == args.size()) {
- record_filename_ = args[i];
+ if (non_option_args.size() == 1) {
+ record_filename_ = non_option_args[0];
}
return true;
}
@@ -128,8 +285,8 @@ void DumpRecordCommand::DumpFileHeader() {
}
printf("attr_size: %" PRId64 "\n", header.attr_size);
if (header.attr_size != sizeof(FileAttr)) {
- PLOG(WARNING) << "record file attr size " << header.attr_size
- << " doesn't match expected attr size " << sizeof(FileAttr);
+ LOG(WARNING) << "record file attr size " << header.attr_size
+ << " doesn't match expected attr size " << sizeof(FileAttr);
}
printf("attrs[file section]: offset %" PRId64 ", size %" PRId64 "\n", header.attrs.offset,
header.attrs.size);
@@ -168,85 +325,131 @@ void DumpRecordCommand::DumpAttrSection() {
}
bool DumpRecordCommand::DumpDataSection() {
- std::unique_ptr<ETMDecoder> etm_decoder;
- ThreadTree thread_tree;
- thread_tree.ShowIpForUnknownSymbol();
- record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree);
-
- auto get_symbol_function = [&](uint32_t pid, uint32_t tid, uint64_t ip, std::string& dso_name,
- std::string& symbol_name, uint64_t& vaddr_in_file,
- bool in_kernel) {
- ThreadEntry* thread = thread_tree.FindThreadOrNew(pid, tid);
- const MapEntry* map = thread_tree.FindMap(thread, ip, in_kernel);
- Dso* dso;
- const Symbol* symbol = thread_tree.FindSymbol(map, ip, &vaddr_in_file, &dso);
- dso_name = dso->Path();
- symbol_name = symbol->DemangledName();
- };
+ thread_tree_.ShowIpForUnknownSymbol();
+ record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
- auto record_callback = [&](std::unique_ptr<Record> r) {
- r->Dump();
- thread_tree.Update(*r);
- if (r->type() == PERF_RECORD_SAMPLE) {
- SampleRecord& sr = *static_cast<SampleRecord*>(r.get());
- bool in_kernel = sr.InKernel();
- if (sr.sample_type & PERF_SAMPLE_CALLCHAIN) {
- PrintIndented(1, "callchain:\n");
- for (size_t i = 0; i < sr.callchain_data.ip_nr; ++i) {
- if (sr.callchain_data.ips[i] >= PERF_CONTEXT_MAX) {
- if (sr.callchain_data.ips[i] == PERF_CONTEXT_USER) {
- in_kernel = false;
- }
- continue;
- }
- std::string dso_name;
- std::string symbol_name;
- uint64_t vaddr_in_file;
- get_symbol_function(sr.tid_data.pid, sr.tid_data.tid, sr.callchain_data.ips[i],
- dso_name, symbol_name, vaddr_in_file, in_kernel);
- PrintIndented(2, "%s (%s[+%" PRIx64 "])\n", symbol_name.c_str(), dso_name.c_str(),
- vaddr_in_file);
- }
+ auto record_callback = [&](std::unique_ptr<Record> r) { return ProcessRecord(r.get()); };
+ return record_file_reader_->ReadDataSection(record_callback);
+}
+
+bool DumpRecordCommand::ProcessRecord(Record* r) {
+ r->Dump();
+ thread_tree_.Update(*r);
+
+ bool res = true;
+ switch (r->type()) {
+ case PERF_RECORD_SAMPLE:
+ ProcessSampleRecord(*static_cast<SampleRecord*>(r));
+ break;
+ case SIMPLE_PERF_RECORD_CALLCHAIN:
+ ProcessCallChainRecord(*static_cast<CallChainRecord*>(r));
+ break;
+ case PERF_RECORD_AUXTRACE_INFO: {
+ etm_decoder_ = ETMDecoder::Create(*static_cast<AuxTraceInfoRecord*>(r), thread_tree_);
+ if (etm_decoder_) {
+ etm_decoder_->EnableDump(etm_dump_option_);
+ } else {
+ res = false;
}
- } else if (r->type() == SIMPLE_PERF_RECORD_CALLCHAIN) {
- CallChainRecord& cr = *static_cast<CallChainRecord*>(r.get());
- PrintIndented(1, "callchain:\n");
- for (size_t i = 0; i < cr.ip_nr; ++i) {
- std::string dso_name;
- std::string symbol_name;
- uint64_t vaddr_in_file;
- get_symbol_function(cr.pid, cr.tid, cr.ips[i], dso_name, symbol_name, vaddr_in_file,
- false);
- PrintIndented(2, "%s (%s[+%" PRIx64 "])\n", symbol_name.c_str(), dso_name.c_str(),
- vaddr_in_file);
+ break;
+ }
+ case PERF_RECORD_AUX: {
+ res = DumpAuxData(*static_cast<AuxRecord*>(r));
+ break;
+ }
+ case PERF_RECORD_TRACING_DATA:
+ case SIMPLE_PERF_RECORD_TRACING_DATA: {
+ ProcessTracingData(*static_cast<TracingDataRecord*>(r));
+ break;
+ }
+ }
+ return res;
+}
+
+void DumpRecordCommand::ProcessSampleRecord(const SampleRecord& sr) {
+ bool in_kernel = sr.InKernel();
+ if (sr.sample_type & PERF_SAMPLE_CALLCHAIN) {
+ PrintIndented(1, "callchain:\n");
+ for (size_t i = 0; i < sr.callchain_data.ip_nr; ++i) {
+ if (sr.callchain_data.ips[i] >= PERF_CONTEXT_MAX) {
+ if (sr.callchain_data.ips[i] == PERF_CONTEXT_USER) {
+ in_kernel = false;
+ }
+ continue;
}
- } else if (r->type() == PERF_RECORD_AUXTRACE_INFO) {
- etm_decoder = ETMDecoder::Create(*static_cast<AuxTraceInfoRecord*>(r.get()), thread_tree);
- if (!etm_decoder) {
- return false;
+ SymbolInfo s =
+ GetSymbolInfo(sr.tid_data.pid, sr.tid_data.tid, sr.callchain_data.ips[i], in_kernel);
+ PrintIndented(2, "%s (%s[+%" PRIx64 "])\n", s.symbol->DemangledName(), s.dso->Path().c_str(),
+ s.vaddr_in_file);
+ }
+ }
+ // Dump tracepoint fields.
+ if (!events_.empty()) {
+ size_t attr_index = record_file_reader_->GetAttrIndexOfRecord(&sr);
+ auto& event = events_[attr_index];
+ if (event.tp_data_size > 0 && sr.raw_data.size >= event.tp_data_size) {
+ PrintIndented(1, "tracepoint fields:\n");
+ for (size_t i = 0; i < event.tp_fields.size(); i++) {
+ auto& field = event.tp_fields[i];
+ std::string s = event.extract_field_functions[i](field, sr.raw_data);
+ PrintIndented(2, "%s: %s\n", field.name.c_str(), s.c_str());
}
- etm_decoder->EnableDump(etm_dump_option_);
- } else if (r->type() == PERF_RECORD_AUX) {
- CHECK(etm_decoder);
- return DumpAuxData(*static_cast<AuxRecord*>(r.get()), *etm_decoder);
}
- return true;
- };
- return record_file_reader_->ReadDataSection(record_callback);
+ }
+}
+
+void DumpRecordCommand::ProcessCallChainRecord(const CallChainRecord& cr) {
+ PrintIndented(1, "callchain:\n");
+ for (size_t i = 0; i < cr.ip_nr; ++i) {
+ SymbolInfo s = GetSymbolInfo(cr.pid, cr.tid, cr.ips[i], false);
+ PrintIndented(2, "%s (%s[+%" PRIx64 "])\n", s.symbol->DemangledName(), s.dso->Path().c_str(),
+ s.vaddr_in_file);
+ }
+}
+
+SymbolInfo DumpRecordCommand::GetSymbolInfo(uint32_t pid, uint32_t tid, uint64_t ip,
+ bool in_kernel) {
+ ThreadEntry* thread = thread_tree_.FindThreadOrNew(pid, tid);
+ const MapEntry* map = thread_tree_.FindMap(thread, ip, in_kernel);
+ SymbolInfo info;
+ info.symbol = thread_tree_.FindSymbol(map, ip, &info.vaddr_in_file, &info.dso);
+ return info;
}
-bool DumpRecordCommand::DumpAuxData(const AuxRecord& aux, ETMDecoder& etm_decoder) {
+bool DumpRecordCommand::DumpAuxData(const AuxRecord& aux) {
size_t size = aux.data->aux_size;
if (size > 0) {
std::unique_ptr<uint8_t[]> data(new uint8_t[size]);
if (!record_file_reader_->ReadAuxData(aux.Cpu(), aux.data->aux_offset, data.get(), size)) {
return false;
}
- return etm_decoder.ProcessData(data.get(), size);
+ return etm_decoder_->ProcessData(data.get(), size);
}
return true;
}
+void DumpRecordCommand::ProcessTracingData(const TracingDataRecord& r) {
+ Tracing tracing(std::vector<char>(r.data, r.data + r.data_size));
+ std::vector<EventAttrWithId> 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) {
+ continue;
+ }
+ TracingFormat format = tracing.GetTracingFormatHavingId(attr->config);
+ event.tp_fields = format.fields;
+ // Decide dump function for each field.
+ for (size_t j = 0; j < event.tp_fields.size(); j++) {
+ auto& field = event.tp_fields[j];
+ event.extract_field_functions.push_back(GetExtractFieldFunction(field));
+ event.tp_data_size += field.elem_count * field.elem_size;
+ }
+ }
+}
+
bool DumpRecordCommand::DumpFeatureSection() {
std::map<int, SectionDesc> section_map = record_file_reader_->FeatureSectionDescriptors();
for (const auto& pair : section_map) {
@@ -269,29 +472,22 @@ bool DumpRecordCommand::DumpFeatureSection() {
std::vector<std::string> cmdline = record_file_reader_->ReadCmdlineFeature();
PrintIndented(1, "cmdline: %s\n", android::base::Join(cmdline, ' ').c_str());
} else if (feature == FEAT_FILE) {
- std::string file_path;
- uint32_t file_type;
- uint64_t min_vaddr;
- uint64_t file_offset_of_min_vaddr;
- std::vector<Symbol> symbols;
- std::vector<uint64_t> dex_file_offsets;
+ FileFeature file;
size_t read_pos = 0;
PrintIndented(1, "file:\n");
- while (record_file_reader_->ReadFileFeature(read_pos, &file_path, &file_type,
- &min_vaddr, &file_offset_of_min_vaddr,
- &symbols, &dex_file_offsets)) {
- PrintIndented(2, "file_path %s\n", file_path.c_str());
- PrintIndented(2, "file_type %s\n", DsoTypeToString(static_cast<DsoType>(file_type)));
- PrintIndented(2, "min_vaddr 0x%" PRIx64 "\n", min_vaddr);
- PrintIndented(2, "file_offset_of_min_vaddr 0x%" PRIx64 "\n", file_offset_of_min_vaddr);
+ while (record_file_reader_->ReadFileFeature(read_pos, &file)) {
+ PrintIndented(2, "file_path %s\n", file.path.c_str());
+ PrintIndented(2, "file_type %s\n", DsoTypeToString(file.type));
+ PrintIndented(2, "min_vaddr 0x%" PRIx64 "\n", file.min_vaddr);
+ PrintIndented(2, "file_offset_of_min_vaddr 0x%" PRIx64 "\n", file.file_offset_of_min_vaddr);
PrintIndented(2, "symbols:\n");
- for (const auto& symbol : symbols) {
+ for (const auto& symbol : file.symbols) {
PrintIndented(3, "%s [0x%" PRIx64 "-0x%" PRIx64 "]\n", symbol.DemangledName(),
symbol.addr, symbol.addr + symbol.len);
}
- if (file_type == static_cast<uint32_t>(DSO_DEX_FILE)) {
+ if (file.type == DSO_DEX_FILE) {
PrintIndented(2, "dex_file_offsets:\n");
- for (uint64_t offset : dex_file_offsets) {
+ for (uint64_t offset : file.dex_file_offsets) {
PrintIndented(3, "0x%" PRIx64 "\n", offset);
}
}
@@ -306,11 +502,23 @@ bool DumpRecordCommand::DumpFeatureSection() {
for (auto offset : record_file_reader_->ReadAuxTraceFeature()) {
PrintIndented(2, "%" PRIu64 "\n", offset);
}
+ } else if (feature == FEAT_DEBUG_UNWIND) {
+ PrintIndented(1, "debug_unwind:\n");
+ if (auto opt_debug_unwind = record_file_reader_->ReadDebugUnwindFeature(); opt_debug_unwind) {
+ for (const DebugUnwindFile& file : opt_debug_unwind.value()) {
+ PrintIndented(2, "path: %s\n", file.path.c_str());
+ PrintIndented(2, "size: %" PRIu64 "\n", file.size);
+ }
+ }
}
}
return true;
}
+} // namespace
+
void RegisterDumpRecordCommand() {
RegisterCommand("dump", [] { return std::unique_ptr<Command>(new DumpRecordCommand); });
}
+
+} // namespace simpleperf
diff --git a/simpleperf/cmd_dumprecord_test.cpp b/simpleperf/cmd_dumprecord_test.cpp
index 76afc1ca..e6dc5c65 100644
--- a/simpleperf/cmd_dumprecord_test.cpp
+++ b/simpleperf/cmd_dumprecord_test.cpp
@@ -20,6 +20,8 @@
#include "get_test_data.h"
#include "test_util.h"
+using namespace simpleperf;
+
static std::unique_ptr<Command> DumpCmd() {
return CreateCommandInstance("dump");
}
@@ -28,6 +30,10 @@ TEST(cmd_dump, record_file_option) {
ASSERT_TRUE(DumpCmd()->Run({GetTestData("perf.data")}));
}
+TEST(cmd_dump, input_option) {
+ ASSERT_TRUE(DumpCmd()->Run({"-i", GetTestData("perf.data")}));
+}
+
TEST(cmd_dump, dump_data_generated_by_linux_perf) {
ASSERT_TRUE(DumpCmd()->Run({GetTestData(PERF_DATA_GENERATED_BY_LINUX_PERF)}));
}
@@ -45,6 +51,21 @@ TEST(cmd_dump, dump_callchain_of_sample_records) {
ASSERT_NE(data.find("__ioctl (/system/lib64/libc.so[+70b6c])"), std::string::npos);
}
+TEST(cmd_dump, dump_tracepoint_fields_of_sample_records) {
+ CaptureStdout capture;
+ ASSERT_TRUE(capture.Start());
+ ASSERT_TRUE(DumpCmd()->Run({GetTestData("perf_with_tracepoint_event.data")}));
+ std::string data = capture.Finish();
+ ASSERT_NE(data.find("prev_comm: sleep"), std::string::npos);
+
+ // dump dynamic field of tracepoint events.
+ ASSERT_TRUE(capture.Start());
+ ASSERT_TRUE(DumpCmd()->Run({GetTestData("perf_with_tracepoint_event_dynamic_field.data")}));
+ data = capture.Finish();
+ ASSERT_NE(data.find("name: /sys/kernel/debug/tracing/events/kprobes/myopen/format"),
+ std::string::npos);
+}
+
TEST(cmd_dump, etm_data) {
CaptureStdout capture;
ASSERT_TRUE(capture.Start());
diff --git a/simpleperf/cmd_help.cpp b/simpleperf/cmd_help.cpp
index b51071b2..1c8cd631 100644
--- a/simpleperf/cmd_help.cpp
+++ b/simpleperf/cmd_help.cpp
@@ -22,6 +22,9 @@
#include "command.h"
+namespace simpleperf {
+namespace {
+
class HelpCommand : public Command {
public:
HelpCommand()
@@ -31,7 +34,7 @@ class HelpCommand : public Command {
" Without subcommand, print short help string for every subcommand.\n"
" With subcommand, print long help string for the subcommand.\n\n"
// clang-format on
- ) {}
+ ) {}
bool Run(const std::vector<std::string>& args) override;
@@ -73,7 +76,7 @@ void HelpCommand::PrintShortHelp() {
" --version Print version of simpleperf.\n"
"subcommands:\n"
// clang-format on
- );
+ );
for (auto& cmd_name : GetAllCommandNames()) {
std::unique_ptr<Command> cmd = CreateCommandInstance(cmd_name);
printf(" %-20s%s\n", cmd_name.c_str(), cmd->ShortHelpString().c_str());
@@ -84,7 +87,10 @@ void HelpCommand::PrintLongHelpForOneCommand(const Command& command) {
printf("%s\n", command.LongHelpString().c_str());
}
+} // namespace
+
void RegisterHelpCommand() {
- RegisterCommand("help",
- [] { return std::unique_ptr<Command>(new HelpCommand); });
+ RegisterCommand("help", [] { return std::unique_ptr<Command>(new HelpCommand); });
}
+
+} // namespace simpleperf
diff --git a/simpleperf/cmd_inject.cpp b/simpleperf/cmd_inject.cpp
index ee27b0b8..d90bd517 100644
--- a/simpleperf/cmd_inject.cpp
+++ b/simpleperf/cmd_inject.cpp
@@ -15,18 +15,45 @@
*/
#include <stdio.h>
+#include <unistd.h>
#include <memory>
+#include <optional>
#include <regex>
#include <string>
+#include <android-base/parseint.h>
+
#include "ETMDecoder.h"
+#include "cmd_inject_impl.h"
#include "command.h"
#include "record_file.h"
+#include "system/extras/simpleperf/etm_branch_list.pb.h"
#include "thread_tree.h"
#include "utils.h"
-using namespace simpleperf;
+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 {
@@ -41,97 +68,192 @@ struct AddrPairHash {
}
};
-struct BinaryInfo {
+enum class OutputFormat {
+ AutoFDO,
+ BranchList,
+};
+
+struct AutoFDOBinaryInfo {
std::unordered_map<AddrPair, uint64_t, AddrPairHash> range_count_map;
std::unordered_map<AddrPair, uint64_t, AddrPairHash> branch_count_map;
};
+using BranchListBinaryInfo =
+ std::unordered_map<uint64_t, std::unordered_map<std::vector<bool>, uint64_t>>;
+
+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_;
+};
+
+constexpr const char* ETM_BRANCH_LIST_PROTO_MAGIC = "simpleperf:EtmBranchList";
+
class InjectCommand : public Command {
public:
InjectCommand()
- : Command("inject", "convert etm instruction tracing data into instr ranges",
+ : Command("inject", "parse etm instruction tracing data",
// clang-format off
"Usage: simpleperf inject [options]\n"
"--binary binary_name Generate data only for binaries matching binary_name regex.\n"
-"-i <file> input perf.data, generated by recording cs-etm event type.\n"
-" Default is perf.data.\n"
+"-i <file> Input file. Default is perf.data. Support below formats:\n"
+" 1. perf.data generated by recording cs-etm event type.\n"
+" 2. branch_list file generated by `inject --output branch-list`.\n"
"-o <file> output file. Default is perf_inject.data.\n"
-" The output is in text format accepted by AutoFDO.\n"
+"--output <format> Select output file format:\n"
+" autofdo -- text format accepted by TextSampleReader\n"
+" of AutoFDO\n"
+" branch-list -- protobuf file in etm_branch_list.proto\n"
+" Default is autofdo.\n"
"--dump-etm type1,type2,... Dump etm data. A type is one of raw, packet and element.\n"
+"--exclude-perf Exclude trace data for the recording process.\n"
"--symdir <dir> Look for binaries in a directory recursively.\n"
+"\n"
+"Examples:\n"
+"1. Generate autofdo text output.\n"
+"$ simpleperf inject -i perf.data -o autofdo.txt --output autofdo\n"
+"\n"
+"2. Generate branch list proto, then convert to autofdo text.\n"
+"$ simpleperf inject -i perf.data -o branch_list.data --output branch-list\n"
+"$ simpleperf inject -i branch_list.data -o autofdo.txt --output autofdo\n"
// clang-format on
),
output_fp_(nullptr, fclose) {}
bool Run(const std::vector<std::string>& args) override {
+ GOOGLE_PROTOBUF_VERIFY_VERSION;
+ // 1. Parse options.
if (!ParseOptions(args)) {
return false;
}
- record_file_reader_ = RecordFileReader::CreateInstance(input_filename_);
- if (!record_file_reader_) {
- return false;
- }
- record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
- output_fp_.reset(fopen(output_filename_.c_str(), "w"));
+
+ // 2. Open output file.
+ const char* open_mode = (output_format_ == OutputFormat::AutoFDO) ? "w" : "wb";
+ output_fp_.reset(fopen(output_filename_.c_str(), open_mode));
if (!output_fp_) {
PLOG(ERROR) << "failed to write to " << output_filename_;
return false;
}
- if (!record_file_reader_->ReadDataSection([this](auto r) { return ProcessRecord(r.get()); })) {
+
+ // 3. Process input file.
+ if (!ProcessInputFile()) {
return false;
}
- if (etm_decoder_ && !etm_decoder_->FinishData()) {
+
+ // 4. Write output file.
+ if (!WriteOutput()) {
return false;
}
- PostProcess();
output_fp_.reset(nullptr);
return true;
}
private:
bool ParseOptions(const std::vector<std::string>& args) {
- for (size_t i = 0; i < args.size(); i++) {
- if (args[i] == "--binary") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- binary_name_regex_ = args[i];
- } else if (args[i] == "-i") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- input_filename_ = args[i];
- } else if (args[i] == "-o") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- output_filename_ = args[i];
- } else if (args[i] == "--dump-etm") {
- if (!NextArgumentOrError(args, &i) || !ParseEtmDumpOption(args[i], &etm_dump_option_)) {
- return false;
- }
- } else if (args[i] == "--symdir") {
- if (!NextArgumentOrError(args, &i) || !Dso::AddSymbolDir(args[i])) {
- return false;
- }
+ const OptionFormatMap option_formats = {
+ {"--binary", {OptionValueType::STRING, OptionType::SINGLE}},
+ {"--dump-etm", {OptionValueType::STRING, OptionType::SINGLE}},
+ {"--exclude-perf", {OptionValueType::NONE, OptionType::SINGLE}},
+ {"-i", {OptionValueType::STRING, OptionType::SINGLE}},
+ {"-o", {OptionValueType::STRING, OptionType::SINGLE}},
+ {"--output", {OptionValueType::STRING, OptionType::SINGLE}},
+ {"--symdir", {OptionValueType::STRING, OptionType::MULTIPLE}},
+ };
+ OptionValueMap options;
+ std::vector<std::pair<OptionName, OptionValue>> ordered_options;
+ if (!PreprocessOptions(args, option_formats, &options, &ordered_options, nullptr)) {
+ return false;
+ }
+
+ if (auto value = options.PullValue("--binary"); value) {
+ binary_name_regex_ = *value->str_value;
+ }
+ if (auto value = options.PullValue("--dump-etm"); value) {
+ if (!ParseEtmDumpOption(*value->str_value, &etm_dump_option_)) {
+ return false;
+ }
+ }
+ exclude_perf_ = options.PullBoolValue("--exclude-perf");
+ options.PullStringValue("-i", &input_filename_);
+ options.PullStringValue("-o", &output_filename_);
+ if (auto value = options.PullValue("--output"); value) {
+ const std::string& output = *value->str_value;
+ if (output == "autofdo") {
+ output_format_ = OutputFormat::AutoFDO;
+ } else if (output == "branch-list") {
+ output_format_ = OutputFormat::BranchList;
} else {
- ReportUnknownOption(args, i);
+ LOG(ERROR) << "unknown format in --output option: " << output;
+ return false;
+ }
+ }
+ if (auto value = options.PullValue("--symdir"); value) {
+ if (!Dso::AddSymbolDir(*value->str_value)) {
return false;
}
}
+ CHECK(options.values.empty());
return true;
}
+ bool ProcessInputFile() {
+ if (IsPerfDataFile(input_filename_)) {
+ record_file_reader_ = RecordFileReader::CreateInstance(input_filename_);
+ if (!record_file_reader_) {
+ return false;
+ }
+ if (exclude_perf_) {
+ const auto& info_map = record_file_reader_->GetMetaInfoFeature();
+ if (auto it = info_map.find("recording_process"); it == info_map.end()) {
+ LOG(ERROR) << input_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;
+ return false;
+ }
+ thread_tree_.ExcludePid(pid);
+ }
+ }
+ record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
+ if (!record_file_reader_->ReadDataSection(
+ [this](auto r) { return ProcessRecord(r.get()); })) {
+ return false;
+ }
+ if (etm_decoder_ && !etm_decoder_->FinishData()) {
+ return false;
+ }
+ return true;
+ }
+ return ProcessBranchListFile();
+ }
+
bool ProcessRecord(Record* r) {
thread_tree_.Update(*r);
if (r->type() == PERF_RECORD_AUXTRACE_INFO) {
- auto instr_range_callback = [this](auto& range) { ProcessInstrRange(range); };
etm_decoder_ = ETMDecoder::Create(*static_cast<AuxTraceInfoRecord*>(r), thread_tree_);
if (!etm_decoder_) {
return false;
}
etm_decoder_->EnableDump(etm_dump_option_);
- etm_decoder_->RegisterCallback(instr_range_callback);
+ if (output_format_ == OutputFormat::AutoFDO) {
+ etm_decoder_->RegisterCallback(
+ [this](const ETMInstrRange& range) { ProcessInstrRange(range); });
+ } else if (output_format_ == OutputFormat::BranchList) {
+ etm_decoder_->RegisterCallback(
+ [this](const ETMBranchList& branch) { ProcessBranchList(branch); });
+ }
} else if (r->type() == PERF_RECORD_AUX) {
AuxRecord* aux = static_cast<AuxRecord*>(r);
uint64_t aux_size = aux->data->aux_size;
@@ -146,28 +268,32 @@ class InjectCommand : public Command {
}
return etm_decoder_->ProcessData(aux_data_buffer_.data(), aux_size);
}
+ } 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;
+ }
}
return true;
}
std::unordered_map<Dso*, bool> dso_filter_cache;
- bool FilterInstrRange(const ETMInstrRange& instr_range) {
- auto lookup = dso_filter_cache.find(instr_range.dso);
+ bool FilterDso(Dso* dso) {
+ auto lookup = dso_filter_cache.find(dso);
if (lookup != dso_filter_cache.end()) {
return lookup->second;
}
- bool match = std::regex_search(instr_range.dso->GetDebugFilePath(),
- binary_name_regex_);
- dso_filter_cache.insert({instr_range.dso, match});
+ bool match = std::regex_search(dso->Path(), binary_name_regex_);
+ dso_filter_cache.insert({dso, match});
return match;
}
void ProcessInstrRange(const ETMInstrRange& instr_range) {
- if (!FilterInstrRange(instr_range)) {
+ if (!FilterDso(instr_range.dso)) {
return;
}
- auto& binary = binary_map_[instr_range.dso];
+ auto& binary = autofdo_binary_map_[instr_range.dso];
binary.range_count_map[AddrPair(instr_range.start_addr, instr_range.end_addr)] +=
instr_range.branch_taken_count + instr_range.branch_not_taken_count;
if (instr_range.branch_taken_count > 0) {
@@ -176,18 +302,148 @@ class InjectCommand : public Command {
}
}
- void PostProcess() {
- // binary_map is used to store instruction ranges, which can have a large amount. And it has
- // a larger access time (instruction ranges * executed time). So it's better to use
+ void ProcessBranchList(const ETMBranchList& branch_list) {
+ if (!FilterDso(branch_list.dso)) {
+ return;
+ }
+
+ ++branch_list_binary_map_[branch_list.dso][branch_list.addr][branch_list.branch];
+ }
+
+ bool ProcessBranchListFile() {
+ if (output_format_ != OutputFormat::AutoFDO) {
+ LOG(ERROR) << "Only support autofdo output when given a branch list file.";
+ return false;
+ }
+ // 1. Load EtmBranchList msg from proto file.
+ auto fd = FileHelper::OpenReadOnly(input_filename_);
+ if (!fd.ok()) {
+ PLOG(ERROR) << "failed to open " << input_filename_;
+ return false;
+ }
+ proto::ETMBranchList branch_list_proto;
+ if (!branch_list_proto.ParseFromFileDescriptor(fd)) {
+ PLOG(ERROR) << "failed to read msg from " << input_filename_;
+ return false;
+ }
+ if (branch_list_proto.magic() != ETM_BRANCH_LIST_PROTO_MAGIC) {
+ PLOG(ERROR) << "file not in format etm_branch_list.proto: " << input_filename_;
+ return false;
+ }
+
+ // 2. Build branch map for each binary, convert them to instr ranges.
+ auto callback = [this](const ETMInstrRange& range) { ProcessInstrRange(range); };
+ auto check_build_id = [](Dso* dso, const BuildId& expected_build_id) {
+ if (expected_build_id.IsEmpty()) {
+ return true;
+ }
+ BuildId build_id;
+ return GetBuildIdFromDsoPath(dso->GetDebugFilePath(), &build_id) &&
+ build_id == expected_build_id;
+ };
+
+ for (size_t i = 0; i < branch_list_proto.binaries_size(); i++) {
+ const auto& binary_proto = branch_list_proto.binaries(i);
+ BuildId build_id(binary_proto.build_id());
+ std::optional<DsoType> dso_type = ToDsoType(binary_proto.type());
+ if (!dso_type.has_value()) {
+ return false;
+ }
+ std::unique_ptr<Dso> dso =
+ Dso::CreateDsoWithBuildId(dso_type.value(), binary_proto.path(), build_id);
+ if (!dso || !FilterDso(dso.get()) || !check_build_id(dso.get(), build_id)) {
+ continue;
+ }
+ // Dso is used in ETMInstrRange in post process, so need to extend its lifetime.
+ Dso* dso_p = dso.get();
+ branch_list_dso_v_.emplace_back(dso.release());
+ auto branch_map = BuildBranchMap(binary_proto);
+
+ if (dso_p->type() == DSO_KERNEL) {
+ if (!ModifyBranchMapForKernel(binary_proto, dso_p, branch_map)) {
+ return false;
+ }
+ }
+
+ if (auto result = ConvertBranchMapToInstrRanges(dso_p, branch_map, callback); !result.ok()) {
+ LOG(WARNING) << "failed to build instr ranges for binary " << dso_p->Path() << ": "
+ << result.error();
+ }
+ }
+ return true;
+ }
+
+ BranchMap BuildBranchMap(const proto::ETMBranchList_Binary& binary_proto) {
+ BranchMap 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();
+ }
+ }
+ return branch_map;
+ }
+
+ bool ModifyBranchMapForKernel(const proto::ETMBranchList_Binary& binary_proto, Dso* dso,
+ BranchMap& branch_map) {
+ if (!binary_proto.has_kernel_info()) {
+ LOG(ERROR) << "no kernel info";
+ return false;
+ }
+ uint64_t kernel_map_start_addr = binary_proto.kernel_info().kernel_start_addr();
+ if (kernel_map_start_addr == 0) {
+ return true;
+ }
+ // Addresses are still kernel ip addrs in memory. Need to convert them to vaddrs in vmlinux.
+ BranchMap new_branch_map;
+ for (auto& p : branch_map) {
+ uint64_t vaddr_in_file = dso->IpToVaddrInFile(p.first, kernel_map_start_addr, 0);
+ new_branch_map[vaddr_in_file] = std::move(p.second);
+ }
+ branch_map = std::move(new_branch_map);
+ return true;
+ }
+
+ bool WriteOutput() {
+ if (output_format_ == OutputFormat::AutoFDO) {
+ GenerateInstrRange();
+ return true;
+ }
+ CHECK(output_format_ == OutputFormat::BranchList);
+ return GenerateBranchList();
+ }
+
+ void GenerateInstrRange() {
+ // autofdo_binary_map is used to store instruction ranges, which can have a large amount. And it
+ // has a larger access time (instruction ranges * executed time). So it's better to use
// unorder_maps to speed up access time. But we also want a stable output here, to compare
// output changes result from code changes. So generate a sorted output here.
std::vector<Dso*> dso_v;
- for (auto& p : binary_map_) {
+ for (auto& p : autofdo_binary_map_) {
dso_v.emplace_back(p.first);
}
std::sort(dso_v.begin(), dso_v.end(), [](Dso* d1, Dso* d2) { return d1->Path() < d2->Path(); });
+ if (dso_v.size() > 1) {
+ fprintf(output_fp_.get(),
+ "// Please split this file. AutoFDO only accepts profile for one binary.\n");
+ }
for (auto dso : dso_v) {
- const BinaryInfo& binary = binary_map_[dso];
+ const AutoFDOBinaryInfo& binary = autofdo_binary_map_[dso];
+ // AutoFDO text format needs file_offsets instead of virtual addrs in a binary. And it uses
+ // below formula: vaddr = file_offset + GetFirstLoadSegmentVaddr().
+ uint64_t first_load_segment_addr = GetFirstLoadSegmentVaddr(dso);
+
+ auto to_offset = [&](uint64_t vaddr) -> uint64_t {
+ if (vaddr == 0) {
+ return 0;
+ }
+ CHECK_GE(vaddr, first_load_segment_addr);
+ return vaddr - first_load_segment_addr;
+ };
// Write range_count_map.
std::map<AddrPair, uint64_t> range_count_map(binary.range_count_map.begin(),
@@ -197,8 +453,8 @@ class InjectCommand : public Command {
const AddrPair& addr_range = pair2.first;
uint64_t count = pair2.second;
- fprintf(output_fp_.get(), "%" PRIx64 "-%" PRIx64 ":%" PRIu64 "\n", addr_range.first,
- addr_range.second, count);
+ fprintf(output_fp_.get(), "%" PRIx64 "-%" PRIx64 ":%" PRIu64 "\n",
+ to_offset(addr_range.first), to_offset(addr_range.second), count);
}
// Write addr_count_map.
@@ -212,8 +468,8 @@ class InjectCommand : public Command {
const AddrPair& branch = pair2.first;
uint64_t count = pair2.second;
- fprintf(output_fp_.get(), "%" PRIx64 "->%" PRIx64 ":%" PRIu64 "\n", branch.first,
- branch.second, count);
+ fprintf(output_fp_.get(), "%" PRIx64 "->%" PRIx64 ":%" PRIu64 "\n", to_offset(branch.first),
+ to_offset(branch.second), count);
}
// Write the binary path in comment.
@@ -221,10 +477,119 @@ class InjectCommand : public Command {
}
}
+ uint64_t GetFirstLoadSegmentVaddr(Dso* dso) {
+ ElfStatus status;
+ if (auto elf = ElfFile::Open(dso->GetDebugFilePath(), &status); elf) {
+ for (const auto& segment : elf->GetProgramHeader()) {
+ if (segment.is_load) {
+ return segment.vaddr;
+ }
+ }
+ }
+ return 0;
+ }
+
+ bool GenerateBranchList() {
+ // Don't produce empty output file.
+ if (branch_list_binary_map_.empty()) {
+ LOG(INFO) << "Skip empty output file.";
+ output_fp_.reset(nullptr);
+ unlink(output_filename_.c_str());
+ return true;
+ }
+
+ proto::ETMBranchList branch_list_proto;
+ branch_list_proto.set_magic(ETM_BRANCH_LIST_PROTO_MAGIC);
+ std::vector<char> branch_buf;
+ for (const auto& dso_p : branch_list_binary_map_) {
+ Dso* dso = dso_p.first;
+ auto& addr_map = dso_p.second;
+ auto binary_proto = branch_list_proto.add_binaries();
+
+ binary_proto->set_path(dso->Path());
+ BuildId build_id = Dso::FindExpectedBuildIdForPath(dso->Path());
+ if (!build_id.IsEmpty()) {
+ binary_proto->set_build_id(build_id.ToString().substr(2));
+ }
+ auto opt_binary_type = ToProtoBinaryType(dso->type());
+ if (!opt_binary_type.has_value()) {
+ return false;
+ }
+ binary_proto->set_type(opt_binary_type.value());
+
+ for (const auto& addr_p : addr_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(BranchToProtoString(branch));
+ branch_proto->set_branch_size(branch.size());
+ branch_proto->set_count(branch_p.second);
+ }
+ }
+
+ if (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.";
+ branch_list_proto.mutable_binaries()->RemoveLast();
+ continue;
+ }
+ if (dso->GetDebugFilePath() == dso->Path()) {
+ // vmlinux isn't available. We still use kernel ip addr. Put kernel start addr in proto
+ // for address conversion later.
+ binary_proto->mutable_kernel_info()->set_kernel_start_addr(kernel_map_start_addr_);
+ } else {
+ // vmlinux is available. We have converted kernel ip addr to vaddr in vmlinux. So no need
+ // to put kernel start addr in proto.
+ binary_proto->mutable_kernel_info()->set_kernel_start_addr(0);
+ }
+ }
+ }
+ if (!branch_list_proto.SerializeToFileDescriptor(fileno(output_fp_.get()))) {
+ PLOG(ERROR) << "failed to write to output file";
+ return false;
+ }
+ return true;
+ }
+
+ 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;
+ }
+ }
+
+ 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;
+ }
+ }
+
std::regex binary_name_regex_{""}; // Default to match everything.
+ bool exclude_perf_ = false;
std::string input_filename_ = "perf.data";
std::string output_filename_ = "perf_inject.data";
- ThreadTree thread_tree_;
+ OutputFormat output_format_ = OutputFormat::AutoFDO;
+ ThreadTreeWithFilter thread_tree_;
std::unique_ptr<RecordFileReader> record_file_reader_;
ETMDumpOption etm_dump_option_;
std::unique_ptr<ETMDecoder> etm_decoder_;
@@ -232,7 +597,11 @@ class InjectCommand : public Command {
std::unique_ptr<FILE, decltype(&fclose)> output_fp_;
// Store results for AutoFDO.
- std::unordered_map<Dso*, BinaryInfo> binary_map_;
+ std::unordered_map<Dso*, AutoFDOBinaryInfo> autofdo_binary_map_;
+ // Store results for BranchList.
+ std::unordered_map<Dso*, BranchListBinaryInfo> branch_list_binary_map_;
+ std::vector<std::unique_ptr<Dso>> branch_list_dso_v_;
+ uint64_t kernel_map_start_addr_ = 0;
};
} // namespace
@@ -240,3 +609,5 @@ class InjectCommand : public Command {
void RegisterInjectCommand() {
return RegisterCommand("inject", [] { return std::unique_ptr<Command>(new InjectCommand); });
}
+
+} // namespace simpleperf
diff --git a/simpleperf/cmd_inject_impl.h b/simpleperf/cmd_inject_impl.h
new file mode 100644
index 00000000..455a9d16
--- /dev/null
+++ b/simpleperf/cmd_inject_impl.h
@@ -0,0 +1,28 @@
+/*
+ * 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 1f057cb0..8668174c 100644
--- a/simpleperf/cmd_inject_test.cpp
+++ b/simpleperf/cmd_inject_test.cpp
@@ -17,55 +17,134 @@
#include <android-base/file.h>
#include <gtest/gtest.h>
+#include "cmd_inject_impl.h"
#include "command.h"
#include "get_test_data.h"
#include "test_util.h"
#include "utils.h"
-static std::unique_ptr<Command> InjectCmd() { return CreateCommandInstance("inject"); }
+using namespace simpleperf;
-TEST(cmd_inject, smoke) {
+static std::unique_ptr<Command> InjectCmd() {
+ return CreateCommandInstance("inject");
+}
+
+static bool RunInjectCmd(std::vector<std::string>&& args) {
+ bool has_input = std::find(args.begin(), args.end(), "-i") != args.end();
+ if (!has_input) {
+ args.insert(args.end(), {"-i", GetTestData(PERF_DATA_ETM_TEST_LOOP)});
+ }
+ args.insert(args.end(), {"--symdir", GetTestDataDir() + "etm"});
+ return InjectCmd()->Run(args);
+}
+
+static bool RunInjectCmd(std::vector<std::string>&& args, std::string* output) {
TemporaryFile tmpfile;
- ASSERT_TRUE(InjectCmd()->Run({"--symdir", GetTestDataDir() + "etm", "-i",
- GetTestData(PERF_DATA_ETM_TEST_LOOP), "-o", tmpfile.path}));
- std::string data;
- ASSERT_TRUE(android::base::ReadFileToString(tmpfile.path, &data));
- // Test that we can find instr range in etm_test_loop binary.
- ASSERT_NE(data.find("etm_test_loop"), std::string::npos);
+ close(tmpfile.release());
+ args.insert(args.end(), {"-o", tmpfile.path});
+ if (!RunInjectCmd(std::move(args))) {
+ return false;
+ }
+ if (output != nullptr) {
+ return android::base::ReadFileToString(tmpfile.path, output);
+ }
+ return true;
+}
+
+static void CheckMatchingExpectedData(std::string& data) {
std::string expected_data;
ASSERT_TRUE(android::base::ReadFileToString(
GetTestData(std::string("etm") + OS_PATH_SEPARATOR + "perf_inject.data"), &expected_data));
+ data.erase(std::remove(data.begin(), data.end(), '\r'), data.end());
ASSERT_EQ(data, expected_data);
}
+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);
+}
+
TEST(cmd_inject, binary_option) {
// Test that data for etm_test_loop is generated when selected by --binary.
- TemporaryFile tmpfile;
- ASSERT_TRUE(InjectCmd()->Run({"--symdir", GetTestDataDir() + "etm", "-i",
- GetTestData(PERF_DATA_ETM_TEST_LOOP), "--binary", "etm_test_loop",
- "-o", tmpfile.path}));
std::string data;
- ASSERT_TRUE(android::base::ReadFileToString(tmpfile.path, &data));
+ ASSERT_TRUE(RunInjectCmd({"--binary", "etm_test_loop"}, &data));
ASSERT_NE(data.find("etm_test_loop"), std::string::npos);
// Test that data for etm_test_loop is generated when selected by regex.
- ASSERT_TRUE(InjectCmd()->Run({"--symdir", GetTestDataDir() + "etm", "-i",
- GetTestData(PERF_DATA_ETM_TEST_LOOP), "--binary", "etm_t.*_loop",
- "-o", tmpfile.path}));
- ASSERT_TRUE(android::base::ReadFileToString(tmpfile.path, &data));
+ ASSERT_TRUE(RunInjectCmd({"--binary", "etm_t.*_loop"}, &data));
ASSERT_NE(data.find("etm_test_loop"), std::string::npos);
// Test that data for etm_test_loop isn't generated when not selected by --binary.
- ASSERT_TRUE(InjectCmd()->Run({"--symdir", GetTestDataDir() + "etm", "-i",
- GetTestData(PERF_DATA_ETM_TEST_LOOP), "--binary",
- "no_etm_test_loop", "-o", tmpfile.path}));
- ASSERT_TRUE(android::base::ReadFileToString(tmpfile.path, &data));
+ ASSERT_TRUE(RunInjectCmd({"--binary", "no_etm_test_loop"}, &data));
ASSERT_EQ(data.find("etm_test_loop"), std::string::npos);
// Test that data for etm_test_loop isn't generated when not selected by regex.
- ASSERT_TRUE(InjectCmd()->Run({"--symdir", GetTestDataDir() + "etm", "-i",
- GetTestData(PERF_DATA_ETM_TEST_LOOP), "--binary",
- "no_etm_test_.*", "-o", tmpfile.path}));
- ASSERT_TRUE(android::base::ReadFileToString(tmpfile.path, &data));
+ ASSERT_TRUE(RunInjectCmd({"--binary", "no_etm_test_.*"}, &data));
ASSERT_EQ(data.find("etm_test_loop"), std::string::npos);
}
+
+TEST(cmd_inject, exclude_perf_option) {
+ ASSERT_FALSE(RunInjectCmd({"--exclude-perf"}, nullptr));
+ std::string perf_with_recording_process =
+ GetTestData(std::string("etm") + OS_PATH_SEPARATOR + "perf_with_recording_process.data");
+ ASSERT_TRUE(RunInjectCmd({"--exclude-perf", "-i", perf_with_recording_process}, nullptr));
+}
+
+TEST(cmd_inject, output_option) {
+ TemporaryFile tmpfile;
+ close(tmpfile.release());
+ ASSERT_TRUE(RunInjectCmd({"--output", "autofdo", "-o", tmpfile.path}));
+ 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);
+ }
+}
+
+TEST(cmd_inject, skip_empty_output_file) {
+ TemporaryFile tmpfile;
+ close(tmpfile.release());
+ ASSERT_TRUE(RunInjectCmd(
+ {"--binary", "not_exist_binary", "--output", "branch-list", "-o", tmpfile.path}));
+ // The empty output file should not be produced.
+ ASSERT_FALSE(IsRegularFile(tmpfile.path));
+ tmpfile.DoNotRemove();
+}
+
+TEST(cmd_inject, inject_kernel_data) {
+ const std::string recording_file =
+ GetTestData(std::string("etm") + OS_PATH_SEPARATOR + "perf_kernel.data");
+
+ // Inject directly to autofdo format.
+ TemporaryFile tmpfile;
+ close(tmpfile.release());
+ ASSERT_TRUE(RunInjectCmd({"-i", recording_file, "-o", tmpfile.path}));
+ std::string autofdo_output;
+ ASSERT_TRUE(android::base::ReadFileToString(tmpfile.path, &autofdo_output));
+ ASSERT_NE(autofdo_output.find("rq_stats.ko"), std::string::npos);
+
+ // Inject through etm branch list.
+ TemporaryFile tmpfile2;
+ close(tmpfile2.release());
+ ASSERT_TRUE(RunInjectCmd({"-i", recording_file, "-o", tmpfile.path, "--output", "branch-list"}));
+ ASSERT_TRUE(RunInjectCmd({"-i", tmpfile.path, "-o", tmpfile2.path}));
+ std::string output;
+ ASSERT_TRUE(android::base::ReadFileToString(tmpfile2.path, &output));
+ ASSERT_EQ(output, autofdo_output);
+}
diff --git a/simpleperf/cmd_kmem.cpp b/simpleperf/cmd_kmem.cpp
index 453a2768..98603969 100644
--- a/simpleperf/cmd_kmem.cpp
+++ b/simpleperf/cmd_kmem.cpp
@@ -29,22 +29,22 @@
#include "tracing.h"
#include "utils.h"
+namespace simpleperf {
namespace {
struct SlabSample {
- const Symbol* symbol; // the function making allocation
- uint64_t ptr; // the start address of the allocated space
- uint64_t bytes_req; // requested space size
- uint64_t bytes_alloc; // allocated space size
- uint64_t sample_count; // count of allocations
- uint64_t gfp_flags; // flags used for allocation
- uint64_t cross_cpu_allocations; // count of allocations freed not on the
- // cpu allocating them
+ const Symbol* symbol; // the function making allocation
+ uint64_t ptr; // the start address of the allocated space
+ uint64_t bytes_req; // requested space size
+ uint64_t bytes_alloc; // allocated space size
+ uint64_t sample_count; // count of allocations
+ uint64_t gfp_flags; // flags used for allocation
+ uint64_t cross_cpu_allocations; // count of allocations freed not on the
+ // cpu allocating them
CallChainRoot<SlabSample> callchain; // a callchain tree representing all
// callchains in this sample
- SlabSample(const Symbol* symbol, uint64_t ptr, uint64_t bytes_req,
- uint64_t bytes_alloc, uint64_t sample_count, uint64_t gfp_flags,
- uint64_t cross_cpu_allocations)
+ SlabSample(const Symbol* symbol, uint64_t ptr, uint64_t bytes_req, uint64_t bytes_alloc,
+ uint64_t sample_count, uint64_t gfp_flags, uint64_t cross_cpu_allocations)
: symbol(symbol),
ptr(ptr),
bytes_req(bytes_req),
@@ -53,9 +53,7 @@ struct SlabSample {
gfp_flags(gfp_flags),
cross_cpu_allocations(cross_cpu_allocations) {}
- uint64_t GetPeriod() const {
- return sample_count;
- }
+ uint64_t GetPeriod() const { return sample_count; }
};
struct SlabAccumulateInfo {
@@ -67,26 +65,22 @@ BUILD_COMPARE_VALUE_FUNCTION(ComparePtr, ptr);
BUILD_COMPARE_VALUE_FUNCTION_REVERSE(CompareBytesReq, bytes_req);
BUILD_COMPARE_VALUE_FUNCTION_REVERSE(CompareBytesAlloc, bytes_alloc);
BUILD_COMPARE_VALUE_FUNCTION(CompareGfpFlags, gfp_flags);
-BUILD_COMPARE_VALUE_FUNCTION_REVERSE(CompareCrossCpuAllocations,
- cross_cpu_allocations);
+BUILD_COMPARE_VALUE_FUNCTION_REVERSE(CompareCrossCpuAllocations, cross_cpu_allocations);
BUILD_DISPLAY_HEX64_FUNCTION(DisplayPtr, ptr);
BUILD_DISPLAY_UINT64_FUNCTION(DisplayBytesReq, bytes_req);
BUILD_DISPLAY_UINT64_FUNCTION(DisplayBytesAlloc, bytes_alloc);
BUILD_DISPLAY_HEX64_FUNCTION(DisplayGfpFlags, gfp_flags);
-BUILD_DISPLAY_UINT64_FUNCTION(DisplayCrossCpuAllocations,
- cross_cpu_allocations);
+BUILD_DISPLAY_UINT64_FUNCTION(DisplayCrossCpuAllocations, cross_cpu_allocations);
-static int CompareFragment(const SlabSample* sample1,
- const SlabSample* sample2) {
+static int CompareFragment(const SlabSample* sample1, const SlabSample* sample2) {
uint64_t frag1 = sample1->bytes_alloc - sample1->bytes_req;
uint64_t frag2 = sample2->bytes_alloc - sample2->bytes_req;
return Compare(frag2, frag1);
}
static std::string DisplayFragment(const SlabSample* sample) {
- return android::base::StringPrintf("%" PRIu64,
- sample->bytes_alloc - sample->bytes_req);
+ return android::base::StringPrintf("%" PRIu64, sample->bytes_alloc - sample->bytes_req);
}
struct SlabSampleTree {
@@ -110,8 +104,7 @@ struct SlabFormat {
TracingFieldPlace gfp_flags;
};
-class SlabSampleTreeBuilder
- : public SampleTreeBuilder<SlabSample, SlabAccumulateInfo> {
+class SlabSampleTreeBuilder : public SampleTreeBuilder<SlabSample, SlabAccumulateInfo> {
public:
SlabSampleTreeBuilder(const SampleComparator<SlabSample>& sample_comparator,
ThreadTree* thread_tree)
@@ -133,8 +126,7 @@ class SlabSampleTreeBuilder
return sample_tree;
}
- void AddSlabFormat(const std::vector<uint64_t>& event_ids,
- SlabFormat format) {
+ void AddSlabFormat(const std::vector<uint64_t>& event_ids, SlabFormat format) {
std::unique_ptr<SlabFormat> p(new SlabFormat(format));
for (auto id : event_ids) {
event_id_to_format_map_[id] = p.get();
@@ -182,11 +174,9 @@ class SlabSampleTreeBuilder
uint64_t bytes_req = format->bytes_req.ReadFromData(raw_data);
uint64_t bytes_alloc = format->bytes_alloc.ReadFromData(raw_data);
uint64_t gfp_flags = format->gfp_flags.ReadFromData(raw_data);
- SlabSample* sample =
- InsertSample(std::unique_ptr<SlabSample>(new SlabSample(
- symbol, ptr, bytes_req, bytes_alloc, 1, gfp_flags, 0)));
- alloc_cpu_record_map_.insert(
- std::make_pair(ptr, std::make_pair(r.cpu_data.cpu, sample)));
+ SlabSample* sample = InsertSample(std::unique_ptr<SlabSample>(
+ new SlabSample(symbol, ptr, bytes_req, bytes_alloc, 1, gfp_flags, 0)));
+ alloc_cpu_record_map_.insert(std::make_pair(ptr, std::make_pair(r.cpu_data.cpu, sample)));
acc_info->bytes_req = bytes_req;
acc_info->bytes_alloc = bytes_alloc;
return sample;
@@ -206,23 +196,20 @@ class SlabSampleTreeBuilder
return nullptr;
}
- SlabSample* CreateBranchSample(const SampleRecord&,
- const BranchStackItemType&) override {
+ SlabSample* CreateBranchSample(const SampleRecord&, const BranchStackItemType&) override {
return nullptr;
}
- SlabSample* CreateCallChainSample(const ThreadEntry*,
- const SlabSample* sample, uint64_t ip, bool in_kernel,
- const std::vector<SlabSample*>& callchain,
- const SlabAccumulateInfo& acc_info) override {
+ SlabSample* CreateCallChainSample(const ThreadEntry*, const SlabSample* sample, uint64_t ip,
+ bool in_kernel, const std::vector<SlabSample*>& callchain,
+ const SlabAccumulateInfo& acc_info) override {
if (!in_kernel) {
return nullptr;
}
const Symbol* symbol = thread_tree_->FindKernelSymbol(ip);
return InsertCallChainSample(
- std::unique_ptr<SlabSample>(
- new SlabSample(symbol, sample->ptr, acc_info.bytes_req,
- acc_info.bytes_alloc, 1, sample->gfp_flags, 0)),
+ std::unique_ptr<SlabSample>(new SlabSample(symbol, sample->ptr, acc_info.bytes_req,
+ acc_info.bytes_alloc, 1, sample->gfp_flags, 0)),
callchain);
}
@@ -256,14 +243,12 @@ class SlabSampleTreeBuilder
std::unordered_map<uint64_t, SlabFormat*> event_id_to_format_map_;
std::vector<std::unique_ptr<SlabFormat>> formats_;
- std::unordered_map<uint64_t, std::pair<uint32_t, SlabSample*>>
- alloc_cpu_record_map_;
+ std::unordered_map<uint64_t, std::pair<uint32_t, SlabSample*>> alloc_cpu_record_map_;
};
using SlabSampleTreeSorter = SampleTreeSorter<SlabSample>;
using SlabSampleTreeDisplayer = SampleTreeDisplayer<SlabSample, SlabSampleTree>;
-using SlabSampleCallgraphDisplayer =
- CallgraphDisplayer<SlabSample, CallChainNode<SlabSample>>;
+using SlabSampleCallgraphDisplayer = CallgraphDisplayer<SlabSample, CallChainNode<SlabSample>>;
struct EventAttrWithName {
perf_event_attr attr;
@@ -274,9 +259,8 @@ struct EventAttrWithName {
class KmemCommand : public Command {
public:
KmemCommand()
- : Command(
- "kmem", "collect kernel memory allocation information",
- // clang-format off
+ : Command("kmem", "collect kernel memory allocation information",
+ // clang-format off
"Usage: kmem (record [record options] | report [report options])\n"
"kmem record\n"
"-g Enable call graph recording. Same as '--call-graph fp'.\n"
@@ -309,8 +293,8 @@ class KmemCommand : public Command {
" the cpu allocating them.\n"
" The default slab sort keys are:\n"
" hit,caller,bytes_req,bytes_alloc,fragment,pingpong.\n"
- // clang-format on
- ),
+ // clang-format on
+ ),
is_record_(false),
use_slab_(false),
accumulate_callchain_(false),
@@ -322,8 +306,7 @@ class KmemCommand : public Command {
bool Run(const std::vector<std::string>& args);
private:
- bool ParseOptions(const std::vector<std::string>& args,
- std::vector<std::string>* left_args);
+ bool ParseOptions(const std::vector<std::string>& args, std::vector<std::string>* left_args);
bool RecordKmemInfo(const std::vector<std::string>& record_args);
bool ReportKmemInfo();
bool PrepareToBuildSampleTree();
@@ -449,10 +432,9 @@ bool KmemCommand::ParseOptions(const std::vector<std::string>& args,
bool KmemCommand::RecordKmemInfo(const std::vector<std::string>& record_args) {
std::vector<std::string> args;
if (use_slab_) {
- std::vector<std::string> trace_events = {
- "kmem:kmalloc", "kmem:kmem_cache_alloc",
- "kmem:kmalloc_node", "kmem:kmem_cache_alloc_node",
- "kmem:kfree", "kmem:kmem_cache_free"};
+ std::vector<std::string> trace_events = {"kmem:kmalloc", "kmem:kmem_cache_alloc",
+ "kmem:kmalloc_node", "kmem:kmem_cache_alloc_node",
+ "kmem:kfree", "kmem:kmem_cache_free"};
for (const auto& name : trace_events) {
if (ParseEventType(name)) {
args.insert(args.end(), {"-e", name});
@@ -497,8 +479,7 @@ bool KmemCommand::ReportKmemInfo() {
bool KmemCommand::PrepareToBuildSampleTree() {
if (use_slab_) {
if (slab_sort_keys_.empty()) {
- slab_sort_keys_ = {"hit", "caller", "bytes_req",
- "bytes_alloc", "fragment", "pingpong"};
+ slab_sort_keys_ = {"hit", "caller", "bytes_req", "bytes_alloc", "fragment", "pingpong"};
}
SampleComparator<SlabSample> comparator;
SampleComparator<SlabSample> sort_comparator;
@@ -512,8 +493,7 @@ bool KmemCommand::PrepareToBuildSampleTree() {
for (const auto& key : slab_sort_keys_) {
if (key == "hit") {
sort_comparator.AddCompareFunction(CompareSampleCount);
- displayer.AddDisplayFunction(accumulated_name + "Hit",
- DisplaySampleCount);
+ displayer.AddDisplayFunction(accumulated_name + "Hit", DisplaySampleCount);
} else if (key == "caller") {
comparator.AddCompareFunction(CompareSymbol);
displayer.AddDisplayFunction("Caller", DisplaySymbol);
@@ -522,16 +502,13 @@ bool KmemCommand::PrepareToBuildSampleTree() {
displayer.AddDisplayFunction("Ptr", DisplayPtr);
} else if (key == "bytes_req") {
sort_comparator.AddCompareFunction(CompareBytesReq);
- displayer.AddDisplayFunction(accumulated_name + "BytesReq",
- DisplayBytesReq);
+ displayer.AddDisplayFunction(accumulated_name + "BytesReq", DisplayBytesReq);
} else if (key == "bytes_alloc") {
sort_comparator.AddCompareFunction(CompareBytesAlloc);
- displayer.AddDisplayFunction(accumulated_name + "BytesAlloc",
- DisplayBytesAlloc);
+ displayer.AddDisplayFunction(accumulated_name + "BytesAlloc", DisplayBytesAlloc);
} else if (key == "fragment") {
sort_comparator.AddCompareFunction(CompareFragment);
- displayer.AddDisplayFunction(accumulated_name + "Fragment",
- DisplayFragment);
+ displayer.AddDisplayFunction(accumulated_name + "Fragment", DisplayFragment);
} else if (key == "gfp_flags") {
comparator.AddCompareFunction(CompareGfpFlags);
displayer.AddDisplayFunction("GfpFlags", DisplayGfpFlags);
@@ -542,10 +519,9 @@ bool KmemCommand::PrepareToBuildSampleTree() {
LOG(ERROR) << "Unknown sort key for slab allocation: " << key;
return false;
}
- slab_sample_tree_builder_.reset(
- new SlabSampleTreeBuilder(comparator, &thread_tree_));
- slab_sample_tree_builder_->SetCallChainSampleOptions(
- accumulate_callchain_, print_callgraph_, !callgraph_show_callee_);
+ slab_sample_tree_builder_.reset(new SlabSampleTreeBuilder(comparator, &thread_tree_));
+ slab_sample_tree_builder_->SetCallChainSampleOptions(accumulate_callchain_, print_callgraph_,
+ !callgraph_show_callee_);
sort_comparator.AddComparator(comparator);
slab_sample_tree_sorter_.reset(new SlabSampleTreeSorter(sort_comparator));
slab_sample_tree_displayer_.reset(new SlabSampleTreeDisplayer(displayer));
@@ -567,8 +543,7 @@ void KmemCommand::ReadEventAttrsFromRecordFile() {
bool KmemCommand::ReadFeaturesFromRecordFile() {
record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
- std::string arch =
- record_file_reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH);
+ std::string arch = record_file_reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH);
if (!arch.empty()) {
record_file_arch_ = GetArchType(arch);
if (record_file_arch_ == ARCH_UNSUPPORTED) {
@@ -581,8 +556,8 @@ bool KmemCommand::ReadFeaturesFromRecordFile() {
}
if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_TRACING_DATA)) {
std::vector<char> tracing_data;
- if (!record_file_reader_->ReadFeatureSection(
- PerfFileFormat::FEAT_TRACING_DATA, &tracing_data)) {
+ if (!record_file_reader_->ReadFeatureSection(PerfFileFormat::FEAT_TRACING_DATA,
+ &tracing_data)) {
return false;
}
ProcessTracingData(tracing_data);
@@ -592,9 +567,7 @@ bool KmemCommand::ReadFeaturesFromRecordFile() {
bool KmemCommand::ReadSampleTreeFromRecordFile() {
if (!record_file_reader_->ReadDataSection(
- [this](std::unique_ptr<Record> record) {
- return ProcessRecord(std::move(record));
- })) {
+ [this](std::unique_ptr<Record> record) { return ProcessRecord(std::move(record)); })) {
return false;
}
if (use_slab_) {
@@ -628,8 +601,7 @@ void KmemCommand::ProcessTracingData(const std::vector<char>& data) {
TracingFormat format = tracing.GetTracingFormatHavingId(trace_event_id);
if (use_slab_) {
if (format.name == "kmalloc" || format.name == "kmem_cache_alloc" ||
- format.name == "kmalloc_node" ||
- format.name == "kmem_cache_alloc_node") {
+ format.name == "kmalloc_node" || format.name == "kmem_cache_alloc_node") {
SlabFormat f;
f.type = SlabFormat::KMEM_ALLOC;
format.GetField("call_site", f.call_site);
@@ -665,8 +637,8 @@ bool KmemCommand::PrintReport() {
if (use_slab_) {
fprintf(report_fp, "\n\n");
PrintSlabReportContext(report_fp);
- slab_sample_tree_displayer_->DisplaySamples(
- report_fp, slab_sample_tree_.samples, &slab_sample_tree_);
+ slab_sample_tree_displayer_->DisplaySamples(report_fp, slab_sample_tree_.samples,
+ &slab_sample_tree_);
}
return true;
}
@@ -677,31 +649,28 @@ void KmemCommand::PrintReportContext(FILE* fp) {
}
fprintf(fp, "Arch: %s\n", GetArchString(record_file_arch_).c_str());
for (const auto& attr : event_attrs_) {
- fprintf(fp, "Event: %s (type %u, config %llu)\n", attr.name.c_str(),
- attr.attr.type, attr.attr.config);
+ fprintf(fp, "Event: %s (type %u, config %llu)\n", attr.name.c_str(), attr.attr.type,
+ attr.attr.config);
}
}
void KmemCommand::PrintSlabReportContext(FILE* fp) {
fprintf(fp, "Slab allocation information:\n");
- fprintf(fp, "Total requested bytes: %" PRIu64 "\n",
- slab_sample_tree_.total_requested_bytes);
- fprintf(fp, "Total allocated bytes: %" PRIu64 "\n",
- slab_sample_tree_.total_allocated_bytes);
- uint64_t fragment = slab_sample_tree_.total_allocated_bytes -
- slab_sample_tree_.total_requested_bytes;
+ fprintf(fp, "Total requested bytes: %" PRIu64 "\n", slab_sample_tree_.total_requested_bytes);
+ fprintf(fp, "Total allocated bytes: %" PRIu64 "\n", slab_sample_tree_.total_allocated_bytes);
+ uint64_t fragment =
+ slab_sample_tree_.total_allocated_bytes - slab_sample_tree_.total_requested_bytes;
double percentage = 0.0;
if (slab_sample_tree_.total_allocated_bytes != 0) {
percentage = 100.0 * fragment / slab_sample_tree_.total_allocated_bytes;
}
fprintf(fp, "Total fragment: %" PRIu64 ", %f%%\n", fragment, percentage);
- fprintf(fp, "Total allocations: %" PRIu64 "\n",
- slab_sample_tree_.nr_allocations);
+ fprintf(fp, "Total allocations: %" PRIu64 "\n", slab_sample_tree_.nr_allocations);
fprintf(fp, "Total frees: %" PRIu64 "\n", slab_sample_tree_.nr_frees);
percentage = 0.0;
if (slab_sample_tree_.nr_allocations != 0) {
- percentage = 100.0 * slab_sample_tree_.nr_cross_cpu_allocations /
- slab_sample_tree_.nr_allocations;
+ percentage =
+ 100.0 * slab_sample_tree_.nr_cross_cpu_allocations / slab_sample_tree_.nr_allocations;
}
fprintf(fp, "Total cross cpu allocation/free: %" PRIu64 ", %f%%\n",
slab_sample_tree_.nr_cross_cpu_allocations, percentage);
@@ -711,6 +680,7 @@ void KmemCommand::PrintSlabReportContext(FILE* fp) {
} // namespace
void RegisterKmemCommand() {
- RegisterCommand("kmem",
- [] { return std::unique_ptr<Command>(new KmemCommand()); });
+ RegisterCommand("kmem", [] { return std::unique_ptr<Command>(new KmemCommand()); });
}
+
+} // namespace simpleperf
diff --git a/simpleperf/cmd_kmem_test.cpp b/simpleperf/cmd_kmem_test.cpp
index 4dab87ed..b34f6148 100644
--- a/simpleperf/cmd_kmem_test.cpp
+++ b/simpleperf/cmd_kmem_test.cpp
@@ -27,6 +27,8 @@
#include "record_file.h"
#include "test_util.h"
+using namespace simpleperf;
+
static std::unique_ptr<Command> KmemCmd() {
return CreateCommandInstance("kmem");
}
@@ -42,14 +44,13 @@ static void KmemReportRawFile(const std::string& perf_data,
ReportResult* result) {
result->success = false;
TemporaryFile tmp_file;
- std::vector<std::string> args = {"report", "-i", perf_data, "-o",
- tmp_file.path};
+ close(tmp_file.release());
+ std::vector<std::string> args = {"report", "-i", perf_data, "-o", tmp_file.path};
args.insert(args.end(), additional_args.begin(), additional_args.end());
ASSERT_TRUE(KmemCmd()->Run(args));
ASSERT_TRUE(android::base::ReadFileToString(tmp_file.path, &result->content));
ASSERT_TRUE(!result->content.empty());
- std::vector<std::string> raw_lines =
- android::base::Split(result->content, "\n");
+ std::vector<std::string> raw_lines = android::base::Split(result->content, "\n");
result->lines.clear();
for (const auto& line : raw_lines) {
std::string s = android::base::Trim(line);
@@ -62,22 +63,21 @@ static void KmemReportRawFile(const std::string& perf_data,
}
static void KmemReportFile(const std::string& perf_data,
- const std::vector<std::string>& additional_args,
- ReportResult* result) {
+ const std::vector<std::string>& additional_args, ReportResult* result) {
KmemReportRawFile(GetTestData(perf_data), additional_args, result);
}
#if defined(__linux__)
#include "environment.h"
-static bool RunKmemRecordCmd(std::vector<std::string> v,
- const char* output_file = nullptr) {
+static bool RunKmemRecordCmd(std::vector<std::string> v, const char* output_file = nullptr) {
std::unique_ptr<TemporaryFile> tmpfile;
std::string out_file;
if (output_file != nullptr) {
out_file = output_file;
} else {
tmpfile.reset(new TemporaryFile);
+ close(tmpfile->release());
out_file = tmpfile->path;
}
v.insert(v.begin(), "record");
@@ -128,9 +128,7 @@ TEST(kmem_cmd, report_all_sort_options) {
ReportResult result;
KmemReportFile(
PERF_DATA_WITH_KMEM_SLAB_CALLGRAPH_RECORD,
- {"--slab-sort",
- "hit,caller,ptr,bytes_req,bytes_alloc,fragment,gfp_flags,pingpong"},
- &result);
+ {"--slab-sort", "hit,caller,ptr,bytes_req,bytes_alloc,fragment,gfp_flags,pingpong"}, &result);
ASSERT_TRUE(result.success);
ASSERT_NE(result.content.find("Ptr"), std::string::npos);
ASSERT_NE(result.content.find("GfpFlags"), std::string::npos);
diff --git a/simpleperf/cmd_list.cpp b/simpleperf/cmd_list.cpp
index ef6ba86a..4966b9c6 100644
--- a/simpleperf/cmd_list.cpp
+++ b/simpleperf/cmd_list.cpp
@@ -22,16 +22,15 @@
#include <android-base/file.h>
#include <android-base/logging.h>
+#include "ETMRecorder.h"
#include "command.h"
#include "environment.h"
-#include "ETMRecorder.h"
#include "event_attr.h"
#include "event_fd.h"
#include "event_selection_set.h"
#include "event_type.h"
-using namespace simpleperf;
-
+namespace simpleperf {
namespace {
enum EventTypeStatus {
@@ -47,8 +46,7 @@ static EventTypeStatus IsEventTypeSupported(const EventType& event_type) {
}
if (event_type.type != PERF_TYPE_RAW) {
perf_event_attr attr = CreateDefaultPerfEventAttr(event_type);
- // Exclude kernel to list supported events even when
- // /proc/sys/kernel/perf_event_paranoid is 2.
+ // 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;
@@ -87,8 +85,7 @@ static EventTypeStatus IsEventTypeSupported(const EventType& event_type) {
}
static void PrintEventTypesOfType(const std::string& type_name, const std::string& type_desc,
- const std::function<bool(const EventType&)>& is_type_fn,
- const std::set<EventType>& event_types) {
+ const std::function<bool(const EventType&)>& is_type_fn) {
printf("List of %s:\n", type_desc.c_str());
if (GetBuildArch() == ARCH_ARM || GetBuildArch() == ARCH_ARM64) {
if (type_name == "raw") {
@@ -104,11 +101,11 @@ static void PrintEventTypesOfType(const std::string& type_name, const std::strin
printf(" # More cache events are available in `simpleperf list raw`.\n");
}
}
- for (auto& event_type : event_types) {
+ auto callback = [&](const EventType& event_type) {
if (is_type_fn(event_type)) {
EventTypeStatus status = IsEventTypeSupported(event_type);
if (status == EventTypeStatus::NOT_SUPPORTED) {
- continue;
+ return true;
}
printf(" %s", event_type.name.c_str());
if (status == EventTypeStatus::MAY_NOT_SUPPORTED) {
@@ -119,7 +116,9 @@ static void PrintEventTypesOfType(const std::string& type_name, const std::strin
}
printf("\n");
}
- }
+ return true;
+ };
+ EventTypeManager::Instance().ForEachType(callback);
printf("\n");
}
@@ -143,8 +142,7 @@ class ListCommand : public Command {
" dwarf-based-call-graph\n"
" trace-offcpu\n"
// clang-format on
- ) {
- }
+ ) {}
bool Run(const std::vector<std::string>& args) override;
@@ -158,28 +156,22 @@ bool ListCommand::Run(const std::vector<std::string>& args) {
}
static std::map<std::string, std::pair<std::string, std::function<bool(const EventType&)>>>
- type_map = {
- {"hw",
- {"hardware events", [](const EventType& e) { return e.type == PERF_TYPE_HARDWARE; }}},
- {"sw",
- {"software events", [](const EventType& e) { return e.type == PERF_TYPE_SOFTWARE; }}},
- {"cache",
- {"hw-cache events", [](const EventType& e) { return e.type == PERF_TYPE_HW_CACHE; }}},
- {"raw",
- {"raw events provided by cpu pmu",
- [](const EventType& e) { return e.type == PERF_TYPE_RAW; }}},
- {"tracepoint",
- {"tracepoint events",
- [](const EventType& e) { return e.type == PERF_TYPE_TRACEPOINT; }}},
+ type_map =
+ { {"hw", {"hardware events", [](const EventType& e) { return e.type == PERF_TYPE_HARDWARE; }}},
+ {"sw", {"software events", [](const EventType& e) { return e.type == PERF_TYPE_SOFTWARE; }}},
+ {"cache", {"hw-cache events", [](const EventType& e) { return e.type == PERF_TYPE_HW_CACHE; }}},
+ {"raw",
+ {"raw events provided by cpu pmu",
+ [](const EventType& e) { return e.type == PERF_TYPE_RAW; }}},
+ {"tracepoint",
+ {"tracepoint events", [](const EventType& e) { return e.type == PERF_TYPE_TRACEPOINT; }}},
#if defined(__arm__) || defined(__aarch64__)
- {"cs-etm",
- {"coresight etm events",
- [](const EventType& e) {
- return e.type == ETMRecorder::GetInstance().GetEtmEventType();
- }}},
+ {"cs-etm",
+ {"coresight etm events",
+ [](const EventType& e) { return e.type == ETMRecorder::GetInstance().GetEtmEventType(); }}},
#endif
- {"pmu", {"pmu events", [](const EventType& e) { return e.IsPmuEvent(); }}},
- };
+ {"pmu", {"pmu events", [](const EventType& e) { return e.IsPmuEvent(); }}},
+ };
std::vector<std::string> names;
if (args.empty()) {
@@ -200,11 +192,9 @@ bool ListCommand::Run(const std::vector<std::string>& args) {
}
}
- auto& event_types = GetAllEventTypes();
-
for (auto& name : names) {
auto it = type_map.find(name);
- PrintEventTypesOfType(name, it->second.first, it->second.second, event_types);
+ PrintEventTypesOfType(name, it->second.first, it->second.second);
}
return true;
}
@@ -226,3 +216,5 @@ void ListCommand::ShowFeatures() {
void RegisterListCommand() {
RegisterCommand("list", [] { return std::unique_ptr<Command>(new ListCommand); });
}
+
+} // namespace simpleperf
diff --git a/simpleperf/cmd_list_test.cpp b/simpleperf/cmd_list_test.cpp
index 6712b0a4..e2f804da 100644
--- a/simpleperf/cmd_list_test.cpp
+++ b/simpleperf/cmd_list_test.cpp
@@ -19,6 +19,8 @@
#include "command.h"
#include "test_util.h"
+using namespace simpleperf;
+
class ListCommandTest : public ::testing::Test {
protected:
virtual void SetUp() {
diff --git a/simpleperf/cmd_merge.cpp b/simpleperf/cmd_merge.cpp
new file mode 100644
index 00000000..d1ac6dca
--- /dev/null
+++ b/simpleperf/cmd_merge.cpp
@@ -0,0 +1,425 @@
+/*
+ * 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.
+ */
+
+#include <stdio.h>
+
+#include <memory>
+#include <regex>
+#include <string>
+
+#include <android-base/macros.h>
+#include <android-base/strings.h>
+
+#include "command.h"
+#include "event_attr.h"
+#include "record_file.h"
+#include "thread_tree.h"
+#include "utils.h"
+
+namespace simpleperf {
+namespace {
+
+class MergedFileFeature {
+ public:
+ MergedFileFeature(FileFeature& file)
+ : path_(file.path),
+ type_(file.type),
+ min_vaddr_(file.min_vaddr),
+ file_offset_of_min_vaddr_(file.file_offset_of_min_vaddr),
+ dex_file_offsets_(std::move(file.dex_file_offsets)) {
+ for (auto& symbol : file.symbols) {
+ symbol_map_.emplace(symbol.addr, std::move(symbol));
+ }
+ }
+
+ bool Merge(FileFeature& file) {
+ if (file.type != type_ || file.min_vaddr != min_vaddr_ ||
+ file.file_offset_of_min_vaddr != file_offset_of_min_vaddr_ ||
+ file.dex_file_offsets != dex_file_offsets_) {
+ return false;
+ }
+ for (auto& symbol : file.symbols) {
+ auto it = symbol_map_.lower_bound(symbol.addr);
+ if (it != symbol_map_.end()) {
+ const auto& found = it->second;
+ if (found.addr == symbol.addr && found.len == symbol.len &&
+ strcmp(found.Name(), symbol.Name()) == 0) {
+ // The symbol already exists in symbol_map.
+ continue;
+ }
+ if (symbol.addr + symbol.len > found.addr) {
+ // an address conflict with the next symbol
+ return false;
+ }
+ }
+ if (it != symbol_map_.begin()) {
+ --it;
+ if (it->second.addr + it->second.len > symbol.addr) {
+ // an address conflict with the previous symbol
+ return false;
+ }
+ }
+ symbol_map_.emplace(symbol.addr, std::move(symbol));
+ }
+ return true;
+ }
+
+ void ToFileFeature(FileFeature* file) const {
+ file->path = path_;
+ file->type = type_;
+ file->min_vaddr = min_vaddr_;
+ file->file_offset_of_min_vaddr = file_offset_of_min_vaddr_;
+ file->symbol_ptrs.clear();
+ for (const auto& [_, symbol] : symbol_map_) {
+ file->symbol_ptrs.emplace_back(&symbol);
+ }
+ file->dex_file_offsets = dex_file_offsets_;
+ }
+
+ private:
+ std::string path_;
+ DsoType type_;
+ uint64_t min_vaddr_;
+ uint64_t file_offset_of_min_vaddr_;
+ std::map<uint64_t, Symbol> symbol_map_;
+ std::vector<uint64_t> dex_file_offsets_;
+
+ DISALLOW_COPY_AND_ASSIGN(MergedFileFeature);
+};
+
+class MergeCommand : public Command {
+ public:
+ MergeCommand()
+ : Command("merge", "merge multiple perf.data into one",
+ // clang-format off
+"Usage: simpleperf merge [options]\n"
+" Merge multiple perf.data into one. The input files should be recorded on the same\n"
+" device using the same event types.\n"
+"-i <file1>,<file2>,... Input recording files separated by comma\n"
+"-o <file> output recording file\n"
+"\n"
+"Examples:\n"
+"$ simpleperf merge -i perf1.data,perf2.data -o perf.data\n"
+ // clang-format on
+ ) {}
+
+ bool Run(const std::vector<std::string>& args) override {
+ // 1. Parse options.
+ if (!ParseOptions(args)) {
+ return false;
+ }
+
+ // 2. Open input files and check if they are mergeable.
+ for (const auto& file : input_files_) {
+ readers_.emplace_back(RecordFileReader::CreateInstance(file));
+ if (!readers_.back()) {
+ return false;
+ }
+ }
+ if (!IsMergeable()) {
+ return false;
+ }
+
+ // 3. Merge files.
+ writer_ = RecordFileWriter::CreateInstance(output_file_);
+ if (!writer_) {
+ return false;
+ }
+ if (!MergeAttrSection() || !MergeDataSection() || !MergeFeatureSection()) {
+ return false;
+ }
+ return writer_->Close();
+ }
+
+ private:
+ bool ParseOptions(const std::vector<std::string>& args) {
+ const OptionFormatMap option_formats = {
+ {"-i", {OptionValueType::STRING, OptionType::MULTIPLE}},
+ {"-o", {OptionValueType::STRING, OptionType::SINGLE}},
+ };
+ OptionValueMap options;
+ std::vector<std::pair<OptionName, OptionValue>> ordered_options;
+ if (!PreprocessOptions(args, option_formats, &options, &ordered_options, nullptr)) {
+ return false;
+ }
+ for (const OptionValue& value : options.PullValues("-i")) {
+ auto files = android::base::Split(*value.str_value, ",");
+ input_files_.insert(input_files_.end(), files.begin(), files.end());
+ }
+ options.PullStringValue("-o", &output_file_);
+
+ CHECK(options.values.empty());
+
+ if (input_files_.empty()) {
+ LOG(ERROR) << "missing input files";
+ return false;
+ }
+ if (output_file_.empty()) {
+ LOG(ERROR) << "missing output file";
+ return false;
+ }
+ return true;
+ }
+
+ bool IsMergeable() { return CheckFeatureSection() && CheckAttrSection(); }
+
+ // Check feature sections to know if the recording environments are the same.
+ bool CheckFeatureSection() {
+ auto get_arch = [](std::unique_ptr<RecordFileReader>& reader) {
+ return reader->ReadFeatureString(PerfFileFormat::FEAT_ARCH);
+ };
+ auto get_kernel_version = [](std::unique_ptr<RecordFileReader>& reader) {
+ return reader->ReadFeatureString(PerfFileFormat::FEAT_OSRELEASE);
+ };
+ auto get_meta_info = [](std::unique_ptr<RecordFileReader>& reader, const char* key) {
+ auto it = reader->GetMetaInfoFeature().find(key);
+ return it == reader->GetMetaInfoFeature().end() ? "" : it->second;
+ };
+ auto get_simpleperf_version = [&](std::unique_ptr<RecordFileReader>& reader) {
+ return get_meta_info(reader, "simpleperf_version");
+ };
+ auto get_trace_offcpu = [&](std::unique_ptr<RecordFileReader>& reader) {
+ return get_meta_info(reader, "trace_offcpu");
+ };
+ auto get_event_types = [&](std::unique_ptr<RecordFileReader>& reader) {
+ std::string s = get_meta_info(reader, "event_type_info");
+ std::vector<std::string> v = android::base::Split(s, "\n");
+ std::sort(v.begin(), v.end());
+ return android::base::Join(v, ";");
+ };
+ auto get_android_device = [&](std::unique_ptr<RecordFileReader>& reader) {
+ return get_meta_info(reader, "product_props");
+ };
+ auto get_android_version = [&](std::unique_ptr<RecordFileReader>& reader) {
+ return get_meta_info(reader, "android_version");
+ };
+ auto get_app_package_name = [&](std::unique_ptr<RecordFileReader>& reader) {
+ return get_meta_info(reader, "app_package_name");
+ };
+ auto get_clockid = [&](std::unique_ptr<RecordFileReader>& reader) {
+ return get_meta_info(reader, "clockid");
+ };
+ auto get_used_features = [](std::unique_ptr<RecordFileReader>& reader) {
+ std::string s;
+ for (const auto& [key, _] : reader->FeatureSectionDescriptors()) {
+ s += std::to_string(key) + ",";
+ }
+ return s;
+ };
+
+ using value_func_t = std::function<std::string(std::unique_ptr<RecordFileReader>&)>;
+ std::vector<std::pair<std::string, value_func_t>> check_entries = {
+ std::make_pair("arch", get_arch),
+ std::make_pair("kernel_version", get_kernel_version),
+ std::make_pair("simpleperf_version", get_simpleperf_version),
+ std::make_pair("trace_offcpu", get_trace_offcpu),
+ std::make_pair("event_types", get_event_types),
+ std::make_pair("android_device", get_android_device),
+ std::make_pair("android_version", get_android_version),
+ std::make_pair("app_package_name", get_app_package_name),
+ std::make_pair("clockid", get_clockid),
+ std::make_pair("used_features", get_used_features),
+ };
+
+ for (const auto& [name, get_value] : check_entries) {
+ std::string value0 = get_value(readers_[0]);
+ for (size_t i = 1; i < readers_.size(); i++) {
+ std::string value = get_value(readers_[i]);
+ if (value != value0) {
+ LOG(ERROR) << input_files_[0] << " and " << input_files_[i] << " are not mergeable for "
+ << name << " difference: " << value0 << " vs " << value;
+ return false;
+ }
+ }
+ }
+
+ if (readers_[0]->HasFeature(PerfFileFormat::FEAT_AUXTRACE)) {
+ LOG(ERROR) << "merging of recording files with auxtrace feature isn't supported";
+ return false;
+ }
+ return true;
+ }
+
+ // Check attr sections to know if recorded event types are the same.
+ bool CheckAttrSection() {
+ std::vector<EventAttrWithId> attrs0 = readers_[0]->AttrSection();
+ for (size_t i = 1; i < readers_.size(); i++) {
+ std::vector<EventAttrWithId> 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) {
+ LOG(ERROR) << input_files_[0] << " and " << input_files_[i]
+ << " are not mergeable for recording different event types";
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ bool MergeAttrSection() { return writer_->WriteAttrSection(readers_[0]->AttrSection()); }
+
+ bool MergeDataSection() {
+ for (size_t i = 0; i < readers_.size(); i++) {
+ if (i != 0) {
+ if (!WriteGapInDataSection(i - 1, i)) {
+ return false;
+ }
+ }
+ auto callback = [this](std::unique_ptr<Record> record) {
+ return ProcessRecord(record.get());
+ };
+ if (!readers_[i]->ReadDataSection(callback)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool ProcessRecord(Record* record) { return writer_->WriteRecord(*record); }
+
+ bool WriteGapInDataSection(size_t prev_reader_id, size_t next_reader_id) {
+ // MergeAttrSection() only maps event_ids in readers_[0] to event attrs. So we need to
+ // 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();
+ 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) {
+ if (auto it = cur_map.find(event_id); it == cur_map.end() || it->second != attr_id) {
+ event_id_data.push_back(attr_id);
+ event_id_data.push_back(event_id);
+ }
+ }
+ }
+ if (!event_id_data.empty()) {
+ EventIdRecord record(event_id_data);
+ if (!ProcessRecord(&record)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool MergeFeatureSection() {
+ std::vector<int> features;
+ for (const auto& [key, _] : readers_[0]->FeatureSectionDescriptors()) {
+ features.push_back(key);
+ }
+ if (!writer_->BeginWriteFeatures(features.size())) {
+ return false;
+ }
+ for (int feature : features) {
+ if (feature == PerfFileFormat::FEAT_OSRELEASE || feature == PerfFileFormat::FEAT_ARCH ||
+ feature == PerfFileFormat::FEAT_BRANCH_STACK ||
+ feature == PerfFileFormat::FEAT_META_INFO || feature == PerfFileFormat::FEAT_CMDLINE) {
+ std::vector<char> data;
+ if (!readers_[0]->ReadFeatureSection(feature, &data) ||
+ !writer_->WriteFeature(feature, data.data(), data.size())) {
+ return false;
+ }
+ } else if (feature == PerfFileFormat::FEAT_BUILD_ID) {
+ WriteBuildIdFeature();
+ } else if (feature == PerfFileFormat::FEAT_FILE) {
+ WriteFileFeature();
+ } else {
+ LOG(WARNING) << "Drop feature " << feature << ", which isn't supported in the merge cmd.";
+ }
+ }
+ return writer_->EndWriteFeatures();
+ }
+
+ bool WriteBuildIdFeature() {
+ std::map<std::string, BuildIdRecord> build_ids;
+ std::unordered_set<std::string> files_to_drop;
+ for (auto& reader : readers_) {
+ for (auto& record : reader->ReadBuildIdFeature()) {
+ auto it = build_ids.find(record.filename);
+ if (it == build_ids.end()) {
+ build_ids.emplace(record.filename, std::move(record));
+ } else if (it->second.build_id != record.build_id) {
+ if (files_to_drop.count(record.filename) == 0) {
+ files_to_drop.emplace(record.filename);
+ LOG(WARNING)
+ << record.filename
+ << " has different build ids in different record files. So drop its build ids.";
+ }
+ }
+ }
+ }
+ std::vector<BuildIdRecord> records;
+ for (auto& [filename, record] : build_ids) {
+ if (files_to_drop.count(filename) == 0) {
+ records.emplace_back(std::move(record));
+ }
+ }
+ return writer_->WriteBuildIdFeature(records);
+ }
+
+ bool WriteFileFeature() {
+ std::map<std::string, MergedFileFeature> file_map;
+ std::unordered_set<std::string> files_to_drop;
+
+ // Read file features.
+ for (auto& reader : readers_) {
+ FileFeature file;
+ size_t read_pos = 0;
+ while (reader->ReadFileFeature(read_pos, &file)) {
+ if (files_to_drop.count(file.path) != 0) {
+ continue;
+ }
+ if (auto it = file_map.find(file.path); it == file_map.end()) {
+ file_map.emplace(file.path, file);
+ } else if (!it->second.Merge(file)) {
+ LOG(WARNING)
+ << file.path
+ << " has address-conflict symbols in different record files. So drop its symbols.";
+ files_to_drop.emplace(file.path);
+ }
+ }
+ }
+ // Write file features.
+ for (const auto& [file_path, file] : file_map) {
+ if (files_to_drop.count(file_path) != 0) {
+ continue;
+ }
+ FileFeature file_feature;
+ file.ToFileFeature(&file_feature);
+ if (!writer_->WriteFileFeature(file_feature)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ std::vector<std::string> input_files_;
+ std::vector<std::unique_ptr<RecordFileReader>> readers_;
+ std::string output_file_;
+ std::unique_ptr<RecordFileWriter> writer_;
+};
+
+} // namespace
+
+void RegisterMergeCommand() {
+ return RegisterCommand("merge", [] { return std::unique_ptr<Command>(new MergeCommand); });
+}
+
+} // namespace simpleperf
diff --git a/simpleperf/cmd_merge_test.cpp b/simpleperf/cmd_merge_test.cpp
new file mode 100644
index 00000000..482d0893
--- /dev/null
+++ b/simpleperf/cmd_merge_test.cpp
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include <optional>
+
+#include <android-base/file.h>
+
+#include "command.h"
+#include "get_test_data.h"
+#include "record.h"
+#include "record_file.h"
+#include "test_util.h"
+#include "utils.h"
+
+using namespace simpleperf;
+
+static std::unique_ptr<Command> MergeCmd() {
+ return CreateCommandInstance("merge");
+}
+
+static std::string GetReport(const std::string& record_file) {
+ TemporaryFile tmpfile;
+ close(tmpfile.release());
+ if (!CreateCommandInstance("report")->Run({"-i", record_file, "-g", "-o", tmpfile.path})) {
+ return "";
+ }
+ std::string data;
+ if (!android::base::ReadFileToString(tmpfile.path, &data)) {
+ return "";
+ }
+ return data;
+}
+
+TEST(merge_cmd, input_output_options) {
+ // missing arguments
+ ASSERT_FALSE(MergeCmd()->Run({}));
+ // missing input files
+ std::string input_file = GetTestData("perf.data");
+ ASSERT_FALSE(MergeCmd()->Run({"-i", input_file}));
+ // missing output file
+ TemporaryFile tmpfile;
+ close(tmpfile.release());
+ ASSERT_FALSE(MergeCmd()->Run({"-o", tmpfile.path}));
+ ASSERT_TRUE(MergeCmd()->Run({"-i", input_file, "-o", tmpfile.path}));
+ // input files separated by comma
+ ASSERT_TRUE(MergeCmd()->Run({"-i", input_file + "," + input_file, "-o", tmpfile.path}));
+ // input files in different -i options
+ ASSERT_TRUE(MergeCmd()->Run({"-i", input_file, "-i", input_file, "-o", tmpfile.path}));
+}
+
+TEST(merge_cmd, merge_two_files) {
+ std::string input_file1 = GetTestData("perf_merge1.data");
+ std::string input_file2 = GetTestData("perf_merge2.data");
+
+ std::string report = GetReport(input_file1);
+ ASSERT_NE(report.find("Samples: 27"), std::string::npos);
+ ASSERT_NE(report.find("malloc"), std::string::npos);
+ ASSERT_EQ(report.find("sleep_main"), std::string::npos);
+ ASSERT_NE(report.find("toybox_main"), std::string::npos);
+
+ report = GetReport(input_file2);
+ ASSERT_NE(report.find("Samples: 31"), std::string::npos);
+ ASSERT_EQ(report.find("malloc"), std::string::npos);
+ ASSERT_NE(report.find("sleep_main"), std::string::npos);
+ ASSERT_NE(report.find("toybox_main"), std::string::npos);
+
+ TemporaryFile tmpfile;
+ close(tmpfile.release());
+ ASSERT_TRUE(MergeCmd()->Run({"-i", input_file1 + "," + input_file2, "-o", tmpfile.path}));
+ report = GetReport(tmpfile.path);
+ // sum of sample counts in input files
+ ASSERT_NE(report.find("Samples: 58"), std::string::npos);
+ // union of symbols in input files
+ ASSERT_NE(report.find("malloc"), std::string::npos);
+ ASSERT_NE(report.find("sleep_main"), std::string::npos);
+ ASSERT_NE(report.find("toybox_main"), std::string::npos);
+}
diff --git a/simpleperf/cmd_monitor.cpp b/simpleperf/cmd_monitor.cpp
new file mode 100644
index 00000000..50f055e1
--- /dev/null
+++ b/simpleperf/cmd_monitor.cpp
@@ -0,0 +1,620 @@
+/*
+ * 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.
+ */
+#include <inttypes.h>
+#include <libgen.h>
+#include <signal.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <sys/utsname.h>
+#include <time.h>
+#include <unistd.h>
+#include <optional>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#if defined(__ANDROID__)
+#include <android-base/properties.h>
+#endif
+
+#include "IOEventLoop.h"
+#include "MapRecordReader.h"
+#include "OfflineUnwinder.h"
+#include "RecordFilter.h"
+#include "command.h"
+#include "dso.h"
+#include "environment.h"
+#include "event_selection_set.h"
+#include "event_type.h"
+#include "read_elf.h"
+#include "read_symbol_map.h"
+#include "record.h"
+#include "thread_tree.h"
+#include "tracing.h"
+#include "utils.h"
+
+namespace simpleperf {
+namespace {
+
+using android::base::ParseUint;
+using android::base::Realpath;
+using android::base::StringAppendF;
+
+struct SymbolInfo {
+ Dso* dso;
+ const Symbol* symbol;
+ uint64_t vaddr_in_file;
+};
+
+// 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;
+
+// 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;
+
+// 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 kRecordBufferSize = 64 * 1024 * 1024;
+static constexpr size_t kSystemWideRecordBufferSize = 256 * 1024 * 1024;
+
+class MonitorCommand : public Command {
+ public:
+ MonitorCommand()
+ : 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"
+" Currently, only supports system-wide collection.\n"
+"\n"
+"Select monitored threads:\n"
+"-a System-wide collection. Use with --exclude-perf to exclude\n"
+" samples for simpleperf process.\n"
+"\n"
+"Select monitored event types:\n"
+"-e event1[:modifier1],event2[:modifier2],...\n"
+" Select a list of events to record. An event can be:\n"
+" 1) an event name listed in `simpleperf list`;\n"
+" 2) a raw PMU event in rN format. N is a hex number.\n"
+" For example, r1b selects event number 0x1b.\n"
+" Modifiers can be added to define how the event should be\n"
+" monitored. Possible modifiers are:\n"
+" u - monitor user space events only\n"
+" k - monitor kernel space events only\n"
+"\n"
+"Select monitoring options:\n"
+"-f freq Set event sample frequency. It means recording at most [freq]\n"
+" 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"
+" 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"
+" is -c 1.\n"
+"--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"
+"-g Same as '--call-graph dwarf'.\n"
+"--duration time_in_sec Monitor for time_in_sec seconds. Here time_in_sec"
+" may be any positive floating point number.\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"
+"\n"
+"Sample filter options:\n"
+"--exclude-perf Exclude samples for simpleperf process.\n"
+RECORD_FILTER_OPTION_HELP_MSG
+"\n"
+ // clang-format on
+ ),
+ system_wide_collection_(false),
+ fp_callchain_sampling_(false),
+ dwarf_callchain_sampling_(false),
+ dump_stack_size_in_dwarf_sampling_(MAX_DUMP_STACK_SIZE),
+ unwind_dwarf_callchain_(true),
+ duration_in_sec_(0),
+ event_selection_set_(false),
+ mmap_page_range_(std::make_pair(1, DESIRED_PAGES_IN_MAPPED_BUFFER)),
+ sample_record_count_(0),
+ last_record_timestamp_(0u),
+ record_filter_(thread_tree_) {
+ // If we run `adb shell simpleperf record xxx` and stop profiling by ctrl-c,
+ // adb closes sockets connecting simpleperf. After that, simpleperf will
+ // receive SIGPIPE when writing to stdout/stderr, which is a problem when we
+ // use '--app' option. So ignore SIGPIPE to finish properly.
+ signal(SIGPIPE, SIG_IGN);
+ }
+
+ bool Run(const std::vector<std::string>& args);
+
+ private:
+ bool ParseOptions(const std::vector<std::string>& args);
+ bool AdjustPerfEventLimit();
+ bool PrepareMonitoring();
+ bool DoMonitoring();
+ bool SetEventSelectionFlags();
+ bool DumpProcessMaps(pid_t pid, const std::unordered_set<pid_t>& tids);
+ void DumpSampleRecord(const SampleRecord& sr);
+ void DumpSampleCallchain(const SampleRecord& sr);
+ bool ProcessRecord(Record* record);
+ SymbolInfo GetSymbolInfo(uint32_t pid, uint32_t tid, uint64_t ip, bool in_kernel);
+ bool DumpMapsForRecord(Record* record);
+ void UpdateRecord(Record* record);
+ bool UnwindRecord(SampleRecord& r);
+
+ 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_;
+ uint32_t dump_stack_size_in_dwarf_sampling_;
+ bool unwind_dwarf_callchain_;
+ std::unique_ptr<OfflineUnwinder> offline_unwinder_;
+ double duration_in_sec_;
+ EventSelectionSet event_selection_set_;
+ std::pair<size_t, size_t> mmap_page_range_;
+ ThreadTree thread_tree_;
+ uint64_t sample_record_count_;
+ uint64_t last_record_timestamp_; // used to insert Mmap2Records for JIT debug info
+ // In system wide recording, record if we have dumped map info for a process.
+ std::unordered_set<pid_t> dumped_processes_;
+ bool exclude_perf_ = false;
+ RecordFilter record_filter_;
+ std::unordered_map<uint64_t, std::string> event_names_;
+
+ std::optional<MapRecordReader> map_record_reader_;
+};
+
+bool MonitorCommand::Run(const std::vector<std::string>& args) {
+ ScopedCurrentArch scoped_arch(GetMachineArch());
+ if (!CheckPerfEventLimit()) {
+ return false;
+ }
+ AllowMoreOpenedFiles();
+
+ if (!ParseOptions(args)) {
+ return false;
+ }
+ if (!AdjustPerfEventLimit()) {
+ return false;
+ }
+
+ if (!PrepareMonitoring()) {
+ return false;
+ }
+ return DoMonitoring();
+}
+
+bool MonitorCommand::PrepareMonitoring() {
+ // 1. Process options before opening perf event files.
+ if (!SetEventSelectionFlags()) {
+ return false;
+ }
+ if (unwind_dwarf_callchain_) {
+ offline_unwinder_ = OfflineUnwinder::Create(false);
+ }
+
+ // 2. Add monitored targets.
+ if (system_wide_collection_) {
+ event_selection_set_.AddMonitoredThreads({-1});
+ } else {
+ LOG(ERROR) << "No threads to monitor. Try `simpleperf help monitor` for help";
+ return false;
+ }
+
+ // 3. Open perf event files and create mapped buffers.
+ 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_)) {
+ return false;
+ }
+ auto callback = std::bind(&MonitorCommand::ProcessRecord, this, std::placeholders::_1);
+ if (!event_selection_set_.PrepareToReadMmapEventData(callback)) {
+ return false;
+ }
+
+ // Keep track of the event names per id.
+ event_names_ = event_selection_set_.GetEventNamesById();
+
+ // 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],
+ event_selection_set_.RecordNotExecutableMaps());
+ map_record_reader_->SetCallback([this](Record* r) { return ProcessRecord(r); });
+
+ // 4. Load kallsyms, if possible.
+ std::string kallsyms;
+ if (LoadKernelSymbols(&kallsyms)) {
+ Dso::SetKallsyms(std::move(kallsyms));
+ }
+ map_record_reader_->ReadKernelMaps();
+
+ // 5. Add read/signal/periodic Events.
+ IOEventLoop* loop = event_selection_set_.GetIOEventLoop();
+ auto exit_loop_callback = [loop]() { return loop->ExitLoop(); };
+ if (!loop->AddSignalEvents({SIGCHLD, SIGINT, SIGTERM}, exit_loop_callback)) {
+ return false;
+ }
+
+ // Only add an event for SIGHUP if we didn't inherit SIG_IGN (e.g. from
+ // nohup).
+ if (!SignalIsIgnored(SIGHUP)) {
+ if (!loop->AddSignalEvent(SIGHUP, exit_loop_callback)) {
+ return false;
+ }
+ }
+
+ if (duration_in_sec_ != 0) {
+ if (!loop->AddPeriodicEvent(SecondToTimeval(duration_in_sec_),
+ [loop]() { return loop->ExitLoop(); })) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool MonitorCommand::DoMonitoring() {
+ if (!event_selection_set_.GetIOEventLoop()->RunLoop()) {
+ return false;
+ }
+ if (!event_selection_set_.FinishReadMmapEventData()) {
+ return false;
+ }
+ LOG(ERROR) << "Processed samples: " << sample_record_count_;
+ return true;
+}
+
+inline const OptionFormatMap& GetMonitorCmdOptionFormats() {
+ static OptionFormatMap option_formats;
+ if (option_formats.empty()) {
+ option_formats = {
+ {"-a", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::NOT_ALLOWED}},
+ {"-c", {OptionValueType::UINT, OptionType::ORDERED, AppRunnerType::ALLOWED}},
+ {"--call-graph", {OptionValueType::STRING, OptionType::ORDERED, AppRunnerType::ALLOWED}},
+ {"--cpu-percent", {OptionValueType::UINT, 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}},
+ {"-f", {OptionValueType::UINT, OptionType::ORDERED, AppRunnerType::ALLOWED}},
+ {"-g", {OptionValueType::NONE, OptionType::ORDERED, AppRunnerType::ALLOWED}},
+ {"-t", {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}},
+ };
+ const OptionFormatMap& record_filter_options = GetRecordFilterOptionFormats();
+ option_formats.insert(record_filter_options.begin(), record_filter_options.end());
+ }
+ return option_formats;
+}
+
+bool MonitorCommand::ParseOptions(const std::vector<std::string>& args) {
+ OptionValueMap options;
+ std::vector<std::pair<OptionName, OptionValue>> ordered_options;
+
+ if (!PreprocessOptions(args, GetMonitorCmdOptionFormats(), &options, &ordered_options, nullptr)) {
+ return false;
+ }
+
+ // Process options.
+ system_wide_collection_ = options.PullBoolValue("-a");
+
+ if (!options.PullUintValue("--cpu-percent", &cpu_time_max_percent_, 1, 100)) {
+ return false;
+ }
+
+ if (!options.PullDoubleValue("--duration", &duration_in_sec_, 1e-9)) {
+ return false;
+ }
+
+ exclude_perf_ = options.PullBoolValue("--exclude-perf");
+ if (!record_filter_.ParseOptions(options)) {
+ return false;
+ }
+
+ 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;
+
+ if (name == "-c" || name == "-f") {
+ if (value.uint_value < 1) {
+ LOG(ERROR) << "invalid " << name << ": " << value.uint_value;
+ return false;
+ }
+ if (name == "-c") {
+ sample_speed_.reset(new SampleSpeed(0, 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_);
+ }
+ wait_setting_speed_event_groups.clear();
+
+ } else if (name == "--call-graph") {
+ std::vector<std::string> strs = android::base::Split(*value.str_value, ",");
+ if (strs[0] == "fp") {
+ fp_callchain_sampling_ = true;
+ dwarf_callchain_sampling_ = false;
+ } else if (strs[0] == "dwarf") {
+ fp_callchain_sampling_ = false;
+ dwarf_callchain_sampling_ = true;
+ if (strs.size() > 1) {
+ uint64_t size;
+ if (!ParseUint(strs[1], &size)) {
+ LOG(ERROR) << "invalid dump stack size in --call-graph option: " << strs[1];
+ return false;
+ }
+ if ((size & 7) != 0) {
+ LOG(ERROR) << "dump stack size " << size << " is not 8-byte aligned.";
+ return false;
+ }
+ if (size >= MAX_DUMP_STACK_SIZE) {
+ LOG(ERROR) << "dump stack size " << size << " is bigger than max allowed size "
+ << MAX_DUMP_STACK_SIZE << ".";
+ return false;
+ }
+ dump_stack_size_in_dwarf_sampling_ = static_cast<uint32_t>(size);
+ }
+ }
+
+ } 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)) {
+ 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") {
+ fp_callchain_sampling_ = false;
+ dwarf_callchain_sampling_ = true;
+ } else {
+ CHECK(false) << "unprocessed option: " << name;
+ }
+ }
+
+ if (event_selection_set_.empty()) {
+ LOG(ERROR) << "No event to record. Use `-e` to specify which event should be monitored.";
+ return false;
+ }
+
+ if (fp_callchain_sampling_) {
+ if (GetBuildArch() == ARCH_ARM) {
+ LOG(WARNING) << "`--callgraph fp` option doesn't work well on arm architecture, "
+ << "consider using `-g` option or profiling on aarch64 architecture.";
+ }
+ }
+
+ if (system_wide_collection_ && event_selection_set_.HasMonitoredTarget()) {
+ LOG(ERROR) << "Record system wide and existing processes/threads can't be "
+ "used at the same time.";
+ return false;
+ }
+
+ if (system_wide_collection_ && !IsRoot()) {
+ LOG(ERROR) << "System wide profiling needs root privilege.";
+ return false;
+ }
+ return true;
+}
+
+bool MonitorCommand::AdjustPerfEventLimit() {
+ bool set_prop = false;
+ // 1. Adjust max_sample_rate.
+ uint64_t cur_max_freq;
+ if (GetMaxSampleFrequency(&cur_max_freq) && cur_max_freq < max_sample_freq_ &&
+ !SetMaxSampleFrequency(max_sample_freq_)) {
+ set_prop = true;
+ }
+ // 2. Adjust perf_cpu_time_max_percent.
+ size_t cur_percent;
+ if (GetCpuTimeMaxPercent(&cur_percent) && cur_percent != cpu_time_max_percent_ &&
+ !SetCpuTimeMaxPercent(cpu_time_max_percent_)) {
+ set_prop = true;
+ }
+ // 3. Adjust perf_event_mlock_kb.
+ long cpus = sysconf(_SC_NPROCESSORS_CONF);
+ uint64_t mlock_kb = cpus * (mmap_page_range_.second + 1) * 4;
+
+ uint64_t cur_mlock_kb;
+ if (GetPerfEventMlockKb(&cur_mlock_kb) && cur_mlock_kb < mlock_kb &&
+ !SetPerfEventMlockKb(mlock_kb)) {
+ set_prop = true;
+ }
+
+ if (GetAndroidVersion() >= kAndroidVersionQ && set_prop) {
+ return SetPerfEventLimits(std::max(max_sample_freq_, cur_max_freq), cpu_time_max_percent_,
+ std::max(mlock_kb, cur_mlock_kb));
+ }
+ return true;
+}
+
+bool MonitorCommand::SetEventSelectionFlags() {
+ event_selection_set_.SampleIdAll();
+ event_selection_set_.WakeupPerSample();
+ if (fp_callchain_sampling_) {
+ event_selection_set_.EnableFpCallChainSampling();
+ } else if (dwarf_callchain_sampling_) {
+ if (!event_selection_set_.EnableDwarfCallChainSampling(dump_stack_size_in_dwarf_sampling_)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool MonitorCommand::ProcessRecord(Record* record) {
+ UpdateRecord(record);
+ last_record_timestamp_ = std::max(last_record_timestamp_, record->Timestamp());
+ // In system wide recording, maps are dumped when they are needed by records.
+ if (system_wide_collection_ && !DumpMapsForRecord(record)) {
+ return false;
+ }
+ if (record->type() == PERF_RECORD_SAMPLE) {
+ auto& r = *static_cast<SampleRecord*>(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)) {
+ return true;
+ }
+
+ // AdjustCallChainGeneratedByKernel() should go before UnwindRecord().
+ // Because we don't want to adjust callchains generated by dwarf unwinder.
+ if (fp_callchain_sampling_ || dwarf_callchain_sampling_) {
+ r.AdjustCallChainGeneratedByKernel();
+ if (!UnwindRecord(r)) {
+ return false;
+ }
+ }
+ DumpSampleRecord(r);
+ if (fp_callchain_sampling_ || dwarf_callchain_sampling_) {
+ DumpSampleCallchain(r);
+ }
+ sample_record_count_++;
+ } else {
+ // Other types of record are forwarded to the thread tree to build the
+ // representation of each processes (mmap, comm, etc).
+ thread_tree_.Update(*record);
+ }
+ return true;
+}
+
+void MonitorCommand::DumpSampleRecord(const SampleRecord& sr) {
+ std::string output("sample");
+ StringAppendF(&output, " name=%s", event_names_[sr.id_data.id].c_str());
+ StringAppendF(&output, " ip=%p", reinterpret_cast<void*>(sr.ip_data.ip));
+ SymbolInfo s = GetSymbolInfo(sr.tid_data.pid, sr.tid_data.tid, sr.ip_data.ip, sr.InKernel());
+ StringAppendF(&output, " symbol=%s (%s[+%" PRIx64 "])", s.symbol->DemangledName(),
+ s.dso->Path().c_str(), s.vaddr_in_file);
+ StringAppendF(&output, " pid=%u tid=%u", sr.tid_data.pid, sr.tid_data.tid);
+ StringAppendF(&output, " cpu=%u", sr.cpu_data.cpu);
+ printf("%s\n", output.c_str());
+ fflush(stdout);
+}
+
+void MonitorCommand::DumpSampleCallchain(const SampleRecord& sr) {
+ bool in_kernel = sr.InKernel();
+ if (sr.sample_type & PERF_SAMPLE_CALLCHAIN) {
+ for (size_t i = 0; i < sr.callchain_data.ip_nr; ++i) {
+ if (sr.callchain_data.ips[i] >= PERF_CONTEXT_MAX) {
+ if (sr.callchain_data.ips[i] == PERF_CONTEXT_USER) {
+ in_kernel = false;
+ }
+ continue;
+ }
+ SymbolInfo s =
+ GetSymbolInfo(sr.tid_data.pid, sr.tid_data.tid, sr.callchain_data.ips[i], in_kernel);
+ std::string output("sample callchain");
+ StringAppendF(&output, " %s (%s[+%" PRIx64 "])", s.symbol->DemangledName(),
+ s.dso->Path().c_str(), s.vaddr_in_file);
+ printf("%s\n", output.c_str());
+ }
+ fflush(stdout);
+ }
+}
+
+SymbolInfo MonitorCommand::GetSymbolInfo(uint32_t pid, uint32_t tid, uint64_t ip, bool in_kernel) {
+ ThreadEntry* thread = thread_tree_.FindThreadOrNew(pid, tid);
+ const MapEntry* map = thread_tree_.FindMap(thread, ip, in_kernel);
+ SymbolInfo info;
+ info.symbol = thread_tree_.FindSymbol(map, ip, &info.vaddr_in_file, &info.dso);
+ return info;
+}
+
+bool MonitorCommand::DumpMapsForRecord(Record* record) {
+ if (record->type() == PERF_RECORD_SAMPLE) {
+ pid_t pid = static_cast<SampleRecord*>(record)->tid_data.pid;
+ if (dumped_processes_.find(pid) == dumped_processes_.end()) {
+ // Dump map info and all thread names for that process.
+ if (!map_record_reader_->ReadProcessMaps(pid, last_record_timestamp_)) {
+ return false;
+ }
+ dumped_processes_.insert(pid);
+ }
+ }
+ return true;
+}
+
+void MonitorCommand::UpdateRecord(Record* record) {
+ if (record->type() == PERF_RECORD_COMM) {
+ auto r = static_cast<CommRecord*>(record);
+ if (r->data->pid == r->data->tid) {
+ std::string s = GetCompleteProcessName(r->data->pid);
+ if (!s.empty()) {
+ r->SetCommandName(s);
+ }
+ }
+ }
+}
+
+bool MonitorCommand::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)) {
+ 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;
+ std::vector<uint64_t> sps;
+ if (!offline_unwinder_->UnwindCallChain(*thread, regs, r.stack_user_data.data,
+ r.GetValidStackSize(), &ips, &sps)) {
+ return false;
+ }
+ r.ReplaceRegAndStackWithCallChain(ips);
+ }
+ return true;
+}
+} // namespace
+
+void RegisterMonitorCommand() {
+ RegisterCommand("monitor", [] { return std::unique_ptr<Command>(new MonitorCommand()); });
+}
+
+} // namespace simpleperf
diff --git a/simpleperf/cmd_monitor_test.cpp b/simpleperf/cmd_monitor_test.cpp
new file mode 100644
index 00000000..963a9808
--- /dev/null
+++ b/simpleperf/cmd_monitor_test.cpp
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include <android-base/strings.h>
+#if defined(__ANDROID__)
+#include <android-base/properties.h>
+#endif
+
+#include <vector>
+
+#include "command.h"
+#include "test_util.h"
+
+using namespace simpleperf;
+
+static std::unique_ptr<Command> MonitorCmd() {
+ return CreateCommandInstance("monitor");
+}
+
+static const char* GetDefaultEvent() {
+ return HasHardwareCounter() ? "cpu-cycles" : "task-clock";
+}
+
+static ::testing::AssertionResult RunMonitorCmd(std::vector<std::string> v, std::string& output) {
+ bool has_event = false;
+ for (auto& arg : v) {
+ if (arg == "-e") {
+ has_event = true;
+ break;
+ }
+ }
+ if (!has_event) {
+ v.insert(v.end(), {"-e", GetDefaultEvent()});
+ }
+
+ v.insert(v.end(), {"--duration", SLEEP_SEC});
+
+ CaptureStdout capture;
+ if (!capture.Start()) {
+ return ::testing::AssertionFailure() << "Unable to capture stdout";
+ }
+ auto result = MonitorCmd()->Run(v);
+ output.append(capture.Finish());
+ return (result ? ::testing::AssertionSuccess() : ::testing::AssertionFailure());
+}
+
+TEST(monitor_cmd, no_options) {
+ std::string output;
+ ASSERT_FALSE(RunMonitorCmd({}, output));
+}
+
+TEST(monitor_cmd, no_event) {
+ ASSERT_FALSE(MonitorCmd()->Run({"-a", "--duration", "1"}));
+}
+
+TEST(monitor_cmd, global) {
+ TEST_REQUIRE_ROOT();
+ std::string output;
+ ASSERT_TRUE(RunMonitorCmd({"-a"}, output));
+ ASSERT_GT(output.size(), 0);
+}
+
+TEST(monitor_cmd, no_perf) {
+ TEST_REQUIRE_ROOT();
+ std::string output;
+ ASSERT_TRUE(RunMonitorCmd({"-a", "--exclude-perf"}, output));
+ ASSERT_GT(output.size(), 0);
+}
+
+TEST(monitor_cmd, with_callchain) {
+ TEST_REQUIRE_ROOT();
+ std::string output;
+ ASSERT_TRUE(RunMonitorCmd({"-a", "-g"}, output));
+ ASSERT_GT(output.size(), 0);
+}
+
+TEST(monitor_cmd, with_callchain_fp) {
+ TEST_REQUIRE_ROOT();
+ std::string output;
+ ASSERT_TRUE(RunMonitorCmd({"-a", "--call-graph", "fp"}, output));
+ ASSERT_GT(output.size(), 0);
+}
+
+TEST(monitor_cmd, with_callchain_dwarf) {
+ TEST_REQUIRE_ROOT();
+ std::string output;
+ ASSERT_TRUE(RunMonitorCmd({"-a", "--call-graph", "dwarf,512"}, output));
+ ASSERT_GT(output.size(), 0);
+}
+
+TEST(monitor_cmd, frequency) {
+ TEST_REQUIRE_ROOT();
+ std::string output;
+ ASSERT_TRUE(RunMonitorCmd({"-a", "-f", "1"}, output));
+}
+
+TEST(monitor_cmd, count) {
+ TEST_REQUIRE_ROOT();
+ std::string output;
+ ASSERT_TRUE(RunMonitorCmd({"-a", "-c", "10000000"}, output));
+}
+
+TEST(monitor_cmd, cpu_percent) {
+ TEST_REQUIRE_ROOT();
+ std::string output;
+ ASSERT_TRUE(RunMonitorCmd({"-a", "--cpu-percent", "1"}, output));
+ ASSERT_GT(output.size(), 0);
+ ASSERT_FALSE(RunMonitorCmd({"-a", "--cpu-percent", "-1"}, output));
+ ASSERT_FALSE(RunMonitorCmd({"-a", "--cpu-percent", "101"}, output));
+}
+
+TEST(monitor_cmd, record_filter_options) {
+ TEST_REQUIRE_ROOT();
+ std::string output;
+ ASSERT_TRUE(
+ RunMonitorCmd({"-a", "--exclude-pid", "1,2", "--exclude-tid", "3,4", "--exclude-process-name",
+ "processA", "--exclude-thread-name", "threadA", "--exclude-uid", "5,6"},
+ output));
+ ASSERT_TRUE(
+ RunMonitorCmd({"-a", "--include-pid", "1,2", "--include-tid", "3,4", "--include-process-name",
+ "processB", "--include-thread-name", "threadB", "--include-uid", "5,6"},
+ output));
+}
diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp
index e9d83174..9d9abb61 100644
--- a/simpleperf/cmd_record.cpp
+++ b/simpleperf/cmd_record.cpp
@@ -22,32 +22,49 @@
#include <sys/utsname.h>
#include <time.h>
#include <unistd.h>
+#include <filesystem>
+#include <optional>
#include <set>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
-#include <android-base/logging.h>
#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>
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-parameter"
+#include <llvm/Support/MemoryBuffer.h>
+#pragma clang diagnostic pop
+
#if defined(__ANDROID__)
#include <android-base/properties.h>
#endif
+#include <unwindstack/Error.h>
#include "CallChainJoiner.h"
-#include "command.h"
-#include "environment.h"
#include "ETMRecorder.h"
-#include "event_selection_set.h"
-#include "event_type.h"
#include "IOEventLoop.h"
#include "JITDebugReader.h"
+#include "MapRecordReader.h"
#include "OfflineUnwinder.h"
+#include "ProbeEvents.h"
+#include "RecordFilter.h"
+#include "cmd_record_impl.h"
+#include "command.h"
+#include "environment.h"
+#include "event_selection_set.h"
+#include "event_type.h"
+#include "kallsyms.h"
#include "read_apk.h"
#include "read_elf.h"
+#include "read_symbol_map.h"
#include "record.h"
#include "record_file.h"
#include "thread_tree.h"
@@ -55,7 +72,11 @@
#include "utils.h"
#include "workload.h"
-using namespace simpleperf;
+namespace simpleperf {
+namespace {
+
+using android::base::ParseUint;
+using android::base::Realpath;
static std::string default_measured_event_type = "cpu-cycles";
@@ -128,7 +149,6 @@ class RecordCommand : public Command {
"-p pid1,pid2,... Record events on existing processes. Mutually exclusive\n"
" with -a.\n"
"-t tid1,tid2,... Record events on existing threads. Mutually exclusive with -a.\n"
-"--exclude-perf Exclude samples for simpleperf process.\n"
"\n"
"Select monitored event types:\n"
"-e event1[:modifier1],event2[:modifier2],...\n"
@@ -136,6 +156,7 @@ class RecordCommand : public Command {
" 1) an event name listed in `simpleperf list`;\n"
" 2) a raw PMU event in rN format. N is a hex number.\n"
" For example, r1b selects event number 0x1b.\n"
+" 3) a kprobe event added by --kprobe option.\n"
" Modifiers can be added to define how the event should be\n"
" monitored. Possible modifiers are:\n"
" u - monitor user space events only\n"
@@ -146,6 +167,11 @@ class RecordCommand : public Command {
" same time.\n"
"--trace-offcpu Generate samples when threads are scheduled off cpu.\n"
" Similar to \"-c 1 -e sched:sched_switch\".\n"
+"--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"
"\n"
"Select monitoring options:\n"
"-f freq Set event sample frequency. It means recording at most [freq]\n"
@@ -195,9 +221,26 @@ class RecordCommand : public Command {
"--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"
-"--include-filter binary1,binary2,...\n"
-" Trace only selected binaries in cs-etm instruction tracing.\n"
-" Each entry is a binary path.\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"
+" An example: 'prev_comm != \"simpleperf\" && (prev_pid > 1)'.\n"
"\n"
"Dwarf unwinding options:\n"
"--post-unwind=(yes|no) If `--call-graph dwarf` option is used, then the user's\n"
@@ -219,6 +262,12 @@ class RecordCommand : public Command {
" 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"
+"--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"
+"Sample filter options:\n"
+"--exclude-perf Exclude samples for simpleperf process.\n"
+RECORD_FILTER_OPTION_HELP_MSG
"\n"
"Recording file options:\n"
"--no-dump-kernel-symbols Don't dump kernel symbols in perf.data. By default\n"
@@ -232,6 +281,7 @@ class RecordCommand : public Command {
"--symfs <dir> Look for files with symbols relative to this directory.\n"
" This option is used to provide files with symbol table and\n"
" 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"
"Other options:\n"
"--exit-with-parent Stop recording when the process starting\n"
@@ -271,7 +321,8 @@ class RecordCommand : public Command {
exclude_kernel_callchain_(false),
allow_callchain_joiner_(true),
callchain_joiner_min_matching_nodes_(1u),
- last_record_timestamp_(0u) {
+ last_record_timestamp_(0u),
+ record_filter_(thread_tree_) {
// If we run `adb shell simpleperf record xxx` and stop profiling by ctrl-c, adb closes
// sockets connecting simpleperf. After that, simpleperf will receive SIGPIPE when writing
// to stdout/stderr, which is a problem when we use '--app' option. So ignore SIGPIPE to
@@ -282,23 +333,23 @@ class RecordCommand : 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);
bool AdjustPerfEventLimit();
bool PrepareRecording(Workload* workload);
bool DoRecording(Workload* workload);
bool PostProcessRecording(const std::vector<std::string>& args);
+ // pre recording functions
bool TraceOffCpu();
bool SetEventSelectionFlags();
bool CreateAndInitRecordFile();
- std::unique_ptr<RecordFileWriter> CreateRecordFile(
- const std::string& filename);
+ std::unique_ptr<RecordFileWriter> CreateRecordFile(const std::string& filename);
bool DumpKernelSymbol();
bool DumpTracingData();
- bool DumpKernelMaps();
- bool DumpUserSpaceMaps();
- bool DumpProcessMaps(pid_t pid, const std::unordered_set<pid_t>& tids);
+ bool DumpMaps();
bool DumpAuxTraceInfo();
+
+ // recording functions
bool ProcessRecord(Record* record);
bool ShouldOmitRecord(Record* record);
bool DumpMapsForRecord(Record* record);
@@ -307,16 +358,22 @@ class RecordCommand : public Command {
bool SaveRecordWithoutUnwinding(Record* record);
bool ProcessJITDebugInfo(const std::vector<JITDebugInfo>& debug_info, bool sync_kernel_records);
bool ProcessControlCmd(IOEventLoop* loop);
-
void UpdateRecord(Record* record);
bool UnwindRecord(SampleRecord& r);
+ bool KeepFailedUnwindingResult(const SampleRecord& r, const std::vector<uint64_t>& ips,
+ const std::vector<uint64_t>& sps);
+
+ // post recording functions
+ std::unique_ptr<RecordFileReader> MoveRecordFile(const std::string& old_filename);
+ bool MergeMapRecords();
bool PostUnwindRecords();
bool JoinCallChains();
bool DumpAdditionalFeatures(const std::vector<std::string>& args);
bool DumpBuildIdFeature();
bool DumpFileFeature();
bool DumpMetaInfoFeature(bool kernel_symbols_available);
- void CollectHitFileInfo(const SampleRecord& r);
+ bool DumpDebugUnwindFeature(const std::unordered_set<Dso*>& dso_set);
+ void CollectHitFileInfo(const SampleRecord& r, std::unordered_set<Dso*>* dso_set);
std::unique_ptr<SampleSpeed> sample_speed_;
bool system_wide_collection_;
@@ -326,6 +383,8 @@ class RecordCommand : public Command {
uint32_t dump_stack_size_in_dwarf_sampling_;
bool unwind_dwarf_callchain_;
bool post_unwind_;
+ bool keep_failed_unwinding_result_ = false;
+ bool keep_failed_unwinding_debug_info_ = false;
std::unique_ptr<OfflineUnwinder> offline_unwinder_;
bool child_inherit_;
double duration_in_sec_;
@@ -370,23 +429,45 @@ class RecordCommand : public Command {
// In system wide recording, record if we have dumped map info for a process.
std::unordered_set<pid_t> dumped_processes_;
bool exclude_perf_ = false;
+ RecordFilter record_filter_;
+
+ std::optional<MapRecordReader> map_record_reader_;
+ std::optional<MapRecordThread> map_record_thread_;
+
+ std::unordered_map<std::string, std::string> extra_meta_info_;
};
bool RecordCommand::Run(const std::vector<std::string>& args) {
+ time_stat_.prepare_recording_time = GetSystemClock();
ScopedCurrentArch scoped_arch(GetMachineArch());
+
if (!CheckPerfEventLimit()) {
return false;
}
AllowMoreOpenedFiles();
std::vector<std::string> workload_args;
- if (!ParseOptions(args, &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)) {
return false;
}
if (!AdjustPerfEventLimit()) {
return false;
}
- ScopedTempFiles scoped_temp_files(android::base::Dirname(record_filename_));
+ std::unique_ptr<ScopedTempFiles> scoped_temp_files =
+ ScopedTempFiles::Create(android::base::Dirname(record_filename_));
+ if (!scoped_temp_files) {
+ PLOG(ERROR) << "Can't create output file in directory "
+ << android::base::Dirname(record_filename_);
+ return false;
+ }
if (!app_package_name_.empty() && !in_app_context_) {
// Some users want to profile non debuggable apps on rooted devices. If we use run-as,
// it will be impossible when using --app. So don't switch to app's context when we are
@@ -403,7 +484,6 @@ bool RecordCommand::Run(const std::vector<std::string>& args) {
return false;
}
}
- time_stat_.prepare_recording_time = GetSystemClock();
if (!PrepareRecording(workload.get())) {
return false;
}
@@ -438,12 +518,12 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
return false;
}
if (unwind_dwarf_callchain_) {
- offline_unwinder_ = OfflineUnwinder::Create(false);
+ bool collect_stat = keep_failed_unwinding_result_;
+ offline_unwinder_ = OfflineUnwinder::Create(collect_stat);
}
if (unwind_dwarf_callchain_ && allow_callchain_joiner_) {
callchain_joiner_.reset(new CallChainJoiner(DEFAULT_CALL_CHAIN_JOINER_CACHE_SIZE,
- callchain_joiner_min_matching_nodes_,
- false));
+ callchain_joiner_min_matching_nodes_, false));
}
// 4. Add monitored targets.
@@ -461,8 +541,7 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
event_selection_set_.AddMonitoredProcesses(pids);
need_to_check_targets = true;
} else {
- LOG(ERROR)
- << "No threads to monitor. Try `simpleperf help record` for help";
+ LOG(ERROR) << "No threads to monitor. Try `simpleperf help record` for help";
return false;
}
} else {
@@ -474,9 +553,12 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
// JIT symfiles are stored in temporary files, and are deleted after recording. But if
// `-g --no-unwind` option is used, we want to keep symfiles to support unwinding in
// the debug-unwind cmd.
- bool keep_symfiles = dwarf_callchain_sampling_ && !unwind_dwarf_callchain_;
- bool sync_with_records = clockid_ == "monotonic";
- jit_debug_reader_.reset(new JITDebugReader(keep_symfiles, sync_with_records));
+ auto symfile_option = (dwarf_callchain_sampling_ && !unwind_dwarf_callchain_)
+ ? JITDebugReader::SymFileOption::kKeepSymFiles
+ : JITDebugReader::SymFileOption::kDropSymFiles;
+ auto sync_option = (clockid_ == "monotonic") ? JITDebugReader::SyncOption::kSyncWithRecords
+ : JITDebugReader::SyncOption::kNoSync;
+ jit_debug_reader_.reset(new JITDebugReader(record_filename_, symfile_option, sync_option));
// To profile java code, need to dump maps containing vdex files, which are not executable.
event_selection_set_.SetRecordNotExecutableMaps(true);
}
@@ -485,15 +567,14 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
if (!event_selection_set_.OpenEventFiles(cpus_)) {
return false;
}
- size_t record_buffer_size = system_wide_collection_ ? kSystemWideRecordBufferSize
- : kRecordBufferSize;
+ size_t record_buffer_size =
+ system_wide_collection_ ? kSystemWideRecordBufferSize : kRecordBufferSize;
if (!event_selection_set_.MmapEventFiles(mmap_page_range_.first, mmap_page_range_.second,
aux_buffer_size_, record_buffer_size,
allow_cutting_samples_, exclude_perf_)) {
return false;
}
- auto callback =
- std::bind(&RecordCommand::ProcessRecord, this, std::placeholders::_1);
+ auto callback = std::bind(&RecordCommand::ProcessRecord, this, std::placeholders::_1);
if (!event_selection_set_.PrepareToReadMmapEventData(callback)) {
return false;
}
@@ -508,9 +589,7 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
return false;
}
IOEventLoop* loop = event_selection_set_.GetIOEventLoop();
- auto exit_loop_callback = [loop]() {
- return loop->ExitLoop();
- };
+ auto exit_loop_callback = [loop]() { return loop->ExitLoop(); };
if (!loop->AddSignalEvents({SIGCHLD, SIGINT, SIGTERM}, exit_loop_callback)) {
return false;
}
@@ -607,7 +686,8 @@ bool RecordCommand::DoRecording(Workload* workload) {
return true;
}
-static bool WriteRecordDataToOutFd(const std::string& in_filename, android::base::unique_fd out_fd) {
+static bool WriteRecordDataToOutFd(const std::string& in_filename,
+ android::base::unique_fd out_fd) {
android::base::unique_fd in_fd(FileHelper::OpenReadOnly(in_filename));
if (in_fd == -1) {
PLOG(ERROR) << "Failed to open " << in_filename;
@@ -633,19 +713,26 @@ static bool WriteRecordDataToOutFd(const std::string& in_filename, android::base
}
bool RecordCommand::PostProcessRecording(const std::vector<std::string>& args) {
- // 1. Post unwind dwarf callchain.
+ // 1. Merge map records dumped while recording by map record thread.
+ if (map_record_thread_) {
+ if (!map_record_thread_->Join() || !MergeMapRecords()) {
+ return false;
+ }
+ }
+
+ // 2. Post unwind dwarf callchain.
if (unwind_dwarf_callchain_ && post_unwind_) {
if (!PostUnwindRecords()) {
return false;
}
}
- // 2. Optionally join Callchains.
+ // 3. Optionally join Callchains.
if (callchain_joiner_) {
JoinCallChains();
}
- // 3. Dump additional features, and close record file.
+ // 4. Dump additional features, and close record file.
if (!DumpAdditionalFeatures(args)) {
return false;
}
@@ -691,64 +778,247 @@ bool RecordCommand::PostProcessRecording(const std::vector<std::string>& args) {
}
}
LOG(DEBUG) << "Prepare recording time "
- << (time_stat_.start_recording_time - time_stat_.prepare_recording_time) / 1e6
- << " ms, recording time "
- << (time_stat_.stop_recording_time - time_stat_.start_recording_time) / 1e6
- << " ms, stop recording time "
- << (time_stat_.finish_recording_time - time_stat_.stop_recording_time) / 1e6
- << " ms, post process time "
- << (time_stat_.post_process_time - time_stat_.finish_recording_time) / 1e6 << " ms.";
+ << (time_stat_.start_recording_time - time_stat_.prepare_recording_time) / 1e6
+ << " ms, recording time "
+ << (time_stat_.stop_recording_time - time_stat_.start_recording_time) / 1e6
+ << " ms, stop recording time "
+ << (time_stat_.finish_recording_time - time_stat_.stop_recording_time) / 1e6
+ << " ms, post process time "
+ << (time_stat_.post_process_time - time_stat_.finish_recording_time) / 1e6 << " ms.";
return true;
}
bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
- std::vector<std::string>* non_option_args) {
- std::vector<size_t> wait_setting_speed_event_groups_;
- size_t i;
- for (i = 0; i < args.size() && !args[i].empty() && args[i][0] == '-'; ++i) {
- if (args[i] == "-a") {
- system_wide_collection_ = true;
- } else if (args[i] == "--app") {
- if (!NextArgumentOrError(args, &i)) {
+ std::vector<std::string>* non_option_args,
+ ProbeEvents* probe_events) {
+ OptionValueMap options;
+ std::vector<std::pair<OptionName, OptionValue>> ordered_options;
+
+ if (!PreprocessOptions(args, GetRecordCmdOptionFormats(), &options, &ordered_options,
+ non_option_args)) {
+ return false;
+ }
+
+ // Process options.
+ system_wide_collection_ = options.PullBoolValue("-a");
+
+ for (const OptionValue& value : options.PullValues("--add-meta-info")) {
+ const std::string& s = *value.str_value;
+ auto split_pos = s.find('=');
+ if (split_pos == std::string::npos || split_pos == 0 || split_pos + 1 == s.size()) {
+ LOG(ERROR) << "invalid meta-info: " << s;
+ return false;
+ }
+ extra_meta_info_[s.substr(0, split_pos)] = s.substr(split_pos + 1);
+ }
+
+ if (auto value = options.PullValue("--addr-filter"); value) {
+ auto filters = ParseAddrFilterOption(*value->str_value);
+ if (filters.empty()) {
+ return false;
+ }
+ event_selection_set_.SetAddrFilters(std::move(filters));
+ }
+
+ if (auto value = options.PullValue("--app"); value) {
+ app_package_name_ = *value->str_value;
+ }
+
+ if (auto value = options.PullValue("--aux-buffer-size"); value) {
+ uint64_t v = value->uint_value;
+ if (v > std::numeric_limits<size_t>::max() || !IsPowerOfTwo(v) || v % sysconf(_SC_PAGE_SIZE)) {
+ LOG(ERROR) << "invalid aux buffer size: " << v;
+ return false;
+ }
+ aux_buffer_size_ = static_cast<size_t>(v);
+ }
+
+ if (options.PullValue("-b")) {
+ branch_sampling_ = branch_sampling_type_map["any"];
+ }
+
+ if (!options.PullUintValue("--callchain-joiner-min-matching-nodes",
+ &callchain_joiner_min_matching_nodes_, 1)) {
+ return false;
+ }
+
+ if (auto value = options.PullValue("--clockid"); value) {
+ clockid_ = *value->str_value;
+ if (clockid_ != "perf") {
+ if (!IsSettingClockIdSupported()) {
+ LOG(ERROR) << "Setting clockid is not supported by the kernel.";
return false;
}
- app_package_name_ = args[i];
- } else if (args[i] == "--aux-buffer-size") {
- if (!GetUintOption(args, &i, &aux_buffer_size_, 0, std::numeric_limits<size_t>::max(),
- true)) {
+ if (clockid_map.find(clockid_) == clockid_map.end()) {
+ LOG(ERROR) << "Invalid clockid: " << clockid_;
return false;
}
- if (!IsPowerOfTwo(aux_buffer_size_) || aux_buffer_size_ % sysconf(_SC_PAGE_SIZE)) {
- LOG(ERROR) << "invalid aux buffer size: " << args[i];
+ }
+ }
+
+ 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.PullDoubleValue("--duration", &duration_in_sec_, 1e-9)) {
+ return false;
+ }
+
+ exclude_perf_ = options.PullBoolValue("--exclude-perf");
+ if (!record_filter_.ParseOptions(options)) {
+ return false;
+ }
+
+ if (options.PullValue("--exit-with-parent")) {
+ prctl(PR_SET_PDEATHSIG, SIGHUP, 0, 0, 0);
+ }
+
+ in_app_context_ = options.PullBoolValue("--in-app");
+
+ for (const OptionValue& value : options.PullValues("-j")) {
+ std::vector<std::string> branch_sampling_types = android::base::Split(*value.str_value, ",");
+ for (auto& type : branch_sampling_types) {
+ auto it = branch_sampling_type_map.find(type);
+ if (it == branch_sampling_type_map.end()) {
+ LOG(ERROR) << "unrecognized branch sampling filter: " << type;
return false;
}
- } else if (args[i] == "-b") {
- branch_sampling_ = branch_sampling_type_map["any"];
- } else if (args[i] == "-c" || args[i] == "-f") {
- uint64_t value;
- if (!GetUintOption(args, &i, &value, 1)) {
+ branch_sampling_ |= it->second;
+ }
+ }
+ keep_failed_unwinding_result_ = options.PullBoolValue("--keep-failed-unwinding-result");
+ keep_failed_unwinding_debug_info_ = options.PullBoolValue("--keep-failed-unwinding-debug-info");
+ if (keep_failed_unwinding_debug_info_) {
+ keep_failed_unwinding_result_ = true;
+ }
+
+ 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)) {
return false;
}
- if (args[i-1] == "-c") {
- sample_speed_.reset(new SampleSpeed(0, value));
+ }
+ }
+
+ if (auto value = options.PullValue("-m"); value) {
+ if (!IsPowerOfTwo(value->uint_value) ||
+ value->uint_value > std::numeric_limits<size_t>::max()) {
+ LOG(ERROR) << "Invalid mmap_pages: '" << value->uint_value << "'";
+ return false;
+ }
+ mmap_page_range_.first = mmap_page_range_.second = value->uint_value;
+ }
+
+ allow_callchain_joiner_ = !options.PullBoolValue("--no-callchain-joiner");
+ allow_cutting_samples_ = !options.PullBoolValue("--no-cut-samples");
+ can_dump_kernel_symbols_ = !options.PullBoolValue("--no-dump-kernel-symbols");
+ dump_symbols_ = !options.PullBoolValue("--no-dump-symbols");
+ child_inherit_ = !options.PullBoolValue("--no-inherit");
+ unwind_dwarf_callchain_ = !options.PullBoolValue("--no-unwind");
+
+ if (auto value = options.PullValue("-o"); value) {
+ record_filename_ = *value->str_value;
+ }
+
+ if (auto value = options.PullValue("--out-fd"); value) {
+ out_fd_.reset(static_cast<int>(value->uint_value));
+ }
+
+ for (const OptionValue& value : options.PullValues("-p")) {
+ if (auto pids = GetTidsFromString(*value.str_value, true); pids) {
+ event_selection_set_.AddMonitoredProcesses(pids.value());
+ } else {
+ return false;
+ }
+ }
+
+ // Use explicit if statements instead of logical operators to avoid short-circuit.
+ if (options.PullValue("--post-unwind")) {
+ post_unwind_ = true;
+ }
+ if (options.PullValue("--post-unwind=yes")) {
+ post_unwind_ = true;
+ }
+ if (options.PullValue("--post-unwind=no")) {
+ post_unwind_ = false;
+ }
+
+ if (!options.PullUintValue("--size-limit", &size_limit_in_bytes_, 1)) {
+ return false;
+ }
+
+ if (auto value = options.PullValue("--start_profiling_fd"); value) {
+ start_profiling_fd_.reset(static_cast<int>(value->uint_value));
+ }
+
+ stdio_controls_profiling_ = options.PullBoolValue("--stdio-controls-profiling");
+
+ if (auto value = options.PullValue("--stop-signal-fd"); value) {
+ stop_signal_fd_.reset(static_cast<int>(value->uint_value));
+ }
+
+ if (auto value = options.PullValue("--symfs"); value) {
+ if (!Dso::SetSymFsDir(*value->str_value)) {
+ return false;
+ }
+ }
+
+ for (const OptionValue& value : options.PullValues("-t")) {
+ if (auto tids = GetTidsFromString(*value.str_value, true); tids) {
+ event_selection_set_.AddMonitoredThreads(tids.value());
+ } else {
+ return false;
+ }
+ }
+
+ trace_offcpu_ = options.PullBoolValue("--trace-offcpu");
+
+ if (auto value = options.PullValue("--tracepoint-events"); value) {
+ if (!EventTypeManager::Instance().ReadTracepointsFromFile(*value->str_value)) {
+ return false;
+ }
+ }
+
+ 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;
+
+ if (name == "-c" || name == "-f") {
+ if (value.uint_value < 1) {
+ LOG(ERROR) << "invalid " << name << ": " << value.uint_value;
+ return false;
+ }
+ if (name == "-c") {
+ sample_speed_.reset(new SampleSpeed(0, value.uint_value));
} else {
- if (value >= INT_MAX) {
- LOG(ERROR) << "sample freq can't be bigger than INT_MAX.";
+ 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, 0));
- max_sample_freq_ = std::max(max_sample_freq_, value);
- }
- for (auto group_id : wait_setting_speed_event_groups_) {
- event_selection_set_.SetSampleSpeed(group_id, *sample_speed_);
+ sample_speed_.reset(new SampleSpeed(value.uint_value, 0));
}
- wait_setting_speed_event_groups_.clear();
- } else if (args[i] == "--call-graph") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
+ for (auto groud_id : wait_setting_speed_event_groups) {
+ event_selection_set_.SetSampleSpeed(groud_id, *sample_speed_);
}
- std::vector<std::string> strs = android::base::Split(args[i], ",");
+ wait_setting_speed_event_groups.clear();
+
+ } else if (name == "--call-graph") {
+ std::vector<std::string> strs = android::base::Split(*value.str_value, ",");
if (strs[0] == "fp") {
fp_callchain_sampling_ = true;
dwarf_callchain_sampling_ = false;
@@ -757,62 +1027,31 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
dwarf_callchain_sampling_ = true;
if (strs.size() > 1) {
uint64_t size;
- if (!android::base::ParseUint(strs[1], &size)) {
+ if (!ParseUint(strs[1], &size)) {
LOG(ERROR) << "invalid dump stack size in --call-graph option: " << strs[1];
return false;
}
if ((size & 7) != 0) {
- LOG(ERROR) << "dump stack size " << size
- << " is not 8-byte aligned.";
+ LOG(ERROR) << "dump stack size " << size << " is not 8-byte aligned.";
return false;
}
if (size >= MAX_DUMP_STACK_SIZE) {
- LOG(ERROR) << "dump stack size " << size
- << " is bigger than max allowed size "
+ LOG(ERROR) << "dump stack size " << size << " is bigger than max allowed size "
<< MAX_DUMP_STACK_SIZE << ".";
return false;
}
dump_stack_size_in_dwarf_sampling_ = static_cast<uint32_t>(size);
}
- } else {
- LOG(ERROR) << "unexpected argument for --call-graph option: "
- << args[i];
- return false;
- }
- } else if (args[i] == "--clockid") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- if (args[i] != "perf") {
- if (!IsSettingClockIdSupported()) {
- LOG(ERROR) << "Setting clockid is not supported by the kernel.";
- return false;
- }
- if (clockid_map.find(args[i]) == clockid_map.end()) {
- LOG(ERROR) << "Invalid clockid: " << args[i];
- return false;
- }
}
- clockid_ = args[i];
- } else if (args[i] == "--cpu") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- cpus_ = GetCpusFromString(args[i]);
- } else if (args[i] == "--cpu-percent") {
- if (!GetUintOption(args, &i, &cpu_time_max_percent_, 1, 100)) {
- return false;
- }
- } else if (args[i] == "--duration") {
- if (!GetDoubleOption(args, &i, &duration_in_sec_, 1e-9)) {
- return false;
- }
- } else if (args[i] == "-e") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- std::vector<std::string> event_types = android::base::Split(args[i], ",");
+
+ } 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)) {
return false;
@@ -820,21 +1059,22 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
if (sample_speed_) {
event_selection_set_.SetSampleSpeed(group_id, *sample_speed_);
} else {
- wait_setting_speed_event_groups_.push_back(group_id);
+ wait_setting_speed_event_groups.push_back(group_id);
}
}
- } else if (args[i] == "--exclude-perf") {
- exclude_perf_ = true;
- } else if (args[i] == "--exit-with-parent") {
- prctl(PR_SET_PDEATHSIG, SIGHUP, 0, 0, 0);
- } else if (args[i] == "-g") {
+
+ } else if (name == "-g") {
fp_callchain_sampling_ = false;
dwarf_callchain_sampling_ = true;
- } else if (args[i] == "--group") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
+ } 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;
+ }
+ }
}
- std::vector<std::string> event_types = android::base::Split(args[i], ",");
size_t group_id;
if (!event_selection_set_.AddEventGroup(event_types, &group_id)) {
return false;
@@ -842,141 +1082,21 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
if (sample_speed_) {
event_selection_set_.SetSampleSpeed(group_id, *sample_speed_);
} else {
- wait_setting_speed_event_groups_.push_back(group_id);
- }
- } else if (args[i] == "--in-app") {
- in_app_context_ = true;
- } else if (args[i] == "--include-filter") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- event_selection_set_.SetIncludeFilters(android::base::Split(args[i], ","));
- } else if (args[i] == "-j") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- std::vector<std::string> branch_sampling_types =
- android::base::Split(args[i], ",");
- for (auto& type : branch_sampling_types) {
- auto it = branch_sampling_type_map.find(type);
- if (it == branch_sampling_type_map.end()) {
- LOG(ERROR) << "unrecognized branch sampling filter: " << type;
- return false;
- }
- branch_sampling_ |= it->second;
- }
- } else if (args[i] == "-m") {
- uint64_t pages;
- if (!GetUintOption(args, &i, &pages)) {
- return false;
- }
- if (!IsPowerOfTwo(pages)) {
- LOG(ERROR) << "Invalid mmap_pages: '" << args[i] << "'";
- return false;
- }
- mmap_page_range_.first = mmap_page_range_.second = pages;
- } else if (args[i] == "--no-dump-kernel-symbols") {
- can_dump_kernel_symbols_ = false;
- } else if (args[i] == "--no-dump-symbols") {
- dump_symbols_ = false;
- } else if (args[i] == "--no-inherit") {
- child_inherit_ = false;
- } else if (args[i] == "--no-unwind") {
- unwind_dwarf_callchain_ = false;
- } else if (args[i] == "--no-callchain-joiner") {
- allow_callchain_joiner_ = false;
- } else if (args[i] == "--callchain-joiner-min-matching-nodes") {
- if (!GetUintOption(args, &i, &callchain_joiner_min_matching_nodes_, 1)) {
- return false;
- }
- } else if (args[i] == "--no-cut-samples") {
- allow_cutting_samples_ = false;
- } else if (args[i] == "-o") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- record_filename_ = args[i];
- } else if (args[i] == "--out-fd") {
- int fd;
- if (!GetUintOption(args, &i, &fd)) {
- return false;
- }
- out_fd_.reset(fd);
- } else if (args[i] == "-p") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- std::set<pid_t> pids;
- if (!GetValidThreadsFromThreadString(args[i], &pids)) {
- return false;
- }
- event_selection_set_.AddMonitoredProcesses(pids);
- } else if (android::base::StartsWith(args[i], "--post-unwind")) {
- if (args[i] == "--post-unwind" || args[i] == "--post-unwind=yes") {
- post_unwind_ = true;
- } else if (args[i] == "--post-unwind=no") {
- post_unwind_ = false;
- } else {
- LOG(ERROR) << "unexpected option " << args[i];
- return false;
- }
- } else if (args[i] == "--size-limit") {
- if (!GetUintOption(args, &i, &size_limit_in_bytes_, 1, std::numeric_limits<uint64_t>::max(),
- true)) {
- return false;
- }
- } else if (args[i] == "--start_profiling_fd") {
- int fd;
- if (!GetUintOption(args, &i, &fd)) {
- return false;
+ wait_setting_speed_event_groups.push_back(group_id);
}
- start_profiling_fd_.reset(fd);
- } else if (args[i] == "--stdio-controls-profiling") {
- stdio_controls_profiling_ = true;
- } else if (args[i] == "--stop-signal-fd") {
- int fd;
- if (!GetUintOption(args, &i, &fd)) {
- return false;
- }
- stop_signal_fd_.reset(fd);
- } else if (args[i] == "--symfs") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- if (!Dso::SetSymFsDir(args[i])) {
- return false;
- }
- } else if (args[i] == "-t") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- std::set<pid_t> tids;
- if (!GetValidThreadsFromThreadString(args[i], &tids)) {
- return false;
- }
- event_selection_set_.AddMonitoredThreads(tids);
- } else if (args[i] == "--trace-offcpu") {
- trace_offcpu_ = true;
- } else if (args[i] == "--tracepoint-events") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- if (!SetTracepointEventsFilePath(args[i])) {
+
+ } else if (name == "--tp-filter") {
+ if (!event_selection_set_.SetTracepointFilter(*value.str_value)) {
return false;
}
- } else if (args[i] == "--") {
- i++;
- break;
} else {
- ReportUnknownOption(args, i);
- return false;
+ CHECK(false) << "unprocessed option: " << name;
}
}
if (!dwarf_callchain_sampling_) {
if (!unwind_dwarf_callchain_) {
- LOG(ERROR)
- << "--no-unwind is only used with `--call-graph dwarf` option.";
+ LOG(ERROR) << "--no-unwind is only used with `--call-graph dwarf` option.";
return false;
}
unwind_dwarf_callchain_ = false;
@@ -1013,10 +1133,6 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
clockid_ = IsSettingClockIdSupported() ? "monotonic" : "perf";
}
- non_option_args->clear();
- for (; i < args.size(); ++i) {
- non_option_args->push_back(args[i]);
- }
return true;
}
@@ -1046,7 +1162,7 @@ bool RecordCommand::AdjustPerfEventLimit() {
set_prop = true;
}
- if (GetAndroidVersion() >= kAndroidVersionP + 1 && set_prop && !in_app_context_) {
+ if (GetAndroidVersion() >= kAndroidVersionQ && set_prop && !in_app_context_) {
return SetPerfEventLimits(std::max(max_sample_freq_, cur_max_freq), cpu_time_max_percent_,
std::max(mlock_kb, cur_mlock_kb));
}
@@ -1068,6 +1184,14 @@ bool RecordCommand::TraceOffCpu() {
LOG(ERROR) << "Dumping regs for tracepoint events is not supported by the kernel";
return false;
}
+ // --trace-offcpu option only works with one of the selected event types.
+ std::set<std::string> accepted_events = {"cpu-cycles", "cpu-clock", "task-clock"};
+ std::vector<const EventType*> events = event_selection_set_.GetEvents();
+ if (events.size() != 1 || accepted_events.find(events[0]->name) == accepted_events.end()) {
+ LOG(ERROR) << "--trace-offcpu option only works with one of events "
+ << android::base::Join(accepted_events, ' ');
+ return false;
+ }
return event_selection_set_.AddEventType("sched:sched_switch");
}
@@ -1079,8 +1203,7 @@ bool RecordCommand::SetEventSelectionFlags() {
if (fp_callchain_sampling_) {
event_selection_set_.EnableFpCallChainSampling();
} else if (dwarf_callchain_sampling_) {
- if (!event_selection_set_.EnableDwarfCallChainSampling(
- dump_stack_size_in_dwarf_sampling_)) {
+ if (!event_selection_set_.EnableDwarfCallChainSampling(dump_stack_size_in_dwarf_sampling_)) {
return false;
}
}
@@ -1097,15 +1220,16 @@ bool RecordCommand::CreateAndInitRecordFile() {
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];
- return DumpKernelSymbol() && DumpTracingData() && DumpKernelMaps() && DumpUserSpaceMaps() &&
- DumpAuxTraceInfo();
+ EventAttrWithId dumping_attr_id = event_selection_set_.GetEventAttrWithId()[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) {
- std::unique_ptr<RecordFileWriter> writer =
- RecordFileWriter::CreateInstance(filename);
+std::unique_ptr<RecordFileWriter> RecordCommand::CreateRecordFile(const std::string& filename) {
+ std::unique_ptr<RecordFileWriter> writer = RecordFileWriter::CreateInstance(filename);
if (writer == nullptr) {
return nullptr;
}
@@ -1118,12 +1242,12 @@ std::unique_ptr<RecordFileWriter> RecordCommand::CreateRecordFile(
bool RecordCommand::DumpKernelSymbol() {
if (can_dump_kernel_symbols_) {
- std::string kallsyms;
- if (event_selection_set_.NeedKernelSymbol() &&
- CheckKernelSymbolAddresses()) {
- if (!android::base::ReadFileToString("/proc/kallsyms", &kallsyms)) {
- PLOG(ERROR) << "failed to read /proc/kallsyms";
- return false;
+ if (event_selection_set_.NeedKernelSymbol()) {
+ std::string kallsyms;
+ if (!LoadKernelSymbols(&kallsyms)) {
+ // Symbol loading may have failed due to the lack of permissions. This
+ // is not fatal, the symbols will appear as "unknown".
+ return true;
}
KernelSymbolRecord r(kallsyms);
if (!ProcessRecord(&r)) {
@@ -1135,8 +1259,7 @@ bool RecordCommand::DumpKernelSymbol() {
}
bool RecordCommand::DumpTracingData() {
- std::vector<const EventType*> tracepoint_event_types =
- event_selection_set_.GetTracepointEvents();
+ std::vector<const EventType*> tracepoint_event_types = event_selection_set_.GetTracepointEvents();
if (tracepoint_event_types.empty() || !CanRecordRawData() || in_app_context_) {
return true; // No need to dump tracing data, or can't do it.
}
@@ -1151,98 +1274,44 @@ bool RecordCommand::DumpTracingData() {
return true;
}
-bool RecordCommand::DumpKernelMaps() {
- KernelMmap kernel_mmap;
- std::vector<KernelMmap> module_mmaps;
- GetKernelAndModuleMmaps(&kernel_mmap, &module_mmaps);
-
- MmapRecord mmap_record(*dumping_attr_id_.attr, true, UINT_MAX, 0, kernel_mmap.start_addr,
- kernel_mmap.len, 0, kernel_mmap.filepath, dumping_attr_id_.ids[0]);
- if (!ProcessRecord(&mmap_record)) {
- return false;
- }
- for (auto& module_mmap : module_mmaps) {
- MmapRecord mmap_record(*dumping_attr_id_.attr, true, UINT_MAX, 0, module_mmap.start_addr,
- module_mmap.len, 0, module_mmap.filepath, dumping_attr_id_.ids[0]);
- if (!ProcessRecord(&mmap_record)) {
- return false;
+bool RecordCommand::DumpMaps() {
+ if (system_wide_collection_) {
+ // 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()) {
+ map_record_thread_.emplace(*map_record_reader_);
+ return true;
+ }
+ if (!event_selection_set_.ExcludeKernel()) {
+ return map_record_reader_->ReadKernelMaps();
}
- }
- return true;
-}
-
-bool RecordCommand::DumpUserSpaceMaps() {
- // For system_wide profiling:
- // If no aux tracing, maps of a process is dumped when needed (first time a sample hits
- // that process).
- // If aux tracing, we don't know which maps will be needed, so dump all process maps.
- if (system_wide_collection_ && !event_selection_set_.HasAuxTrace()) {
return true;
}
+ if (!event_selection_set_.ExcludeKernel() && !map_record_reader_->ReadKernelMaps()) {
+ return false;
+ }
// Map from process id to a set of thread ids in that process.
std::unordered_map<pid_t, std::unordered_set<pid_t>> process_map;
- if (system_wide_collection_) {
- for (auto pid : GetAllProcesses()) {
- process_map[pid] = std::unordered_set<pid_t>();
- }
- } else {
- for (pid_t pid : event_selection_set_.GetMonitoredProcesses()) {
- std::vector<pid_t> tids = GetThreadsInProcess(pid);
- process_map[pid].insert(tids.begin(), tids.end());
- }
- for (pid_t tid : event_selection_set_.GetMonitoredThreads()) {
- pid_t pid;
- if (GetProcessForThread(tid, &pid)) {
- process_map[pid].insert(tid);
- }
- }
+ for (pid_t pid : event_selection_set_.GetMonitoredProcesses()) {
+ std::vector<pid_t> tids = GetThreadsInProcess(pid);
+ process_map[pid].insert(tids.begin(), tids.end());
}
-
- // Dump each process.
- for (auto& pair : process_map) {
- if (!DumpProcessMaps(pair.first, pair.second)) {
- return false;
+ for (pid_t tid : event_selection_set_.GetMonitoredThreads()) {
+ pid_t pid;
+ if (GetProcessForThread(tid, &pid)) {
+ process_map[pid].insert(tid);
}
}
- return true;
-}
-bool RecordCommand::DumpProcessMaps(pid_t pid, const std::unordered_set<pid_t>& tids) {
- // Dump mmap records.
- std::vector<ThreadMmap> thread_mmaps;
- if (!GetThreadMmapsInProcess(pid, &thread_mmaps)) {
- // The process may exit before we get its info.
- return true;
- }
- const perf_event_attr& attr = *dumping_attr_id_.attr;
- uint64_t event_id = dumping_attr_id_.ids[0];
- for (const auto& map : thread_mmaps) {
- if (!(map.prot & PROT_EXEC) && !event_selection_set_.RecordNotExecutableMaps()) {
- continue;
- }
- Mmap2Record record(attr, false, pid, pid, map.start_addr, map.len,
- map.pgoff, map.prot, map.name, event_id, last_record_timestamp_);
- if (!ProcessRecord(&record)) {
- return false;
- }
- }
- // Dump process name.
- std::string name = GetCompleteProcessName(pid);
- if (!name.empty()) {
- CommRecord record(attr, pid, pid, name, event_id, last_record_timestamp_);
- if (!ProcessRecord(&record)) {
+ // Dump each process.
+ for (const auto& [pid, tids] : process_map) {
+ if (!map_record_reader_->ReadProcessMaps(pid, tids, 0)) {
return false;
}
}
- // Dump thread info.
- for (const auto& tid : tids) {
- if (tid != pid && GetThreadName(tid, &name)) {
- CommRecord comm_record(attr, pid, tid, name, event_id, last_record_timestamp_);
- if (!ProcessRecord(&comm_record)) {
- return false;
- }
- }
- }
return true;
}
@@ -1264,6 +1333,13 @@ bool RecordCommand::ProcessRecord(Record* record) {
if (system_wide_collection_ && !DumpMapsForRecord(record)) {
return false;
}
+ // 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))) {
+ return true;
+ }
+ }
if (unwind_dwarf_callchain_) {
if (post_unwind_) {
return SaveRecordForPostUnwinding(record);
@@ -1308,9 +1384,7 @@ bool RecordCommand::DumpMapsForRecord(Record* record) {
pid_t pid = static_cast<SampleRecord*>(record)->tid_data.pid;
if (dumped_processes_.find(pid) == dumped_processes_.end()) {
// Dump map info and all thread names for that process.
- std::vector<pid_t> tids = GetThreadsInProcess(pid);
- if (!tids.empty() &&
- !DumpProcessMaps(pid, std::unordered_set<pid_t>(tids.begin(), tids.end()))) {
+ if (!map_record_reader_->ReadProcessMaps(pid, last_record_timestamp_)) {
return false;
}
dumped_processes_.insert(pid);
@@ -1374,10 +1448,10 @@ bool RecordCommand::ProcessJITDebugInfo(const std::vector<JITDebugInfo>& debug_i
EventAttrWithId attr_id = event_selection_set_.GetEventAttrWithId()[0];
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(*attr_id.attr, false, info.pid, info.pid,
- info.jit_code_addr, info.jit_code_len, 0, map_flags::PROT_JIT_SYMFILE_MAP,
+ uint64_t timestamp =
+ jit_debug_reader_->SyncWithRecords() ? info.timestamp : last_record_timestamp_;
+ Mmap2Record record(*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, attr_id.ids[0], timestamp);
if (!ProcessRecord(&record)) {
return false;
@@ -1385,8 +1459,8 @@ bool RecordCommand::ProcessJITDebugInfo(const std::vector<JITDebugInfo>& debug_i
} else {
if (info.extracted_dex_file_map) {
ThreadMmap& map = *info.extracted_dex_file_map;
- uint64_t timestamp = jit_debug_reader_->SyncWithRecords() ? info.timestamp
- : last_record_timestamp_;
+ uint64_t timestamp =
+ jit_debug_reader_->SyncWithRecords() ? info.timestamp : last_record_timestamp_;
Mmap2Record record(*attr_id.attr, false, info.pid, info.pid, map.start_addr, map.len,
map.pgoff, map.prot, map.name, attr_id.ids[0], timestamp);
if (!ProcessRecord(&record)) {
@@ -1494,13 +1568,10 @@ 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) &&
+ 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)) {
- ThreadEntry* thread =
- thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
+ 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;
std::vector<uint64_t> sps;
@@ -1519,33 +1590,94 @@ bool RecordCommand::UnwindRecord(SampleRecord& r) {
return false;
}
}
+ if (keep_failed_unwinding_result_ && !KeepFailedUnwindingResult(r, ips, sps)) {
+ return false;
+ }
r.ReplaceRegAndStackWithCallChain(ips);
- if (callchain_joiner_) {
- return callchain_joiner_->AddCallChain(r.tid_data.pid, r.tid_data.tid,
- CallChainJoiner::ORIGINAL_OFFLINE, ips, sps);
+ if (callchain_joiner_ &&
+ !callchain_joiner_->AddCallChain(r.tid_data.pid, r.tid_data.tid,
+ CallChainJoiner::ORIGINAL_OFFLINE, ips, sps)) {
+ return false;
}
}
return true;
}
-bool RecordCommand::PostUnwindRecords() {
- // 1. Move records from record_filename_ to a temporary file.
+bool RecordCommand::KeepFailedUnwindingResult(const SampleRecord& r,
+ const std::vector<uint64_t>& ips,
+ const std::vector<uint64_t>& sps) {
+ auto& result = offline_unwinder_->GetUnwindingResult();
+ if (result.error_code != unwindstack::ERROR_NONE) {
+ if (keep_failed_unwinding_debug_info_) {
+ return record_file_writer_->WriteRecord(UnwindingResultRecord(
+ r.time_data.time, result, r.regs_user_data, r.stack_user_data, ips, sps));
+ }
+ return record_file_writer_->WriteRecord(
+ UnwindingResultRecord(r.time_data.time, result, {}, {}, {}, {}));
+ }
+ return true;
+}
+
+std::unique_ptr<RecordFileReader> RecordCommand::MoveRecordFile(const std::string& old_filename) {
if (!record_file_writer_->Close()) {
- return false;
+ return nullptr;
}
record_file_writer_.reset();
- std::unique_ptr<TemporaryFile> tmp_file = ScopedTempFiles::CreateTempFile();
- if (!Workload::RunCmd({"mv", record_filename_, tmp_file->path})) {
- return false;
+ {
+ std::error_code ec;
+ std::filesystem::rename(record_filename_, old_filename, ec);
+ if (ec) {
+ LOG(ERROR) << "Failed to rename: " << ec.message();
+ return nullptr;
+ }
+ }
+ record_file_writer_ = CreateRecordFile(record_filename_);
+ if (!record_file_writer_) {
+ return nullptr;
}
- std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmp_file->path);
+ return RecordFileReader::CreateInstance(old_filename);
+}
+
+bool RecordCommand::MergeMapRecords() {
+ // 1. Move records from record_filename_ to a temporary file.
+ auto tmp_file = ScopedTempFiles::CreateTempFile();
+ auto reader = MoveRecordFile(tmp_file->path);
if (!reader) {
return false;
}
- // 2. Read records from the temporary file, and write unwound records back to record_filename_.
- record_file_writer_ = CreateRecordFile(record_filename_);
- if (!record_file_writer_) {
+ // 2. Copy map records from map record thread.
+ auto callback = [this](Record* r) {
+ UpdateRecord(r);
+ if (ShouldOmitRecord(r)) {
+ return true;
+ }
+ return record_file_writer_->WriteRecord(*r);
+ };
+ if (!map_record_thread_->ReadMapRecords(callback)) {
+ return false;
+ }
+
+ // 3. Copy data section from the old recording file.
+ std::vector<char> buf(64 * 1024);
+ uint64_t offset = reader->FileHeader().data.offset;
+ uint64_t left_size = reader->FileHeader().data.size;
+ while (left_size > 0) {
+ size_t nread = std::min<size_t>(left_size, buf.size());
+ if (!reader->ReadAtOffset(offset, buf.data(), nread) ||
+ !record_file_writer_->WriteData(buf.data(), nread)) {
+ return false;
+ }
+ offset += nread;
+ left_size -= nread;
+ }
+ return true;
+}
+
+bool RecordCommand::PostUnwindRecords() {
+ auto tmp_file = ScopedTempFiles::CreateTempFile();
+ auto reader = MoveRecordFile(tmp_file->path);
+ if (!reader) {
return false;
}
sample_record_count_ = 0;
@@ -1562,23 +1694,14 @@ bool RecordCommand::JoinCallChains() {
return false;
}
// 2. Move records from record_filename_ to a temporary file.
- if (!record_file_writer_->Close()) {
- return false;
- }
- record_file_writer_.reset();
- std::unique_ptr<TemporaryFile> tmp_file = ScopedTempFiles::CreateTempFile();
- if (!Workload::RunCmd({"mv", record_filename_, tmp_file->path})) {
+ auto tmp_file = ScopedTempFiles::CreateTempFile();
+ auto reader = MoveRecordFile(tmp_file->path);
+ if (!reader) {
return false;
}
// 3. Read records from the temporary file, and write record with joined call chains back
// to record_filename_.
- std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmp_file->path);
- record_file_writer_ = CreateRecordFile(record_filename_);
- if (!reader || !record_file_writer_) {
- return false;
- }
-
auto record_callback = [&](std::unique_ptr<Record> r) {
if (r->type() != PERF_RECORD_SAMPLE) {
return record_file_writer_->WriteRecord(*r);
@@ -1604,25 +1727,57 @@ bool RecordCommand::JoinCallChains() {
return reader->ReadDataSection(record_callback);
}
-bool RecordCommand::DumpAdditionalFeatures(
- const std::vector<std::string>& args) {
+static void LoadSymbolMapFile(int pid, const std::string& package, ThreadTree* thread_tree) {
+ // On Linux, symbol map files usually go to /tmp/perf-<pid>.map
+ // On Android, there is no directory where any process can create files.
+ // For now, use /data/local/tmp/perf-<pid>.map, which works for standalone programs,
+ // and /data/data/<package>/perf-<pid>.map, which works for apps.
+ auto path = package.empty()
+ ? android::base::StringPrintf("/data/local/tmp/perf-%d.map", pid)
+ : android::base::StringPrintf("/data/data/%s/perf-%d.map", package.c_str(), pid);
+
+ auto symbols = ReadSymbolMapFromFile(path);
+ if (!symbols.empty()) {
+ thread_tree->AddSymbolsForProcess(pid, &symbols);
+ }
+}
+
+bool RecordCommand::DumpAdditionalFeatures(const std::vector<std::string>& args) {
// Read data section of perf.data to collect hit file information.
thread_tree_.ClearThreadAndMap();
bool kernel_symbols_available = false;
- if (CheckKernelSymbolAddresses()) {
- Dso::ReadKernelSymbolsFromProc();
+ std::string kallsyms;
+ if (event_selection_set_.NeedKernelSymbol() && LoadKernelSymbols(&kallsyms)) {
+ Dso::SetKallsyms(kallsyms);
kernel_symbols_available = true;
}
+ std::unordered_set<int> loaded_symbol_maps;
std::vector<uint64_t> auxtrace_offset;
+ std::unordered_set<Dso*> debug_unwinding_files;
+ bool failed_unwinding_sample = false;
+
auto callback = [&](const Record* r) {
thread_tree_.Update(*r);
if (r->type() == PERF_RECORD_SAMPLE) {
- CollectHitFileInfo(*reinterpret_cast<const SampleRecord*>(r));
+ auto sample = reinterpret_cast<const SampleRecord*>(r);
+ // Symbol map files are available after recording. Load one for the process.
+ if (loaded_symbol_maps.insert(sample->tid_data.pid).second) {
+ LoadSymbolMapFile(sample->tid_data.pid, app_package_name_, &thread_tree_);
+ }
+ if (failed_unwinding_sample) {
+ failed_unwinding_sample = false;
+ CollectHitFileInfo(*sample, &debug_unwinding_files);
+ } else {
+ CollectHitFileInfo(*sample, nullptr);
+ }
} else if (r->type() == PERF_RECORD_AUXTRACE) {
auto auxtrace = static_cast<const AuxTraceRecord*>(r);
auxtrace_offset.emplace_back(auxtrace->location.file_offset - auxtrace->size());
+ } else if (r->type() == SIMPLE_PERF_RECORD_UNWINDING_RESULT) {
+ failed_unwinding_sample = true;
}
};
+
if (!record_file_writer_->ReadDataSection(callback)) {
return false;
}
@@ -1634,6 +1789,9 @@ bool RecordCommand::DumpAdditionalFeatures(
if (!auxtrace_offset.empty()) {
feature_count++;
}
+ if (keep_failed_unwinding_debug_info_) {
+ feature_count += 2;
+ }
if (!record_file_writer_->BeginWriteFeatures(feature_count)) {
return false;
}
@@ -1648,12 +1806,10 @@ bool RecordCommand::DumpAdditionalFeatures(
PLOG(ERROR) << "uname() failed";
return false;
}
- if (!record_file_writer_->WriteFeatureString(PerfFileFormat::FEAT_OSRELEASE,
- uname_buf.release)) {
+ if (!record_file_writer_->WriteFeatureString(PerfFileFormat::FEAT_OSRELEASE, uname_buf.release)) {
return false;
}
- if (!record_file_writer_->WriteFeatureString(PerfFileFormat::FEAT_ARCH,
- uname_buf.machine)) {
+ if (!record_file_writer_->WriteFeatureString(PerfFileFormat::FEAT_ARCH, uname_buf.machine)) {
return false;
}
@@ -1666,8 +1822,7 @@ bool RecordCommand::DumpAdditionalFeatures(
if (!record_file_writer_->WriteCmdlineFeature(cmdline)) {
return false;
}
- if (branch_sampling_ != 0 &&
- !record_file_writer_->WriteBranchStackFeature()) {
+ if (branch_sampling_ != 0 && !record_file_writer_->WriteBranchStackFeature()) {
return false;
}
if (!DumpMetaInfoFeature(kernel_symbols_available)) {
@@ -1676,6 +1831,9 @@ bool RecordCommand::DumpAdditionalFeatures(
if (!auxtrace_offset.empty() && !record_file_writer_->WriteAuxTraceFeature(auxtrace_offset)) {
return false;
}
+ if (keep_failed_unwinding_debug_info_ && !DumpDebugUnwindFeature(debug_unwinding_files)) {
+ return false;
+ }
if (!record_file_writer_->EndWriteFeatures()) {
return false;
@@ -1697,29 +1855,30 @@ bool RecordCommand::DumpBuildIdFeature() {
if (!GetKernelBuildId(&build_id)) {
continue;
}
- build_id_records.push_back(
- BuildIdRecord(true, UINT_MAX, build_id, dso->Path()));
+ build_id_records.push_back(BuildIdRecord(true, UINT_MAX, build_id, dso->Path()));
} else if (dso->type() == DSO_KERNEL_MODULE) {
- std::string path = dso->Path();
- std::string module_name = basename(&path[0]);
- if (android::base::EndsWith(module_name, ".ko")) {
- module_name = module_name.substr(0, module_name.size() - 3);
+ 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 (!GetModuleBuildId(module_name, &build_id)) {
- LOG(DEBUG) << "can't read build_id for module " << module_name;
- continue;
+ 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();
}
- build_id_records.push_back(BuildIdRecord(true, UINT_MAX, build_id, path));
} else if (dso->type() == DSO_ELF_FILE) {
- if (dso->Path() == DEFAULT_EXECNAME_FOR_THREAD_MMAP) {
+ 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()));
+ build_id_records.push_back(BuildIdRecord(false, UINT_MAX, build_id, dso->Path()));
}
}
if (!record_file_writer_->WriteBuildIdFeature(build_id_records)) {
@@ -1730,11 +1889,19 @@ bool RecordCommand::DumpBuildIdFeature() {
bool RecordCommand::DumpFileFeature() {
std::vector<Dso*> dso_v = thread_tree_.GetAllDsos();
- return record_file_writer_->WriteFileFeatures(thread_tree_.GetAllDsos());
+ // To parse ETM data for kernel modules, we need to dump memory address for kernel modules.
+ if (event_selection_set_.HasAuxTrace() && !event_selection_set_.ExcludeKernel()) {
+ for (Dso* dso : dso_v) {
+ if (dso->type() == DSO_KERNEL_MODULE) {
+ dso->CreateDumpId();
+ }
+ }
+ }
+ return record_file_writer_->WriteFileFeatures(dso_v);
}
bool RecordCommand::DumpMetaInfoFeature(bool kernel_symbols_available) {
- std::unordered_map<std::string, std::string> info_map;
+ std::unordered_map<std::string, std::string> info_map = extra_meta_info_;
info_map["simpleperf_version"] = GetSimpleperfVersion();
info_map["system_wide_collection"] = system_wide_collection_ ? "true" : "false";
info_map["trace_offcpu"] = trace_offcpu_ ? "true" : "false";
@@ -1742,79 +1909,187 @@ bool RecordCommand::DumpMetaInfoFeature(bool kernel_symbols_available) {
// understanding of event types, even if they are on another machine.
info_map["event_type_info"] = ScopedEventTypes::BuildString(event_selection_set_.GetEvents());
#if defined(__ANDROID__)
- info_map["product_props"] = android::base::StringPrintf("%s:%s:%s",
- android::base::GetProperty("ro.product.manufacturer", "").c_str(),
- android::base::GetProperty("ro.product.model", "").c_str(),
- android::base::GetProperty("ro.product.name", "").c_str());
+ info_map["product_props"] = android::base::StringPrintf(
+ "%s:%s:%s", android::base::GetProperty("ro.product.manufacturer", "").c_str(),
+ android::base::GetProperty("ro.product.model", "").c_str(),
+ android::base::GetProperty("ro.product.name", "").c_str());
info_map["android_version"] = android::base::GetProperty("ro.build.version.release", "");
+ info_map["android_sdk_version"] = android::base::GetProperty("ro.build.version.sdk", "");
+ info_map["android_build_type"] = android::base::GetProperty("ro.build.type", "");
if (!app_package_name_.empty()) {
info_map["app_package_name"] = app_package_name_;
+ if (IsRoot()) {
+ info_map["app_type"] = GetAppType(app_package_name_);
+ }
+ }
+ if (event_selection_set_.HasAuxTrace()) {
+ // used by --exclude-perf in cmd_inject.cpp
+ info_map["recording_process"] = std::to_string(getpid());
}
#endif
info_map["clockid"] = clockid_;
info_map["timestamp"] = std::to_string(time(nullptr));
info_map["kernel_symbols_available"] = kernel_symbols_available ? "true" : "false";
+ if (dwarf_callchain_sampling_ && !unwind_dwarf_callchain_) {
+ OfflineUnwinder::CollectMetaInfo(&info_map);
+ }
return record_file_writer_->WriteMetaInfoFeature(info_map);
}
-void RecordCommand::CollectHitFileInfo(const SampleRecord& r) {
- const ThreadEntry* thread =
- thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
- const MapEntry* map =
- thread_tree_.FindMap(thread, r.ip_data.ip, r.InKernel());
- Dso* dso = map->dso;
- const Symbol* symbol;
- if (dump_symbols_) {
- symbol = thread_tree_.FindSymbol(map, r.ip_data.ip, nullptr, &dso);
- if (!symbol->HasDumpId()) {
- dso->CreateSymbolDumpId(symbol);
- }
- }
- if (!dso->HasDumpId() && dso->type() != DSO_UNKNOWN_FILE) {
- dso->CreateDumpId();
- }
- if (r.sample_type & PERF_SAMPLE_CALLCHAIN) {
- bool in_kernel = r.InKernel();
- bool first_ip = true;
- for (uint64_t i = 0; i < r.callchain_data.ip_nr; ++i) {
- uint64_t ip = r.callchain_data.ips[i];
- if (ip >= PERF_CONTEXT_MAX) {
- switch (ip) {
- case PERF_CONTEXT_KERNEL:
- in_kernel = true;
- break;
- case PERF_CONTEXT_USER:
- in_kernel = false;
- break;
- default:
- LOG(DEBUG) << "Unexpected perf_context in callchain: " << std::hex
- << ip;
- }
- } else {
- if (first_ip) {
- first_ip = false;
- // Remove duplication with sample ip.
- if (ip == r.ip_data.ip) {
- continue;
- }
- }
- map = thread_tree_.FindMap(thread, ip, in_kernel);
- dso = map->dso;
- if (dump_symbols_) {
- symbol = thread_tree_.FindSymbol(map, ip, nullptr, &dso);
- if (!symbol->HasDumpId()) {
- dso->CreateSymbolDumpId(symbol);
- }
- }
- if (!dso->HasDumpId() && dso->type() != DSO_UNKNOWN_FILE) {
- dso->CreateDumpId();
+bool RecordCommand::DumpDebugUnwindFeature(const std::unordered_set<Dso*>& dso_set) {
+ DebugUnwindFeature debug_unwind_feature;
+ debug_unwind_feature.reserve(dso_set.size());
+ for (const Dso* dso : dso_set) {
+ if (dso->type() != DSO_ELF_FILE) {
+ continue;
+ }
+ const std::string& filename = dso->GetDebugFilePath();
+ std::unique_ptr<ElfFile> elf = ElfFile::Open(filename);
+ if (elf) {
+ llvm::MemoryBuffer* buffer = elf->GetMemoryBuffer();
+ debug_unwind_feature.resize(debug_unwind_feature.size() + 1);
+ auto& debug_unwind_file = debug_unwind_feature.back();
+ debug_unwind_file.path = filename;
+ debug_unwind_file.size = buffer->getBufferSize();
+ if (!record_file_writer_->WriteFeature(PerfFileFormat::FEAT_DEBUG_UNWIND_FILE,
+ buffer->getBufferStart(), buffer->getBufferSize())) {
+ return false;
+ }
+ } else {
+ LOG(WARNING) << "failed to keep " << filename << " in debug_unwind_feature section";
+ }
+ }
+ return record_file_writer_->WriteDebugUnwindFeature(debug_unwind_feature);
+}
+
+void RecordCommand::CollectHitFileInfo(const SampleRecord& r, std::unordered_set<Dso*>* dso_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);
+ 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;
+ if (dump_symbols_) {
+ const Symbol* symbol = thread_tree_.FindSymbol(map, ips[i], nullptr, &dso);
+ if (!symbol->HasDumpId()) {
+ dso->CreateSymbolDumpId(symbol);
+ }
+ }
+ if (!dso->HasDumpId() && dso->type() != DSO_UNKNOWN_FILE) {
+ dso->CreateDumpId();
+ }
+ if (dso_set != nullptr) {
+ dso_set->insert(dso);
+ }
+ }
+}
+
+} // namespace
+
+static bool ConsumeStr(const char*& p, const char* s) {
+ if (strncmp(p, s, strlen(s)) == 0) {
+ p += strlen(s);
+ return true;
+ }
+ return false;
+}
+
+static bool ConsumeAddr(const char*& p, uint64_t* addr) {
+ errno = 0;
+ char* end;
+ *addr = strtoull(p, &end, 0);
+ if (errno == 0 && p != end) {
+ p = end;
+ return true;
+ }
+ return false;
+}
+
+// To reduce function length, not all format errors are checked.
+static bool ParseOneAddrFilter(const std::string& s, std::vector<AddrFilter>* filters) {
+ std::vector<std::string> args = android::base::Split(s, " ");
+ if (args.size() != 2) {
+ return false;
+ }
+
+ uint64_t addr1;
+ uint64_t addr2;
+ uint64_t off1;
+ uint64_t off2;
+ std::string path;
+
+ if (auto p = s.data(); ConsumeStr(p, "start") && ConsumeAddr(p, &addr1)) {
+ if (*p == '\0') {
+ // start <kernel_addr>
+ filters->emplace_back(AddrFilter::KERNEL_START, addr1, 0, "");
+ return true;
+ }
+ if (ConsumeStr(p, "@") && *p != '\0') {
+ // start <vaddr>@<file_path>
+ if (auto elf = ElfFile::Open(p); elf && elf->VaddrToOff(addr1, &off1) && Realpath(p, &path)) {
+ filters->emplace_back(AddrFilter::FILE_START, off1, 0, path);
+ return true;
+ }
+ }
+ }
+ if (auto p = s.data(); ConsumeStr(p, "stop") && ConsumeAddr(p, &addr1)) {
+ if (*p == '\0') {
+ // stop <kernel_addr>
+ filters->emplace_back(AddrFilter::KERNEL_STOP, addr1, 0, "");
+ return true;
+ }
+ if (ConsumeStr(p, "@") && *p != '\0') {
+ // stop <vaddr>@<file_path>
+ if (auto elf = ElfFile::Open(p); elf && elf->VaddrToOff(addr1, &off1) && Realpath(p, &path)) {
+ filters->emplace_back(AddrFilter::FILE_STOP, off1, 0, path);
+ return true;
+ }
+ }
+ }
+ if (auto p = s.data(); ConsumeStr(p, "filter") && ConsumeAddr(p, &addr1) && ConsumeStr(p, "-") &&
+ ConsumeAddr(p, &addr2)) {
+ if (*p == '\0') {
+ // filter <kernel_addr_start>-<kernel_addr_end>
+ filters->emplace_back(AddrFilter::KERNEL_RANGE, addr1, addr2 - addr1, "");
+ return true;
+ }
+ if (ConsumeStr(p, "@") && *p != '\0') {
+ // filter <vaddr_start>-<vaddr_end>@<file_path>
+ if (auto elf = ElfFile::Open(p); elf && elf->VaddrToOff(addr1, &off1) &&
+ elf->VaddrToOff(addr2, &off2) && Realpath(p, &path)) {
+ filters->emplace_back(AddrFilter::FILE_RANGE, off1, off2 - off1, path);
+ return true;
+ }
+ }
+ }
+ if (auto p = s.data(); ConsumeStr(p, "filter") && *p != '\0') {
+ // filter <file_path>
+ path = android::base::Trim(p);
+ if (auto elf = ElfFile::Open(path); elf) {
+ for (const ElfSegment& seg : elf->GetProgramHeader()) {
+ if (seg.is_executable) {
+ filters->emplace_back(AddrFilter::FILE_RANGE, seg.file_offset, seg.file_size, path);
}
}
+ return true;
}
}
+ return false;
+}
+
+std::vector<AddrFilter> ParseAddrFilterOption(const std::string& s) {
+ std::vector<AddrFilter> filters;
+ for (const auto& str : android::base::Split(s, ",")) {
+ if (!ParseOneAddrFilter(str, &filters)) {
+ LOG(ERROR) << "failed to parse addr filter: " << str;
+ return {};
+ }
+ }
+ return filters;
}
void RegisterRecordCommand() {
- RegisterCommand("record",
- [] { return std::unique_ptr<Command>(new RecordCommand()); });
+ RegisterCommand("record", [] { return std::unique_ptr<Command>(new RecordCommand()); });
}
+
+} // namespace simpleperf
diff --git a/simpleperf/cmd_record_impl.h b/simpleperf/cmd_record_impl.h
new file mode 100644
index 00000000..6164297a
--- /dev/null
+++ b/simpleperf/cmd_record_impl.h
@@ -0,0 +1,97 @@
+/*
+ * 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>
+
+#include "RecordFilter.h"
+#include "command.h"
+
+namespace simpleperf {
+
+struct AddrFilter;
+
+std::vector<AddrFilter> ParseAddrFilterOption(const std::string& s);
+
+inline const OptionFormatMap& GetRecordCmdOptionFormats() {
+ static OptionFormatMap option_formats;
+ if (option_formats.empty()) {
+ option_formats = {
+ {"-a", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::NOT_ALLOWED}},
+ {"--add-meta-info",
+ {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}},
+ {"--addr-filter", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--app", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::NOT_ALLOWED}},
+ {"--aux-buffer-size", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"-b", {OptionValueType::NONE, 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-percent", {OptionValueType::UINT, 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}},
+ {"--exit-with-parent", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"-f", {OptionValueType::UINT, OptionType::ORDERED, AppRunnerType::ALLOWED}},
+ {"-g", {OptionValueType::NONE, OptionType::ORDERED, AppRunnerType::ALLOWED}},
+ {"--group", {OptionValueType::STRING, OptionType::ORDERED, AppRunnerType::ALLOWED}},
+ {"--in-app", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"-j", {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}},
+ {"--keep-failed-unwinding-result",
+ {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--keep-failed-unwinding-debug-info",
+ {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::NOT_ALLOWED}},
+ {"--kprobe", {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::NOT_ALLOWED}},
+ {"-m", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--no-callchain-joiner",
+ {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--no-cut-samples", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--no-dump-kernel-symbols",
+ {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--no-dump-symbols", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--no-inherit", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--no-unwind", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::NOT_ALLOWED}},
+ {"-o", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::NOT_ALLOWED}},
+ {"--out-fd", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::CHECK_FD}},
+ {"-p", {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}},
+ {"--post-unwind", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--post-unwind=no", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--post-unwind=yes", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--size-limit", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--start_profiling_fd",
+ {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::CHECK_FD}},
+ {"--stdio-controls-profiling",
+ {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--stop-signal-fd", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::CHECK_FD}},
+ {"--symfs", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::CHECK_PATH}},
+ {"-t", {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}},
+ {"--tp-filter", {OptionValueType::STRING, OptionType::ORDERED, AppRunnerType::ALLOWED}},
+ {"--trace-offcpu", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--tracepoint-events",
+ {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::CHECK_PATH}},
+ };
+ const OptionFormatMap& record_filter_options = GetRecordFilterOptionFormats();
+ option_formats.insert(record_filter_options.begin(), record_filter_options.end());
+ }
+ return option_formats;
+}
+
+} // namespace simpleperf
diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp
index fdb6f5e3..350a24f8 100644
--- a/simpleperf/cmd_record_test.cpp
+++ b/simpleperf/cmd_record_test.cpp
@@ -20,32 +20,37 @@
#include <stdlib.h>
#include <unistd.h>
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/stringprintf.h>
-#include <android-base/strings.h>
-
-#if defined(__ANDROID__)
-#include <android-base/properties.h>
-#endif
-
+#include <filesystem>
#include <map>
#include <memory>
#include <regex>
#include <thread>
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/test_utils.h>
+
+#include "ETMRecorder.h"
+#include "ProbeEvents.h"
+#include "cmd_record_impl.h"
#include "command.h"
#include "environment.h"
-#include "ETMRecorder.h"
#include "event_selection_set.h"
#include "get_test_data.h"
+#include "kallsyms.h"
#include "record.h"
#include "record_file.h"
#include "test_util.h"
#include "thread_tree.h"
+using android::base::Realpath;
+using android::base::StringPrintf;
using namespace simpleperf;
using namespace PerfFileFormat;
+namespace fs = std::filesystem;
static std::unique_ptr<Command> RecordCmd() {
return CreateCommandInstance("record");
@@ -55,8 +60,7 @@ static const char* GetDefaultEvent() {
return HasHardwareCounter() ? "cpu-cycles" : "task-clock";
}
-static bool RunRecordCmd(std::vector<std::string> v,
- const char* output_file = nullptr) {
+static bool RunRecordCmd(std::vector<std::string> v, const char* output_file = nullptr) {
bool has_event = false;
for (auto& arg : v) {
if (arg == "-e" || arg == "--group") {
@@ -88,8 +92,8 @@ TEST(record_cmd, system_wide_option) {
TEST_IN_ROOT(ASSERT_TRUE(RunRecordCmd({"-a"})));
}
-void CheckEventType(const std::string& record_file, const std::string& event_type,
- uint64_t sample_period, uint64_t sample_freq) {
+static void CheckEventType(const std::string& record_file, const std::string& event_type,
+ uint64_t sample_period, uint64_t sample_freq) {
const EventType* type = FindEventTypeByName(event_type);
ASSERT_TRUE(type != nullptr);
std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(record_file);
@@ -131,8 +135,8 @@ TEST(record_cmd, freq_option) {
TEST(record_cmd, multiple_freq_or_sample_period_option) {
TemporaryFile tmpfile;
- ASSERT_TRUE(RunRecordCmd({"-f", "99", "-e", "task-clock", "-c", "1000000", "-e",
- "cpu-clock"}, tmpfile.path));
+ ASSERT_TRUE(RunRecordCmd({"-f", "99", "-e", "task-clock", "-c", "1000000", "-e", "cpu-clock"},
+ tmpfile.path));
CheckEventType(tmpfile.path, "task-clock", 0, 99u);
CheckEventType(tmpfile.path, "cpu-clock", 1000000u, 0u);
}
@@ -145,18 +149,15 @@ TEST(record_cmd, output_file_option) {
TEST(record_cmd, dump_kernel_mmap) {
TemporaryFile tmpfile;
ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
- std::unique_ptr<RecordFileReader> reader =
- RecordFileReader::CreateInstance(tmpfile.path);
+ std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
ASSERT_TRUE(reader != nullptr);
std::vector<std::unique_ptr<Record>> records = reader->DataSection();
ASSERT_GT(records.size(), 0U);
bool have_kernel_mmap = false;
for (auto& record : records) {
if (record->type() == PERF_RECORD_MMAP) {
- const MmapRecord* mmap_record =
- static_cast<const MmapRecord*>(record.get());
- if (strcmp(mmap_record->filename, DEFAULT_KERNEL_MMAP_NAME) == 0 ||
- strcmp(mmap_record->filename, DEFAULT_KERNEL_MMAP_NAME_PERF) == 0) {
+ const MmapRecord* mmap_record = static_cast<const MmapRecord*>(record.get());
+ if (android::base::StartsWith(mmap_record->filename, DEFAULT_KERNEL_MMAP_NAME)) {
have_kernel_mmap = true;
break;
}
@@ -168,12 +169,10 @@ TEST(record_cmd, dump_kernel_mmap) {
TEST(record_cmd, dump_build_id_feature) {
TemporaryFile tmpfile;
ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
- std::unique_ptr<RecordFileReader> reader =
- RecordFileReader::CreateInstance(tmpfile.path);
+ std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
ASSERT_TRUE(reader != nullptr);
const FileHeader& file_header = reader->FileHeader();
- ASSERT_TRUE(file_header.features[FEAT_BUILD_ID / 8] &
- (1 << (FEAT_BUILD_ID % 8)));
+ ASSERT_TRUE(file_header.features[FEAT_BUILD_ID / 8] & (1 << (FEAT_BUILD_ID % 8)));
ASSERT_GT(reader->FeatureSectionDescriptors().size(), 0u);
}
@@ -246,86 +245,6 @@ TEST(record_cmd, system_wide_fp_callchain_sampling) {
TEST_IN_ROOT(ASSERT_TRUE(RunRecordCmd({"-a", "--call-graph", "fp"})));
}
-bool IsInNativeAbi() {
- static int in_native_abi = -1;
- if (in_native_abi == -1) {
- FILE* fp = popen("uname -m", "re");
- char buf[40];
- memset(buf, '\0', sizeof(buf));
- CHECK_EQ(fgets(buf, sizeof(buf), fp), buf);
- pclose(fp);
- std::string s = buf;
- in_native_abi = 1;
- if (GetBuildArch() == ARCH_X86_32 || GetBuildArch() == ARCH_X86_64) {
- if (s.find("86") == std::string::npos) {
- in_native_abi = 0;
- }
- } else if (GetBuildArch() == ARCH_ARM || GetBuildArch() == ARCH_ARM64) {
- if (s.find("arm") == std::string::npos && s.find("aarch64") == std::string::npos) {
- in_native_abi = 0;
- }
- }
- }
- return in_native_abi == 1;
-}
-
-static bool InCloudAndroid() {
-#if defined(__i386__) || defined(__x86_64__)
-#if defined(__ANDROID__)
- std::string prop_value = android::base::GetProperty("ro.build.flavor", "");
- if (android::base::StartsWith(prop_value, "cf_x86_phone") ||
- android::base::StartsWith(prop_value, "aosp_cf_x86_phone")) {
- return true;
- }
-#endif
-#endif
- return false;
-}
-
-bool HasTracepointEvents() {
- static int has_tracepoint_events = -1;
- if (has_tracepoint_events == -1) {
- // Cloud Android doesn't support tracepoint events.
- has_tracepoint_events = InCloudAndroid() ? 0 : 1;
- }
- return has_tracepoint_events == 1;
-}
-
-bool HasHardwareCounter() {
- static int has_hw_counter = -1;
- if (has_hw_counter == -1) {
- // Cloud Android doesn't have hardware counters.
- has_hw_counter = InCloudAndroid() ? 0 : 1;
-#if defined(__arm__)
- std::string cpu_info;
- if (android::base::ReadFileToString("/proc/cpuinfo", &cpu_info)) {
- std::string hardware = GetHardwareFromCpuInfo(cpu_info);
- if (std::regex_search(hardware, std::regex(R"(i\.MX6.*Quad)")) ||
- std::regex_search(hardware, std::regex(R"(SC7731e)")) ||
- std::regex_search(hardware, std::regex(R"(Qualcomm Technologies, Inc MSM8909)")) ||
- std::regex_search(hardware, std::regex(R"(Broadcom STB \(Flattened Device Tree\))"))) {
- has_hw_counter = 0;
- }
- }
-#endif
- }
- return has_hw_counter == 1;
-}
-
-bool HasPmuCounter() {
- static int has_pmu_counter = -1;
- if (has_pmu_counter == -1) {
- has_pmu_counter = 0;
- for (auto& event_type : GetAllEventTypes()) {
- if (event_type.IsPmuEvent()) {
- has_pmu_counter = 1;
- break;
- }
- }
- }
- return has_pmu_counter == 1;
-}
-
TEST(record_cmd, dwarf_callchain_sampling) {
OMIT_TEST_ON_NON_NATIVE_ABIS();
ASSERT_TRUE(IsDwarfCallChainSamplingSupported());
@@ -365,8 +284,8 @@ TEST(record_cmd, post_unwind_option) {
TEST(record_cmd, existing_processes) {
std::vector<std::unique_ptr<Workload>> workloads;
CreateProcesses(2, &workloads);
- std::string pid_list = android::base::StringPrintf(
- "%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
+ std::string pid_list =
+ android::base::StringPrintf("%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
ASSERT_TRUE(RunRecordCmd({"-p", pid_list}));
}
@@ -374,8 +293,8 @@ TEST(record_cmd, existing_threads) {
std::vector<std::unique_ptr<Workload>> workloads;
CreateProcesses(2, &workloads);
// Process id can also be used as thread id in linux.
- std::string tid_list = android::base::StringPrintf(
- "%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
+ std::string tid_list =
+ android::base::StringPrintf("%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
ASSERT_TRUE(RunRecordCmd({"-t", tid_list}));
}
@@ -396,11 +315,9 @@ TEST(record_cmd, mmap_page_option) {
ASSERT_FALSE(RunRecordCmd({"-m", "7"}));
}
-static void CheckKernelSymbol(const std::string& path, bool need_kallsyms,
- bool* success) {
+static void CheckKernelSymbol(const std::string& path, bool need_kallsyms, bool* success) {
*success = false;
- std::unique_ptr<RecordFileReader> reader =
- RecordFileReader::CreateInstance(path);
+ std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(path);
ASSERT_TRUE(reader != nullptr);
std::vector<std::unique_ptr<Record>> records = reader->DataSection();
bool has_kernel_symbol_records = false;
@@ -409,7 +326,8 @@ static void CheckKernelSymbol(const std::string& path, bool need_kallsyms,
has_kernel_symbol_records = true;
}
}
- bool require_kallsyms = need_kallsyms && CheckKernelSymbolAddresses();
+ std::string kallsyms;
+ bool require_kallsyms = need_kallsyms && LoadKernelSymbols(&kallsyms);
ASSERT_EQ(require_kallsyms, has_kernel_symbol_records);
*success = true;
}
@@ -430,17 +348,11 @@ static void ProcessSymbolsInPerfDataFile(
const std::function<bool(const Symbol&, uint32_t)>& callback) {
auto reader = RecordFileReader::CreateInstance(perf_data_file);
ASSERT_TRUE(reader);
- std::string file_path;
- uint32_t file_type;
- uint64_t min_vaddr;
- uint64_t file_offset_of_min_vaddr;
- std::vector<Symbol> symbols;
- std::vector<uint64_t> dex_file_offsets;
+ FileFeature file;
size_t read_pos = 0;
- while (reader->ReadFileFeature(read_pos, &file_path, &file_type, &min_vaddr,
- &file_offset_of_min_vaddr, &symbols, &dex_file_offsets)) {
- for (const auto& symbol : symbols) {
- if (callback(symbol, file_type)) {
+ while (reader->ReadFileFeature(read_pos, &file)) {
+ for (const auto& symbol : file.symbols) {
+ if (callback(symbol, file.type)) {
return;
}
}
@@ -482,10 +394,7 @@ TEST(record_cmd, no_dump_symbols) {
}
TEST(record_cmd, dump_kernel_symbols) {
- if (!IsRoot()) {
- GTEST_LOG_(INFO) << "Test requires root privilege";
- return;
- }
+ TEST_REQUIRE_ROOT();
TemporaryFile tmpfile;
ASSERT_TRUE(RecordCmd()->Run({"-a", "-o", tmpfile.path, "-e", GetDefaultEvent(), "sleep", "1"}));
bool has_kernel_symbols = false;
@@ -501,9 +410,9 @@ TEST(record_cmd, dump_kernel_symbols) {
TEST(record_cmd, group_option) {
ASSERT_TRUE(RunRecordCmd({"--group", "task-clock,cpu-clock", "-m", "16"}));
- ASSERT_TRUE(RunRecordCmd({"--group", "task-clock,cpu-clock", "--group",
- "task-clock:u,cpu-clock:u", "--group",
- "task-clock:k,cpu-clock:k", "-m", "16"}));
+ ASSERT_TRUE(
+ RunRecordCmd({"--group", "task-clock,cpu-clock", "--group", "task-clock:u,cpu-clock:u",
+ "--group", "task-clock:k,cpu-clock:k", "-m", "16"}));
}
TEST(record_cmd, symfs_option) {
@@ -521,8 +430,7 @@ TEST(record_cmd, duration_option) {
TEST(record_cmd, support_modifier_for_clock_events) {
for (const std::string& e : {"cpu-clock", "task-clock"}) {
for (const std::string& m : {"u", "k"}) {
- ASSERT_TRUE(RunRecordCmd({"-e", e + ":" + m})) << "event " << e << ":"
- << m;
+ ASSERT_TRUE(RunRecordCmd({"-e", e + ":" + m})) << "event " << e << ":" << m;
}
}
}
@@ -555,7 +463,8 @@ TEST(record_cmd, stop_when_no_more_targets) {
sleep(1);
});
thread.detach();
- while (tid == 0);
+ while (tid == 0)
+ ;
ASSERT_TRUE(RecordCmd()->Run(
{"-o", tmpfile.path, "-t", std::to_string(tid), "--in-app", "-e", GetDefaultEvent()}));
}
@@ -610,8 +519,8 @@ TEST(record_cmd, cpu_clock_for_a_long_time) {
CreateProcesses(1, &workloads);
std::string pid = std::to_string(workloads[0]->GetPid());
TemporaryFile tmpfile;
- ASSERT_TRUE(RecordCmd()->Run(
- {"-e", "cpu-clock", "-o", tmpfile.path, "-p", pid, "--duration", "3"}));
+ ASSERT_TRUE(
+ RecordCmd()->Run({"-e", "cpu-clock", "-o", tmpfile.path, "-p", pid, "--duration", "3"}));
}
TEST(record_cmd, dump_regs_for_tracepoint_events) {
@@ -631,11 +540,21 @@ TEST(record_cmd, trace_offcpu_option) {
OMIT_TEST_ON_NON_NATIVE_ABIS();
TemporaryFile tmpfile;
ASSERT_TRUE(RunRecordCmd({"--trace-offcpu", "-f", "1000"}, tmpfile.path));
+ CheckEventType(tmpfile.path, "sched:sched_switch", 1u, 0u);
std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
ASSERT_TRUE(reader);
auto info_map = reader->GetMetaInfoFeature();
ASSERT_EQ(info_map["trace_offcpu"], "true");
- CheckEventType(tmpfile.path, "sched:sched_switch", 1u, 0u);
+ // Release recording environment in perf.data, to avoid affecting tests below.
+ reader.reset();
+
+ // --trace-offcpu only works with cpu-clock, task-clock and cpu-cycles. cpu-cycles has been
+ // tested above.
+ ASSERT_TRUE(RunRecordCmd({"--trace-offcpu", "-e", "cpu-clock"}));
+ ASSERT_TRUE(RunRecordCmd({"--trace-offcpu", "-e", "task-clock"}));
+ ASSERT_FALSE(RunRecordCmd({"--trace-offcpu", "-e", "page-faults"}));
+ // --trace-offcpu doesn't work with more than one event.
+ ASSERT_FALSE(RunRecordCmd({"--trace-offcpu", "-e", "cpu-clock,task-clock"}));
}
TEST(record_cmd, exit_with_parent_option) {
@@ -773,9 +692,7 @@ class RecordingAppHelper {
return app_helper_.InstallApk(apk_path, package_name);
}
- bool StartApp(const std::string& start_cmd) {
- return app_helper_.StartApp(start_cmd);
- }
+ bool StartApp(const std::string& start_cmd) { return app_helper_.StartApp(start_cmd); }
bool RecordData(const std::string& record_cmd) {
std::vector<std::string> args = android::base::Split(record_cmd, " ");
@@ -796,17 +713,19 @@ class RecordingAppHelper {
return success;
}
+ std::string GetDataPath() const { return perf_data_file_.path; }
+
private:
AppHelper app_helper_;
TemporaryFile perf_data_file_;
};
-static void TestRecordingApps(const std::string& app_name) {
+static void TestRecordingApps(const std::string& app_name, const std::string& app_type) {
RecordingAppHelper helper;
// Bring the app to foreground to avoid no samples.
ASSERT_TRUE(helper.StartApp("am start " + app_name + "/.MainActivity"));
- ASSERT_TRUE(helper.RecordData("--app " + app_name + " -g --duration 3 -e " + GetDefaultEvent()));
+ ASSERT_TRUE(helper.RecordData("--app " + app_name + " -g --duration 10 -e " + GetDefaultEvent()));
// Check if we can profile Java code by looking for a Java method name in dumped symbols, which
// is app_name + ".MainActivity$1.run".
@@ -814,28 +733,38 @@ static void TestRecordingApps(const std::string& app_name) {
const std::string expected_method_name = "run";
auto process_symbol = [&](const char* name) {
return strstr(name, expected_class_name.c_str()) != nullptr &&
- strstr(name, expected_method_name.c_str()) != nullptr;
+ strstr(name, expected_method_name.c_str()) != nullptr;
};
ASSERT_TRUE(helper.CheckData(process_symbol));
+
+ // Check app_package_name and app_type.
+ auto reader = RecordFileReader::CreateInstance(helper.GetDataPath());
+ ASSERT_TRUE(reader);
+ const std::unordered_map<std::string, std::string>& meta_info = reader->GetMetaInfoFeature();
+ auto it = meta_info.find("app_package_name");
+ ASSERT_NE(it, meta_info.end());
+ ASSERT_EQ(it->second, app_name);
+ it = meta_info.find("app_type");
+ ASSERT_NE(it, meta_info.end());
+ ASSERT_EQ(it->second, app_type);
}
TEST(record_cmd, app_option_for_debuggable_app) {
TEST_REQUIRE_APPS();
SetRunInAppToolForTesting(true, false);
- TestRecordingApps("com.android.simpleperf.debuggable");
+ TestRecordingApps("com.android.simpleperf.debuggable", "debuggable");
SetRunInAppToolForTesting(false, true);
- TestRecordingApps("com.android.simpleperf.debuggable");
+ TestRecordingApps("com.android.simpleperf.debuggable", "debuggable");
}
TEST(record_cmd, app_option_for_profileable_app) {
TEST_REQUIRE_APPS();
SetRunInAppToolForTesting(false, true);
- TestRecordingApps("com.android.simpleperf.profileable");
+ TestRecordingApps("com.android.simpleperf.profileable", "profileable");
}
-TEST(record_cmd, record_java_app) {
#if defined(__ANDROID__)
- RecordingAppHelper helper;
+static void RecordJavaApp(RecordingAppHelper& helper) {
// 1. Install apk.
ASSERT_TRUE(helper.InstallApk(GetTestData("DisplayBitmaps.apk"),
"com.example.android.displayingbitmaps"));
@@ -852,9 +781,20 @@ TEST(record_cmd, record_java_app) {
// 3. Record perf.data.
SetRunInAppToolForTesting(true, true);
ASSERT_TRUE(helper.RecordData(
- "-e cpu-clock --app com.example.android.displayingbitmaps -g --duration 10"));
+ "-e cpu-clock --app com.example.android.displayingbitmaps -g --duration 15"));
+}
+#endif // defined(__ANDROID__)
- // 4. Check perf.data.
+TEST(record_cmd, record_java_app) {
+#if defined(__ANDROID__)
+ RecordingAppHelper helper;
+
+ RecordJavaApp(helper);
+ if (HasFailure()) {
+ return;
+ }
+
+ // Check perf.data by looking for java symbols.
auto process_symbol = [&](const char* name) {
#if !defined(IN_CTS_TEST)
const char* expected_name_with_keyguard = "androidx.test.runner"; // when screen is locked
@@ -906,6 +846,55 @@ TEST(record_cmd, record_native_app) {
#endif
}
+TEST(record_cmd, check_trampoline_after_art_jni_methods) {
+ // Test if art jni methods are called by art_jni_trampoline.
+#if defined(__ANDROID__)
+ RecordingAppHelper helper;
+
+ RecordJavaApp(helper);
+ if (HasFailure()) {
+ return;
+ }
+
+ // Check if art::Method_invoke() is called by art_jni_trampoline.
+ auto reader = RecordFileReader::CreateInstance(helper.GetDataPath());
+ ASSERT_TRUE(reader);
+ ThreadTree thread_tree;
+
+ auto get_symbol_name = [&](ThreadEntry* thread, uint64_t ip) -> std::string {
+ const MapEntry* map = thread_tree.FindMap(thread, ip, false);
+ const Symbol* symbol = thread_tree.FindSymbol(map, ip, nullptr, nullptr);
+ return symbol->DemangledName();
+ };
+
+ bool has_check = false;
+
+ auto process_record = [&](std::unique_ptr<Record> r) {
+ thread_tree.Update(*r);
+ if (r->type() == PERF_RECORD_SAMPLE) {
+ auto sample = static_cast<SampleRecord*>(r.get());
+ ThreadEntry* thread = thread_tree.FindThreadOrNew(sample->tid_data.pid, sample->tid_data.tid);
+ size_t kernel_ip_count;
+ std::vector<uint64_t> ips = sample->GetCallChain(&kernel_ip_count);
+ for (size_t i = kernel_ip_count; i < ips.size(); i++) {
+ std::string sym_name = get_symbol_name(thread, ips[i]);
+ if (android::base::StartsWith(sym_name, "art::Method_invoke") && i + 1 < ips.size()) {
+ has_check = true;
+ if (get_symbol_name(thread, ips[i + 1]) != "art_jni_trampoline") {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ };
+ ASSERT_TRUE(reader->ReadDataSection(process_record));
+ ASSERT_TRUE(has_check);
+#else
+ GTEST_LOG_(INFO) << "This test tests a function only available on Android.";
+#endif
+}
+
TEST(record_cmd, no_cut_samples_option) {
ASSERT_TRUE(RunRecordCmd({"--no-cut-samples"}));
}
@@ -944,6 +933,15 @@ TEST(record_cmd, cs_etm_event) {
ASSERT_TRUE(has_aux);
}
+TEST(record_cmd, cs_etm_system_wide) {
+ TEST_REQUIRE_ROOT();
+ if (!ETMRecorder::GetInstance().CheckEtmSupport()) {
+ GTEST_LOG_(INFO) << "Omit this test since etm isn't supported on this device";
+ return;
+ }
+ ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "-a"}));
+}
+
TEST(record_cmd, aux_buffer_size_option) {
if (!ETMRecorder::GetInstance().CheckEtmSupport()) {
GTEST_LOG_(INFO) << "Omit this test since etm isn't supported on this device";
@@ -956,7 +954,7 @@ TEST(record_cmd, aux_buffer_size_option) {
ASSERT_FALSE(RunRecordCmd({"-e", "cs-etm", "--aux-buffer-size", "12k"}));
}
-TEST(record_cmd, include_filter_option) {
+TEST(record_cmd, addr_filter_option) {
TEST_REQUIRE_HW_COUNTER();
if (!ETMRecorder::GetInstance().CheckEtmSupport()) {
GTEST_LOG_(INFO) << "Omit this test since etm isn't supported on this device";
@@ -969,12 +967,12 @@ TEST(record_cmd, include_filter_option) {
pclose(fp);
path = android::base::Trim(path);
std::string sleep_exec_path;
- ASSERT_TRUE(android::base::Realpath(path, &sleep_exec_path));
- // --include-filter doesn't apply to cpu-cycles.
- ASSERT_FALSE(RunRecordCmd({"--include-filter", sleep_exec_path}));
+ ASSERT_TRUE(Realpath(path, &sleep_exec_path));
+ // --addr-filter doesn't apply to cpu-cycles.
+ ASSERT_FALSE(RunRecordCmd({"--addr-filter", "filter " + sleep_exec_path}));
TemporaryFile record_file;
- ASSERT_TRUE(
- RunRecordCmd({"-e", "cs-etm", "--include-filter", sleep_exec_path}, record_file.path));
+ ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--addr-filter", "filter " + sleep_exec_path},
+ record_file.path));
TemporaryFile inject_file;
ASSERT_TRUE(
CreateCommandInstance("inject")->Run({"-i", record_file.path, "-o", inject_file.path}));
@@ -987,6 +985,33 @@ TEST(record_cmd, include_filter_option) {
ASSERT_EQ(dso, sleep_exec_path);
}
}
+
+ // Test if different filter types are accepted by the kernel.
+ auto elf = ElfFile::Open(sleep_exec_path);
+ uint64_t off;
+ uint64_t addr = elf->ReadMinExecutableVaddr(&off);
+ // file start
+ std::string filter = StringPrintf("start 0x%" PRIx64 "@%s", addr, sleep_exec_path.c_str());
+ ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--addr-filter", filter}));
+ // file stop
+ filter = StringPrintf("stop 0x%" PRIx64 "@%s", addr, sleep_exec_path.c_str());
+ ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--addr-filter", filter}));
+ // file range
+ filter = StringPrintf("filter 0x%" PRIx64 "-0x%" PRIx64 "@%s", addr, addr + 4,
+ sleep_exec_path.c_str());
+ ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--addr-filter", filter}));
+ // If kernel panic, try backporting "perf/core: Fix crash when using HW tracing kernel
+ // filters".
+ // kernel start
+ uint64_t fake_kernel_addr = (1ULL << 63);
+ filter = StringPrintf("start 0x%" PRIx64, fake_kernel_addr);
+ ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--addr-filter", filter}));
+ // kernel stop
+ filter = StringPrintf("stop 0x%" PRIx64, fake_kernel_addr);
+ ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--addr-filter", filter}));
+ // kernel range
+ filter = StringPrintf("filter 0x%" PRIx64 "-0x%" PRIx64, fake_kernel_addr, fake_kernel_addr + 4);
+ ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--addr-filter", filter}));
}
TEST(record_cmd, pmu_event_option) {
@@ -1023,3 +1048,146 @@ TEST(record_cmd, exclude_perf_option) {
}));
}
}
+
+TEST(record_cmd, tp_filter_option) {
+ TEST_REQUIRE_HOST_ROOT();
+ TEST_REQUIRE_TRACEPOINT_EVENTS();
+ // Test string operands both with quotes and without quotes.
+ for (const auto& filter :
+ std::vector<std::string>({"prev_comm != 'sleep'", "prev_comm != sleep"})) {
+ TemporaryFile tmpfile;
+ ASSERT_TRUE(RunRecordCmd({"-e", "sched:sched_switch", "--tp-filter", filter}, tmpfile.path))
+ << filter;
+ CaptureStdout capture;
+ ASSERT_TRUE(capture.Start());
+ ASSERT_TRUE(CreateCommandInstance("dump")->Run({tmpfile.path}));
+ std::string data = capture.Finish();
+ // Check that samples with prev_comm == sleep are filtered out. Although we do the check all the
+ // time, it only makes sense when running as root. Tracepoint event fields are not allowed
+ // to record unless running as root.
+ ASSERT_EQ(data.find("prev_comm: sleep"), std::string::npos) << filter;
+ }
+}
+
+TEST(record_cmd, ParseAddrFilterOption) {
+ auto option_to_str = [](const std::string& option) {
+ auto filters = ParseAddrFilterOption(option);
+ std::string s;
+ for (auto& filter : filters) {
+ if (!s.empty()) {
+ s += ',';
+ }
+ s += filter.ToString();
+ }
+ return s;
+ };
+ std::string path;
+ ASSERT_TRUE(Realpath(GetTestData(ELF_FILE), &path));
+
+ // Test file filters.
+ ASSERT_EQ(option_to_str("filter " + path), "filter 0x0/0x73c@" + path);
+ ASSERT_EQ(option_to_str("filter 0x400502-0x400527@" + path), "filter 0x502/0x25@" + path);
+ ASSERT_EQ(option_to_str("start 0x400502@" + path + ",stop 0x400527@" + path),
+ "start 0x502@" + path + ",stop 0x527@" + path);
+
+ // Test '-' in file path. Create a temporary file with '-' in name.
+ TemporaryDir tmpdir;
+ fs::path tmpfile = fs::path(tmpdir.path) / "elf-with-hyphen";
+ ASSERT_TRUE(fs::copy_file(path, tmpfile));
+ ASSERT_EQ(option_to_str("filter " + tmpfile.string()), "filter 0x0/0x73c@" + tmpfile.string());
+
+ // Test kernel filters.
+ ASSERT_EQ(option_to_str("filter 0x12345678-0x1234567a"), "filter 0x12345678/0x2");
+ ASSERT_EQ(option_to_str("start 0x12345678,stop 0x1234567a"), "start 0x12345678,stop 0x1234567a");
+}
+
+TEST(record_cmd, kprobe_option) {
+ TEST_REQUIRE_ROOT();
+ ProbeEvents probe_events;
+ 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"}));
+ // 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"}));
+}
+
+TEST(record_cmd, record_filter_options) {
+ ASSERT_TRUE(
+ RunRecordCmd({"--exclude-pid", "1,2", "--exclude-tid", "3,4", "--exclude-process-name",
+ "processA", "--exclude-thread-name", "threadA", "--exclude-uid", "5,6"}));
+ ASSERT_TRUE(
+ RunRecordCmd({"--include-pid", "1,2", "--include-tid", "3,4", "--include-process-name",
+ "processB", "--include-thread-name", "threadB", "--include-uid", "5,6"}));
+}
+
+TEST(record_cmd, keep_failed_unwinding_result_option) {
+ OMIT_TEST_ON_NON_NATIVE_ABIS();
+ std::vector<std::unique_ptr<Workload>> workloads;
+ CreateProcesses(1, &workloads);
+ std::string pid = std::to_string(workloads[0]->GetPid());
+ ASSERT_TRUE(RunRecordCmd(
+ {"-p", pid, "-g", "--keep-failed-unwinding-result", "--keep-failed-unwinding-debug-info"}));
+}
+
+TEST(record_cmd, kernel_address_warning) {
+ TEST_REQUIRE_NON_ROOT();
+ const std::string warning_msg = "Access to kernel symbol addresses is restricted.";
+ CapturedStderr capture;
+
+ // When excluding kernel samples, no kernel address warning is printed.
+ ResetKernelAddressWarning();
+ TemporaryFile tmpfile;
+ ASSERT_TRUE(RunRecordCmd({"-e", "cpu-clock:u"}, tmpfile.path));
+ capture.Stop();
+ ASSERT_EQ(capture.str().find(warning_msg), std::string::npos);
+
+ // When not excluding kernel samples, kernel address warning is printed once.
+ capture.Reset();
+ capture.Start();
+ ResetKernelAddressWarning();
+ ASSERT_TRUE(RunRecordCmd({"-e", "cpu-clock"}, tmpfile.path));
+ capture.Stop();
+ std::string output = capture.str();
+ auto pos = output.find(warning_msg);
+ ASSERT_NE(pos, std::string::npos);
+ ASSERT_EQ(output.find(warning_msg, pos + warning_msg.size()), std::string::npos);
+}
+
+TEST(record_cmd, add_meta_info_option) {
+ TemporaryFile tmpfile;
+ ASSERT_TRUE(RunRecordCmd({"--add-meta-info", "key1=value1", "--add-meta-info", "key2=value2"},
+ tmpfile.path));
+ auto reader = RecordFileReader::CreateInstance(tmpfile.path);
+ ASSERT_TRUE(reader);
+
+ const std::unordered_map<std::string, std::string>& meta_info = reader->GetMetaInfoFeature();
+ auto it = meta_info.find("key1");
+ ASSERT_NE(it, meta_info.end());
+ ASSERT_EQ(it->second, "value1");
+ it = meta_info.find("key2");
+ ASSERT_NE(it, meta_info.end());
+ ASSERT_EQ(it->second, "value2");
+
+ // Report error for invalid meta info.
+ ASSERT_FALSE(RunRecordCmd({"--add-meta-info", "key1"}, tmpfile.path));
+ ASSERT_FALSE(RunRecordCmd({"--add-meta-info", "key1="}, tmpfile.path));
+ ASSERT_FALSE(RunRecordCmd({"--add-meta-info", "=value1"}, tmpfile.path));
+}
+
+TEST(record_cmd, device_meta_info) {
+ TemporaryFile tmpfile;
+ ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
+ auto reader = RecordFileReader::CreateInstance(tmpfile.path);
+ ASSERT_TRUE(reader);
+
+ const std::unordered_map<std::string, std::string>& meta_info = reader->GetMetaInfoFeature();
+ auto it = meta_info.find("android_sdk_version");
+ ASSERT_NE(it, meta_info.end());
+ ASSERT_FALSE(it->second.empty());
+ it = meta_info.find("android_build_type");
+ ASSERT_NE(it, meta_info.end());
+ ASSERT_FALSE(it->second.empty());
+}
diff --git a/simpleperf/cmd_report.cpp b/simpleperf/cmd_report.cpp
index 8e67f5e0..399e4d82 100644
--- a/simpleperf/cmd_report.cpp
+++ b/simpleperf/cmd_report.cpp
@@ -41,10 +41,16 @@
#include "tracing.h"
#include "utils.h"
+namespace simpleperf {
namespace {
+using android::base::Split;
+
static std::set<std::string> branch_sort_keys = {
- "dso_from", "dso_to", "symbol_from", "symbol_to",
+ "dso_from",
+ "dso_to",
+ "symbol_from",
+ "symbol_to",
};
struct BranchFromEntry {
const MapEntry* map;
@@ -52,8 +58,7 @@ struct BranchFromEntry {
uint64_t vaddr_in_file;
uint64_t flags;
- BranchFromEntry()
- : map(nullptr), symbol(nullptr), vaddr_in_file(0), flags(0) {}
+ BranchFromEntry() : map(nullptr), symbol(nullptr), vaddr_in_file(0), flags(0) {}
};
struct SampleEntry {
@@ -62,6 +67,7 @@ struct SampleEntry {
// accumuated when appearing in other sample's callchain
uint64_t accumulated_period;
uint64_t sample_count;
+ int cpu;
pid_t pid;
pid_t tid;
const char* thread_comm;
@@ -72,13 +78,14 @@ struct SampleEntry {
// a callchain tree representing all callchains in the sample
CallChainRoot<SampleEntry> callchain;
- SampleEntry(uint64_t time, uint64_t period, uint64_t accumulated_period,
- uint64_t sample_count, const ThreadEntry* thread,
- const MapEntry* map, const Symbol* symbol, uint64_t vaddr_in_file)
+ SampleEntry(uint64_t time, uint64_t period, uint64_t accumulated_period, uint64_t sample_count,
+ int cpu, const ThreadEntry* thread, const MapEntry* map, const Symbol* symbol,
+ uint64_t vaddr_in_file)
: time(time),
period(period),
accumulated_period(accumulated_period),
sample_count(sample_count),
+ cpu(cpu),
pid(thread->pid),
tid(thread->tid),
thread_comm(thread->comm),
@@ -90,9 +97,7 @@ struct SampleEntry {
SampleEntry(SampleEntry&&) = default;
SampleEntry(SampleEntry&) = delete;
- uint64_t GetPeriod() const {
- return period;
- }
+ uint64_t GetPeriod() const { return period; }
};
struct SampleTree {
@@ -100,11 +105,16 @@ struct SampleTree {
uint64_t total_samples;
uint64_t total_period;
uint64_t total_error_callchains;
+ std::string event_name;
};
BUILD_COMPARE_VALUE_FUNCTION(CompareVaddrInFile, vaddr_in_file);
BUILD_DISPLAY_HEX64_FUNCTION(DisplayVaddrInFile, vaddr_in_file);
+static std::string DisplayEventName(const SampleEntry*, const SampleTree* info) {
+ return info->event_name;
+}
+
class ReportCmdSampleTreeBuilder : public SampleTreeBuilder<SampleEntry, uint64_t> {
public:
ReportCmdSampleTreeBuilder(const SampleComparator<SampleEntry>& sample_comparator,
@@ -115,11 +125,13 @@ class ReportCmdSampleTreeBuilder : public SampleTreeBuilder<SampleEntry, uint64_
total_period_(0),
total_error_callchains_(0) {}
- void SetFilters(const std::unordered_set<int>& pid_filter,
+ void SetFilters(const std::unordered_set<int>& cpu_filter,
+ const std::unordered_set<int>& pid_filter,
const std::unordered_set<int>& tid_filter,
const std::unordered_set<std::string>& comm_filter,
const std::unordered_set<std::string>& dso_filter,
const std::unordered_set<std::string>& symbol_filter) {
+ cpu_filter_ = cpu_filter;
pid_filter_ = pid_filter;
tid_filter_ = tid_filter;
comm_filter_ = comm_filter;
@@ -127,6 +139,8 @@ class ReportCmdSampleTreeBuilder : public SampleTreeBuilder<SampleEntry, uint64_
symbol_filter_ = symbol_filter;
}
+ void SetEventName(const std::string& event_name) { event_name_ = event_name; }
+
SampleTree GetSampleTree() {
AddCallChainDuplicateInfo();
SampleTree sample_tree;
@@ -134,6 +148,7 @@ class ReportCmdSampleTreeBuilder : public SampleTreeBuilder<SampleEntry, uint64_
sample_tree.total_samples = total_samples_;
sample_tree.total_period = total_period_;
sample_tree.total_error_callchains = total_error_callchains_;
+ sample_tree.event_name = event_name_;
return sample_tree;
}
@@ -148,36 +163,28 @@ class ReportCmdSampleTreeBuilder : public SampleTreeBuilder<SampleEntry, uint64_
protected:
virtual uint64_t GetPeriod(const SampleRecord& r) = 0;
- SampleEntry* CreateSample(const SampleRecord& r, bool in_kernel,
- uint64_t* acc_info) override {
- const ThreadEntry* thread =
- thread_tree_->FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
- const MapEntry* map =
- thread_tree_->FindMap(thread, r.ip_data.ip, in_kernel);
+ SampleEntry* CreateSample(const SampleRecord& r, bool in_kernel, uint64_t* acc_info) override {
+ const ThreadEntry* thread = thread_tree_->FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
+ const MapEntry* map = thread_tree_->FindMap(thread, r.ip_data.ip, in_kernel);
uint64_t vaddr_in_file;
- const Symbol* symbol =
- thread_tree_->FindSymbol(map, r.ip_data.ip, &vaddr_in_file);
+ const Symbol* symbol = thread_tree_->FindSymbol(map, r.ip_data.ip, &vaddr_in_file);
uint64_t period = GetPeriod(r);
*acc_info = period;
- return InsertSample(std::unique_ptr<SampleEntry>(
- new SampleEntry(r.time_data.time, period, 0, 1, thread, map, symbol, vaddr_in_file)));
+ return InsertSample(std::make_unique<SampleEntry>(r.time_data.time, period, 0, 1, r.Cpu(),
+ thread, map, symbol, vaddr_in_file));
}
- SampleEntry* CreateBranchSample(const SampleRecord& r,
- const BranchStackItemType& item) override {
- const ThreadEntry* thread =
- thread_tree_->FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
+ SampleEntry* CreateBranchSample(const SampleRecord& r, const BranchStackItemType& item) override {
+ const ThreadEntry* thread = thread_tree_->FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
const MapEntry* from_map = thread_tree_->FindMap(thread, item.from);
uint64_t from_vaddr_in_file;
- const Symbol* from_symbol =
- thread_tree_->FindSymbol(from_map, item.from, &from_vaddr_in_file);
+ const Symbol* from_symbol = thread_tree_->FindSymbol(from_map, item.from, &from_vaddr_in_file);
const MapEntry* to_map = thread_tree_->FindMap(thread, item.to);
uint64_t to_vaddr_in_file;
- const Symbol* to_symbol =
- thread_tree_->FindSymbol(to_map, item.to, &to_vaddr_in_file);
- std::unique_ptr<SampleEntry> sample(
- new SampleEntry(r.time_data.time, r.period_data.period, 0, 1, thread,
- to_map, to_symbol, to_vaddr_in_file));
+ const Symbol* to_symbol = thread_tree_->FindSymbol(to_map, item.to, &to_vaddr_in_file);
+ auto sample =
+ std::make_unique<SampleEntry>(r.time_data.time, r.period_data.period, 0, 1, r.Cpu(), thread,
+ to_map, to_symbol, to_vaddr_in_file);
sample->branch_from.map = from_map;
sample->branch_from.symbol = from_symbol;
sample->branch_from.vaddr_in_file = from_vaddr_in_file;
@@ -197,8 +204,8 @@ class ReportCmdSampleTreeBuilder : public SampleTreeBuilder<SampleEntry, uint64_
}
uint64_t vaddr_in_file;
const Symbol* symbol = thread_tree_->FindSymbol(map, ip, &vaddr_in_file);
- std::unique_ptr<SampleEntry> callchain_sample(new SampleEntry(
- sample->time, 0, acc_info, 0, thread, map, symbol, vaddr_in_file));
+ auto callchain_sample = std::make_unique<SampleEntry>(sample->time, 0, acc_info, 0, sample->cpu,
+ thread, map, symbol, vaddr_in_file);
callchain_sample->thread_comm = sample->thread_comm;
return InsertCallChainSample(std::move(callchain_sample), callchain);
}
@@ -207,30 +214,25 @@ class ReportCmdSampleTreeBuilder : public SampleTreeBuilder<SampleEntry, uint64_
return thread_tree_->FindThreadOrNew(sample->pid, sample->tid);
}
- uint64_t GetPeriodForCallChain(const uint64_t& acc_info) override {
- return acc_info;
- }
+ uint64_t GetPeriodForCallChain(const uint64_t& acc_info) override { return acc_info; }
bool FilterSample(const SampleEntry* sample) override {
- if (!pid_filter_.empty() &&
- pid_filter_.find(sample->pid) == pid_filter_.end()) {
+ if (!cpu_filter_.empty() && cpu_filter_.count(sample->cpu) == 0) {
return false;
}
- if (!tid_filter_.empty() &&
- tid_filter_.find(sample->tid) == tid_filter_.end()) {
+ if (!pid_filter_.empty() && pid_filter_.count(sample->pid) == 0) {
return false;
}
- if (!comm_filter_.empty() &&
- comm_filter_.find(sample->thread_comm) == comm_filter_.end()) {
+ if (!tid_filter_.empty() && tid_filter_.count(sample->tid) == 0) {
return false;
}
- if (!dso_filter_.empty() &&
- dso_filter_.find(sample->map->dso->Path()) == dso_filter_.end()) {
+ if (!comm_filter_.empty() && comm_filter_.count(sample->thread_comm) == 0) {
return false;
}
- if (!symbol_filter_.empty() &&
- symbol_filter_.find(sample->symbol->DemangledName()) ==
- symbol_filter_.end()) {
+ if (!dso_filter_.empty() && dso_filter_.count(sample->map->dso->GetReportPath().data()) == 0) {
+ return false;
+ }
+ if (!symbol_filter_.empty() && symbol_filter_.count(sample->symbol->DemangledName()) == 0) {
return false;
}
return true;
@@ -250,6 +252,7 @@ class ReportCmdSampleTreeBuilder : public SampleTreeBuilder<SampleEntry, uint64_
private:
ThreadTree* thread_tree_;
+ std::unordered_set<int> cpu_filter_;
std::unordered_set<int> pid_filter_;
std::unordered_set<int> tid_filter_;
std::unordered_set<std::string> comm_filter_;
@@ -259,6 +262,8 @@ class ReportCmdSampleTreeBuilder : public SampleTreeBuilder<SampleEntry, uint64_
uint64_t total_samples_;
uint64_t total_period_;
uint64_t total_error_callchains_;
+
+ std::string event_name_;
};
// Build sample tree based on event count in each sample.
@@ -266,12 +271,10 @@ class EventCountSampleTreeBuilder : public ReportCmdSampleTreeBuilder {
public:
EventCountSampleTreeBuilder(const SampleComparator<SampleEntry>& sample_comparator,
ThreadTree* thread_tree)
- : ReportCmdSampleTreeBuilder(sample_comparator, thread_tree) { }
+ : ReportCmdSampleTreeBuilder(sample_comparator, thread_tree) {}
protected:
- uint64_t GetPeriod(const SampleRecord& r) override {
- return r.period_data.period;
- }
+ uint64_t GetPeriod(const SampleRecord& r) override { return r.period_data.period; }
};
// Build sample tree based on the time difference between current sample and next sample.
@@ -279,7 +282,7 @@ class TimestampSampleTreeBuilder : public ReportCmdSampleTreeBuilder {
public:
TimestampSampleTreeBuilder(const SampleComparator<SampleEntry>& sample_comparator,
ThreadTree* thread_tree)
- : ReportCmdSampleTreeBuilder(sample_comparator, thread_tree) { }
+ : ReportCmdSampleTreeBuilder(sample_comparator, thread_tree) {}
void ReportCmdProcessSampleRecord(std::shared_ptr<SampleRecord>& r) override {
pid_t tid = static_cast<pid_t>(r->tid_data.tid);
@@ -314,6 +317,7 @@ struct SampleTreeBuilderOptions {
std::unordered_set<std::string> comm_filter;
std::unordered_set<std::string> dso_filter;
std::unordered_set<std::string> symbol_filter;
+ std::unordered_set<int> cpu_filter;
std::unordered_set<int> pid_filter;
std::unordered_set<int> tid_filter;
bool use_branch_address;
@@ -329,7 +333,7 @@ struct SampleTreeBuilderOptions {
} else {
builder.reset(new EventCountSampleTreeBuilder(comparator, thread_tree));
}
- builder->SetFilters(pid_filter, tid_filter, comm_filter, dso_filter, symbol_filter);
+ builder->SetFilters(cpu_filter, pid_filter, tid_filter, comm_filter, dso_filter, symbol_filter);
builder->SetBranchSampleOption(use_branch_address);
builder->SetCallChainSampleOptions(accumulate_callchain, build_callchain,
use_caller_as_callchain_root);
@@ -338,18 +342,14 @@ struct SampleTreeBuilderOptions {
};
using ReportCmdSampleTreeSorter = SampleTreeSorter<SampleEntry>;
-using ReportCmdSampleTreeDisplayer =
- SampleTreeDisplayer<SampleEntry, SampleTree>;
+using ReportCmdSampleTreeDisplayer = SampleTreeDisplayer<SampleEntry, SampleTree>;
-using ReportCmdCallgraphDisplayer =
- CallgraphDisplayer<SampleEntry, CallChainNode<SampleEntry>>;
+using ReportCmdCallgraphDisplayer = CallgraphDisplayer<SampleEntry, CallChainNode<SampleEntry>>;
-class ReportCmdCallgraphDisplayerWithVaddrInFile
- : public ReportCmdCallgraphDisplayer {
+class ReportCmdCallgraphDisplayerWithVaddrInFile : public ReportCmdCallgraphDisplayer {
protected:
std::string PrintSampleName(const SampleEntry* sample) override {
- return android::base::StringPrintf("%s [+0x%" PRIx64 "]",
- sample->symbol->DemangledName(),
+ return android::base::StringPrintf("%s [+0x%" PRIx64 "]", sample->symbol->DemangledName(),
sample->vaddr_in_file);
}
};
@@ -362,9 +362,8 @@ struct EventAttrWithName {
class ReportCommand : public Command {
public:
ReportCommand()
- : Command(
- "report", "report sampling information in perf.data",
- // clang-format off
+ : Command("report", "report sampling information in perf.data",
+ // clang-format off
"Usage: simpleperf report [options]\n"
"The default options are: -i perf.data --sort comm,pid,tid,dso,symbol.\n"
"-b Use the branch-to addresses in sampled take branches instead of the\n"
@@ -372,6 +371,10 @@ class ReportCommand : public Command {
" option.\n"
"--children Print the overhead accumulated by appearing in the callchain.\n"
"--comms comm1,comm2,... Report only for selected comms.\n"
+"--cpu cpu_item1,cpu_item2,...\n"
+" Report samples on the selected cpus. cpu_item can be cpu\n"
+" number like 1, or cpu range like 0-3.\n"
+"--csv Report in csv format.\n"
"--dsos dso1,dso2,... Report only for selected dsos.\n"
"--full-callgraph Print full call graph. Used with -g option. By default,\n"
" brief call graph is printed.\n"
@@ -412,8 +415,8 @@ class ReportCommand : public Command {
"--symfs <dir> Look for files with symbols relative to this directory.\n"
"--tids tid1,tid2,... Report only for selected tids.\n"
"--vmlinux <file> Parse kernel symbols from <file>.\n"
- // clang-format on
- ),
+ // clang-format on
+ ),
record_filename_("perf.data"),
record_file_arch_(GetBuildArch()),
use_branch_address_(false),
@@ -432,6 +435,8 @@ class ReportCommand : public Command {
private:
bool ParseOptions(const std::vector<std::string>& args);
+ bool BuildSampleComparatorAndDisplayer(bool print_sample_count,
+ const std::vector<std::string>& sort_keys);
void ReadMetaInfoFromRecordFile();
bool ReadEventAttrFromRecordFile();
bool ReadFeaturesFromRecordFile();
@@ -466,6 +471,7 @@ class ReportCommand : public Command {
bool brief_callgraph_;
bool trace_offcpu_;
size_t sched_switch_attr_id_;
+ bool report_csv_ = false;
std::string report_filename_;
};
@@ -503,136 +509,140 @@ bool ReportCommand::Run(const std::vector<std::string>& args) {
}
bool ReportCommand::ParseOptions(const std::vector<std::string>& args) {
- bool demangle = true;
- bool show_ip_for_unknown_symbol = true;
- std::string vmlinux;
- bool print_sample_count = false;
- std::vector<std::string> sort_keys = {"comm", "pid", "tid", "dso", "symbol"};
+ static OptionFormatMap option_formats = {
+ {"-b", {OptionValueType::NONE, OptionType::SINGLE}},
+ {"--children", {OptionValueType::NONE, OptionType::SINGLE}},
+ {"--comms", {OptionValueType::STRING, OptionType::MULTIPLE}},
+ {"--cpu", {OptionValueType::STRING, OptionType::MULTIPLE}},
+ {"--csv", {OptionValueType::NONE, OptionType::SINGLE}},
+ {"--dsos", {OptionValueType::STRING, OptionType::MULTIPLE}},
+ {"--full-callgraph", {OptionValueType::NONE, OptionType::SINGLE}},
+ {"-g", {OptionValueType::OPT_STRING, OptionType::SINGLE}},
+ {"-i", {OptionValueType::STRING, OptionType::SINGLE}},
+ {"--kallsyms", {OptionValueType::STRING, OptionType::SINGLE}},
+ {"--max-stack", {OptionValueType::UINT, OptionType::SINGLE}},
+ {"-n", {OptionValueType::NONE, OptionType::SINGLE}},
+ {"--no-demangle", {OptionValueType::NONE, OptionType::SINGLE}},
+ {"--no-show-ip", {OptionValueType::NONE, OptionType::SINGLE}},
+ {"-o", {OptionValueType::STRING, OptionType::SINGLE}},
+ {"--percent-limit", {OptionValueType::DOUBLE, OptionType::SINGLE}},
+ {"--pids", {OptionValueType::STRING, OptionType::MULTIPLE}},
+ {"--tids", {OptionValueType::STRING, OptionType::MULTIPLE}},
+ {"--raw-period", {OptionValueType::NONE, OptionType::SINGLE}},
+ {"--sort", {OptionValueType::STRING, OptionType::SINGLE}},
+ {"--symbols", {OptionValueType::STRING, OptionType::MULTIPLE}},
+ {"--symfs", {OptionValueType::STRING, OptionType::SINGLE}},
+ {"--vmlinux", {OptionValueType::STRING, OptionType::SINGLE}},
+ };
+
+ OptionValueMap options;
+ std::vector<std::pair<OptionName, OptionValue>> ordered_options;
+ if (!PreprocessOptions(args, option_formats, &options, &ordered_options, nullptr)) {
+ return false;
+ }
- for (size_t i = 0; i < args.size(); ++i) {
- if (args[i] == "-b") {
- use_branch_address_ = true;
- } else if (args[i] == "--children") {
- accumulate_callchain_ = true;
- } else if (args[i] == "--comms" || args[i] == "--dsos") {
- std::unordered_set<std::string>& filter =
- (args[i] == "--comms" ? sample_tree_builder_options_.comm_filter
- : sample_tree_builder_options_.dso_filter);
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- std::vector<std::string> strs = android::base::Split(args[i], ",");
- filter.insert(strs.begin(), strs.end());
- } else if (args[i] == "--full-callgraph") {
- brief_callgraph_ = false;
- } else if (args[i] == "-g") {
- print_callgraph_ = true;
- accumulate_callchain_ = true;
- if (i + 1 < args.size() && args[i + 1][0] != '-') {
- ++i;
- if (args[i] == "callee") {
- callgraph_show_callee_ = true;
- } else if (args[i] == "caller") {
- callgraph_show_callee_ = false;
- } else {
- LOG(ERROR) << "Unknown argument with -g option: " << args[i];
- return false;
- }
- }
- } else if (args[i] == "-i") {
- if (!NextArgumentOrError(args, &i)) {
+ // Process options.
+ use_branch_address_ = options.PullBoolValue("-b");
+ accumulate_callchain_ = options.PullBoolValue("--children");
+ for (const OptionValue& value : options.PullValues("--comms")) {
+ std::vector<std::string> strs = Split(*value.str_value, ",");
+ sample_tree_builder_options_.comm_filter.insert(strs.begin(), strs.end());
+ }
+ for (const OptionValue& value : options.PullValues("--cpu")) {
+ if (auto cpus = GetCpusFromString(*value.str_value); cpus) {
+ sample_tree_builder_options_.cpu_filter.insert(cpus->begin(), cpus->end());
+ } else {
+ return false;
+ }
+ }
+ report_csv_ = options.PullBoolValue("--csv");
+ for (const OptionValue& value : options.PullValues("--dsos")) {
+ std::vector<std::string> strs = Split(*value.str_value, ",");
+ sample_tree_builder_options_.dso_filter.insert(strs.begin(), strs.end());
+ }
+ brief_callgraph_ = !options.PullBoolValue("--full-callgraph");
+
+ if (auto value = options.PullValue("-g"); value) {
+ print_callgraph_ = true;
+ accumulate_callchain_ = true;
+ if (value->str_value != nullptr) {
+ if (*value->str_value == "callee") {
+ callgraph_show_callee_ = true;
+ } else if (*value->str_value == "caller") {
+ callgraph_show_callee_ = false;
+ } else {
+ LOG(ERROR) << "Unknown argument with -g option: " << *value->str_value;
return false;
}
- record_filename_ = args[i];
+ }
+ }
+ options.PullStringValue("-i", &record_filename_);
+ if (auto value = options.PullValue("--kallsyms"); value) {
+ std::string kallsyms;
+ if (!android::base::ReadFileToString(*value->str_value, &kallsyms)) {
+ LOG(ERROR) << "Can't read kernel symbols from " << *value->str_value;
+ return false;
+ }
+ Dso::SetKallsyms(kallsyms);
+ }
+ if (!options.PullUintValue("--max-stack", &callgraph_max_stack_)) {
+ return false;
+ }
+ bool print_sample_count = options.PullBoolValue("-n");
- } else if (args[i] == "--kallsyms") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- std::string kallsyms;
- if (!android::base::ReadFileToString(args[i], &kallsyms)) {
- LOG(ERROR) << "Can't read kernel symbols from " << args[i];
- return false;
- }
- Dso::SetKallsyms(kallsyms);
- } else if (args[i] == "--max-stack") {
- if (!GetUintOption(args, &i, &callgraph_max_stack_)) {
- return false;
- }
- } else if (args[i] == "-n") {
- print_sample_count = true;
-
- } else if (args[i] == "--no-demangle") {
- demangle = false;
- } else if (args[i] == "--no-show-ip") {
- show_ip_for_unknown_symbol = false;
- } else if (args[i] == "-o") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- report_filename_ = args[i];
- } else if (args[i] == "--percent-limit") {
- if (!GetDoubleOption(args, &i, &callgraph_percent_limit_)) {
- return false;
- }
- } else if (args[i] == "--pids" || args[i] == "--tids") {
- const std::string& option = args[i];
- std::unordered_set<int>& filter =
- (option == "--pids" ? sample_tree_builder_options_.pid_filter
- : sample_tree_builder_options_.tid_filter);
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- std::vector<std::string> strs = android::base::Split(args[i], ",");
- for (const auto& s : strs) {
- int id;
- if (!android::base::ParseInt(s.c_str(), &id, 0)) {
- LOG(ERROR) << "invalid id in " << option << " option: " << s;
- return false;
- }
- filter.insert(id);
- }
- } else if (args[i] == "--raw-period") {
- raw_period_ = true;
- } else if (args[i] == "--sort") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- sort_keys = android::base::Split(args[i], ",");
- } else if (args[i] == "--symbols") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- std::vector<std::string> strs = android::base::Split(args[i], ";");
- sample_tree_builder_options_.symbol_filter.insert(strs.begin(), strs.end());
- } else if (args[i] == "--symfs") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- if (!Dso::SetSymFsDir(args[i])) {
- return false;
- }
- } else if (args[i] == "--vmlinux") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- vmlinux = args[i];
+ Dso::SetDemangle(!options.PullBoolValue("--no-demangle"));
+
+ if (!options.PullBoolValue("--no-show-ip")) {
+ thread_tree_.ShowIpForUnknownSymbol();
+ }
+
+ options.PullStringValue("-o", &report_filename_);
+ if (!options.PullDoubleValue("--percent-limit", &callgraph_percent_limit_, 0)) {
+ return false;
+ }
+
+ for (const OptionValue& value : options.PullValues("--pids")) {
+ if (auto pids = GetTidsFromString(*value.str_value, false); pids) {
+ sample_tree_builder_options_.pid_filter.insert(pids->begin(), pids->end());
} else {
- ReportUnknownOption(args, i);
return false;
}
}
+ for (const OptionValue& value : options.PullValues("--tids")) {
+ if (auto tids = GetTidsFromString(*value.str_value, false); tids) {
+ sample_tree_builder_options_.tid_filter.insert(tids->begin(), tids->end());
+ } else {
+ return false;
+ }
+ }
+ raw_period_ = options.PullBoolValue("--raw-period");
+
+ std::vector<std::string> sort_keys = {"comm", "pid", "tid", "dso", "symbol"};
+ if (auto value = options.PullValue("--sort"); value) {
+ sort_keys = Split(*value->str_value, ",");
+ }
- Dso::SetDemangle(demangle);
- if (!vmlinux.empty()) {
- Dso::SetVmlinux(vmlinux);
+ for (const OptionValue& value : options.PullValues("--symbols")) {
+ std::vector<std::string> symbols = Split(*value.str_value, ";");
+ sample_tree_builder_options_.symbol_filter.insert(symbols.begin(), symbols.end());
}
- if (show_ip_for_unknown_symbol) {
- thread_tree_.ShowIpForUnknownSymbol();
+ if (auto value = options.PullValue("--symfs"); value) {
+ if (!Dso::SetSymFsDir(*value->str_value)) {
+ return false;
+ }
}
+ if (auto value = options.PullValue("--vmlinux"); value) {
+ Dso::SetVmlinux(*value->str_value);
+ }
+ CHECK(options.values.empty());
+ return BuildSampleComparatorAndDisplayer(print_sample_count, sort_keys);
+}
+bool ReportCommand::BuildSampleComparatorAndDisplayer(bool print_sample_count,
+ const std::vector<std::string>& sort_keys) {
SampleDisplayer<SampleEntry, SampleTree> displayer;
+ displayer.SetReportFormat(report_csv_);
SampleComparator<SampleEntry> comparator;
if (accumulate_callchain_) {
@@ -655,8 +665,7 @@ bool ReportCommand::ParseOptions(const std::vector<std::string>& args) {
}
for (auto& key : sort_keys) {
- if (!use_branch_address_ &&
- branch_sort_keys.find(key) != branch_sort_keys.end()) {
+ if (!use_branch_address_ && branch_sort_keys.find(key) != branch_sort_keys.end()) {
LOG(ERROR) << "sort key '" << key << "' can only be used with -b option.";
return false;
}
@@ -695,6 +704,17 @@ bool ReportCommand::ParseOptions(const std::vector<std::string>& args) {
return false;
}
}
+
+ if (report_csv_) {
+ if (accumulate_callchain_) {
+ displayer.AddDisplayFunction("AccEventCount", DisplayAccumulatedPeriod);
+ displayer.AddDisplayFunction("SelfEventCount", DisplaySelfPeriod);
+ } else {
+ displayer.AddDisplayFunction("EventCount", DisplaySelfPeriod);
+ }
+ displayer.AddDisplayFunction("EventName", DisplayEventName);
+ }
+
if (print_callgraph_) {
bool has_symbol_key = false;
bool has_vaddr_in_file_key = false;
@@ -707,8 +727,7 @@ bool ReportCommand::ParseOptions(const std::vector<std::string>& args) {
}
if (has_symbol_key) {
if (has_vaddr_in_file_key) {
- displayer.AddExclusiveDisplayFunction(
- ReportCmdCallgraphDisplayerWithVaddrInFile());
+ displayer.AddExclusiveDisplayFunction(ReportCmdCallgraphDisplayerWithVaddrInFile());
} else {
displayer.AddExclusiveDisplayFunction(ReportCmdCallgraphDisplayer(
callgraph_max_stack_, callgraph_percent_limit_, brief_callgraph_));
@@ -758,8 +777,7 @@ bool ReportCommand::ReadEventAttrFromRecordFile() {
}
}
if (!has_branch_stack) {
- LOG(ERROR) << record_filename_
- << " is not recorded with branch stack sampling option.";
+ LOG(ERROR) << record_filename_ << " is not recorded with branch stack sampling option.";
return false;
}
}
@@ -779,8 +797,7 @@ bool ReportCommand::ReadEventAttrFromRecordFile() {
bool ReportCommand::ReadFeaturesFromRecordFile() {
record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
- std::string arch =
- record_file_reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH);
+ std::string arch = record_file_reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH);
if (!arch.empty()) {
record_file_arch_ = GetArchType(arch);
if (record_file_arch_ == ARCH_UNSUPPORTED) {
@@ -799,9 +816,8 @@ bool ReportCommand::ReadFeaturesFromRecordFile() {
if (s == "-a") {
system_wide_collection_ = true;
break;
- } else if (s == "--call-graph" || s == "--cpu" || s == "-e" ||
- s == "-f" || s == "-F" || s == "-j" || s == "-m" ||
- s == "-o" || s == "-p" || s == "-t") {
+ } else if (s == "--call-graph" || s == "--cpu" || s == "-e" || s == "-f" || s == "-F" ||
+ s == "-j" || s == "-m" || s == "-o" || s == "-p" || s == "-t") {
i++;
} else if (!s.empty() && s[0] != '-') {
break;
@@ -811,8 +827,8 @@ bool ReportCommand::ReadFeaturesFromRecordFile() {
}
if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_TRACING_DATA)) {
std::vector<char> tracing_data;
- if (!record_file_reader_->ReadFeatureSection(
- PerfFileFormat::FEAT_TRACING_DATA, &tracing_data)) {
+ if (!record_file_reader_->ReadFeatureSection(PerfFileFormat::FEAT_TRACING_DATA,
+ &tracing_data)) {
return false;
}
if (!ProcessTracingData(tracing_data)) {
@@ -831,12 +847,15 @@ bool ReportCommand::ReadSampleTreeFromRecordFile() {
for (size_t i = 0; i < event_attrs_.size(); ++i) {
sample_tree_builder_.push_back(sample_tree_builder_options_.CreateSampleTreeBuilder());
+ sample_tree_builder_.back()->SetEventName(event_attrs_[i].name);
+ OfflineUnwinder* unwinder = sample_tree_builder_.back()->GetUnwinder();
+ if (unwinder != nullptr) {
+ unwinder->LoadMetaInfo(record_file_reader_->GetMetaInfoFeature());
+ }
}
if (!record_file_reader_->ReadDataSection(
- [this](std::unique_ptr<Record> record) {
- return ProcessRecord(std::move(record));
- })) {
+ [this](std::unique_ptr<Record> record) { return ProcessRecord(std::move(record)); })) {
return false;
}
for (size_t i = 0; i < sample_tree_builder_.size(); ++i) {
@@ -866,7 +885,6 @@ bool ReportCommand::ProcessRecord(std::unique_ptr<Record> record) {
return true;
}
-
void ReportCommand::ProcessSampleRecordInTraceOffCpuMode(std::unique_ptr<Record> record,
size_t attr_id) {
std::shared_ptr<SampleRecord> r(static_cast<SampleRecord*>(record.release()));
@@ -916,8 +934,8 @@ bool ReportCommand::PrintReport() {
}
EventAttrWithName& attr = event_attrs_[i];
SampleTree& sample_tree = sample_tree_[i];
- fprintf(report_fp, "Event: %s (type %u, config %llu)\n", attr.name.c_str(),
- attr.attr.type, attr.attr.config);
+ fprintf(report_fp, "Event: %s (type %u, config %llu)\n", attr.name.c_str(), attr.attr.type,
+ attr.attr.config);
fprintf(report_fp, "Samples: %" PRIu64 "\n", sample_tree.total_samples);
if (sample_tree.total_error_callchains != 0) {
fprintf(report_fp, "Error Callchains: %" PRIu64 ", %f%%\n",
@@ -946,6 +964,7 @@ void ReportCommand::PrintReportContext(FILE* report_fp) {
} // namespace
void RegisterReportCommand() {
- RegisterCommand("report",
- [] { return std::unique_ptr<Command>(new ReportCommand()); });
+ RegisterCommand("report", [] { return std::unique_ptr<Command>(new ReportCommand()); });
}
+
+} // namespace simpleperf
diff --git a/simpleperf/cmd_report_sample.cpp b/simpleperf/cmd_report_sample.cpp
index e133fb28..b547d3c9 100644
--- a/simpleperf/cmd_report_sample.cpp
+++ b/simpleperf/cmd_report_sample.cpp
@@ -21,22 +21,25 @@
#include <android-base/strings.h>
-#include "system/extras/simpleperf/report_sample.pb.h"
+#include "system/extras/simpleperf/cmd_report_sample.pb.h"
#include <google/protobuf/io/coded_stream.h>
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include "OfflineUnwinder.h"
#include "command.h"
#include "event_attr.h"
#include "event_type.h"
#include "record_file.h"
+#include "report_utils.h"
#include "thread_tree.h"
#include "utils.h"
-namespace proto = simpleperf_report_proto;
-
+namespace simpleperf {
namespace {
+namespace proto = simpleperf_report_proto;
+
static const char PROT_FILE_MAGIC[] = "SIMPLEPERF";
static const uint16_t PROT_FILE_VERSION = 1u;
@@ -56,19 +59,66 @@ class ProtobufFileReader : public google::protobuf::io::CopyingInputStream {
public:
explicit ProtobufFileReader(FILE* in_fp) : in_fp_(in_fp) {}
- int Read(void* buffer, int size) override {
- return fread(buffer, 1, size, in_fp_);
- }
+ int Read(void* buffer, int size) override { return fread(buffer, 1, size, in_fp_); }
private:
FILE* in_fp_;
};
-struct CallEntry {
- Dso* dso;
- const Symbol* symbol;
- uint64_t vaddr_in_file;
-};
+static proto::Sample_CallChainEntry_ExecutionType ToProtoExecutionType(
+ CallChainExecutionType type) {
+ switch (type) {
+ case CallChainExecutionType::NATIVE_METHOD:
+ return proto::Sample_CallChainEntry_ExecutionType_NATIVE_METHOD;
+ case CallChainExecutionType::INTERPRETED_JVM_METHOD:
+ return proto::Sample_CallChainEntry_ExecutionType_INTERPRETED_JVM_METHOD;
+ case CallChainExecutionType::JIT_JVM_METHOD:
+ return proto::Sample_CallChainEntry_ExecutionType_JIT_JVM_METHOD;
+ case CallChainExecutionType::ART_METHOD:
+ return proto::Sample_CallChainEntry_ExecutionType_ART_METHOD;
+ }
+ CHECK(false) << "unexpected execution type";
+ return proto::Sample_CallChainEntry_ExecutionType_NATIVE_METHOD;
+}
+
+static const char* ProtoExecutionTypeToString(proto::Sample_CallChainEntry_ExecutionType type) {
+ switch (type) {
+ case proto::Sample_CallChainEntry_ExecutionType_NATIVE_METHOD:
+ return "native_method";
+ case proto::Sample_CallChainEntry_ExecutionType_INTERPRETED_JVM_METHOD:
+ return "interpreted_jvm_method";
+ case proto::Sample_CallChainEntry_ExecutionType_JIT_JVM_METHOD:
+ return "jit_jvm_method";
+ case proto::Sample_CallChainEntry_ExecutionType_ART_METHOD:
+ return "art_method";
+ }
+ CHECK(false) << "unexpected execution type: " << type;
+ return "";
+}
+
+static const char* ProtoUnwindingErrorCodeToString(
+ proto::Sample_UnwindingResult_ErrorCode error_code) {
+ switch (error_code) {
+ case proto::Sample_UnwindingResult::ERROR_NONE:
+ return "ERROR_NONE";
+ case proto::Sample_UnwindingResult::ERROR_UNKNOWN:
+ return "ERROR_UNKNOWN";
+ case proto::Sample_UnwindingResult::ERROR_NOT_ENOUGH_STACK:
+ return "ERROR_NOT_ENOUGH_STACK";
+ case proto::Sample_UnwindingResult::ERROR_MEMORY_INVALID:
+ return "ERROR_MEMORY_INVALID";
+ case proto::Sample_UnwindingResult::ERROR_UNWIND_INFO:
+ return "ERROR_UNWIND_INFO";
+ case proto::Sample_UnwindingResult::ERROR_INVALID_MAP:
+ return "ERROR_INVALID_MAP";
+ case proto::Sample_UnwindingResult::ERROR_MAX_FRAME_EXCEEDED:
+ return "ERROR_MAX_FRAME_EXCEEDED";
+ case proto::Sample_UnwindingResult::ERROR_REPEATED_FRAME:
+ return "ERROR_REPEATED_FRAME";
+ case proto::Sample_UnwindingResult::ERROR_INVALID_ELF:
+ return "ERROR_INVALID_ELF";
+ }
+}
class ReportSampleCommand : public Command {
public:
@@ -84,12 +134,14 @@ class ReportSampleCommand : public Command {
"-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"
-"--protobuf Use protobuf format in report_sample.proto to output samples.\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"
// clang-format on
),
@@ -103,7 +155,7 @@ class ReportSampleCommand : public Command {
trace_offcpu_(false),
remove_unknown_kernel_symbols_(false),
kernel_symbols_available_(false),
- show_art_frames_(false) {}
+ callchain_report_builder_(thread_tree_) {}
bool Run(const std::vector<std::string>& args) override;
@@ -116,14 +168,14 @@ class ReportSampleCommand : public Command {
void UpdateThreadName(uint32_t pid, uint32_t tid);
bool ProcessSampleRecord(const SampleRecord& r);
bool PrintSampleRecordInProtobuf(const SampleRecord& record,
- const std::vector<CallEntry>& entries);
- bool GetCallEntry(const ThreadEntry* thread, bool in_kernel, uint64_t ip, bool omit_unknown_dso,
- CallEntry* entry);
+ const std::vector<CallChainReportEntry>& entries);
+ void AddUnwindingResultInProtobuf(proto::Sample_UnwindingResult* proto_unwinding_result);
bool WriteRecordInProtobuf(proto::Record& proto_record);
bool PrintLostSituationInProtobuf();
bool PrintFileInfoInProtobuf();
bool PrintThreadInfoInProtobuf();
- bool PrintSampleRecord(const SampleRecord& record, const std::vector<CallEntry>& entries);
+ bool PrintSampleRecord(const SampleRecord& record,
+ const std::vector<CallChainReportEntry>& entries);
void PrintLostSituation();
std::string record_filename_;
@@ -141,9 +193,11 @@ class ReportSampleCommand : public Command {
std::vector<std::string> event_types_;
bool remove_unknown_kernel_symbols_;
bool kernel_symbols_available_;
- bool show_art_frames_;
+ 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::unique_ptr<UnwindingResultRecord> last_unwinding_result_;
};
bool ReportSampleCommand::Run(const std::vector<std::string>& args) {
@@ -191,10 +245,8 @@ bool ReportSampleCommand::Run(const std::vector<std::string>& args) {
return false;
}
protobuf_writer.reset(new ProtobufFileWriter(report_fp_));
- protobuf_os.reset(new google::protobuf::io::CopyingOutputStreamAdaptor(
- protobuf_writer.get()));
- protobuf_coded_os.reset(
- new google::protobuf::io::CodedOutputStream(protobuf_os.get()));
+ protobuf_os.reset(new google::protobuf::io::CopyingOutputStreamAdaptor(protobuf_writer.get()));
+ protobuf_coded_os.reset(new google::protobuf::io::CodedOutputStream(protobuf_os.get()));
coded_os_ = protobuf_coded_os.get();
}
@@ -203,9 +255,7 @@ bool ReportSampleCommand::Run(const std::vector<std::string>& args) {
return false;
}
if (!record_file_reader_->ReadDataSection(
- [this](std::unique_ptr<Record> record) {
- return ProcessRecord(std::move(record));
- })) {
+ [this](std::unique_ptr<Record> record) { return ProcessRecord(std::move(record)); })) {
return false;
}
@@ -237,42 +287,44 @@ bool ReportSampleCommand::Run(const std::vector<std::string>& args) {
}
bool ReportSampleCommand::ParseOptions(const std::vector<std::string>& args) {
- for (size_t i = 0; i < args.size(); ++i) {
- if (args[i] == "--dump-protobuf-report") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- dump_protobuf_report_file_ = args[i];
- } else if (args[i] == "-i") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- record_filename_ = args[i];
- } else if (args[i] == "-o") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- report_filename_ = args[i];
- } else if (args[i] == "--protobuf") {
- use_protobuf_ = true;
- } else if (args[i] == "--show-callchain") {
- show_callchain_ = true;
- } else if (args[i] == "--remove-unknown-kernel-symbols") {
- remove_unknown_kernel_symbols_ = true;
- } else if (args[i] == "--show-art-frames") {
- show_art_frames_ = true;
- } else if (args[i] == "--symdir") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- if (!Dso::AddSymbolDir(args[i])) {
- return false;
- }
- } else {
- ReportUnknownOption(args, i);
+ const OptionFormatMap option_formats = {
+ {"--dump-protobuf-report", {OptionValueType::STRING, OptionType::SINGLE}},
+ {"-i", {OptionValueType::STRING, OptionType::SINGLE}},
+ {"-o", {OptionValueType::STRING, OptionType::SINGLE}},
+ {"--proguard-mapping-file", {OptionValueType::STRING, OptionType::MULTIPLE}},
+ {"--protobuf", {OptionValueType::NONE, OptionType::SINGLE}},
+ {"--show-callchain", {OptionValueType::NONE, OptionType::SINGLE}},
+ {"--remove-unknown-kernel-symbols", {OptionValueType::NONE, OptionType::SINGLE}},
+ {"--show-art-frames", {OptionValueType::NONE, OptionType::SINGLE}},
+ {"--show-execution-type", {OptionValueType::NONE, OptionType::SINGLE}},
+ {"--symdir", {OptionValueType::STRING, OptionType::MULTIPLE}},
+ };
+ OptionValueMap options;
+ std::vector<std::pair<OptionName, OptionValue>> ordered_options;
+ if (!PreprocessOptions(args, option_formats, &options, &ordered_options, nullptr)) {
+ return false;
+ }
+ options.PullStringValue("--dump-protobuf-report", &dump_protobuf_report_file_);
+ options.PullStringValue("-i", &record_filename_);
+ options.PullStringValue("-o", &report_filename_);
+ for (const OptionValue& value : options.PullValues("--proguard-mapping-file")) {
+ if (!callchain_report_builder_.AddProguardMappingFile(*value.str_value)) {
+ return false;
+ }
+ }
+ use_protobuf_ = options.PullBoolValue("--protobuf");
+ show_callchain_ = options.PullBoolValue("--show-callchain");
+ remove_unknown_kernel_symbols_ = options.PullBoolValue("--remove-unknown-kernel-symbols");
+ if (options.PullBoolValue("--show-art-frames")) {
+ callchain_report_builder_.SetRemoveArtFrame(false);
+ }
+ show_execution_type_ = options.PullBoolValue("--show-execution-type");
+ for (const OptionValue& value : options.PullValues("--symdir")) {
+ if (!Dso::AddSymbolDir(*value.str_value)) {
return false;
}
}
+ CHECK(options.values.empty());
if (use_protobuf_ && report_filename_.empty()) {
report_filename_ = "report_sample.trace";
@@ -282,8 +334,7 @@ bool ReportSampleCommand::ParseOptions(const std::vector<std::string>& args) {
bool ReportSampleCommand::DumpProtobufReport(const std::string& filename) {
GOOGLE_PROTOBUF_VERIFY_VERSION;
- std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(filename.c_str(), "rb"),
- fclose);
+ std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(filename.c_str(), "rb"), fclose);
if (fp == nullptr) {
PLOG(ERROR) << "failed to open " << filename;
return false;
@@ -343,8 +394,7 @@ bool ReportSampleCommand::DumpProtobufReport(const std::string& filename) {
FprintIndented(report_fp_, 1, "callchain:\n");
for (int i = 0; i < sample.callchain_size(); ++i) {
const proto::Sample_CallChainEntry& callchain = sample.callchain(i);
- FprintIndented(report_fp_, 2, "vaddr_in_file: %" PRIx64 "\n",
- callchain.vaddr_in_file());
+ FprintIndented(report_fp_, 2, "vaddr_in_file: %" PRIx64 "\n", callchain.vaddr_in_file());
FprintIndented(report_fp_, 2, "file_id: %u\n", callchain.file_id());
int32_t symbol_id = callchain.symbol_id();
FprintIndented(report_fp_, 2, "symbol_id: %d\n", symbol_id);
@@ -356,14 +406,25 @@ bool ReportSampleCommand::DumpProtobufReport(const std::string& filename) {
max_symbol_id_map[callchain.file_id()] =
std::max(max_symbol_id_map[callchain.file_id()], symbol_id);
}
+ if (callchain.has_execution_type()) {
+ FprintIndented(report_fp_, 2, "execution_type: %s\n",
+ ProtoExecutionTypeToString(callchain.execution_type()));
+ }
+ }
+ if (sample.has_unwinding_result()) {
+ FprintIndented(report_fp_, 1, "unwinding_result:\n");
+ FprintIndented(report_fp_, 2, "raw_error_code: %u\n",
+ sample.unwinding_result().raw_error_code());
+ FprintIndented(report_fp_, 2, "error_addr: 0x%" PRIx64 "\n",
+ sample.unwinding_result().error_addr());
+ FprintIndented(report_fp_, 2, "error_code: %s\n",
+ ProtoUnwindingErrorCodeToString(sample.unwinding_result().error_code()));
}
} else if (proto_record.has_lost()) {
auto& lost = proto_record.lost();
FprintIndented(report_fp_, 0, "lost_situation:\n");
- FprintIndented(report_fp_, 1, "sample_count: %" PRIu64 "\n",
- lost.sample_count());
- FprintIndented(report_fp_, 1, "lost_count: %" PRIu64 "\n",
- lost.lost_count());
+ FprintIndented(report_fp_, 1, "sample_count: %" PRIu64 "\n", lost.sample_count());
+ FprintIndented(report_fp_, 1, "lost_count: %" PRIu64 "\n", lost.lost_count());
} else if (proto_record.has_file()) {
auto& file = proto_record.file();
FprintIndented(report_fp_, 0, "file:\n");
@@ -376,8 +437,8 @@ bool ReportSampleCommand::DumpProtobufReport(const std::string& filename) {
FprintIndented(report_fp_, 1, "mangled_symbol: %s\n", file.mangled_symbol(i).c_str());
}
if (file.id() != files.size()) {
- LOG(ERROR) << "file id doesn't increase orderly, expected "
- << files.size() << ", really " << file.id();
+ LOG(ERROR) << "file id doesn't increase orderly, expected " << files.size() << ", really "
+ << file.id();
return false;
}
files.push_back(file.symbol_size());
@@ -404,13 +465,12 @@ bool ReportSampleCommand::DumpProtobufReport(const std::string& filename) {
}
for (auto pair : max_symbol_id_map) {
if (pair.first >= files.size()) {
- LOG(ERROR) << "file_id(" << pair.first << ") >= file count ("
- << files.size() << ")";
+ LOG(ERROR) << "file_id(" << pair.first << ") >= file count (" << files.size() << ")";
return false;
}
if (static_cast<uint32_t>(pair.second) >= files[pair.first]) {
- LOG(ERROR) << "symbol_id(" << pair.second << ") >= symbol count ("
- << files[pair.first] << ") in file_id( " << pair.first << ")";
+ LOG(ERROR) << "symbol_id(" << pair.second << ") >= symbol count (" << files[pair.first]
+ << ") in file_id( " << pair.first << ")";
return false;
}
}
@@ -464,13 +524,23 @@ bool ReportSampleCommand::PrintMetaInfo() {
bool ReportSampleCommand::ProcessRecord(std::unique_ptr<Record> record) {
thread_tree_.Update(*record);
- if (record->type() == PERF_RECORD_SAMPLE) {
- return ProcessSampleRecord(*static_cast<SampleRecord*>(record.get()));
- }
- if (record->type() == PERF_RECORD_LOST) {
- lost_count_ += static_cast<const LostRecord*>(record.get())->lost;
+ bool result = true;
+ switch (record->type()) {
+ case PERF_RECORD_SAMPLE: {
+ result = ProcessSampleRecord(*static_cast<SampleRecord*>(record.get()));
+ last_unwinding_result_.reset();
+ break;
+ }
+ case SIMPLE_PERF_RECORD_UNWINDING_RESULT: {
+ last_unwinding_result_.reset(static_cast<UnwindingResultRecord*>(record.release()));
+ break;
+ }
+ case PERF_RECORD_LOST: {
+ lost_count_ += static_cast<const LostRecord*>(record.get())->lost;
+ break;
+ }
}
- return true;
+ return result;
}
bool ReportSampleCommand::ProcessSampleRecord(const SampleRecord& r) {
@@ -488,34 +558,15 @@ bool ReportSampleCommand::ProcessSampleRecord(const SampleRecord& r) {
kernel_ip_count = std::min(kernel_ip_count, static_cast<size_t>(1u));
}
sample_count_++;
- std::vector<CallEntry> entries;
- bool near_java_method = false;
- auto is_entry_for_interpreter = [](const CallEntry& entry) {
- return android::base::EndsWith(entry.dso->Path(), "/libart.so");
- };
const ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
- for (size_t i = 0; i < ips.size(); ++i) {
- bool omit_unknown_dso = i > 0u;
- CallEntry entry;
- if (!GetCallEntry(thread, i < kernel_ip_count, ips[i], omit_unknown_dso, &entry)) {
+ std::vector<CallChainReportEntry> entries =
+ 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);
break;
}
- if (!show_art_frames_) {
- // Remove interpreter frames both before and after the Java frame.
- if (entry.dso->IsForJavaMethod()) {
- near_java_method = true;
- while (!entries.empty() && is_entry_for_interpreter(entries.back())) {
- entries.pop_back();
- }
- } else if (is_entry_for_interpreter(entry)) {
- if (near_java_method) {
- continue;
- }
- } else {
- near_java_method = false;
- }
- }
- entries.push_back(entry);
}
if (use_protobuf_) {
uint64_t key = (static_cast<uint64_t>(r.tid_data.pid) << 32) | r.tid_data.tid;
@@ -525,8 +576,8 @@ bool ReportSampleCommand::ProcessSampleRecord(const SampleRecord& r) {
return PrintSampleRecord(r, entries);
}
-bool ReportSampleCommand::PrintSampleRecordInProtobuf(const SampleRecord& r,
- const std::vector<CallEntry>& entries) {
+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);
@@ -534,7 +585,8 @@ bool ReportSampleCommand::PrintSampleRecordInProtobuf(const SampleRecord& r,
sample->set_thread_id(r.tid_data.tid);
sample->set_event_type_id(record_file_reader_->GetAttrIndexOfRecord(&r));
- for (const CallEntry& node : entries) {
+ bool complete_callchain = false;
+ for (const auto& node : entries) {
proto::Sample_CallChainEntry* callchain = sample->add_callchain();
uint32_t file_id;
if (!node.dso->GetDumpId(&file_id)) {
@@ -549,21 +601,82 @@ bool ReportSampleCommand::PrintSampleRecordInProtobuf(const SampleRecord& r,
callchain->set_vaddr_in_file(node.vaddr_in_file);
callchain->set_file_id(file_id);
callchain->set_symbol_id(symbol_id);
+ 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)) {
+ 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());
+ }
return WriteRecordInProtobuf(proto_record);
}
+void ReportSampleCommand::AddUnwindingResultInProtobuf(
+ 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;
+ switch (unwinding_result.error_code) {
+ case UnwindStackErrorCode::ERROR_NONE:
+ error_code = proto::Sample_UnwindingResult::ERROR_NONE;
+ break;
+ case UnwindStackErrorCode::ERROR_MEMORY_INVALID: {
+ // We dumped stack data in range [stack_start, stack_end) for dwarf unwinding.
+ // If the failed-to-read memory addr is within [stack_end, stack_end + 128k], then
+ // probably we didn't dump enough stack data.
+ // 128k is a guess number. The size of stack used in one function layer is usually smaller
+ // than it. And using a bigger value is more likely to be false positive.
+ if (unwinding_result.error_addr >= unwinding_result.stack_end &&
+ unwinding_result.error_addr <= unwinding_result.stack_end + 128 * 1024) {
+ error_code = proto::Sample_UnwindingResult::ERROR_NOT_ENOUGH_STACK;
+ } else {
+ error_code = proto::Sample_UnwindingResult::ERROR_MEMORY_INVALID;
+ }
+ break;
+ }
+ case UnwindStackErrorCode::ERROR_UNWIND_INFO:
+ error_code = proto::Sample_UnwindingResult::ERROR_UNWIND_INFO;
+ break;
+ case UnwindStackErrorCode::ERROR_INVALID_MAP:
+ error_code = proto::Sample_UnwindingResult::ERROR_INVALID_MAP;
+ break;
+ case UnwindStackErrorCode::ERROR_MAX_FRAMES_EXCEEDED:
+ error_code = proto::Sample_UnwindingResult::ERROR_MAX_FRAME_EXCEEDED;
+ break;
+ case UnwindStackErrorCode::ERROR_REPEATED_FRAME:
+ error_code = proto::Sample_UnwindingResult::ERROR_REPEATED_FRAME;
+ break;
+ case UnwindStackErrorCode::ERROR_INVALID_ELF:
+ error_code = proto::Sample_UnwindingResult::ERROR_INVALID_ELF;
+ break;
+ case UnwindStackErrorCode::ERROR_UNSUPPORTED:
+ case UnwindStackErrorCode::ERROR_THREAD_DOES_NOT_EXIST:
+ case UnwindStackErrorCode::ERROR_THREAD_TIMEOUT:
+ case UnwindStackErrorCode::ERROR_SYSTEM_CALL:
+ // These error_codes shouldn't happen in simpleperf's use of libunwindstack.
+ error_code = proto::Sample_UnwindingResult::ERROR_UNKNOWN;
+ break;
+ default:
+ LOG(ERROR) << "unknown unwinding error code: " << unwinding_result.error_code;
+ error_code = proto::Sample_UnwindingResult::ERROR_UNKNOWN;
+ break;
+ }
+ proto_unwinding_result->set_error_code(error_code);
+}
+
bool ReportSampleCommand::WriteRecordInProtobuf(proto::Record& proto_record) {
coded_os_->WriteLittleEndian32(proto_record.ByteSize());
if (!proto_record.SerializeToCodedStream(coded_os_)) {
@@ -573,22 +686,6 @@ bool ReportSampleCommand::WriteRecordInProtobuf(proto::Record& proto_record) {
return true;
}
-bool ReportSampleCommand::GetCallEntry(const ThreadEntry* thread,
- bool in_kernel, uint64_t ip,
- bool omit_unknown_dso,
- CallEntry* entry) {
- const MapEntry* map = thread_tree_.FindMap(thread, ip, in_kernel);
- if (omit_unknown_dso && thread_tree_.IsUnknownDso(map->dso)) {
- return false;
- }
- entry->symbol = thread_tree_.FindSymbol(map, ip, &(entry->vaddr_in_file), &(entry->dso));
- // If we can't find symbol, use the dso shown in the map.
- if (entry->symbol == thread_tree_.UnknownSymbol()) {
- entry->dso = map->dso;
- }
- return true;
-}
-
bool ReportSampleCommand::PrintLostSituationInProtobuf() {
proto::Record proto_record;
proto::LostSituation* lost = proto_record.mutable_lost();
@@ -616,7 +713,7 @@ bool ReportSampleCommand::PrintFileInfoInProtobuf() {
proto::Record proto_record;
proto::File* file = proto_record.mutable_file();
file->set_id(file_id);
- file->set_path(dso->Path());
+ file->set_path(std::string{dso->GetReportPath()});
const std::vector<Symbol>& symbols = dso->GetSymbols();
std::vector<const Symbol*> dump_symbols;
for (const auto& sym : symbols) {
@@ -624,14 +721,11 @@ bool ReportSampleCommand::PrintFileInfoInProtobuf() {
dump_symbols.push_back(&sym);
}
}
- std::sort(dump_symbols.begin(), dump_symbols.end(),
- Symbol::CompareByDumpId);
+ std::sort(dump_symbols.begin(), dump_symbols.end(), Symbol::CompareByDumpId);
for (const auto& sym : dump_symbols) {
- std::string* symbol = file->add_symbol();
- *symbol = sym->DemangledName();
- std::string* mangled_symbol = file->add_mangled_symbol();
- *mangled_symbol = sym->Name();
+ file->add_symbol(sym->DemangledName());
+ file->add_mangled_symbol(sym->Name());
}
if (!WriteRecordInProtobuf(proto_record)) {
return false;
@@ -657,10 +751,10 @@ bool ReportSampleCommand::PrintThreadInfoInProtobuf() {
}
bool ReportSampleCommand::PrintSampleRecord(const SampleRecord& r,
- const std::vector<CallEntry>& entries) {
+ const std::vector<CallChainReportEntry>& entries) {
FprintIndented(report_fp_, 0, "sample:\n");
FprintIndented(report_fp_, 1, "event_type: %s\n",
- event_types_[record_file_reader_->GetAttrIndexOfRecord(&r)].c_str());
+ 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);
@@ -668,15 +762,23 @@ bool ReportSampleCommand::PrintSampleRecord(const SampleRecord& r,
FprintIndented(report_fp_, 1, "thread_name: %s\n", thread_name);
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->Path().c_str());
+ FprintIndented(report_fp_, 1, "file: %s\n", entries[0].dso->GetReportPath().data());
FprintIndented(report_fp_, 1, "symbol: %s\n", entries[0].symbol->DemangledName());
+ if (show_execution_type_) {
+ FprintIndented(report_fp_, 1, "execution_type: %s\n",
+ ProtoExecutionTypeToString(ToProtoExecutionType(entries[0].execution_type)));
+ }
if (entries.size() > 1u) {
FprintIndented(report_fp_, 1, "callchain:\n");
for (size_t i = 1u; i < entries.size(); ++i) {
FprintIndented(report_fp_, 2, "vaddr_in_file: %" PRIx64 "\n", entries[i].vaddr_in_file);
- FprintIndented(report_fp_, 2, "file: %s\n", entries[i].dso->Path().c_str());
+ FprintIndented(report_fp_, 2, "file: %s\n", entries[i].dso->GetReportPath().data());
FprintIndented(report_fp_, 2, "symbol: %s\n", entries[i].symbol->DemangledName());
+ if (show_execution_type_) {
+ FprintIndented(report_fp_, 1, "execution_type: %s\n",
+ ProtoExecutionTypeToString(ToProtoExecutionType(entries[i].execution_type)));
+ }
}
}
return true;
@@ -691,7 +793,8 @@ void ReportSampleCommand::PrintLostSituation() {
} // namespace
void RegisterReportSampleCommand() {
- RegisterCommand("report-sample", [] {
- return std::unique_ptr<Command>(new ReportSampleCommand());
- });
+ RegisterCommand("report-sample",
+ [] { return std::unique_ptr<Command>(new ReportSampleCommand()); });
}
+
+} // namespace simpleperf
diff --git a/simpleperf/report_sample.proto b/simpleperf/cmd_report_sample.proto
index bbe46fb2..e1e0e4aa 100644
--- a/simpleperf/report_sample.proto
+++ b/simpleperf/cmd_report_sample.proto
@@ -1,4 +1,4 @@
-// The file format generated by report_sample.proto is as below:
+// The file format generated by cmd_report_sample.proto is as below:
// char magic[10] = "SIMPLEPERF";
// LittleEndian16(version) = 1;
// LittleEndian32(record_size_0)
@@ -33,6 +33,17 @@ message Sample {
// If the function name is found, it is a valid index in the symbol table
// of File with 'id' field being file_id, otherwise it is -1.
optional int32 symbol_id = 3;
+
+ enum ExecutionType {
+ // methods belong to native libraries, AOT compiled JVM code and ART methods not used near
+ // JVM methods
+ NATIVE_METHOD = 0;
+ INTERPRETED_JVM_METHOD = 1;
+ JIT_JVM_METHOD = 2;
+ // ART methods used near JVM methods. It's shown only when --show-art-frames is used.
+ ART_METHOD = 3;
+ }
+ optional ExecutionType execution_type = 4 [default = NATIVE_METHOD];
}
repeated CallChainEntry callchain = 3;
@@ -53,6 +64,32 @@ message Sample {
// An index in meta_info.event_type, shows which event type current sample belongs to.
optional uint32 event_type_id = 5;
+
+ message UnwindingResult {
+ // error code provided by libunwindstack, in
+ // https://cs.android.com/android/platform/superproject/+/master:system/unwinding/libunwindstack/include/unwindstack/Error.h
+ optional uint32 raw_error_code = 1;
+ // error addr provided by libunwindstack
+ optional uint64 error_addr = 2;
+
+ // error code interpreted by simpleperf
+ enum ErrorCode {
+ ERROR_NONE = 0; // No error
+ ERROR_UNKNOWN = 1; // Error not interpreted by simpleperf, see raw_error_code
+ ERROR_NOT_ENOUGH_STACK = 2; // Simpleperf doesn't record enough stack data
+ ERROR_MEMORY_INVALID = 3; // Memory read failed
+ ERROR_UNWIND_INFO = 4; // No debug info in binary to support unwinding
+ ERROR_INVALID_MAP = 5; // Unwind in an invalid map
+ ERROR_MAX_FRAME_EXCEEDED = 6; // Stopped at MAX_UNWINDING_FRAMES, which is 512.
+ ERROR_REPEATED_FRAME = 7; // The last frame has the same pc/sp as the next.
+ ERROR_INVALID_ELF = 8; // Unwind in an invalid elf file
+ }
+ optional ErrorCode error_code = 3;
+ }
+
+ // Unwinding result is provided for samples without a complete callchain, when recorded with
+ // --keep-failed-unwinding-result or --keep-failed-unwinding-debug-info.
+ optional UnwindingResult unwinding_result = 6;
}
message LostSituation {
diff --git a/simpleperf/cmd_report_sample_test.cpp b/simpleperf/cmd_report_sample_test.cpp
index 9543634d..38e3c423 100644
--- a/simpleperf/cmd_report_sample_test.cpp
+++ b/simpleperf/cmd_report_sample_test.cpp
@@ -21,25 +21,26 @@
#include "command.h"
#include "get_test_data.h"
+using namespace simpleperf;
+
static std::unique_ptr<Command> ReportSampleCmd() {
return CreateCommandInstance("report-sample");
}
TEST(cmd_report_sample, text) {
- ASSERT_TRUE(
- ReportSampleCmd()->Run({"-i", GetTestData(PERF_DATA_WITH_SYMBOLS)}));
+ ASSERT_TRUE(ReportSampleCmd()->Run({"-i", GetTestData(PERF_DATA_WITH_SYMBOLS)}));
}
TEST(cmd_report_sample, output_option) {
TemporaryFile tmpfile;
- ASSERT_TRUE(ReportSampleCmd()->Run(
- {"-i", GetTestData(PERF_DATA_WITH_SYMBOLS), "-o", tmpfile.path}));
+ ASSERT_TRUE(
+ ReportSampleCmd()->Run({"-i", GetTestData(PERF_DATA_WITH_SYMBOLS), "-o", tmpfile.path}));
}
TEST(cmd_report_sample, show_callchain_option) {
TemporaryFile tmpfile;
- ASSERT_TRUE(ReportSampleCmd()->Run({"-i", GetTestData(CALLGRAPH_FP_PERF_DATA),
- "-o", tmpfile.path, "--show-callchain"}));
+ ASSERT_TRUE(ReportSampleCmd()->Run(
+ {"-i", GetTestData(CALLGRAPH_FP_PERF_DATA), "-o", tmpfile.path, "--show-callchain"}));
}
static void GetProtobufReport(const std::string& test_data_file, std::string* protobuf_report,
@@ -50,8 +51,8 @@ static void GetProtobufReport(const std::string& test_data_file, std::string* pr
"--protobuf"};
args.insert(args.end(), extra_args.begin(), extra_args.end());
ASSERT_TRUE(ReportSampleCmd()->Run(args));
- ASSERT_TRUE(ReportSampleCmd()->Run({"--dump-protobuf-report", tmpfile.path,
- "-o", tmpfile2.path}));
+ ASSERT_TRUE(
+ ReportSampleCmd()->Run({"--dump-protobuf-report", tmpfile.path, "-o", tmpfile2.path}));
ASSERT_TRUE(android::base::ReadFileToString(tmpfile2.path, protobuf_report));
}
@@ -105,8 +106,7 @@ TEST(cmd_report_sample, app_package_name_in_meta_info) {
TEST(cmd_report_sample, remove_unknown_kernel_symbols) {
std::string data;
// Test --remove-unknown-kernel-symbols on perf.data with kernel_symbols_available=false.
- GetProtobufReport(PERF_DATA_WITH_KERNEL_SYMBOLS_AVAILABLE_FALSE, &data,
- {"--show-callchain"});
+ GetProtobufReport(PERF_DATA_WITH_KERNEL_SYMBOLS_AVAILABLE_FALSE, &data, {"--show-callchain"});
ASSERT_NE(data.find("time: 1368182962424044"), std::string::npos);
ASSERT_NE(data.find("path: [kernel.kallsyms]"), std::string::npos);
ASSERT_NE(data.find("path: /system/lib64/libc.so"), std::string::npos);
@@ -121,8 +121,7 @@ TEST(cmd_report_sample, remove_unknown_kernel_symbols) {
ASSERT_NE(data.find("path: /system/lib64/libc.so"), std::string::npos);
// Test --remove-unknown-kernel-symbols on perf.data with kernel_symbols_available=true.
- GetProtobufReport(PERF_DATA_WITH_KERNEL_SYMBOLS_AVAILABLE_TRUE, &data,
- {"--show-callchain"});
+ GetProtobufReport(PERF_DATA_WITH_KERNEL_SYMBOLS_AVAILABLE_TRUE, &data, {"--show-callchain"});
ASSERT_NE(data.find("time: 1368297633794862"), std::string::npos);
ASSERT_NE(data.find("path: [kernel.kallsyms]"), std::string::npos);
ASSERT_NE(data.find("symbol: binder_ioctl_write_read"), std::string::npos);
@@ -144,6 +143,23 @@ TEST(cmd_report_sample, show_art_frames_option) {
ASSERT_NE(data.find("artMterpAsmInstructionStart"), std::string::npos);
}
+TEST(cmd_report_sample, show_execution_type_option) {
+ std::string data;
+ GetProtobufReport("perf_display_bitmaps.data", &data,
+ {"--show-callchain", "--show-execution-type"});
+ ASSERT_NE(data.find("execution_type: interpreted_jvm_method"), std::string::npos);
+ // We convert JIT frames to map to dex files. So there is no file named jit_app_cache in the
+ // report. But the execution type of a JIT frame isn't changed.
+ ASSERT_EQ(data.find("jit_app_cache"), std::string::npos);
+ ASSERT_NE(data.find("execution_type: jit_jvm_method"), std::string::npos);
+ // art_method is shown only when --show-art-frames is used.
+ ASSERT_EQ(data.find("execution_type: art_method"), std::string::npos);
+
+ GetProtobufReport("perf_display_bitmaps.data", &data,
+ {"--show-callchain", "--show-execution-type", "--show-art-frames"});
+ ASSERT_NE(data.find("execution_type: art_method"), std::string::npos);
+}
+
TEST(cmd_report_sample, show_symbols_before_and_after_demangle) {
std::string data;
GetProtobufReport(PERF_DATA_WITH_INTERPRETER_FRAMES, &data, {"--show-callchain"});
@@ -161,3 +177,35 @@ TEST(cmd_report_sample, symdir_option) {
{"--symdir", GetTestDataDir() + CORRECT_SYMFS_FOR_BUILD_ID_CHECK});
ASSERT_NE(data.find("symbol: main"), std::string::npos);
}
+
+TEST(cmd_report_sample, show_art_jni_methods) {
+ std::string data;
+ GetProtobufReport("perf_display_bitmaps.data", &data, {"--show-callchain"});
+ ASSERT_NE(data.find("art::Method_invoke"), std::string::npos);
+ // Don't show art_jni_trampoline.
+ ASSERT_EQ(data.find("art_jni_trampoline"), std::string::npos);
+}
+
+TEST(cmd_report_sample, show_unwinding_result) {
+ std::string data;
+ GetProtobufReport("perf_with_failed_unwinding_debug_info.data", &data, {"--show-callchain"});
+ ASSERT_NE(data.find("error_code: ERROR_INVALID_MAP"), std::string::npos);
+}
+
+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"});
+ 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")});
+ 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);
+}
diff --git a/simpleperf/cmd_report_test.cpp b/simpleperf/cmd_report_test.cpp
index e112d0d0..fb79c931 100644
--- a/simpleperf/cmd_report_test.cpp
+++ b/simpleperf/cmd_report_test.cpp
@@ -16,10 +16,12 @@
#include <gtest/gtest.h>
+#include <regex>
#include <set>
#include <unordered_map>
#include <android-base/file.h>
+#include <android-base/parseint.h>
#include <android-base/strings.h>
#include "command.h"
@@ -28,25 +30,25 @@
#include "read_apk.h"
#include "test_util.h"
+using namespace simpleperf;
+
static std::unique_ptr<Command> ReportCmd() {
return CreateCommandInstance("report");
}
class ReportCommandTest : public ::testing::Test {
protected:
- void Report(
- const std::string& perf_data,
- const std::vector<std::string>& add_args = std::vector<std::string>()) {
+ void Report(const std::string& perf_data,
+ const std::vector<std::string>& add_args = std::vector<std::string>()) {
ReportRaw(GetTestData(perf_data), add_args);
}
- void ReportRaw(
- const std::string& perf_data,
- const std::vector<std::string>& add_args = std::vector<std::string>()) {
+ void ReportRaw(const std::string& perf_data,
+ const std::vector<std::string>& add_args = std::vector<std::string>()) {
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, "--symfs", GetTestDataDir(),
+ "-o", tmp_file.path};
args.insert(args.end(), add_args.begin(), add_args.end());
ASSERT_TRUE(ReportCmd()->Run(args));
ASSERT_TRUE(android::base::ReadFileToString(tmp_file.path, &content));
@@ -63,6 +65,17 @@ class ReportCommandTest : public ::testing::Test {
success = true;
}
+ size_t GetSampleCount() {
+ std::smatch m;
+ if (std::regex_search(content, m, std::regex(R"(Samples: (\d+))"))) {
+ size_t count;
+ if (android::base::ParseUint(m[1], &count)) {
+ return count;
+ }
+ }
+ return 0;
+ }
+
std::string content;
std::vector<std::string> lines;
bool success;
@@ -84,8 +97,7 @@ TEST_F(ReportCommandTest, sort_option_pid) {
Report(PERF_DATA, {"--sort", "pid"});
ASSERT_TRUE(success);
size_t line_index = 0;
- while (line_index < lines.size() &&
- lines[line_index].find("Pid") == std::string::npos) {
+ while (line_index < lines.size() && lines[line_index].find("Pid") == std::string::npos) {
line_index++;
}
ASSERT_LT(line_index + 2, lines.size());
@@ -95,8 +107,7 @@ TEST_F(ReportCommandTest, sort_option_more_than_one) {
Report(PERF_DATA, {"--sort", "comm,pid,dso,symbol"});
ASSERT_TRUE(success);
size_t line_index = 0;
- while (line_index < lines.size() &&
- lines[line_index].find("Overhead") == std::string::npos) {
+ while (line_index < lines.size() && lines[line_index].find("Overhead") == std::string::npos) {
line_index++;
}
ASSERT_LT(line_index + 1, lines.size());
@@ -114,8 +125,7 @@ TEST_F(ReportCommandTest, children_option) {
for (size_t i = 0; i < lines.size(); ++i) {
char name[1024];
std::pair<double, double> pair;
- if (sscanf(lines[i].c_str(), "%lf%%%lf%%%s", &pair.first, &pair.second,
- name) == 3) {
+ if (sscanf(lines[i].c_str(), "%lf%%%lf%%%s", &pair.first, &pair.second, name) == 3) {
map.insert(std::make_pair(name, pair));
}
}
@@ -167,8 +177,7 @@ TEST_F(ReportCommandTest, callgraph_option) {
static bool AllItemsWithString(std::vector<std::string>& lines,
const std::vector<std::string>& strs) {
size_t line_index = 0;
- while (line_index < lines.size() &&
- lines[line_index].find("Overhead") == std::string::npos) {
+ while (line_index < lines.size() && lines[line_index].find("Overhead") == std::string::npos) {
line_index++;
}
if (line_index == lines.size() || line_index + 1 == lines.size()) {
@@ -195,19 +204,16 @@ TEST_F(ReportCommandTest, pid_filter_option) {
ASSERT_TRUE(success);
ASSERT_FALSE(AllItemsWithString(lines, {"17441"}));
ASSERT_FALSE(AllItemsWithString(lines, {"17441", "17443"}));
- Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS,
- {"--sort", "pid", "--pids", "17441"});
+ Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--sort", "pid", "--pids", "17441"});
ASSERT_TRUE(success);
ASSERT_TRUE(AllItemsWithString(lines, {"17441"}));
- Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS,
- {"--sort", "pid", "--pids", "17441,17443"});
+ Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--sort", "pid", "--pids", "17441,17443"});
ASSERT_TRUE(success);
ASSERT_TRUE(AllItemsWithString(lines, {"17441", "17443"}));
// Test that --pids option is not the same as --tids option.
// Thread 17445 and 17441 are in process 17441.
- Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS,
- {"--sort", "tid", "--pids", "17441"});
+ Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--sort", "tid", "--pids", "17441"});
ASSERT_TRUE(success);
ASSERT_NE(content.find("17441"), std::string::npos);
ASSERT_NE(content.find("17445"), std::string::npos);
@@ -219,7 +225,7 @@ TEST_F(ReportCommandTest, wrong_pid_filter_option) {
Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--pids", "2,bogus"});
exit(success ? 0 : 1);
},
- testing::ExitedWithCode(1), "invalid id in --pids option: bogus");
+ testing::ExitedWithCode(1), "Invalid tid 'bogus'");
}
TEST_F(ReportCommandTest, tid_filter_option) {
@@ -227,12 +233,10 @@ TEST_F(ReportCommandTest, tid_filter_option) {
ASSERT_TRUE(success);
ASSERT_FALSE(AllItemsWithString(lines, {"17441"}));
ASSERT_FALSE(AllItemsWithString(lines, {"17441", "17445"}));
- Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS,
- {"--sort", "tid", "--tids", "17441"});
+ Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--sort", "tid", "--tids", "17441"});
ASSERT_TRUE(success);
ASSERT_TRUE(AllItemsWithString(lines, {"17441"}));
- Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS,
- {"--sort", "tid", "--tids", "17441,17445"});
+ Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--sort", "tid", "--tids", "17441,17445"});
ASSERT_TRUE(success);
ASSERT_TRUE(AllItemsWithString(lines, {"17441", "17445"}));
}
@@ -243,7 +247,7 @@ TEST_F(ReportCommandTest, wrong_tid_filter_option) {
Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--tids", "2,bogus"});
exit(success ? 0 : 1);
},
- testing::ExitedWithCode(1), "invalid id in --tids option: bogus");
+ testing::ExitedWithCode(1), "Invalid tid 'bogus'");
}
TEST_F(ReportCommandTest, comm_filter_option) {
@@ -277,12 +281,10 @@ TEST_F(ReportCommandTest, symbol_filter_option) {
ASSERT_TRUE(success);
ASSERT_FALSE(AllItemsWithString(lines, {"func2(int, int)"}));
ASSERT_FALSE(AllItemsWithString(lines, {"main", "func2(int, int)"}));
- Report(PERF_DATA_WITH_SYMBOLS,
- {"--sort", "symbol", "--symbols", "func2(int, int)"});
+ Report(PERF_DATA_WITH_SYMBOLS, {"--sort", "symbol", "--symbols", "func2(int, int)"});
ASSERT_TRUE(success);
ASSERT_TRUE(AllItemsWithString(lines, {"func2(int, int)"}));
- Report(PERF_DATA_WITH_SYMBOLS,
- {"--sort", "symbol", "--symbols", "main;func2(int, int)"});
+ Report(PERF_DATA_WITH_SYMBOLS, {"--sort", "symbol", "--symbols", "main;func2(int, int)"});
ASSERT_TRUE(success);
ASSERT_TRUE(AllItemsWithString(lines, {"main", "func2(int, int)"}));
}
@@ -302,19 +304,16 @@ TEST_F(ReportCommandTest, use_branch_address) {
}
}
}
- ASSERT_NE(hit_set.find(std::make_pair<std::string, std::string>(
- "GlobalFunc", "CalledFunc")),
+ ASSERT_NE(hit_set.find(std::make_pair<std::string, std::string>("GlobalFunc", "CalledFunc")),
hit_set.end());
- ASSERT_NE(hit_set.find(std::make_pair<std::string, std::string>(
- "CalledFunc", "GlobalFunc")),
+ ASSERT_NE(hit_set.find(std::make_pair<std::string, std::string>("CalledFunc", "GlobalFunc")),
hit_set.end());
}
TEST_F(ReportCommandTest, report_symbols_of_nativelib_in_apk) {
Report(NATIVELIB_IN_APK_PERF_DATA);
ASSERT_TRUE(success);
- ASSERT_NE(content.find(GetUrlInApk(APK_FILE, NATIVELIB_IN_APK)),
- std::string::npos);
+ ASSERT_NE(content.find(GetUrlInApk(APK_FILE, NATIVELIB_IN_APK)), std::string::npos);
ASSERT_NE(content.find("Func2"), std::string::npos);
}
@@ -362,8 +361,7 @@ TEST_F(ReportCommandTest, report_sort_vaddr_in_file) {
}
TEST_F(ReportCommandTest, check_build_id) {
- Report(PERF_DATA_FOR_BUILD_ID_CHECK,
- {"--symfs", GetTestData(CORRECT_SYMFS_FOR_BUILD_ID_CHECK)});
+ Report(PERF_DATA_FOR_BUILD_ID_CHECK, {"--symfs", GetTestData(CORRECT_SYMFS_FOR_BUILD_ID_CHECK)});
ASSERT_TRUE(success);
ASSERT_NE(content.find("main"), std::string::npos);
ASSERT_EXIT(
@@ -393,8 +391,7 @@ TEST_F(ReportCommandTest, no_show_ip_option) {
TEST_F(ReportCommandTest, no_symbol_table_warning) {
ASSERT_EXIT(
{
- Report(PERF_DATA,
- {"--symfs", GetTestData(SYMFS_FOR_NO_SYMBOL_TABLE_WARNING)});
+ Report(PERF_DATA, {"--symfs", GetTestData(SYMFS_FOR_NO_SYMBOL_TABLE_WARNING)});
if (!success) {
exit(1);
}
@@ -409,8 +406,7 @@ TEST_F(ReportCommandTest, no_symbol_table_warning) {
TEST_F(ReportCommandTest, read_elf_file_warning) {
ASSERT_EXIT(
{
- Report(PERF_DATA,
- {"--symfs", GetTestData(SYMFS_FOR_READ_ELF_FILE_WARNING)});
+ Report(PERF_DATA, {"--symfs", GetTestData(SYMFS_FOR_READ_ELF_FILE_WARNING)});
if (!success) {
exit(1);
}
@@ -439,12 +435,10 @@ TEST_F(ReportCommandTest, max_stack_and_percent_limit_option) {
ASSERT_TRUE(success);
ASSERT_NE(content.find("89.03"), std::string::npos);
- Report(PERF_DATA_MAX_STACK_AND_PERCENT_LIMIT,
- {"-g", "--percent-limit", "90"});
+ Report(PERF_DATA_MAX_STACK_AND_PERCENT_LIMIT, {"-g", "--percent-limit", "90"});
ASSERT_TRUE(success);
ASSERT_EQ(content.find("89.03"), std::string::npos);
- Report(PERF_DATA_MAX_STACK_AND_PERCENT_LIMIT,
- {"-g", "--percent-limit", "70"});
+ Report(PERF_DATA_MAX_STACK_AND_PERCENT_LIMIT, {"-g", "--percent-limit", "70"});
ASSERT_TRUE(success);
ASSERT_NE(content.find("89.03"), std::string::npos);
}
@@ -495,6 +489,49 @@ TEST_F(ReportCommandTest, report_big_trace_data) {
ASSERT_TRUE(success);
}
+TEST_F(ReportCommandTest, csv_option) {
+ Report(PERF_DATA, {"--csv"});
+ ASSERT_TRUE(success);
+ ASSERT_NE(content.find("EventCount,EventName"), std::string::npos);
+
+ Report(CALLGRAPH_FP_PERF_DATA, {"--children", "--csv"});
+ ASSERT_TRUE(success);
+ ASSERT_NE(content.find("AccEventCount,SelfEventCount,EventName"), std::string::npos);
+}
+
+TEST_F(ReportCommandTest, dso_path_for_jit_cache) {
+ Report("perf_with_jit_symbol.data", {"--sort", "dso"});
+ ASSERT_TRUE(success);
+ ASSERT_NE(content.find("[JIT app cache]"), std::string::npos);
+
+ // Check if we can filter dso by "[JIT app cache]".
+ Report("perf_with_jit_symbol.data", {"--dsos", "[JIT app cache]"});
+ ASSERT_TRUE(success);
+ ASSERT_NE(content.find("[JIT app cache]"), std::string::npos);
+}
+
+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"));
+}
+
+TEST_F(ReportCommandTest, cpu_option) {
+ Report("perf.data");
+ ASSERT_TRUE(success);
+ ASSERT_EQ(2409, GetSampleCount());
+ Report("perf.data", {"--cpu", "2"});
+ ASSERT_TRUE(success);
+ ASSERT_EQ(603, GetSampleCount());
+ Report("perf.data", {"--cpu", "2-6,16"});
+ ASSERT_TRUE(success);
+ ASSERT_EQ(1806, GetSampleCount());
+ Report("perf.data", {"--cpu", "2-6", "--cpu", "16"});
+ ASSERT_TRUE(success);
+ ASSERT_EQ(1806, GetSampleCount());
+ ASSERT_FALSE(ReportCmd()->Run({"-i", GetTestData("perf.data"), "--cpu", "-2"}));
+}
+
#if defined(__linux__)
#include "event_selection_set.h"
@@ -510,16 +547,14 @@ TEST_F(ReportCommandTest, dwarf_callgraph) {
CreateProcesses(1, &workloads);
std::string pid = std::to_string(workloads[0]->GetPid());
TemporaryFile tmp_file;
- ASSERT_TRUE(
- RecordCmd()->Run({"-p", pid, "-g", "-o", tmp_file.path, "sleep", SLEEP_SEC}));
+ ASSERT_TRUE(RecordCmd()->Run({"-p", pid, "-g", "-o", tmp_file.path, "sleep", SLEEP_SEC}));
ReportRaw(tmp_file.path, {"-g"});
ASSERT_TRUE(success);
}
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);
+ ASSERT_NE(content.find(GetUrlInApk(APK_FILE, NATIVELIB_IN_APK)), std::string::npos);
ASSERT_NE(content.find("Func2"), std::string::npos);
ASSERT_NE(content.find("Func1"), std::string::npos);
ASSERT_NE(content.find("GlobalFunc"), std::string::npos);
@@ -533,8 +568,8 @@ TEST_F(ReportCommandTest, exclude_kernel_callchain) {
CreateProcesses(1, &workloads);
std::string pid = std::to_string(workloads[0]->GetPid());
TemporaryFile tmpfile;
- ASSERT_TRUE(RecordCmd()->Run({"--trace-offcpu", "-e", "cpu-cycles:u", "-p", pid,
- "--duration", "2", "-o", tmpfile.path, "-g"}));
+ ASSERT_TRUE(RecordCmd()->Run({"--trace-offcpu", "-e", "cpu-cycles:u", "-p", pid, "--duration",
+ "2", "-o", tmpfile.path, "-g"}));
ReportRaw(tmpfile.path, {"-g"});
ASSERT_TRUE(success);
ASSERT_EQ(content.find("[kernel.kallsyms]"), std::string::npos);
diff --git a/simpleperf/cmd_stat.cpp b/simpleperf/cmd_stat.cpp
index 1ee6ef81..6a4fb1ab 100644
--- a/simpleperf/cmd_stat.cpp
+++ b/simpleperf/cmd_stat.cpp
@@ -22,6 +22,7 @@
#include <algorithm>
#include <chrono>
+#include <optional>
#include <set>
#include <string>
#include <string_view>
@@ -32,6 +33,7 @@
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
+#include "IOEventLoop.h"
#include "cmd_stat_impl.h"
#include "command.h"
#include "environment.h"
@@ -39,14 +41,13 @@
#include "event_fd.h"
#include "event_selection_set.h"
#include "event_type.h"
-#include "IOEventLoop.h"
#include "utils.h"
#include "workload.h"
-using namespace simpleperf;
-
namespace simpleperf {
+using android::base::Split;
+
static std::vector<std::string> default_measured_event_types{
"cpu-cycles", "stalled-cycles-frontend", "stalled-cycles-backend",
"instructions", "branch-instructions", "branch-misses",
@@ -123,26 +124,29 @@ void CounterSummaries::GenerateComments(double duration_in_sec) {
}
void CounterSummaries::Show(FILE* fp) {
+ bool show_thread = !summaries_.empty() && summaries_[0].thread != nullptr;
+ bool show_cpu = !summaries_.empty() && summaries_[0].cpu != -1;
if (csv_) {
- ShowCSV(fp);
+ ShowCSV(fp, show_thread, show_cpu);
} else {
- ShowText(fp);
+ ShowText(fp, show_thread, show_cpu);
}
}
-void CounterSummaries::ShowCSV(FILE* fp) {
+void CounterSummaries::ShowCSV(FILE* fp, bool show_thread, bool show_cpu) {
for (auto& s : summaries_) {
- if (s.thread != nullptr) {
+ if (show_thread) {
fprintf(fp, "%s,%d,%d,", s.thread->name.c_str(), s.thread->pid, s.thread->tid);
}
+ if (show_cpu) {
+ fprintf(fp, "%d,", s.cpu);
+ }
fprintf(fp, "%s,%s,%s,(%.0f%%)%s\n", s.readable_count.c_str(), s.Name().c_str(),
s.comment.c_str(), 1.0 / s.scale * 100, (s.auto_generated ? " (generated)," : ","));
}
}
-void CounterSummaries::ShowText(FILE* fp) {
- bool show_thread = !summaries_.empty() && summaries_[0].thread != nullptr;
- bool show_cpu = !summaries_.empty() && summaries_[0].cpu != -1;
+void CounterSummaries::ShowText(FILE* fp, bool show_thread, bool show_cpu) {
std::vector<std::string> titles;
if (show_thread) {
@@ -288,8 +292,6 @@ std::string CounterSummaries::GetRateComment(const CounterSummary& s, char sep)
return "";
}
-} // namespace simpleperf
-
namespace {
// devfreq may use performance counters to calculate memory latency (as in
@@ -337,8 +339,9 @@ class DevfreqCounters {
class StatCommand : public Command {
public:
StatCommand()
- : Command("stat", "gather performance counter information",
- // clang-format off
+ : Command(
+ "stat", "gather performance counter information",
+ // clang-format off
"Usage: simpleperf stat [options] [command [command-args]]\n"
" Gather performance counter information of running [command].\n"
" And -a/-p/-t option can be used to change target of counter information.\n"
@@ -380,6 +383,18 @@ class StatCommand : public Command {
"--per-thread Print counters for each thread.\n"
"-p pid1,pid2,... Stat events on existing processes. Mutually exclusive with -a.\n"
"-t tid1,tid2,... Stat events on existing threads. Mutually exclusive with -a.\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"
+" the order of keys used to sort the report.\n"
+" Possible keys include:\n"
+" count -- event count for each entry\n"
+" count_per_thread -- event count for a thread on all cpus\n"
+" cpu -- cpu id\n"
+" pid -- process id\n"
+" tid -- thread id\n"
+" comm -- thread name\n"
+" The default sort keys are:\n"
+" count_per_thread,tid,cpu,count\n"
#if defined(__ANDROID__)
"--use-devfreq-counters On devices with Qualcomm SOCs, some hardware counters may be used\n"
" to monitor memory latency (in drivers/devfreq/arm-memlat-mon.c),\n"
@@ -396,8 +411,8 @@ class StatCommand : public Command {
"--out-fd <fd> Write output to a file descriptor.\n"
"--stop-signal-fd <fd> Stop stating when fd is readable.\n"
#endif
- // clang-format on
- ),
+ // clang-format on
+ ),
verbose_mode_(false),
system_wide_collection_(false),
child_inherit_(true),
@@ -409,6 +424,8 @@ class StatCommand : public Command {
in_app_context_(false) {
// Die if parent exits.
prctl(PR_SET_PDEATHSIG, SIGHUP, 0, 0, 0);
+ // Set default sort keys. Full key list is in BuildSummaryComparator().
+ sort_keys_ = {"count_per_thread", "tid", "cpu", "count"};
}
bool Run(const std::vector<std::string>& args);
@@ -420,8 +437,7 @@ class StatCommand : public Command {
void SetEventSelectionFlags();
void MonitorEachThread();
void AdjustToIntervalOnlyValues(std::vector<CountersInfo>& counters);
- bool ShowCounters(const std::vector<CountersInfo>& counters,
- double duration_in_sec, FILE* fp);
+ bool ShowCounters(const std::vector<CountersInfo>& counters, double duration_in_sec, FILE* fp);
bool verbose_mode_;
bool system_wide_collection_;
@@ -444,6 +460,9 @@ class StatCommand : public Command {
bool report_per_thread_ = false;
// used to report event count for each thread
std::unordered_map<pid_t, ThreadInfo> thread_info_;
+ // used to sort report
+ std::vector<std::string> sort_keys_;
+ std::optional<SummaryComparator> summary_comparator_;
};
bool StatCommand::Run(const std::vector<std::string>& args) {
@@ -499,8 +518,7 @@ bool StatCommand::Run(const std::vector<std::string>& args) {
std::set<pid_t> pids = WaitForAppProcesses(app_package_name_);
event_selection_set_.AddMonitoredProcesses(pids);
} else {
- LOG(ERROR)
- << "No threads to monitor. Try `simpleperf help stat` for help\n";
+ LOG(ERROR) << "No threads to monitor. Try `simpleperf help stat` for help\n";
return false;
}
} else {
@@ -546,9 +564,7 @@ bool StatCommand::Run(const std::vector<std::string>& args) {
if (need_to_check_targets && !event_selection_set_.StopWhenNoMoreTargets()) {
return false;
}
- auto exit_loop_callback = [loop]() {
- return loop->ExitLoop();
- };
+ auto exit_loop_callback = [loop]() { return loop->ExitLoop(); };
if (!loop->AddSignalEvents({SIGCHLD, SIGINT, SIGTERM, SIGHUP}, exit_loop_callback)) {
return false;
}
@@ -563,26 +579,23 @@ bool StatCommand::Run(const std::vector<std::string>& args) {
}
}
auto print_counters = [&]() {
- auto end_time = std::chrono::steady_clock::now();
- if (!event_selection_set_.ReadCounters(&counters)) {
- return false;
- }
- double duration_in_sec =
- std::chrono::duration_cast<std::chrono::duration<double>>(end_time -
- start_time)
- .count();
- if (interval_only_values_) {
- AdjustToIntervalOnlyValues(counters);
- }
- if (!ShowCounters(counters, duration_in_sec, fp)) {
- return false;
- }
- return true;
+ auto end_time = std::chrono::steady_clock::now();
+ if (!event_selection_set_.ReadCounters(&counters)) {
+ return false;
+ }
+ double duration_in_sec =
+ std::chrono::duration_cast<std::chrono::duration<double>>(end_time - start_time).count();
+ if (interval_only_values_) {
+ AdjustToIntervalOnlyValues(counters);
+ }
+ if (!ShowCounters(counters, duration_in_sec, fp)) {
+ return false;
+ }
+ return true;
};
if (interval_in_ms_ != 0) {
- if (!loop->AddPeriodicEvent(SecondToTimeval(interval_in_ms_ / 1000.0),
- print_counters)) {
+ if (!loop->AddPeriodicEvent(SecondToTimeval(interval_in_ms_ / 1000.0), print_counters)) {
return false;
}
}
@@ -605,113 +618,101 @@ 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::set<pid_t> tid_set;
- size_t i;
- for (i = 0; i < args.size() && args[i].size() > 0 && args[i][0] == '-'; ++i) {
- if (args[i] == "-a") {
- system_wide_collection_ = true;
- } else if (args[i] == "--app") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- app_package_name_ = args[i];
- } else if (args[i] == "--cpu") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- cpus_ = GetCpusFromString(args[i]);
- } else if (args[i] == "--csv") {
- csv_ = true;
- } else if (args[i] == "--duration") {
- if (!GetDoubleOption(args, &i, &duration_in_sec_, 1e-9)) {
- return false;
- }
- } else if (args[i] == "--interval") {
- if (!GetDoubleOption(args, &i, &interval_in_ms_, 1e-9)) {
- return false;
- }
- } else if (args[i] == "--interval-only-values") {
- interval_only_values_ = true;
- } else if (args[i] == "-e") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- std::vector<std::string> event_types = android::base::Split(args[i], ",");
- for (auto& event_type : event_types) {
- if (!event_selection_set_.AddEventType(event_type)) {
- return false;
- }
- }
- } else if (args[i] == "--group") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- std::vector<std::string> event_types = android::base::Split(args[i], ",");
- if (!event_selection_set_.AddEventGroup(event_types)) {
- return false;
- }
- } else if (args[i] == "--in-app") {
- in_app_context_ = true;
- } else if (args[i] == "--no-inherit") {
- child_inherit_ = false;
- } else if (args[i] == "-o") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- output_filename_ = args[i];
- } else if (args[i] == "--out-fd") {
- int fd;
- if (!GetUintOption(args, &i, &fd)) {
- return false;
- }
- out_fd_.reset(fd);
- } else if (args[i] == "--per-core") {
- report_per_core_ = true;
- } else if (args[i] == "--per-thread") {
- report_per_thread_ = true;
- } else if (args[i] == "-p") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- std::set<pid_t> pids;
- if (!GetValidThreadsFromThreadString(args[i], &pids)) {
- return false;
- }
- event_selection_set_.AddMonitoredProcesses(pids);
- } else if (args[i] == "--stop-signal-fd") {
- int fd;
- if (!GetUintOption(args, &i, &fd)) {
- return false;
- }
- stop_signal_fd_.reset(fd);
- } else if (args[i] == "-t") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- std::set<pid_t> tids;
- if (!GetValidThreadsFromThreadString(args[i], &tids)) {
- return false;
- }
- event_selection_set_.AddMonitoredThreads(tids);
- } else if (args[i] == "--tracepoint-events") {
- if (!NextArgumentOrError(args, &i)) {
- return false;
- }
- if (!SetTracepointEventsFilePath(args[i])) {
+ OptionValueMap options;
+ std::vector<std::pair<OptionName, OptionValue>> ordered_options;
+
+ if (!PreprocessOptions(args, GetStatCmdOptionFormats(), &options, &ordered_options,
+ non_option_args)) {
+ return false;
+ }
+
+ // Process options.
+ system_wide_collection_ = options.PullBoolValue("-a");
+
+ 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)) {
+ return false;
+ }
+ if (!options.PullDoubleValue("--interval", &interval_in_ms_, 1e-9)) {
+ return false;
+ }
+ 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)) {
return false;
}
-#if defined(__ANDROID__)
- } else if (args[i] == "--use-devfreq-counters") {
- use_devfreq_counters_ = true;
-#endif
- } else if (args[i] == "--verbose") {
- verbose_mode_ = true;
+ }
+ }
+
+ 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) {
+ output_filename_ = *value->str_value;
+ }
+ if (auto value = options.PullValue("--out-fd"); value) {
+ out_fd_.reset(static_cast<int>(value->uint_value));
+ }
+
+ report_per_core_ = options.PullBoolValue("--per-core");
+ report_per_thread_ = options.PullBoolValue("--per-thread");
+
+ for (const OptionValue& value : options.PullValues("-p")) {
+ if (auto pids = GetTidsFromString(*value.str_value, true); pids) {
+ event_selection_set_.AddMonitoredProcesses(pids.value());
} else {
- ReportUnknownOption(args, i);
return false;
}
}
+ if (auto value = options.PullValue("--sort"); value) {
+ sort_keys_ = Split(*value->str_value, ",");
+ }
+
+ if (auto value = options.PullValue("--stop-signal-fd"); value) {
+ stop_signal_fd_.reset(static_cast<int>(value->uint_value));
+ }
+
+ for (const OptionValue& value : options.PullValues("-t")) {
+ if (auto tids = GetTidsFromString(*value.str_value, true); tids) {
+ event_selection_set_.AddMonitoredThreads(tids.value());
+ } else {
+ return false;
+ }
+ }
+
+ if (auto value = options.PullValue("--tracepoint-events"); value) {
+ if (!EventTypeManager::Instance().ReadTracepointsFromFile(*value->str_value)) {
+ return false;
+ }
+ }
+
+ use_devfreq_counters_ = options.PullBoolValue("--use-devfreq-counters");
+ verbose_mode_ = options.PullBoolValue("--verbose");
+
+ CHECK(options.values.empty());
+ CHECK(ordered_options.empty());
+
if (system_wide_collection_ && event_selection_set_.HasMonitoredTarget()) {
LOG(ERROR) << "Stat system wide and existing processes/threads can't be "
"used at the same time.";
@@ -722,9 +723,11 @@ bool StatCommand::ParseOptions(const std::vector<std::string>& args,
return false;
}
- non_option_args->clear();
- for (; i < args.size(); ++i) {
- non_option_args->push_back(args[i]);
+ if (report_per_core_ || report_per_thread_) {
+ summary_comparator_ = BuildSummaryComparator(sort_keys_, report_per_thread_, report_per_core_);
+ if (!summary_comparator_) {
+ return false;
+ }
}
return true;
}
@@ -734,8 +737,7 @@ bool StatCommand::AddDefaultMeasuredEventTypes() {
// It is not an error when some event types in the default list are not
// supported by the kernel.
const EventType* type = FindEventTypeByName(name);
- if (type != nullptr &&
- IsEventAttrSupported(CreateDefaultPerfEventAttr(*type), name)) {
+ if (type != nullptr && IsEventAttrSupported(CreateDefaultPerfEventAttr(*type), name)) {
if (!event_selection_set_.AddEventType(name)) {
return false;
}
@@ -799,8 +801,8 @@ void StatCommand::AdjustToIntervalOnlyValues(std::vector<CountersInfo>& counters
}
}
-bool StatCommand::ShowCounters(const std::vector<CountersInfo>& counters,
- double duration_in_sec, FILE* fp) {
+bool StatCommand::ShowCounters(const std::vector<CountersInfo>& counters, double duration_in_sec,
+ FILE* fp) {
if (csv_) {
fprintf(fp, "Performance counter statistics,\n");
} else {
@@ -811,26 +813,26 @@ bool StatCommand::ShowCounters(const std::vector<CountersInfo>& counters,
for (auto& counters_info : counters) {
for (auto& counter_info : counters_info.counters) {
if (csv_) {
- fprintf(fp, "%s,tid,%d,cpu,%d,count,%" PRIu64 ",time_enabled,%" PRIu64
- ",time running,%" PRIu64 ",id,%" PRIu64 ",\n",
- counters_info.event_name.c_str(), counter_info.tid,
- counter_info.cpu, counter_info.counter.value,
- counter_info.counter.time_enabled,
+ fprintf(fp,
+ "%s,tid,%d,cpu,%d,count,%" PRIu64 ",time_enabled,%" PRIu64
+ ",time running,%" PRIu64 ",id,%" PRIu64 ",\n",
+ counters_info.event_name.c_str(), counter_info.tid, counter_info.cpu,
+ counter_info.counter.value, counter_info.counter.time_enabled,
counter_info.counter.time_running, counter_info.counter.id);
} else {
fprintf(fp,
"%s(tid %d, cpu %d): count %" PRIu64 ", time_enabled %" PRIu64
", time running %" PRIu64 ", id %" PRIu64 "\n",
- counters_info.event_name.c_str(), counter_info.tid,
- counter_info.cpu, counter_info.counter.value,
- counter_info.counter.time_enabled,
+ counters_info.event_name.c_str(), counter_info.tid, counter_info.cpu,
+ counter_info.counter.value, counter_info.counter.time_enabled,
counter_info.counter.time_running, counter_info.counter.id);
}
}
}
}
- CounterSummaryBuilder builder(report_per_thread_, report_per_core_, csv_, thread_info_);
+ CounterSummaryBuilder builder(report_per_thread_, report_per_core_, csv_, thread_info_,
+ summary_comparator_);
for (const auto& info : counters) {
builder.AddCountersForOneEventType(info);
}
@@ -880,6 +882,7 @@ bool StatCommand::ShowCounters(const std::vector<CountersInfo>& counters,
} // namespace
void RegisterStatCommand() {
- RegisterCommand("stat",
- [] { return std::unique_ptr<Command>(new StatCommand); });
+ RegisterCommand("stat", [] { return std::unique_ptr<Command>(new StatCommand); });
}
+
+} // namespace simpleperf
diff --git a/simpleperf/cmd_stat_impl.h b/simpleperf/cmd_stat_impl.h
index a7f60df7..facd85eb 100644
--- a/simpleperf/cmd_stat_impl.h
+++ b/simpleperf/cmd_stat_impl.h
@@ -20,12 +20,15 @@
#include <sys/types.h>
#include <algorithm>
+#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
#include <android-base/stringprintf.h>
+#include "SampleComparator.h"
+#include "command.h"
#include "event_selection_set.h"
namespace simpleperf {
@@ -83,6 +86,9 @@ struct CounterSummary {
std::string comment;
bool auto_generated;
+ // used to sort summaries by count_per_thread
+ uint64_t count_per_thread = 0;
+
CounterSummary(const std::string& type_name, const std::string& modifier, uint32_t group_id,
const ThreadInfo* thread, int cpu, uint64_t count, uint64_t runtime_in_ns,
double scale, bool auto_generated, bool csv)
@@ -149,15 +155,61 @@ struct CounterSummary {
}
};
+BUILD_COMPARE_VALUE_FUNCTION_REVERSE(CompareSummaryCount, count);
+BUILD_COMPARE_VALUE_FUNCTION_REVERSE(CompareSummaryCountPerThread, count_per_thread);
+BUILD_COMPARE_VALUE_FUNCTION(CompareSummaryCpu, cpu);
+BUILD_COMPARE_VALUE_FUNCTION(CompareSummaryPid, thread->pid);
+BUILD_COMPARE_VALUE_FUNCTION(CompareSummaryTid, thread->tid);
+BUILD_COMPARE_VALUE_FUNCTION(CompareSummaryComm, thread->name);
+
+using SummaryComparator = SampleComparator<CounterSummary>;
+
+inline std::optional<SummaryComparator> BuildSummaryComparator(const std::vector<std::string>& keys,
+ bool report_per_thread,
+ bool report_per_core) {
+ SummaryComparator comparator;
+ for (auto& key : keys) {
+ if (key == "count") {
+ comparator.AddCompareFunction(CompareSummaryCount);
+ } else if (key == "count_per_thread") {
+ if (report_per_thread) {
+ comparator.AddCompareFunction(CompareSummaryCountPerThread);
+ }
+ } else if (key == "cpu") {
+ if (report_per_core) {
+ comparator.AddCompareFunction(CompareSummaryCpu);
+ }
+ } else if (key == "pid") {
+ if (report_per_thread) {
+ comparator.AddCompareFunction(CompareSummaryPid);
+ }
+ } else if (key == "tid") {
+ if (report_per_thread) {
+ comparator.AddCompareFunction(CompareSummaryTid);
+ }
+ } else if (key == "comm") {
+ if (report_per_thread) {
+ comparator.AddCompareFunction(CompareSummaryComm);
+ }
+ } else {
+ LOG(ERROR) << "Unknown sort key: " << key;
+ return {};
+ }
+ }
+ return comparator;
+}
+
// Build a vector of CounterSummary.
class CounterSummaryBuilder {
public:
CounterSummaryBuilder(bool report_per_thread, bool report_per_core, bool csv,
- const std::unordered_map<pid_t, ThreadInfo>& thread_map)
+ const std::unordered_map<pid_t, ThreadInfo>& thread_map,
+ const std::optional<SummaryComparator>& comparator)
: report_per_thread_(report_per_thread),
report_per_core_(report_per_core),
csv_(csv),
- thread_map_(thread_map) {}
+ thread_map_(thread_map),
+ summary_comparator_(comparator) {}
void AddCountersForOneEventType(const CountersInfo& info) {
std::unordered_map<uint64_t, CounterSum> sum_map;
@@ -214,34 +266,31 @@ class CounterSummaryBuilder {
void SortSummaries(std::vector<CounterSummary>::iterator begin,
std::vector<CounterSummary>::iterator end) {
- if (report_per_thread_ && report_per_core_) {
- // First sort by event count for all cpus in a thread, then sort by event count of each cpu.
- std::unordered_map<pid_t, uint64_t> count_per_thread;
- for (auto it = begin; it != end; ++it) {
- count_per_thread[it->thread->tid] += it->count;
- }
- std::sort(begin, end, [&](const CounterSummary& s1, const CounterSummary& s2) {
- pid_t tid1 = s1.thread->tid;
- pid_t tid2 = s2.thread->tid;
- if (tid1 != tid2) {
- if (count_per_thread[tid1] != count_per_thread[tid2]) {
- return count_per_thread[tid1] > count_per_thread[tid2];
- }
- return tid1 < tid2;
+ // Generate count_per_thread value for sorting.
+ if (report_per_thread_) {
+ if (report_per_core_) {
+ std::unordered_map<pid_t, uint64_t> count_per_thread;
+ for (auto it = begin; it != end; ++it) {
+ count_per_thread[it->thread->tid] += it->count;
}
- return s1.count > s2.count;
- });
- } else {
- std::sort(begin, end, [](const CounterSummary& s1, const CounterSummary& s2) {
- return s1.count > s2.count;
- });
+ for (auto it = begin; it != end; ++it) {
+ it->count_per_thread = count_per_thread[it->thread->tid];
+ }
+ } else {
+ for (auto it = begin; it != end; ++it) {
+ it->count_per_thread = it->count;
+ }
+ }
}
+
+ std::sort(begin, end, summary_comparator_.value());
};
const bool report_per_thread_;
const bool report_per_core_;
const bool csv_;
const std::unordered_map<pid_t, ThreadInfo>& thread_map_;
+ const std::optional<SummaryComparator>& summary_comparator_;
std::vector<CounterSummary> summaries_;
};
@@ -262,17 +311,48 @@ class CounterSummaries {
void AutoGenerateSummaries();
void GenerateComments(double duration_in_sec);
void Show(FILE* fp);
- void ShowCSV(FILE* fp);
- void ShowText(FILE* fp);
private:
std::string GetCommentForSummary(const CounterSummary& s, double duration_in_sec);
std::string GetRateComment(const CounterSummary& s, char sep);
bool FindRunningTimeForSummary(const CounterSummary& summary, double* running_time_in_sec);
+ void ShowCSV(FILE* fp, bool show_thread, bool show_core);
+ void ShowText(FILE* fp, bool show_thread, bool show_core);
private:
std::vector<CounterSummary> summaries_;
bool csv_;
};
+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}},
+ {"--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}},
+ {"--in-app", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::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}},
+ {"-p", {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}},
+ {"--per-core", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--per-thread", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--sort", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--stop-signal-fd", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::CHECK_FD}},
+ {"-t", {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}},
+ {"--tracepoint-events",
+ {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::CHECK_PATH}},
+ {"--use-devfreq-counters",
+ {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::NOT_ALLOWED}},
+ {"--verbose", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ };
+ return option_formats;
+}
+
} // namespace simpleperf \ No newline at end of file
diff --git a/simpleperf/cmd_stat_test.cpp b/simpleperf/cmd_stat_test.cpp
index 6b6196a9..3e68ac6b 100644
--- a/simpleperf/cmd_stat_test.cpp
+++ b/simpleperf/cmd_stat_test.cpp
@@ -35,7 +35,9 @@ static std::unique_ptr<Command> StatCmd() {
return CreateCommandInstance("stat");
}
-TEST(stat_cmd, no_options) { ASSERT_TRUE(StatCmd()->Run({"sleep", "1"})); }
+TEST(stat_cmd, no_options) {
+ ASSERT_TRUE(StatCmd()->Run({"sleep", "1"}));
+}
TEST(stat_cmd, event_option) {
ASSERT_TRUE(StatCmd()->Run({"-e", "cpu-clock,task-clock", "sleep", "1"}));
@@ -50,8 +52,7 @@ TEST(stat_cmd, verbose_option) {
}
TEST(stat_cmd, tracepoint_event) {
- TEST_IN_ROOT(ASSERT_TRUE(
- StatCmd()->Run({"-a", "-e", "sched:sched_switch", "sleep", "1"})));
+ TEST_IN_ROOT(ASSERT_TRUE(StatCmd()->Run({"-a", "-e", "sched:sched_switch", "sleep", "1"})));
}
TEST(stat_cmd, rN_event) {
@@ -86,25 +87,23 @@ TEST(stat_cmd, pmu_event) {
GTEST_LOG_(INFO) << "Omit arch " << GetBuildArch();
return;
}
- TEST_IN_ROOT(ASSERT_TRUE(
- StatCmd()->Run({"-a", "-e", event_string, "sleep", "1"})));
+ TEST_IN_ROOT(ASSERT_TRUE(StatCmd()->Run({"-a", "-e", event_string, "sleep", "1"})));
}
TEST(stat_cmd, event_modifier) {
TEST_REQUIRE_HW_COUNTER();
- ASSERT_TRUE(
- StatCmd()->Run({"-e", "cpu-cycles:u,cpu-cycles:k", "sleep", "1"}));
+ ASSERT_TRUE(StatCmd()->Run({"-e", "cpu-cycles:u,cpu-cycles:k", "sleep", "1"}));
}
void RunWorkloadFunction() {
while (true) {
- for (volatile int i = 0; i < 10000; ++i);
+ for (volatile int i = 0; i < 10000; ++i)
+ ;
usleep(1);
}
}
-void CreateProcesses(size_t count,
- std::vector<std::unique_ptr<Workload>>* workloads) {
+void CreateProcesses(size_t count, std::vector<std::unique_ptr<Workload>>* workloads) {
workloads->clear();
// Create workloads run longer than profiling time.
for (size_t i = 0; i < count; ++i) {
@@ -119,8 +118,8 @@ void CreateProcesses(size_t count,
TEST(stat_cmd, existing_processes) {
std::vector<std::unique_ptr<Workload>> workloads;
CreateProcesses(2, &workloads);
- std::string pid_list = android::base::StringPrintf(
- "%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
+ std::string pid_list =
+ android::base::StringPrintf("%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
ASSERT_TRUE(StatCmd()->Run({"-p", pid_list, "sleep", "1"}));
}
@@ -128,8 +127,8 @@ TEST(stat_cmd, existing_threads) {
std::vector<std::unique_ptr<Workload>> workloads;
CreateProcesses(2, &workloads);
// Process id can be used as thread id in linux.
- std::string tid_list = android::base::StringPrintf(
- "%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
+ std::string tid_list =
+ android::base::StringPrintf("%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
ASSERT_TRUE(StatCmd()->Run({"-t", tid_list, "sleep", "1"}));
}
@@ -140,8 +139,7 @@ TEST(stat_cmd, no_monitored_threads) {
TEST(stat_cmd, group_option) {
TEST_REQUIRE_HW_COUNTER();
- ASSERT_TRUE(
- StatCmd()->Run({"--group", "cpu-clock,page-faults", "sleep", "1"}));
+ ASSERT_TRUE(StatCmd()->Run({"--group", "cpu-clock,page-faults", "sleep", "1"}));
ASSERT_TRUE(StatCmd()->Run({"--group", "cpu-cycles,instructions", "--group",
"cpu-cycles:u,instructions:u", "--group",
"cpu-cycles:k,instructions:k", "sleep", "1"}));
@@ -150,8 +148,8 @@ TEST(stat_cmd, group_option) {
TEST(stat_cmd, auto_generated_summary) {
TEST_REQUIRE_HW_COUNTER();
TemporaryFile tmp_file;
- ASSERT_TRUE(StatCmd()->Run({"--group", "instructions:u,instructions:k", "-o",
- tmp_file.path, "sleep", "1"}));
+ ASSERT_TRUE(StatCmd()->Run(
+ {"--group", "instructions:u,instructions:k", "-o", tmp_file.path, "sleep", "1"}));
std::string s;
ASSERT_TRUE(android::base::ReadFileToString(tmp_file.path, &s));
size_t pos = s.find("instructions:u");
@@ -164,24 +162,22 @@ TEST(stat_cmd, auto_generated_summary) {
}
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.2", "-p", std::to_string(getpid()), "--in-app"}));
ASSERT_TRUE(StatCmd()->Run({"--duration", "1", "sleep", "2"}));
}
TEST(stat_cmd, interval_option) {
TemporaryFile tmp_file;
- ASSERT_TRUE(
- StatCmd()->Run({"--interval", "500.0", "--duration", "1.2", "-o",
- tmp_file.path, "sleep", "2"}));
+ ASSERT_TRUE(StatCmd()->Run(
+ {"--interval", "500.0", "--duration", "1.2", "-o", tmp_file.path, "sleep", "2"}));
std::string s;
ASSERT_TRUE(android::base::ReadFileToString(tmp_file.path, &s));
size_t count = 0;
size_t pos = 0;
std::string subs = "statistics:";
- while((pos = s.find(subs, pos)) != s.npos) {
+ while ((pos = s.find(subs, pos)) != s.npos) {
pos += subs.size();
- ++count ;
+ ++count;
}
ASSERT_EQ(count, 2UL);
}
@@ -192,8 +188,8 @@ TEST(stat_cmd, interval_option_in_system_wide) {
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"})));
+ TEST_IN_ROOT(ASSERT_TRUE(
+ StatCmd()->Run({"-a", "--interval", "100", "--interval-only-values", "--duration", "0.3"})));
}
TEST(stat_cmd, no_modifier_for_clock_events) {
@@ -221,7 +217,8 @@ TEST(stat_cmd, stop_when_no_more_targets) {
sleep(1);
});
thread.detach();
- while (tid == 0);
+ while (tid == 0)
+ ;
ASSERT_TRUE(StatCmd()->Run({"-t", std::to_string(tid), "--in-app"}));
}
@@ -253,8 +250,8 @@ TEST(stat_cmd, calculating_cpu_frequency) {
if (line.find("task-clock") != std::string::npos) {
ASSERT_EQ(sscanf(line.c_str(), "%lf(ms)", &task_clock_in_ms), 1);
} else if (line.find("cpu-cycles") != std::string::npos) {
- ASSERT_EQ(sscanf(line.c_str(), "%" SCNu64 ",cpu-cycles,%lf", &cpu_cycle_count,
- &cpu_frequency), 2);
+ ASSERT_EQ(
+ sscanf(line.c_str(), "%" SCNu64 ",cpu-cycles,%lf", &cpu_cycle_count, &cpu_frequency), 2);
}
}
ASSERT_NE(task_clock_in_ms, 0.0f);
@@ -276,10 +273,12 @@ TEST(stat_cmd, set_comm_in_another_thread) {
std::thread child([&]() {
child_tid = gettid();
// stay on a cpu to make the monitored events of the child thread on that cpu.
- while (!stop_child) {}
+ while (!stop_child) {
+ }
});
- while (child_tid == 0) {}
+ while (child_tid == 0) {
+ }
{
EventSelectionSet set(true);
@@ -345,6 +344,11 @@ TEST(stat_cmd, per_core_option) {
TEST_IN_ROOT(StatCmd()->Run({"--per-core", "-a", "--duration", "0.1"}));
}
+TEST(stat_cmd, sort_option) {
+ ASSERT_TRUE(
+ StatCmd()->Run({"--per-thread", "--per-core", "--sort", "cpu,count", "sleep", "0.1"}));
+}
+
TEST(stat_cmd, counter_sum) {
PerfCounter counter;
counter.value = 1;
@@ -371,30 +375,44 @@ TEST(stat_cmd, counter_sum) {
class StatCmdSummaryBuilderTest : public ::testing::Test {
protected:
- void AddCounter(int event_id, pid_t tid, int cpu, int value, int time_enabled, int time_running) {
- if (thread_map_.count(tid) == 0) {
- ThreadInfo& thread = thread_map_[tid];
- thread.pid = thread.tid = tid;
- thread.name = "thread" + std::to_string(tid);
+ struct CounterArg {
+ int event_id = 0;
+ int tid = 0;
+ int cpu = 0;
+ int value = 1;
+ int time_enabled = 1;
+ int time_running = 1;
+ };
+
+ void SetUp() override { sort_keys_ = {"count_per_thread", "tid", "cpu", "count"}; }
+
+ void AddCounter(const CounterArg& arg) {
+ if (thread_map_.count(arg.tid) == 0) {
+ ThreadInfo& thread = thread_map_[arg.tid];
+ thread.pid = thread.tid = arg.tid;
+ thread.name = "thread" + std::to_string(arg.tid);
}
- if (event_id >= counters_.size()) {
- counters_.resize(event_id + 1);
- counters_[event_id].group_id = 0;
- counters_[event_id].event_name = "event" + std::to_string(event_id);
+ if (arg.event_id >= counters_.size()) {
+ counters_.resize(arg.event_id + 1);
+ counters_[arg.event_id].group_id = 0;
+ counters_[arg.event_id].event_name = "event" + std::to_string(arg.event_id);
}
- CountersInfo& info = counters_[event_id];
+ CountersInfo& info = counters_[arg.event_id];
info.counters.resize(info.counters.size() + 1);
CounterInfo& counter = info.counters.back();
- counter.tid = tid;
- counter.cpu = cpu;
+ counter.tid = arg.tid;
+ counter.cpu = arg.cpu;
counter.counter.id = 0;
- counter.counter.value = value;
- counter.counter.time_enabled = time_enabled;
- counter.counter.time_running = time_running;
+ counter.counter.value = arg.value;
+ counter.counter.time_enabled = arg.time_enabled;
+ counter.counter.time_running = arg.time_running;
}
std::vector<CounterSummary> BuildSummary(bool report_per_thread, bool report_per_core) {
- CounterSummaryBuilder builder(report_per_thread, report_per_core, false, thread_map_);
+ std::optional<SummaryComparator> comparator =
+ BuildSummaryComparator(sort_keys_, report_per_thread, report_per_core);
+ CounterSummaryBuilder builder(report_per_thread, report_per_core, false, thread_map_,
+ comparator);
for (auto& info : counters_) {
builder.AddCountersForOneEventType(info);
}
@@ -403,11 +421,12 @@ class StatCmdSummaryBuilderTest : public ::testing::Test {
std::unordered_map<pid_t, ThreadInfo> thread_map_;
std::vector<CountersInfo> counters_;
+ std::vector<std::string> sort_keys_;
};
TEST_F(StatCmdSummaryBuilderTest, multiple_events) {
- AddCounter(0, 0, 0, 1, 1, 1);
- AddCounter(1, 0, 0, 2, 2, 2);
+ AddCounter({.event_id = 0, .value = 1, .time_enabled = 1, .time_running = 1});
+ AddCounter({.event_id = 1, .value = 2, .time_enabled = 2, .time_running = 2});
std::vector<CounterSummary> summaries = BuildSummary(false, false);
ASSERT_EQ(summaries.size(), 2);
ASSERT_EQ(summaries[0].type_name, "event0");
@@ -419,10 +438,10 @@ TEST_F(StatCmdSummaryBuilderTest, multiple_events) {
}
TEST_F(StatCmdSummaryBuilderTest, default_aggregate) {
- AddCounter(0, 0, 0, 1, 1, 1);
- AddCounter(0, 0, 1, 1, 1, 1);
- AddCounter(0, 1, 0, 1, 1, 1);
- AddCounter(0, 1, 1, 2, 2, 1);
+ 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});
+ AddCounter({.tid = 1, .cpu = 0, .value = 1, .time_enabled = 1, .time_running = 1});
+ AddCounter({.tid = 1, .cpu = 1, .value = 2, .time_enabled = 2, .time_running = 1});
std::vector<CounterSummary> summaries = BuildSummary(false, false);
ASSERT_EQ(summaries.size(), 1);
ASSERT_EQ(summaries[0].count, 5);
@@ -430,10 +449,10 @@ TEST_F(StatCmdSummaryBuilderTest, default_aggregate) {
}
TEST_F(StatCmdSummaryBuilderTest, per_thread_aggregate) {
- AddCounter(0, 0, 0, 1, 1, 1);
- AddCounter(0, 0, 1, 1, 1, 1);
- AddCounter(0, 1, 0, 1, 1, 1);
- AddCounter(0, 1, 1, 2, 2, 1);
+ 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});
+ AddCounter({.tid = 1, .cpu = 0, .value = 1, .time_enabled = 1, .time_running = 1});
+ AddCounter({.tid = 1, .cpu = 1, .value = 2, .time_enabled = 2, .time_running = 1});
std::vector<CounterSummary> summaries = BuildSummary(true, false);
ASSERT_EQ(summaries.size(), 2);
ASSERT_EQ(summaries[0].thread->tid, 1);
@@ -447,47 +466,88 @@ TEST_F(StatCmdSummaryBuilderTest, per_thread_aggregate) {
}
TEST_F(StatCmdSummaryBuilderTest, per_core_aggregate) {
- AddCounter(0, 0, 0, 1, 1, 1);
- AddCounter(0, 0, 1, 1, 1, 1);
- AddCounter(0, 1, 0, 1, 1, 1);
- AddCounter(0, 1, 1, 2, 2, 1);
+ 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});
+ AddCounter({.tid = 1, .cpu = 0, .value = 1, .time_enabled = 1, .time_running = 1});
+ AddCounter({.tid = 1, .cpu = 1, .value = 2, .time_enabled = 2, .time_running = 1});
std::vector<CounterSummary> summaries = BuildSummary(false, true);
- ASSERT_EQ(summaries.size(), 2);
ASSERT_TRUE(summaries[0].thread == nullptr);
- ASSERT_EQ(summaries[0].cpu, 1);
- ASSERT_EQ(summaries[0].count, 3);
- ASSERT_NEAR(summaries[0].scale, 1.5, 1e-5);
+ ASSERT_EQ(summaries[0].cpu, 0);
+ ASSERT_EQ(summaries[0].count, 2);
+ ASSERT_NEAR(summaries[0].scale, 1.0, 1e-5);
+ ASSERT_EQ(summaries.size(), 2);
ASSERT_TRUE(summaries[1].thread == nullptr);
- ASSERT_EQ(summaries[1].cpu, 0);
- ASSERT_EQ(summaries[1].count, 2);
- ASSERT_NEAR(summaries[1].scale, 1.0, 1e-5);
+ ASSERT_EQ(summaries[1].cpu, 1);
+ ASSERT_EQ(summaries[1].count, 3);
+ ASSERT_NEAR(summaries[1].scale, 1.5, 1e-5);
}
TEST_F(StatCmdSummaryBuilderTest, per_thread_core_aggregate) {
- AddCounter(0, 0, 0, 1, 1, 1);
- AddCounter(0, 0, 1, 2, 1, 1);
- AddCounter(0, 1, 0, 3, 1, 1);
- AddCounter(0, 1, 1, 4, 2, 1);
+ 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});
+ AddCounter({.tid = 1, .cpu = 0, .value = 3, .time_enabled = 1, .time_running = 1});
+ AddCounter({.tid = 1, .cpu = 1, .value = 4, .time_enabled = 2, .time_running = 1});
std::vector<CounterSummary> summaries = BuildSummary(true, true);
ASSERT_EQ(summaries.size(), 4);
ASSERT_EQ(summaries[0].thread->tid, 1);
- ASSERT_EQ(summaries[0].cpu, 1);
- ASSERT_EQ(summaries[0].count, 4);
- ASSERT_NEAR(summaries[0].scale, 2.0, 1e-5);
+ ASSERT_EQ(summaries[0].cpu, 0);
+ ASSERT_EQ(summaries[0].count, 3);
+ ASSERT_NEAR(summaries[0].scale, 1.0, 1e-5);
ASSERT_EQ(summaries[1].thread->tid, 1);
- ASSERT_EQ(summaries[1].cpu, 0);
- ASSERT_EQ(summaries[1].count, 3);
- ASSERT_NEAR(summaries[1].scale, 1.0, 1e-5);
+ ASSERT_EQ(summaries[1].cpu, 1);
+ ASSERT_EQ(summaries[1].count, 4);
+ ASSERT_NEAR(summaries[1].scale, 2.0, 1e-5);
ASSERT_EQ(summaries[2].thread->tid, 0);
- ASSERT_EQ(summaries[2].cpu, 1);
- ASSERT_EQ(summaries[2].count, 2);
+ ASSERT_EQ(summaries[2].cpu, 0);
+ ASSERT_EQ(summaries[2].count, 1);
ASSERT_NEAR(summaries[2].scale, 1.0, 1e-5);
ASSERT_EQ(summaries[3].thread->tid, 0);
- ASSERT_EQ(summaries[3].cpu, 0);
- ASSERT_EQ(summaries[3].count, 1);
+ ASSERT_EQ(summaries[3].cpu, 1);
+ ASSERT_EQ(summaries[3].count, 2);
ASSERT_NEAR(summaries[3].scale, 1.0, 1e-5);
}
+TEST_F(StatCmdSummaryBuilderTest, sort_key_count) {
+ sort_keys_ = {"count"};
+ AddCounter({.tid = 0, .cpu = 0, .value = 1});
+ AddCounter({.tid = 1, .cpu = 1, .value = 2});
+ std::vector<CounterSummary> summaries = BuildSummary(true, true);
+ ASSERT_EQ(summaries[0].count, 2);
+ ASSERT_EQ(summaries[1].count, 1);
+}
+
+TEST_F(StatCmdSummaryBuilderTest, sort_key_count_per_thread) {
+ sort_keys_ = {"count_per_thread", "count"};
+ AddCounter({.tid = 0, .cpu = 0, .value = 1});
+ AddCounter({.tid = 0, .cpu = 1, .value = 5});
+ AddCounter({.tid = 1, .cpu = 0, .value = 3});
+ std::vector<CounterSummary> summaries = BuildSummary(true, true);
+ ASSERT_EQ(summaries[0].count, 5);
+ ASSERT_EQ(summaries[1].count, 1);
+ ASSERT_EQ(summaries[2].count, 3);
+}
+
+TEST_F(StatCmdSummaryBuilderTest, sort_key_cpu) {
+ sort_keys_ = {"cpu"};
+ AddCounter({.tid = 0, .cpu = 1, .value = 2});
+ AddCounter({.tid = 1, .cpu = 0, .value = 1});
+ std::vector<CounterSummary> summaries = BuildSummary(false, true);
+ ASSERT_EQ(summaries[0].cpu, 0);
+ ASSERT_EQ(summaries[1].cpu, 1);
+}
+
+TEST_F(StatCmdSummaryBuilderTest, sort_key_pid_tid_name) {
+ AddCounter({.tid = 0, .cpu = 0, .value = 1});
+ AddCounter({.tid = 1, .cpu = 0, .value = 2});
+
+ for (auto& key : std::vector<std::string>({"tid", "pid", "comm"})) {
+ sort_keys_ = {key};
+ std::vector<CounterSummary> summaries = BuildSummary(true, false);
+ ASSERT_EQ(summaries[0].count, 1) << "key = " << key;
+ ASSERT_EQ(summaries[1].count, 2) << "key = " << key;
+ }
+}
+
class StatCmdSummariesTest : public ::testing::Test {
protected:
void AddSummary(const std::string event_name, pid_t tid, int cpu, uint64_t count,
diff --git a/simpleperf/cmd_trace_sched.cpp b/simpleperf/cmd_trace_sched.cpp
index a5961ec9..0fcabd28 100644
--- a/simpleperf/cmd_trace_sched.cpp
+++ b/simpleperf/cmd_trace_sched.cpp
@@ -24,20 +24,21 @@
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
+#include "SampleDisplayer.h"
#include "command.h"
#include "event_selection_set.h"
#include "record.h"
#include "record_file.h"
-#include "SampleDisplayer.h"
#include "tracing.h"
#include "utils.h"
-using android::base::StringPrintf;
-
+namespace simpleperf {
namespace {
+using android::base::StringPrintf;
+
struct SampleInfo {
- uint64_t timestamp; // the time when the kernel generates the sample
+ uint64_t timestamp; // the time when the kernel generates the sample
uint64_t runtime_in_ns; // the runtime of the thread in the sample
SampleInfo(uint64_t timestamp = 0, uint64_t runtime_in_ns = 0)
: timestamp(timestamp), runtime_in_ns(runtime_in_ns) {}
@@ -91,8 +92,7 @@ class TraceSchedCommand : public Command {
duration_in_sec_(10.0),
spinloop_check_period_in_sec_(1.0),
spinloop_check_rate_(0.8),
- show_threads_(false) {
- }
+ show_threads_(false) {}
bool Run(const std::vector<std::string>& args);
@@ -172,9 +172,13 @@ bool TraceSchedCommand::RecordSchedEvents(const std::string& record_file_path) {
}
std::unique_ptr<Command> record_cmd = CreateCommandInstance("record");
CHECK(record_cmd);
- std::vector<std::string> record_args = {"-e", "sched:sched_stat_runtime", "-a",
- "--duration", std::to_string(duration_in_sec_),
- "-o", record_file_path};
+ std::vector<std::string> record_args = {"-e",
+ "sched:sched_stat_runtime",
+ "-a",
+ "--duration",
+ std::to_string(duration_in_sec_),
+ "-o",
+ record_file_path};
if (IsSettingClockIdSupported()) {
record_args.push_back("--clockid");
record_args.push_back("monotonic");
@@ -265,8 +269,8 @@ void TraceSchedCommand::ProcessSampleRecord(const SampleRecord& record) {
if (thread.spin_info.runtime_in_check_period > time_period_in_ns * spinloop_check_rate_) {
// Detect a spin loop.
thread.spin_info.spinloop_count++;
- double rate = std::min(1.0,
- static_cast<double>(thread.spin_info.runtime_in_check_period) / time_period_in_ns);
+ double rate = std::min(
+ 1.0, static_cast<double>(thread.spin_info.runtime_in_check_period) / time_period_in_ns);
if (rate > thread.spin_info.max_rate) {
thread.spin_info.max_rate = rate;
thread.spin_info.max_rate_start_timestamp = start_timestamp;
@@ -377,12 +381,9 @@ void TraceSchedCommand::ReportProcessInfo(const std::vector<ProcessInfo>& proces
displayer.AddDisplayFunction("Percentage", [](const ReportEntry* entry) {
return StringPrintf("%.2f%%", entry->percentage);
});
- displayer.AddDisplayFunction("Pid", [](const ReportEntry* entry) {
- return StringPrintf("%d", entry->pid);
- });
- displayer.AddDisplayFunction("Name", [](const ReportEntry* entry) {
- return entry->name;
- });
+ displayer.AddDisplayFunction(
+ "Pid", [](const ReportEntry* entry) { return StringPrintf("%d", entry->pid); });
+ displayer.AddDisplayFunction("Name", [](const ReportEntry* entry) { return entry->name; });
for (auto& entry : entries) {
displayer.AdjustWidth(&entry);
}
@@ -395,17 +396,17 @@ void TraceSchedCommand::ReportProcessInfo(const std::vector<ProcessInfo>& proces
for (auto& thread : process.threads) {
if (thread->spin_info.spinloop_count != 0u) {
double percentage = 100.0 * thread->spin_info.max_rate;
- double duration_in_ns = thread->spin_info.max_rate_end_timestamp -
- thread->spin_info.max_rate_start_timestamp;
+ double duration_in_ns =
+ thread->spin_info.max_rate_end_timestamp - thread->spin_info.max_rate_start_timestamp;
double running_time_in_ns = duration_in_ns * thread->spin_info.max_rate;
- printf("Detect %" PRIu64 " spin loops in process %s (%d) thread %s (%d),\n"
+ printf("Detect %" PRIu64
+ " spin loops in process %s (%d) thread %s (%d),\n"
"max rate at [%.6f s - %.6f s], taken %.3f ms / %.3f ms (%.2f%%).\n",
thread->spin_info.spinloop_count, process.name.c_str(), process.process_id,
thread->name.c_str(), thread->thread_id,
thread->spin_info.max_rate_start_timestamp / 1e9,
- thread->spin_info.max_rate_end_timestamp / 1e9,
- running_time_in_ns / 1e6, duration_in_ns / 1e6,
- percentage);
+ thread->spin_info.max_rate_end_timestamp / 1e9, running_time_in_ns / 1e6,
+ duration_in_ns / 1e6, percentage);
}
}
}
@@ -416,3 +417,5 @@ void TraceSchedCommand::ReportProcessInfo(const std::vector<ProcessInfo>& proces
void RegisterTraceSchedCommand() {
RegisterCommand("trace-sched", [] { return std::unique_ptr<Command>(new TraceSchedCommand()); });
}
+
+} // namespace simpleperf
diff --git a/simpleperf/cmd_trace_sched_test.cpp b/simpleperf/cmd_trace_sched_test.cpp
index c7676491..11ba3ba5 100644
--- a/simpleperf/cmd_trace_sched_test.cpp
+++ b/simpleperf/cmd_trace_sched_test.cpp
@@ -36,6 +36,7 @@
#include "test_util.h"
#include "thread_tree.h"
+using namespace simpleperf;
using namespace PerfFileFormat;
static std::unique_ptr<Command> TraceSchedCmd() {
@@ -43,21 +44,20 @@ static std::unique_ptr<Command> TraceSchedCmd() {
}
TEST(trace_sched_cmd, smoke) {
- TEST_IN_ROOT({
- ASSERT_TRUE(TraceSchedCmd()->Run({"--duration", "1"}));
- });
+ TEST_IN_ROOT({ ASSERT_TRUE(TraceSchedCmd()->Run({"--duration", "1"})); });
}
TEST(trace_sched_cmd, report_smoke) {
CaptureStdout capture;
ASSERT_TRUE(capture.Start());
- ASSERT_TRUE(TraceSchedCmd()->Run({"--record-file", GetTestData(PERF_DATA_SCHED_STAT_RUNTIME),
- "--show-threads"}));
+ ASSERT_TRUE(TraceSchedCmd()->Run(
+ {"--record-file", GetTestData(PERF_DATA_SCHED_STAT_RUNTIME), "--show-threads"}));
std::string data = capture.Finish();
ASSERT_NE(data.find("Process 3845.961 ms 94.90% 8603 examplepurejava"),
std::string::npos);
ASSERT_NE(data.find("Thread 3845.961 ms 94.90% 8615 BusyThread"), std::string::npos);
ASSERT_NE(data.find("Detect 3 spin loops in process examplepurejava (8603) thread "
"BusyThread (8615),\nmax rate at [326962.439095 s - 326963.442418 s], "
- "taken 997.813 ms / 1003.323 ms (99.45%)."), std::string::npos);
+ "taken 997.813 ms / 1003.323 ms (99.45%)."),
+ std::string::npos);
}
diff --git a/simpleperf/command.cpp b/simpleperf/command.cpp
index d00fc224..2960c068 100644
--- a/simpleperf/command.cpp
+++ b/simpleperf/command.cpp
@@ -16,6 +16,8 @@
#include "command.h"
+#include <string.h>
+
#include <algorithm>
#include <map>
#include <string>
@@ -27,7 +29,7 @@
#include "utils.h"
-using namespace simpleperf;
+namespace simpleperf {
bool Command::NextArgumentOrError(const std::vector<std::string>& args, size_t* pi) {
if (*pi + 1 == args.size()) {
@@ -39,6 +41,89 @@ bool Command::NextArgumentOrError(const std::vector<std::string>& args, size_t*
return true;
}
+bool Command::PreprocessOptions(const std::vector<std::string>& args,
+ const OptionFormatMap& option_formats, OptionValueMap* options,
+ std::vector<std::pair<OptionName, OptionValue>>* ordered_options,
+ std::vector<std::string>* non_option_args) {
+ options->values.clear();
+ ordered_options->clear();
+ size_t i;
+ for (i = 0; i < args.size() && !args[i].empty() && args[i][0] == '-'; i++) {
+ auto it = option_formats.find(args[i]);
+ if (it == option_formats.end()) {
+ if (args[i] == "--") {
+ i++;
+ break;
+ }
+ ReportUnknownOption(args, i);
+ return false;
+ }
+ const OptionName& name = it->first;
+ const OptionFormat& format = it->second;
+ OptionValue value;
+ memset(&value, 0, sizeof(value));
+
+ if (i + 1 == args.size()) {
+ if (format.value_type != OptionValueType::NONE &&
+ format.value_type != OptionValueType::OPT_STRING) {
+ LOG(ERROR) << "No argument following " << name << " option. Try `simpleperf help " << name_
+ << "`";
+ return false;
+ }
+ } else {
+ switch (format.value_type) {
+ case OptionValueType::NONE:
+ break;
+ case OptionValueType::STRING:
+ value.str_value = &args[++i];
+ break;
+ case OptionValueType::OPT_STRING:
+ if (!args[i + 1].empty() && args[i + 1][0] != '-') {
+ value.str_value = &args[++i];
+ }
+ break;
+ case OptionValueType::UINT:
+ if (!android::base::ParseUint(args[++i], &value.uint_value,
+ std::numeric_limits<uint64_t>::max(), true)) {
+ LOG(ERROR) << "Invalid argument for option " << name << ": " << args[i];
+ return false;
+ }
+ break;
+ case OptionValueType::DOUBLE:
+ if (!android::base::ParseDouble(args[++i], &value.double_value)) {
+ LOG(ERROR) << "Invalid argument for option " << name << ": " << args[i];
+ return false;
+ }
+ break;
+ }
+ }
+
+ switch (format.type) {
+ case OptionType::SINGLE:
+ if (auto it = options->values.find(name); it != options->values.end()) {
+ it->second = value;
+ } else {
+ options->values.emplace(name, value);
+ }
+ break;
+ case OptionType::MULTIPLE:
+ options->values.emplace(name, value);
+ break;
+ case OptionType::ORDERED:
+ ordered_options->emplace_back(name, value);
+ break;
+ }
+ }
+ if (i < args.size()) {
+ if (non_option_args == nullptr) {
+ LOG(ERROR) << "Invalid option " << args[i] << ". Try `simpleperf help " << name_ << "`";
+ return false;
+ }
+ non_option_args->assign(args.begin() + i, args.end());
+ }
+ return true;
+}
+
bool Command::GetDoubleOption(const std::vector<std::string>& args, size_t* pi, double* value,
double min, double max) {
if (!NextArgumentOrError(args, pi)) {
@@ -92,6 +177,7 @@ extern void RegisterHelpCommand();
extern void RegisterInjectCommand();
extern void RegisterListCommand();
extern void RegisterKmemCommand();
+extern void RegisterMergeCommand();
extern void RegisterRecordCommand();
extern void RegisterReportCommand();
extern void RegisterReportSampleCommand();
@@ -99,6 +185,7 @@ extern void RegisterStatCommand();
extern void RegisterDebugUnwindCommand();
extern void RegisterTraceSchedCommand();
extern void RegisterAPICommands();
+extern void RegisterMonitorCommand();
class CommandRegister {
public:
@@ -107,6 +194,7 @@ class CommandRegister {
RegisterHelpCommand();
RegisterInjectCommand();
RegisterKmemCommand();
+ RegisterMergeCommand();
RegisterReportCommand();
RegisterReportSampleCommand();
#if defined(__linux__)
@@ -115,6 +203,7 @@ class CommandRegister {
RegisterStatCommand();
RegisterDebugUnwindCommand();
RegisterTraceSchedCommand();
+ RegisterMonitorCommand();
#if defined(__ANDROID__)
RegisterAPICommands();
#endif
@@ -124,49 +213,57 @@ class CommandRegister {
CommandRegister command_register;
-static void StderrLogger(android::base::LogId, android::base::LogSeverity severity,
- const char*, const char* file, unsigned int line, const char* message) {
+static void StderrLogger(android::base::LogId, android::base::LogSeverity severity, const char*,
+ const char* file, unsigned int line, const char* message) {
static const char log_characters[] = "VDIWEFF";
char severity_char = log_characters[severity];
fprintf(stderr, "simpleperf %c %s:%u] %s\n", severity_char, file, line, message);
}
-namespace simpleperf {
bool log_to_android_buffer = false;
-}
bool RunSimpleperfCmd(int argc, char** argv) {
android::base::InitLogging(argv, StderrLogger);
std::vector<std::string> args;
android::base::LogSeverity log_severity = android::base::INFO;
log_to_android_buffer = false;
+ const OptionFormatMap& common_option_formats = GetCommonOptionFormatMap();
- for (int i = 1; i < argc; ++i) {
- if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
+ int i;
+ for (i = 1; i < argc && strcmp(argv[i], "--") != 0; ++i) {
+ std::string option_name = argv[i];
+ auto it = common_option_formats.find(option_name);
+ if (it == common_option_formats.end()) {
+ args.emplace_back(std::move(option_name));
+ continue;
+ }
+ if (it->second.value_type != OptionValueType::NONE && i + 1 == argc) {
+ LOG(ERROR) << "Missing argument for " << option_name;
+ return false;
+ }
+ if (option_name == "-h" || option_name == "--help") {
args.insert(args.begin(), "help");
- } else if (strcmp(argv[i], "--log") == 0) {
- if (i + 1 < argc) {
- ++i;
- if (!GetLogSeverity(argv[i], &log_severity)) {
- LOG(ERROR) << "Unknown log severity: " << argv[i];
- return false;
- }
- } else {
- LOG(ERROR) << "Missing argument for --log option.\n";
- return false;
+ } else if (option_name == "--log") {
+ if (!GetLogSeverity(argv[i + 1], &log_severity)) {
+ LOG(ERROR) << "Unknown log severity: " << argv[i + 1];
}
+ ++i;
#if defined(__ANDROID__)
- } else if (strcmp(argv[i], "--log-to-android-buffer") == 0) {
+ } else if (option_name == "--log-to-android-buffer") {
android::base::SetLogger(android::base::LogdLogger());
log_to_android_buffer = true;
#endif
- } else if (strcmp(argv[i], "--version") == 0) {
+ } else if (option_name == "--version") {
LOG(INFO) << "Simpleperf version " << GetSimpleperfVersion();
return true;
} else {
- args.push_back(argv[i]);
+ CHECK(false) << "Unreachable code";
}
}
+ while (i < argc) {
+ args.emplace_back(argv[i++]);
+ }
+
android::base::ScopedLogSeverity severity(log_severity);
if (args.empty()) {
@@ -190,3 +287,5 @@ bool RunSimpleperfCmd(int argc, char** argv) {
_Exit(result ? 0 : 1);
return result;
}
+
+} // namespace simpleperf
diff --git a/simpleperf/command.h b/simpleperf/command.h
index fe71a31a..d06c5154 100644
--- a/simpleperf/command.h
+++ b/simpleperf/command.h
@@ -18,39 +18,152 @@
#define SIMPLE_PERF_COMMAND_H_
#include <functional>
-#include <memory>
#include <limits>
+#include <map>
+#include <memory>
+#include <optional>
#include <string>
+#include <unordered_map>
#include <vector>
#include <android-base/logging.h>
#include <android-base/macros.h>
#include <android-base/parseint.h>
-class Command {
- public:
- Command(const std::string& name, const std::string& short_help_string,
- const std::string& long_help_string)
- : name_(name), short_help_string_(short_help_string), long_help_string_(long_help_string) {
+namespace simpleperf {
+
+using OptionName = std::string;
+
+enum class OptionType {
+ SINGLE, // this option has a single value (use the last one in the arg list)
+ MULTIPLE, // this option can have multiple values (keep all values appeared in the arg list)
+ ORDERED, // keep the order of this option in the arg list
+};
+
+enum class OptionValueType {
+ NONE, // No value is needed
+ STRING,
+ OPT_STRING, // optional string
+ UINT,
+ DOUBLE,
+};
+
+// Whether an option is allowed to pass through simpleperf_app_runner.
+enum class AppRunnerType {
+ NOT_ALLOWED,
+ ALLOWED,
+ CHECK_FD,
+ CHECK_PATH,
+};
+
+struct OptionFormat {
+ OptionValueType value_type;
+ OptionType type;
+ AppRunnerType app_runner_type = AppRunnerType::NOT_ALLOWED;
+};
+
+using OptionFormatMap = std::unordered_map<OptionName, OptionFormat>;
+
+union OptionValue {
+ const std::string* str_value;
+ uint64_t uint_value;
+ double double_value;
+};
+
+struct OptionValueMap {
+ std::multimap<OptionName, OptionValue> values;
+
+ bool PullBoolValue(const OptionName& name) { return PullValue(name).has_value(); }
+
+ template <typename T>
+ bool PullUintValue(const OptionName& name, T* value, uint64_t min = 0,
+ uint64_t max = std::numeric_limits<T>::max()) {
+ if (auto option_value = PullValue(name); option_value) {
+ if (option_value->uint_value < min || option_value->uint_value > max) {
+ LOG(ERROR) << "invalid " << name << ": " << option_value->uint_value;
+ return false;
+ }
+ *value = option_value->uint_value;
+ }
+ return true;
}
- virtual ~Command() {
+ bool PullDoubleValue(const OptionName& name, double* value,
+ double min = std::numeric_limits<double>::lowest(),
+ double max = std::numeric_limits<double>::max()) {
+ if (auto option_value = PullValue(name); option_value) {
+ if (option_value->double_value < min || option_value->double_value > max) {
+ LOG(ERROR) << "invalid " << name << ": " << option_value->double_value;
+ return false;
+ }
+ *value = option_value->double_value;
+ }
+ return true;
}
- const std::string& Name() const {
- return name_;
+ void PullStringValue(const OptionName& name, std::string* value) {
+ if (auto option_value = PullValue(name); option_value) {
+ CHECK(option_value->str_value != nullptr);
+ *value = *option_value->str_value;
+ }
}
- const std::string& ShortHelpString() const {
- return short_help_string_;
+ std::optional<OptionValue> PullValue(const OptionName& name) {
+ std::optional<OptionValue> res;
+ if (auto it = values.find(name); it != values.end()) {
+ res.emplace(it->second);
+ values.erase(it);
+ }
+ return res;
}
- const std::string LongHelpString() const {
- return long_help_string_;
+ std::vector<OptionValue> PullValues(const OptionName& name) {
+ auto pair = values.equal_range(name);
+ if (pair.first != pair.second) {
+ std::vector<OptionValue> res;
+ for (auto it = pair.first; it != pair.second; ++it) {
+ res.emplace_back(it->second);
+ }
+ values.erase(name);
+ return res;
+ }
+ return {};
}
+};
+
+inline const OptionFormatMap& GetCommonOptionFormatMap() {
+ static const OptionFormatMap option_formats = {
+ {"-h", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--help", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--log", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--log-to-android-buffer",
+ {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--version", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ };
+ return option_formats;
+}
+
+class Command {
+ public:
+ Command(const std::string& name, const std::string& short_help_string,
+ const std::string& long_help_string)
+ : name_(name), short_help_string_(short_help_string), long_help_string_(long_help_string) {}
+
+ virtual ~Command() {}
+
+ const std::string& Name() const { return name_; }
+
+ const std::string& ShortHelpString() const { return short_help_string_; }
+
+ const std::string LongHelpString() const { return long_help_string_; }
virtual bool Run(const std::vector<std::string>& args) = 0;
+ bool PreprocessOptions(const std::vector<std::string>& args,
+ const OptionFormatMap& option_formats, OptionValueMap* options,
+ std::vector<std::pair<OptionName, OptionValue>>* ordered_options,
+ std::vector<std::string>* non_option_args = nullptr);
+
template <typename T>
bool GetUintOption(const std::vector<std::string>& args, size_t* pi, T* value, uint64_t min = 0,
uint64_t max = std::numeric_limits<T>::max(), bool allow_suffixes = false) {
@@ -88,8 +201,8 @@ std::unique_ptr<Command> CreateCommandInstance(const std::string& cmd_name);
const std::vector<std::string> GetAllCommandNames();
bool RunSimpleperfCmd(int argc, char** argv);
-namespace simpleperf {
extern bool log_to_android_buffer;
-}
+
+} // namespace simpleperf
#endif // SIMPLE_PERF_COMMAND_H_
diff --git a/simpleperf/command_test.cpp b/simpleperf/command_test.cpp
index 29c745e3..392ec8ac 100644
--- a/simpleperf/command_test.cpp
+++ b/simpleperf/command_test.cpp
@@ -18,14 +18,13 @@
#include "command.h"
+using namespace simpleperf;
+
class MockCommand : public Command {
public:
- MockCommand() : Command("mock", "mock_short_help", "mock_long_help") {
- }
+ MockCommand() : Command("mock", "mock_short_help", "mock_long_help") {}
- bool Run(const std::vector<std::string>&) override {
- return true;
- }
+ bool Run(const std::vector<std::string>&) override { return true; }
};
TEST(command, CreateCommandInstance) {
@@ -70,3 +69,112 @@ TEST(command, GetValueForOption) {
ASSERT_TRUE(command.GetDoubleOption({"-s", "3.2"}, &i, &double_value, 0, 4));
ASSERT_DOUBLE_EQ(double_value, 3.2);
}
+
+TEST(command, PreprocessOptions) {
+ MockCommand cmd;
+ OptionValueMap options;
+ std::vector<std::pair<OptionName, OptionValue>> ordered_options;
+ std::vector<std::string> non_option_args;
+
+ OptionFormatMap option_formats = {
+ {"--bool-option", {OptionValueType::NONE, OptionType::SINGLE}},
+ {"--str-option", {OptionValueType::STRING, OptionType::MULTIPLE}},
+ {"--str2-option", {OptionValueType::STRING, OptionType::SINGLE}},
+ {"--opt-str-option", {OptionValueType::OPT_STRING, OptionType::MULTIPLE}},
+ {"--uint-option", {OptionValueType::UINT, OptionType::SINGLE}},
+ {"--double-option", {OptionValueType::DOUBLE, OptionType::SINGLE}},
+
+ // ordered options
+ {"--ord-str-option", {OptionValueType::STRING, OptionType::ORDERED}},
+ {"--ord-uint-option", {OptionValueType::UINT, OptionType::ORDERED}},
+ };
+
+ // Check options.
+ std::vector<std::string> args = {
+ "--bool-option", "--str-option", "str1", "--str-option",
+ "str1_2", "--str2-option", "str2_value", "--opt-str-option",
+ "--opt-str-option", "opt_str", "--uint-option", "34",
+ "--double-option", "-32.75"};
+ ASSERT_TRUE(cmd.PreprocessOptions(args, option_formats, &options, &ordered_options, nullptr));
+ ASSERT_TRUE(options.PullBoolValue("--bool-option"));
+ auto values = options.PullValues("--str-option");
+ ASSERT_EQ(values.size(), 2);
+ ASSERT_EQ(*values[0].str_value, "str1");
+ ASSERT_EQ(*values[1].str_value, "str1_2");
+ std::string str2_value;
+ options.PullStringValue("--str2-option", &str2_value);
+ ASSERT_EQ(str2_value, "str2_value");
+ values = options.PullValues("--opt-str-option");
+ ASSERT_EQ(values.size(), 2);
+ ASSERT_TRUE(values[0].str_value == nullptr);
+ ASSERT_EQ(*values[1].str_value, "opt_str");
+ size_t uint_value;
+ ASSERT_TRUE(options.PullUintValue("--uint-option", &uint_value));
+ ASSERT_EQ(uint_value, 34);
+ double double_value;
+ ASSERT_TRUE(options.PullDoubleValue("--double-option", &double_value));
+ ASSERT_DOUBLE_EQ(double_value, -32.75);
+ ASSERT_TRUE(options.values.empty());
+
+ // Check ordered options.
+ args = {"--ord-str-option", "str1", "--ord-uint-option", "32", "--ord-str-option", "str2"};
+ ASSERT_TRUE(cmd.PreprocessOptions(args, option_formats, &options, &ordered_options, nullptr));
+ ASSERT_EQ(ordered_options.size(), 3);
+ ASSERT_EQ(ordered_options[0].first, "--ord-str-option");
+ ASSERT_EQ(*(ordered_options[0].second.str_value), "str1");
+ ASSERT_EQ(ordered_options[1].first, "--ord-uint-option");
+ ASSERT_EQ(ordered_options[1].second.uint_value, 32);
+ ASSERT_EQ(ordered_options[2].first, "--ord-str-option");
+ ASSERT_EQ(*(ordered_options[2].second.str_value), "str2");
+
+ // Check non_option_args.
+ ASSERT_TRUE(cmd.PreprocessOptions({"arg1", "--arg2"}, option_formats, &options, &ordered_options,
+ &non_option_args));
+ ASSERT_EQ(non_option_args, std::vector<std::string>({"arg1", "--arg2"}));
+ // "--" can force following args to be non_option_args.
+ ASSERT_TRUE(cmd.PreprocessOptions({"--", "--bool-option"}, option_formats, &options,
+ &ordered_options, &non_option_args));
+ ASSERT_EQ(non_option_args, std::vector<std::string>({"--bool-option"}));
+ // Pass nullptr to not accept non option args.
+ ASSERT_FALSE(cmd.PreprocessOptions({"non_option_arg"}, option_formats, &options, &ordered_options,
+ nullptr));
+
+ // Check different errors.
+ // unknown option
+ ASSERT_FALSE(cmd.PreprocessOptions({"--unknown-option"}, option_formats, &options,
+ &ordered_options, nullptr));
+ // no option value
+ ASSERT_FALSE(
+ cmd.PreprocessOptions({"--str-option"}, option_formats, &options, &ordered_options, nullptr));
+ // wrong option value format
+ ASSERT_FALSE(cmd.PreprocessOptions({"--uint-option", "-2"}, option_formats, &options,
+ &ordered_options, nullptr));
+ ASSERT_FALSE(cmd.PreprocessOptions({"--double-option", "str"}, option_formats, &options,
+ &ordered_options, nullptr));
+ // unexpected non_option_args
+ ASSERT_FALSE(cmd.PreprocessOptions({"non_option_args"}, option_formats, &options,
+ &ordered_options, nullptr));
+}
+
+TEST(command, OptionValueMap) {
+ OptionValue value;
+ value.uint_value = 10;
+
+ OptionValueMap options;
+ uint64_t uint_value;
+ options.values.emplace("--uint-option", value);
+ ASSERT_FALSE(options.PullUintValue("--uint-option", &uint_value, 11));
+ options.values.emplace("--uint-option", value);
+ ASSERT_FALSE(options.PullUintValue("--uint-option", &uint_value, 0, 9));
+ options.values.emplace("--uint-option", value);
+ ASSERT_TRUE(options.PullUintValue("--uint-option", &uint_value, 10, 10));
+
+ double double_value;
+ value.double_value = 0.0;
+ options.values.emplace("--double-option", value);
+ ASSERT_FALSE(options.PullDoubleValue("--double-option", &double_value, 1.0));
+ options.values.emplace("--double-option", value);
+ ASSERT_FALSE(options.PullDoubleValue("--double-option", &double_value, -2.0, -1.0));
+ options.values.emplace("--double-option", value);
+ ASSERT_TRUE(options.PullDoubleValue("--double-option", &double_value, 0.0, 0.0));
+}
diff --git a/simpleperf/cpu_hotplug_test.cpp b/simpleperf/cpu_hotplug_test.cpp
index a698d2f9..9b78c644 100644
--- a/simpleperf/cpu_hotplug_test.cpp
+++ b/simpleperf/cpu_hotplug_test.cpp
@@ -37,6 +37,8 @@
#include "event_type.h"
#include "utils.h"
+using namespace simpleperf;
+
static auto test_duration_for_long_tests = std::chrono::seconds(120);
static auto cpu_hotplug_interval = std::chrono::microseconds(1000);
static bool verbose_mode = false;
@@ -84,8 +86,7 @@ class ScopedMpdecisionKiller {
#else
class ScopedMpdecisionKiller {
public:
- ScopedMpdecisionKiller() {
- }
+ ScopedMpdecisionKiller() {}
};
#endif
@@ -136,9 +137,10 @@ static bool SetCpuOnline(int cpu, bool online) {
break;
}
LOG(ERROR) << "reading cpu retry count = " << retry_count << ", requested = " << online
- << ", real = " << ret;
+ << ", real = " << ret;
if (++retry_count == 10000) {
- LOG(ERROR) << "setting cpu " << cpu << (online ? " online" : " offline") << " seems not to take effect";
+ LOG(ERROR) << "setting cpu " << cpu << (online ? " online" : " offline")
+ << " seems not to take effect";
return false;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
@@ -198,9 +200,7 @@ struct CpuToggleThreadArg {
std::atomic<bool> end_flag;
std::atomic<bool> cpu_hotplug_failed;
- CpuToggleThreadArg(int cpu)
- : toggle_cpu(cpu), end_flag(false), cpu_hotplug_failed(false) {
- }
+ CpuToggleThreadArg(int cpu) : toggle_cpu(cpu), end_flag(false), cpu_hotplug_failed(false) {}
};
static void CpuToggleThread(CpuToggleThreadArg* arg) {
@@ -312,7 +312,6 @@ TEST(cpu_offline, offline_while_ioctl_enable) {
GTEST_LOG_(INFO) << "Have Tested " << (diff.count() / 60.0) << " minutes.";
}
cur_time = std::chrono::steady_clock::now();
-
}
std::unique_ptr<EventFd> event_fd =
EventFd::OpenEventFile(attr, -1, test_cpu, nullptr, event_type_modifier->name, false);
@@ -325,7 +324,8 @@ TEST(cpu_offline, offline_while_ioctl_enable) {
ASSERT_TRUE(event_fd->SetEnableEvent(true));
iterations++;
if (verbose_mode) {
- GTEST_LOG_(INFO) << "Test offline while ioctl(PERF_EVENT_IOC_ENABLE) for " << iterations << " times.";
+ GTEST_LOG_(INFO) << "Test offline while ioctl(PERF_EVENT_IOC_ENABLE) for " << iterations
+ << " times.";
}
}
if (cpu_toggle_arg.cpu_hotplug_failed) {
@@ -393,7 +393,7 @@ TEST(cpu_offline, offline_while_user_process_profiling) {
auto diff = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::steady_clock::now() - start_time);
if (verbose_mode) {
- GTEST_LOG_(INFO) << "Have Tested " << (diff.count() / 60.0) << " minutes.";
+ GTEST_LOG_(INFO) << "Have Tested " << (diff.count() / 60.0) << " minutes.";
}
cur_time = std::chrono::steady_clock::now();
}
@@ -411,7 +411,8 @@ TEST(cpu_offline, offline_while_user_process_profiling) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
iterations++;
if (verbose_mode) {
- GTEST_LOG_(INFO) << "Test offline while user process profiling for " << iterations << " times.";
+ GTEST_LOG_(INFO) << "Test offline while user process profiling for " << iterations
+ << " times.";
}
}
if (cpu_toggle_arg.cpu_hotplug_failed) {
@@ -469,11 +470,12 @@ int main(int argc, char** argv) {
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "--help") == 0) {
printf("--long_test_duration <second> Set test duration for long tests. Default is 120s.\n");
- printf("--cpu_hotplug_interval <microseconds> Set cpu hotplug interval. Default is 1000us.\n");
+ printf(
+ "--cpu_hotplug_interval <microseconds> Set cpu hotplug interval. Default is 1000us.\n");
printf("--verbose Show verbose log.\n");
} else if (strcmp(argv[i], "--long_test_duration") == 0) {
if (i + 1 < argc) {
- int second_count = atoi(argv[i+1]);
+ int second_count = atoi(argv[i + 1]);
if (second_count <= 0) {
fprintf(stderr, "Invalid arg for --long_test_duration.\n");
return 1;
@@ -483,7 +485,7 @@ int main(int argc, char** argv) {
}
} else if (strcmp(argv[i], "--cpu_hotplug_interval") == 0) {
if (i + 1 < argc) {
- int microsecond_count = atoi(argv[i+1]);
+ int microsecond_count = atoi(argv[i + 1]);
if (microsecond_count <= 0) {
fprintf(stderr, "Invalid arg for --cpu_hotplug_interval\n");
return 1;
diff --git a/simpleperf/demo/.clang-format b/simpleperf/demo/.clang-format
new file mode 120000
index 00000000..81ff3c41
--- /dev/null
+++ b/simpleperf/demo/.clang-format
@@ -0,0 +1 @@
+../../.clang-format-none \ No newline at end of file
diff --git a/simpleperf/demo/CppApi/app/src/main/AndroidManifest.xml b/simpleperf/demo/CppApi/app/src/main/AndroidManifest.xml
index 2b97b3b7..26b51b30 100644
--- a/simpleperf/demo/CppApi/app/src/main/AndroidManifest.xml
+++ b/simpleperf/demo/CppApi/app/src/main/AndroidManifest.xml
@@ -1,21 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="simpleperf.demo.cpp_api">
+ package="simpleperf.demo.cpp_api">
- <application
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/AppTheme">
- <activity android:name=".MainActivity">
+ <application android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity android:name=".MainActivity"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
+ <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
-</manifest> \ No newline at end of file
+</manifest>
diff --git a/simpleperf/demo/JavaApi/app/build.gradle b/simpleperf/demo/JavaApi/app/build.gradle
index be32645c..e53f7dab 100644
--- a/simpleperf/demo/JavaApi/app/build.gradle
+++ b/simpleperf/demo/JavaApi/app/build.gradle
@@ -1,13 +1,13 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 28
+ compileSdkVersion 29
defaultConfig {
applicationId "simpleperf.demo.java_api"
// Simpleperf profiles interpreted/jitted Java code on Android >= P.
// https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/README.md#prepare-an-android-application
minSdkVersion 28
- targetSdkVersion 28
+ targetSdkVersion 29
versionCode 1
versionName "1.0"
}
diff --git a/simpleperf/demo/JavaApi/app/src/main/AndroidManifest.xml b/simpleperf/demo/JavaApi/app/src/main/AndroidManifest.xml
index 7a3c9efb..f89708e3 100644
--- a/simpleperf/demo/JavaApi/app/src/main/AndroidManifest.xml
+++ b/simpleperf/demo/JavaApi/app/src/main/AndroidManifest.xml
@@ -1,21 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="simpleperf.demo.java_api">
+ package="simpleperf.demo.java_api">
- <application
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/AppTheme">
- <activity android:name=".MainActivity">
+ <application android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity android:name=".MainActivity"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
+ <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
-</manifest> \ No newline at end of file
+</manifest>
diff --git a/simpleperf/demo/README.md b/simpleperf/demo/README.md
index 6f9317fb..2005fa22 100644
--- a/simpleperf/demo/README.md
+++ b/simpleperf/demo/README.md
@@ -8,7 +8,7 @@
- [Profile a Java application](#profile-a-java-application)
- [Profile a Java/C++ application](#profile-a-javac-application)
- [Profile a Kotlin application](#profile-a-kotlin-application)
-- [Profile via app_api](#profile-via-appapi)
+- [Profile via app_api](#profile-via-app_api)
## Introduction
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/AndroidManifest.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/AndroidManifest.xml
index 33e820ea..8cb40742 100644
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/AndroidManifest.xml
+++ b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/AndroidManifest.xml
@@ -1,25 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.simpleperf.simpleperfexampleofkotlin">
+ package="com.example.simpleperf.simpleperfexampleofkotlin">
- <application
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/AppTheme">
- <activity android:name=".MainActivity">
+ <application android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity android:name=".MainActivity"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
+ <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".SleepActivity"
- android:exported="true">
+ android:exported="true">
</activity>
</application>
-</manifest> \ No newline at end of file
+</manifest>
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml
index f42ec17d..ee42fead 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml
+++ b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml
@@ -1,28 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.simpleperf.simpleperfexamplepurejava">
+ package="com.example.simpleperf.simpleperfexamplepurejava">
- <application
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/AppTheme">
- <activity android:name=".MainActivity">
+ <application android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity android:name=".MainActivity"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
+ <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".SleepActivity"
- android:exported="true" />
+ android:exported="true"/>
<activity android:name=".MultiProcessActivity"
- android:exported="true" />
+ android:exported="true"/>
<service android:name=".MultiProcessService"
- android:process=":multiprocess_service" />
+ android:process=":multiprocess_service"/>
</application>
</manifest>
diff --git a/simpleperf/demo/SimpleperfExampleWithNative/app/src/main/AndroidManifest.xml b/simpleperf/demo/SimpleperfExampleWithNative/app/src/main/AndroidManifest.xml
index de778e5e..de964677 100644
--- a/simpleperf/demo/SimpleperfExampleWithNative/app/src/main/AndroidManifest.xml
+++ b/simpleperf/demo/SimpleperfExampleWithNative/app/src/main/AndroidManifest.xml
@@ -1,28 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.simpleperf.simpleperfexamplewithnative">
+ package="com.example.simpleperf.simpleperfexamplewithnative">
- <application
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/AppTheme">
- <activity android:name=".MainActivity">
+ <application android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity android:name=".MainActivity"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
+ <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
- <activity
- android:name=".SleepActivity"
- android:exported="true"></activity>
+ <activity android:name=".SleepActivity"
+ android:exported="true"/>
<activity android:name=".MixActivity"
- android:exported="true">
+ android:exported="true">
</activity>
</application>
-</manifest> \ No newline at end of file
+</manifest>
diff --git a/simpleperf/doc/README.md b/simpleperf/doc/README.md
index f0cddd2a..2f3b0854 100644
--- a/simpleperf/doc/README.md
+++ b/simpleperf/doc/README.md
@@ -20,8 +20,9 @@ The latest document is [here](https://android.googlesource.com/platform/system/e
- [Executable commands reference](#executable-commands-reference)
- [Scripts reference](#scripts-reference)
- [Answers to common issues](#answers-to-common-issues)
- - [Why we suggest profiling on Android &gt;= N devices?](#why-we-suggest-profiling-on-android-gt-n-devices)
+ - [Why we suggest profiling on Android >= N devices?](#why-we-suggest-profiling-on-android--n-devices)
- [Suggestions about recording call graphs](#suggestions-about-recording-call-graphs)
+ - [Why we can't always get complete DWARF-based call graphs](#why-we-cant-always-get-complete-dwarf-based-call-graphs)
- [How to solve missing symbols in report?](#how-to-solve-missing-symbols-in-report)
- [Fix broken callchain stopped at C functions](#fix-broken-callchain-stopped-at-c-functions)
- [Show annotated source code and disassembly](#show-annotated-source-code-and-disassembly)
@@ -88,7 +89,7 @@ bin/${host}/${arch}/simpleperf: simpleperf executables used on the host, only su
bin/${host}/${arch}/libsimpleperf_report.${so/dylib/dll}: report shared libraries used on the host.
-*.py, inferno: Python scripts used for recording and reporting.
+*.py, inferno, purgatorio: Python scripts used for recording and reporting. Details are in [scripts_reference.md](scripts_reference.md).
## Android application profiling
@@ -159,6 +160,18 @@ graphs. It can be supported in two ways:
1. Use unstripped native binaries when building the apk, as [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/demo/SimpleperfExampleWithNative/app/profiling.gradle).
2. Download 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?
+
+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.
+
+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.
+
### How to solve missing symbols in report?
The simpleperf record command collects symbols on device in perf.data. But if the native libraries
@@ -233,7 +246,7 @@ Patches can be uploaded to android-review.googlesource.com as [here](https://sou
or sent to email addresses listed [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/OWNERS).
If you want to compile simpleperf C++ source code, follow below steps:
-1. Download AOSP master branch as [here](https://source.android.com/setup/build/requirements).
+1. Download AOSP main branch as [here](https://source.android.com/setup/build/requirements).
2. Build simpleperf.
```sh
$ . build/envsetup.sh
diff --git a/simpleperf/doc/android_application_profiling.md b/simpleperf/doc/android_application_profiling.md
index 259c121e..f0e06c08 100644
--- a/simpleperf/doc/android_application_profiling.md
+++ b/simpleperf/doc/android_application_profiling.md
@@ -18,6 +18,7 @@ Profiling an Android application involves three steps:
- [Record and report call graph](#record-and-report-call-graph)
- [Report in html interface](#report-in-html-interface)
- [Show flamegraph](#show-flamegraph)
+ - [Report in Android Studio](#report-in-android-studio)
- [Record both on CPU time and off CPU time](#record-both-on-cpu-time-and-off-cpu-time)
- [Profile from launch](#profile-from-launch)
- [Control recording in application code](#control-recording-in-application-code)
@@ -243,6 +244,17 @@ $ FlameGraph/stackcollapse-perf.pl out.perf >out.folded
$ FlameGraph/flamegraph.pl out.folded >a.svg
```
+## Report in Android Studio
+
+simpleperf report-sample command can convert perf.data into protobuf format accepted by
+Android Studio cpu profiler. The conversion can be done either on device or on host. If you have
+more symbol info on host, then prefer do it on host with --symdir option.
+
+```sh
+$ simpleperf report-sample --protobuf --show-callchain -i perf.data -o perf.trace
+# Then open perf.trace in Android Studio to show it.
+```
+
## Record both on CPU time and off CPU time
We can [record both on CPU time and off CPU time](executable_commands_reference.md#record-both-on-cpu-time-and-off-cpu-time).
diff --git a/simpleperf/doc/android_platform_profiling.md b/simpleperf/doc/android_platform_profiling.md
index afa1927b..21cf0f42 100644
--- a/simpleperf/doc/android_platform_profiling.md
+++ b/simpleperf/doc/android_platform_profiling.md
@@ -1,10 +1,19 @@
# Android platform profiling
+## Table of Contents
+- [Android platform profiling](#android-platform-profiling)
+ - [Table of Contents](#table-of-contents)
+ - [General Tips](#general-tips)
+ - [Start simpleperf from system_server process](#start-simpleperf-from-system_server-process)
+ - [Hardware PMU counter limit](#hardware-pmu-counter-limit)
+
+## General Tips
+
Here are some tips for Android platform developers, who build and flash system images on rooted
devices:
1. After running `adb root`, simpleperf can be used to profile any process or system wide.
-2. It is recommended to use the latest simpleperf available in AOSP master, if you are not working
-on the current master branch. Scripts are in `system/extras/simpleperf/scripts`, binaries are in
+2. It is recommended to use the latest simpleperf available in AOSP main, if you are not working
+on the current main branch. Scripts are in `system/extras/simpleperf/scripts`, binaries are in
`system/extras/simpleperf/scripts/bin/android`.
3. It is recommended to use `app_profiler.py` for recording, and `report_html.py` for reporting.
Below is an example.
@@ -34,3 +43,39 @@ $ python binary_cache_builder.py -lib $ANDROID_PRODUCT_OUT/symbols
$ python report_html.py --add_source_code --source_dirs $ANDROID_BUILD_TOP --add_disassembly \
--binary_filter surfaceflinger.so
```
+
+## Start simpleperf from system_server process
+
+Sometimes we want to profile a process/system-wide when a special situation happens. In this case,
+we can add code starting simpleperf at the point where the situation is detected.
+
+1. Disable selinux by `adb shell setenforce 0`. Because selinux only allows simpleperf running
+ in shell or debuggable/profileable apps.
+
+2. Add below code at the point where the special situation is detected.
+
+```java
+try {
+ // for capability check
+ Os.prctl(OsConstants.PR_CAP_AMBIENT, OsConstants.PR_CAP_AMBIENT_RAISE,
+ OsConstants.CAP_SYS_PTRACE, 0, 0);
+ // Write to /data instead of /data/local/tmp. Because /data can be written by system user.
+ Runtime.getRuntime().exec("/system/bin/simpleperf record -g -p " + String.valueOf(Process.myPid())
+ + " -o /data/perf.data --duration 30 --log-to-android-buffer --log verbose");
+} catch (Exception e) {
+ Slog.e(TAG, "error while running simpleperf");
+ e.printStackTrace();
+}
+```
+
+## Hardware PMU counter limit
+
+When monitoring instruction and cache related perf events (in hw/cache/raw/pmu category of list cmd),
+these events are mapped to PMU counters on each cpu core. But each core only has a limited number
+of PMU counters. If number of events > number of PMU counters, then the counters are multiplexed
+among events, which probably isn't what we want.
+
+On Pixel devices, the number of PMU counters on each core is usually 7, of which 4 of them are used
+by the kernel to monitor memory latency. So only 3 counters are available. It's fine to monitor up
+to 3 PMU events at the same time. To monitor more than 3 events, the `--use-devfreq-counters` option
+can be used to borrow from the counters used by the kernel.
diff --git a/simpleperf/doc/executable_commands_reference.md b/simpleperf/doc/executable_commands_reference.md
index 5d51a158..6e696d43 100644
--- a/simpleperf/doc/executable_commands_reference.md
+++ b/simpleperf/doc/executable_commands_reference.md
@@ -486,6 +486,9 @@ trace-offcpu is implemented using sched:sched_switch tracepoint event, which may
on old kernels. But it is guaranteed to be supported on devices >= Android O MR1. We can check
whether trace-offcpu is supported as below.
+trace-offcpu only works with recording one of events: cpu-cycles, cpu-clock, task-clock. Using it
+with other events or multiple events doesn't make much sense and makes the report confusing.
+
```sh
$ simpleperf list --show-features
dwarf-based-call-graph
diff --git a/simpleperf/doc/jit_symbols.md b/simpleperf/doc/jit_symbols.md
new file mode 100644
index 00000000..8d0a2a93
--- /dev/null
+++ b/simpleperf/doc/jit_symbols.md
@@ -0,0 +1,59 @@
+# JIT symbols
+
+## Table of contents
+- [Java JIT symbols](#java-jit-symbols)
+- [Generic JIT symbols](#generic-jit-symbols)
+ - [Symbol map file location for application](#symbol-map-file-location-for-application)
+ - [Symbol map file location for standalone program](#symbol-map-file-location-for-standalone-program)
+ - [Symbol map file format](#symbol-map-file-format)
+ - [Known issues](#known-issues)
+
+## Java JIT symbols
+
+On Android >= P, simpleperf supports profiling Java code, no matter whether it is executed by
+the interpreter, or JITed, or compiled into native instructions. So you don't need to do anything.
+
+For details on Android O and N, see
+[android_application_profiling.md](./android_application_profiling.md#prepare-an-android-application).
+
+## Generic JIT symbols
+
+Simpleperf supports picking up symbols from per-pid symbol map files, somewhat similar to what
+Linux kernel perftool does. Application should create those files at specific locations.
+
+### Symbol map file location for application
+
+Application should create symbol map files in its data directory.
+
+For example, process `123` of application `foo.bar.baz` should create
+`/data/data/foo.bar.baz/perf-123.map`.
+
+### Symbol map file location for standalone program
+
+Standalone programs should create symbol map files in `/data/local/tmp`.
+
+For example, standalone program process `123` should create `/data/local/tmp/perf-123.map`.
+
+### Symbol map file format
+
+Symbol map file is a text file.
+
+Every line describes a new symbol. Line format is:
+```
+<symbol-absolute-address> <symbol-size> <symbol-name>
+```
+
+For example:
+```
+0x10000000 0x16 jit_symbol_one
+0x20000000 0x332 jit_symbol_two
+0x20002004 0x8 jit_symbol_three
+```
+
+### Known issues
+
+Current implementation gets confused if memory pages where JIT symbols reside are reused by mapping
+a file either before or after.
+
+For example, if memory pages were first used by `dlopen("libfoo.so")`, then freed by `dlclose`,
+then allocated for JIT symbols - simpleperf will report symbols from `libfoo.so` instead.
diff --git a/simpleperf/doc/scripts_reference.md b/simpleperf/doc/scripts_reference.md
index 28476daf..734c56d2 100644
--- a/simpleperf/doc/scripts_reference.md
+++ b/simpleperf/doc/scripts_reference.md
@@ -13,6 +13,7 @@
- [report.py](#reportpy)
- [report_html.py](#reporthtmlpy)
- [inferno](#inferno)
+ - [purgatorio](#purgatorio)
- [pprof_proto_generator.py](#pprofprotogeneratorpy)
- [report_sample.py](#reportsamplepy)
- [simpleperf_report_lib.py](#simpleperfreportlibpy)
@@ -208,6 +209,10 @@ $ ./inferno.sh -sc --record_file perf.data
$ ./inferno.sh -np surfaceflinger
```
+## purgatorio
+
+[purgatorio](../scripts/purgatorio/README.md) is a visualization tool to show samples in time order.
+
## pprof_proto_generator.py
It converts a profiling data file into pprof.proto, a format used by [pprof](https://github.com/google/pprof).
diff --git a/simpleperf/dso.cpp b/simpleperf/dso.cpp
index 6db0f6b7..73e21ef8 100644
--- a/simpleperf/dso.cpp
+++ b/simpleperf/dso.cpp
@@ -22,23 +22,32 @@
#include <algorithm>
#include <limits>
#include <memory>
+#include <optional>
+#include <string_view>
#include <vector>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
+#include "JITDebugReader.h"
#include "environment.h"
+#include "kallsyms.h"
#include "read_apk.h"
#include "read_dex_file.h"
#include "read_elf.h"
#include "utils.h"
+namespace simpleperf {
+
+using android::base::EndsWith;
+using android::base::StartsWith;
+
namespace simpleperf_dso_impl {
std::string RemovePathSeparatorSuffix(const std::string& path) {
// Don't remove path separator suffix for '/'.
- if (android::base::EndsWith(path, OS_PATH_SEPARATOR) && path.size() > 1u) {
+ if (EndsWith(path, OS_PATH_SEPARATOR) && path.size() > 1u) {
return path.substr(0, path.size() - 1);
}
return path;
@@ -87,7 +96,9 @@ void DebugElfFileFinder::CollectBuildIdInDir(const std::string& dir) {
CollectBuildIdInDir(path);
} else {
BuildId build_id;
- if (GetBuildIdFromElfFile(path, &build_id) == ElfStatus::NO_ERROR) {
+ ElfStatus status;
+ auto elf = ElfFile::Open(path, &status);
+ if (status == ElfStatus::NO_ERROR && elf->GetBuildId(&build_id) == ElfStatus::NO_ERROR) {
build_id_to_file_map_[build_id.ToString()] = path;
}
}
@@ -102,6 +113,29 @@ void DebugElfFileFinder::SetVdsoFile(const std::string& vdso_file, bool is_64bit
}
}
+static bool CheckDebugFilePath(const std::string& path, BuildId& build_id,
+ bool report_build_id_mismatch) {
+ ElfStatus status;
+ auto elf = ElfFile::Open(path, &status);
+ if (!elf) {
+ return false;
+ }
+ BuildId debug_build_id;
+ status = elf->GetBuildId(&debug_build_id);
+ if (status != ElfStatus::NO_ERROR && status != ElfStatus::NO_BUILD_ID) {
+ return false;
+ }
+
+ // Native libraries in apks and kernel modules may not have build ids.
+ // So build_id and debug_build_id can either be empty, or have the same value.
+ bool match = build_id == debug_build_id;
+ if (!match && report_build_id_mismatch) {
+ LOG(WARNING) << path << " isn't used because of build id mismatch: expected " << build_id
+ << ", real " << debug_build_id;
+ }
+ return match;
+}
+
std::string DebugElfFileFinder::FindDebugFile(const std::string& dso_path, bool force_64bit,
BuildId& build_id) {
if (dso_path == "[vdso]") {
@@ -115,22 +149,12 @@ std::string DebugElfFileFinder::FindDebugFile(const std::string& dso_path, bool
// Try reading build id from file if we don't already have one.
GetBuildIdFromDsoPath(dso_path, &build_id);
}
- auto check_path = [&](const std::string& path) {
- BuildId debug_build_id;
- GetBuildIdFromDsoPath(path, &debug_build_id);
- if (build_id.IsEmpty()) {
- // Native libraries in apks may not have build ids. When looking for a debug elf file without
- // build id (build id is empty), the debug file should exist and also not have build id.
- return IsRegularFile(path) && debug_build_id.IsEmpty();
- }
- return build_id == debug_build_id;
- };
// 1. Try build_id_to_file_map.
if (!build_id_to_file_map_.empty()) {
if (!build_id.IsEmpty() || GetBuildIdFromDsoPath(dso_path, &build_id)) {
auto it = build_id_to_file_map_.find(build_id.ToString());
- if (it != build_id_to_file_map_.end() && check_path(it->second)) {
+ if (it != build_id_to_file_map_.end() && CheckDebugFilePath(it->second, build_id, false)) {
return it->second;
}
}
@@ -138,18 +162,18 @@ std::string DebugElfFileFinder::FindDebugFile(const std::string& dso_path, bool
if (!symfs_dir_.empty()) {
// 2. Try concatenating symfs_dir and dso_path.
std::string path = GetPathInSymFsDir(dso_path);
- if (check_path(path)) {
+ if (CheckDebugFilePath(path, build_id, true)) {
return path;
}
// 3. Try concatenating symfs_dir and basename of dso_path.
path = symfs_dir_ + OS_PATH_SEPARATOR + android::base::Basename(dso_path);
- if (check_path(path)) {
+ if (CheckDebugFilePath(path, build_id, false)) {
return path;
}
}
// 4. Try concatenating /usr/lib/debug and dso_path.
// Linux host can store debug shared libraries in /usr/lib/debug.
- if (check_path("/usr/lib/debug" + dso_path)) {
+ if (CheckDebugFilePath("/usr/lib/debug" + dso_path, build_id, false)) {
return "/usr/lib/debug" + dso_path;
}
return dso_path;
@@ -157,7 +181,7 @@ std::string DebugElfFileFinder::FindDebugFile(const std::string& dso_path, bool
std::string DebugElfFileFinder::GetPathInSymFsDir(const std::string& path) {
auto add_symfs_prefix = [&](const std::string& path) {
- if (android::base::StartsWith(path, OS_PATH_SEPARATOR)) {
+ if (StartsWith(path, OS_PATH_SEPARATOR)) {
return symfs_dir_ + path;
}
return symfs_dir_ + OS_PATH_SEPARATOR + path;
@@ -178,7 +202,7 @@ std::string DebugElfFileFinder::GetPathInSymFsDir(const std::string& path) {
std::replace(elf_path.begin(), elf_path.end(), '/', OS_PATH_SEPARATOR);
return add_symfs_prefix(elf_path);
}
-} // namespace simpleperf_dso_imp
+} // namespace simpleperf_dso_impl
static OneTimeFreeAllocator symbol_name_allocator;
@@ -187,34 +211,45 @@ Symbol::Symbol(std::string_view name, uint64_t addr, uint64_t len)
len(len),
name_(symbol_name_allocator.AllocateString(name)),
demangled_name_(nullptr),
- dump_id_(UINT_MAX) {
-}
+ dump_id_(UINT_MAX) {}
const char* Symbol::DemangledName() const {
if (demangled_name_ == nullptr) {
const std::string s = Dso::Demangle(name_);
- if (s == name_) {
- demangled_name_ = name_;
- } else {
- demangled_name_ = symbol_name_allocator.AllocateString(s);
- }
+ SetDemangledName(s);
}
return demangled_name_;
}
+void Symbol::SetDemangledName(std::string_view name) const {
+ if (name == name_) {
+ demangled_name_ = name_;
+ } else {
+ demangled_name_ = symbol_name_allocator.AllocateString(name);
+ }
+}
+
+static bool CompareSymbolToAddr(const Symbol& s, uint64_t addr) {
+ return s.addr < addr;
+}
+
+static bool CompareAddrToSymbol(uint64_t addr, const Symbol& s) {
+ return addr < s.addr;
+}
+
bool Dso::demangle_ = true;
std::string Dso::vmlinux_;
std::string Dso::kallsyms_;
-bool Dso::read_kernel_symbols_from_proc_;
std::unordered_map<std::string, BuildId> Dso::build_id_map_;
size_t Dso::dso_count_;
uint32_t Dso::g_dump_id_;
simpleperf_dso_impl::DebugElfFileFinder Dso::debug_elf_file_finder_;
-void Dso::SetDemangle(bool demangle) { demangle_ = demangle; }
+void Dso::SetDemangle(bool demangle) {
+ demangle_ = demangle;
+}
-extern "C" char* __cxa_demangle(const char* mangled_name, char* buf, size_t* n,
- int* status);
+extern "C" char* __cxa_demangle(const char* mangled_name, char* buf, size_t* n, int* status);
std::string Dso::Demangle(const std::string& name) {
if (!demangle_) {
@@ -249,14 +284,14 @@ bool Dso::AddSymbolDir(const std::string& symbol_dir) {
return debug_elf_file_finder_.AddSymbolDir(symbol_dir);
}
-void Dso::SetVmlinux(const std::string& vmlinux) { vmlinux_ = vmlinux; }
+void Dso::SetVmlinux(const std::string& vmlinux) {
+ vmlinux_ = vmlinux;
+}
-void Dso::SetBuildIds(
- const std::vector<std::pair<std::string, BuildId>>& build_ids) {
+void Dso::SetBuildIds(const std::vector<std::pair<std::string, BuildId>>& build_ids) {
std::unordered_map<std::string, BuildId> map;
for (auto& pair : build_ids) {
- LOG(DEBUG) << "build_id_map: " << pair.first << ", "
- << pair.second.ToString();
+ LOG(DEBUG) << "build_id_map: " << pair.first << ", " << pair.second.ToString();
map.insert(pair);
}
build_id_map_ = std::move(map);
@@ -302,7 +337,6 @@ Dso::~Dso() {
demangle_ = true;
vmlinux_.clear();
kallsyms_.clear();
- read_kernel_symbols_from_proc_ = false;
build_id_map_.clear();
g_dump_id_ = 0;
debug_elf_file_finder_.Reset();
@@ -320,13 +354,15 @@ uint32_t Dso::CreateSymbolDumpId(const Symbol* symbol) {
return symbol->dump_id_;
}
+std::optional<uint64_t> Dso::IpToFileOffset(uint64_t ip, uint64_t map_start, uint64_t map_pgoff) {
+ return ip - map_start + map_pgoff;
+}
+
const Symbol* Dso::FindSymbol(uint64_t vaddr_in_dso) {
if (!is_loaded_) {
- Load();
+ LoadSymbols();
}
- auto it = std::upper_bound(symbols_.begin(), symbols_.end(),
- Symbol("", vaddr_in_dso, 0),
- Symbol::CompareValueByAddr);
+ auto it = std::upper_bound(symbols_.begin(), symbols_.end(), vaddr_in_dso, CompareAddrToSymbol);
if (it != symbols_.begin()) {
--it;
if (it->addr <= vaddr_in_dso && (it->addr + it->len > vaddr_in_dso)) {
@@ -351,35 +387,39 @@ void Dso::AddUnknownSymbol(uint64_t vaddr_in_dso, const std::string& name) {
unknown_symbols_.insert(std::make_pair(vaddr_in_dso, Symbol(name, vaddr_in_dso, 1)));
}
-bool Dso::IsForJavaMethod() {
+bool Dso::IsForJavaMethod() const {
if (type_ == DSO_DEX_FILE) {
return true;
}
if (type_ == DSO_ELF_FILE) {
- // JIT symfiles for JITed Java methods are dumped as temporary files, whose name are in format
- // "TemporaryFile-XXXXXX".
+ if (JITDebugReader::IsPathInJITSymFile(path_)) {
+ return true;
+ }
+ // JITDebugReader in old versions generates symfiles in 'TemporaryFile-XXXXXX'.
size_t pos = path_.rfind('/');
pos = (pos == std::string::npos) ? 0 : pos + 1;
- return strncmp(&path_[pos], "TemporaryFile", strlen("TemporaryFile")) == 0;
+ return StartsWith(std::string_view(&path_[pos], path_.size() - pos), "TemporaryFile");
}
return false;
}
-void Dso::Load() {
- is_loaded_ = true;
- std::vector<Symbol> symbols = LoadSymbols();
- if (symbols_.empty()) {
- symbols_ = std::move(symbols);
- } else {
- std::vector<Symbol> merged_symbols;
- std::set_union(symbols_.begin(), symbols_.end(), symbols.begin(), symbols.end(),
- std::back_inserter(merged_symbols), Symbol::CompareValueByAddr);
- symbols_ = std::move(merged_symbols);
+void Dso::LoadSymbols() {
+ if (!is_loaded_) {
+ is_loaded_ = true;
+ std::vector<Symbol> symbols = LoadSymbolsImpl();
+ if (symbols_.empty()) {
+ symbols_ = std::move(symbols);
+ } else {
+ std::vector<Symbol> merged_symbols;
+ std::set_union(symbols_.begin(), symbols_.end(), symbols.begin(), symbols.end(),
+ std::back_inserter(merged_symbols), Symbol::CompareValueByAddr);
+ symbols_ = std::move(merged_symbols);
+ }
}
}
-static void ReportReadElfSymbolResult(ElfStatus result, const std::string& path,
- const std::string& debug_file_path,
+static void ReportReadElfSymbolResult(
+ ElfStatus result, const std::string& path, const std::string& debug_file_path,
android::base::LogSeverity warning_loglevel = android::base::WARNING) {
if (result == ElfStatus::NO_ERROR) {
LOG(VERBOSE) << "Read symbols from " << debug_file_path << " successfully";
@@ -412,49 +452,45 @@ class DexFileDso : public Dso {
: Dso(DSO_DEX_FILE, path, debug_file_path) {}
void AddDexFileOffset(uint64_t dex_file_offset) override {
- auto it = std::lower_bound(dex_file_offsets_.begin(), dex_file_offsets_.end(),
- dex_file_offset);
+ auto it = std::lower_bound(dex_file_offsets_.begin(), dex_file_offsets_.end(), dex_file_offset);
if (it != dex_file_offsets_.end() && *it == dex_file_offset) {
return;
}
dex_file_offsets_.insert(it, dex_file_offset);
}
- const std::vector<uint64_t>* DexFileOffsets() override {
- return &dex_file_offsets_;
- }
+ const std::vector<uint64_t>* DexFileOffsets() override { return &dex_file_offsets_; }
uint64_t IpToVaddrInFile(uint64_t ip, uint64_t map_start, uint64_t map_pgoff) override {
return ip - map_start + map_pgoff;
}
- std::vector<Symbol> LoadSymbols() override {
+ std::vector<Symbol> LoadSymbolsImpl() override {
std::vector<Symbol> symbols;
- std::vector<DexFileSymbol> dex_file_symbols;
auto tuple = SplitUrlInApk(debug_file_path_);
bool status = false;
+ auto symbol_callback = [&](DexFileSymbol* symbol) {
+ symbols.emplace_back(symbol->name, symbol->addr, symbol->size);
+ };
if (std::get<0>(tuple)) {
std::unique_ptr<ArchiveHelper> ahelper = ArchiveHelper::CreateInstance(std::get<1>(tuple));
ZipEntry entry;
std::vector<uint8_t> data;
- if (ahelper &&
- ahelper->FindEntry(std::get<2>(tuple), &entry) && ahelper->GetEntryData(entry, &data)) {
+ if (ahelper && ahelper->FindEntry(std::get<2>(tuple), &entry) &&
+ ahelper->GetEntryData(entry, &data)) {
status = ReadSymbolsFromDexFileInMemory(data.data(), data.size(), dex_file_offsets_,
- &dex_file_symbols);
+ symbol_callback);
}
} else {
- status = ReadSymbolsFromDexFile(debug_file_path_, dex_file_offsets_, &dex_file_symbols);
+ status = ReadSymbolsFromDexFile(debug_file_path_, dex_file_offsets_, symbol_callback);
}
if (!status) {
- android::base::LogSeverity level = symbols_.empty() ? android::base::WARNING
- : android::base::DEBUG;
+ android::base::LogSeverity level =
+ symbols_.empty() ? android::base::WARNING : android::base::DEBUG;
LOG(level) << "Failed to read symbols from " << debug_file_path_;
return symbols;
}
LOG(VERBOSE) << "Read symbols from " << debug_file_path_ << " successfully";
- for (auto& symbol : dex_file_symbols) {
- symbols.emplace_back(symbol.name, symbol.offset, symbol.len);
- }
SortAndFixSymbols(symbols);
return symbols;
}
@@ -468,6 +504,16 @@ class ElfDso : public Dso {
ElfDso(const std::string& path, const std::string& debug_file_path)
: Dso(DSO_ELF_FILE, path, debug_file_path) {}
+ std::string_view GetReportPath() const override {
+ if (JITDebugReader::IsPathInJITSymFile(path_)) {
+ if (path_.find(kJITAppCacheFile) != path_.npos) {
+ return "[JIT app cache]";
+ }
+ return "[JIT zygote cache]";
+ }
+ return path_;
+ }
+
void SetMinExecutableVaddr(uint64_t min_vaddr, uint64_t file_offset) override {
min_vaddr_ = min_vaddr;
file_offset_of_min_vaddr_ = file_offset;
@@ -480,29 +526,14 @@ class ElfDso : public Dso {
if (min_vaddr_ == uninitialized_value) {
min_vaddr_ = 0;
BuildId build_id = GetExpectedBuildId();
- uint64_t addr;
- uint64_t offset;
- ElfStatus result;
- auto tuple = SplitUrlInApk(debug_file_path_);
- if (std::get<0>(tuple)) {
- EmbeddedElf* elf = ApkInspector::FindElfInApkByName(std::get<1>(tuple),
- std::get<2>(tuple));
- if (elf == nullptr) {
- result = ElfStatus::FILE_NOT_FOUND;
- } else {
- result = ReadMinExecutableVirtualAddressFromEmbeddedElfFile(
- elf->filepath(), elf->entry_offset(), elf->entry_size(), build_id, &addr, &offset);
- }
- } else {
- result = ReadMinExecutableVirtualAddressFromElfFile(debug_file_path_, build_id, &addr,
- &offset);
- }
- if (result != ElfStatus::NO_ERROR) {
- LOG(WARNING) << "failed to read min virtual address of "
- << GetDebugFilePath() << ": " << result;
+
+ ElfStatus status;
+ auto elf = ElfFile::Open(debug_file_path_, &build_id, &status);
+ if (elf) {
+ min_vaddr_ = elf->ReadMinExecutableVaddr(&file_offset_of_min_vaddr_);
} else {
- min_vaddr_ = addr;
- file_offset_of_min_vaddr_ = offset;
+ LOG(WARNING) << "failed to read min virtual address of " << debug_file_path_ << ": "
+ << status;
}
}
*min_vaddr = min_vaddr_;
@@ -543,9 +574,9 @@ class ElfDso : public Dso {
}
protected:
- std::vector<Symbol> LoadSymbols() override {
+ std::vector<Symbol> LoadSymbolsImpl() override {
if (dex_file_dso_) {
- return dex_file_dso_->LoadSymbols();
+ return dex_file_dso_->LoadSymbolsImpl();
}
std::vector<Symbol> symbols;
BuildId build_id = GetExpectedBuildId();
@@ -555,17 +586,9 @@ class ElfDso : public Dso {
}
};
ElfStatus status;
- std::tuple<bool, std::string, std::string> tuple = SplitUrlInApk(debug_file_path_);
- if (std::get<0>(tuple)) {
- EmbeddedElf* elf = ApkInspector::FindElfInApkByName(std::get<1>(tuple), std::get<2>(tuple));
- if (elf == nullptr) {
- status = ElfStatus::FILE_NOT_FOUND;
- } else {
- status = ParseSymbolsFromEmbeddedElfFile(elf->filepath(), elf->entry_offset(),
- elf->entry_size(), build_id, symbol_callback);
- }
- } else {
- status = ParseSymbolsFromElfFile(debug_file_path_, build_id, symbol_callback);
+ auto elf = ElfFile::Open(debug_file_path_, &build_id, &status);
+ if (elf) {
+ status = elf->ParseSymbols(symbol_callback);
}
ReportReadElfSymbolResult(status, path_, debug_file_path_,
symbols_.empty() ? android::base::WARNING : android::base::DEBUG);
@@ -584,27 +607,108 @@ class ElfDso : public Dso {
class KernelDso : public Dso {
public:
KernelDso(const std::string& path, const std::string& debug_file_path)
- : Dso(DSO_KERNEL, path, debug_file_path) {}
+ : Dso(DSO_KERNEL, path, debug_file_path) {
+ 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_;
+ has_debug_file_ = true;
+ }
+ } else if (IsRegularFile(debug_file_path_)) {
+ has_debug_file_ = true;
+ }
+ }
- uint64_t IpToVaddrInFile(uint64_t ip, uint64_t, uint64_t) override {
+ // IpToVaddrInFile() and LoadSymbols() must be consistent in fixing addresses changed by kernel
+ // address space layout randomization.
+ uint64_t IpToVaddrInFile(uint64_t ip, uint64_t map_start, uint64_t) override {
+ if (map_start != 0 && GetKernelStartAddr() != 0) {
+ // Fix kernel addresses changed by kernel address randomization.
+ fix_kernel_address_randomization_ = true;
+ return ip - map_start + GetKernelStartAddr();
+ }
return ip;
}
+ std::optional<uint64_t> IpToFileOffset(uint64_t ip, uint64_t map_start, uint64_t) override {
+ if (map_start != 0 && GetKernelStartOffset() != 0) {
+ return ip - map_start + GetKernelStartOffset();
+ }
+ return std::nullopt;
+ }
+
protected:
- std::vector<Symbol> LoadSymbols() override {
+ std::vector<Symbol> LoadSymbolsImpl() override {
std::vector<Symbol> symbols;
- BuildId build_id = GetExpectedBuildId();
- if (!vmlinux_.empty()) {
- auto symbol_callback = [&](const ElfFileSymbol& symbol) {
- if (symbol.is_func) {
- symbols.emplace_back(symbol.name, symbol.vaddr, symbol.len);
+ if (has_debug_file_) {
+ ReadSymbolsFromDebugFile(&symbols);
+ }
+
+ if (symbols.empty() && !kallsyms_.empty()) {
+ ReadSymbolsFromKallsyms(kallsyms_, &symbols);
+ }
+#if defined(__linux__)
+ if (symbols.empty()) {
+ ReadSymbolsFromProc(&symbols);
+ }
+#endif // defined(__linux__)
+ SortAndFixSymbols(symbols);
+ if (!symbols.empty()) {
+ symbols.back().len = std::numeric_limits<uint64_t>::max() - symbols.back().addr;
+ }
+ return symbols;
+ }
+
+ private:
+ void ReadSymbolsFromDebugFile(std::vector<Symbol>* symbols) {
+ if (!fix_kernel_address_randomization_) {
+ LOG(WARNING) << "Don't know how to fix addresses changed by kernel address randomization. So "
+ "symbols in "
+ << debug_file_path_ << " are not used";
+ return;
+ }
+ // symbols_ are kernel symbols got from /proc/kallsyms while recording. Those symbols are
+ // not fixed for kernel address randomization. So clear them to avoid mixing them with
+ // symbols in debug_file_path.
+ symbols_.clear();
+
+ auto symbol_callback = [&](const ElfFileSymbol& symbol) {
+ if (symbol.is_func) {
+ symbols->emplace_back(symbol.name, symbol.vaddr, symbol.len);
+ }
+ };
+ ElfStatus status;
+ if (auto elf = ElfFile::Open(debug_file_path_, &status); elf) {
+ status = elf->ParseSymbols(symbol_callback);
+ }
+ ReportReadElfSymbolResult(status, path_, debug_file_path_);
+ }
+
+ void ReadSymbolsFromKallsyms(std::string& kallsyms, std::vector<Symbol>* symbols) {
+ auto symbol_callback = [&](const KernelSymbol& symbol) {
+ if (strchr("TtWw", symbol.type) && symbol.addr != 0u) {
+ if (symbol.module == nullptr) {
+ symbols->emplace_back(symbol.name, symbol.addr, 0);
+ } else {
+ std::string name = std::string(symbol.name) + " [" + symbol.module + "]";
+ symbols->emplace_back(name, symbol.addr, 0);
}
- };
- ElfStatus status = ParseSymbolsFromElfFile(vmlinux_, build_id, symbol_callback);
- ReportReadElfSymbolResult(status, path_, vmlinux_);
- } else if (!kallsyms_.empty()) {
- symbols = ReadSymbolsFromKallsyms(kallsyms_);
- } else if (read_kernel_symbols_from_proc_ || !build_id.IsEmpty()) {
+ }
+ return false;
+ };
+ ProcessKernelSymbols(kallsyms, symbol_callback);
+ if (symbols->empty()) {
+ LOG(WARNING) << "Symbol addresses in /proc/kallsyms on device are all zero. "
+ "`echo 0 >/proc/sys/kernel/kptr_restrict` if possible.";
+ }
+ }
+
+#if defined(__linux__)
+ void ReadSymbolsFromProc(std::vector<Symbol>* symbols) {
+ BuildId build_id = GetExpectedBuildId();
+ if (!build_id.IsEmpty()) {
// Try /proc/kallsyms only when asked to do so, or when build id matches.
// Otherwise, it is likely to use /proc/kallsyms on host for perf.data recorded on device.
bool can_read_kallsyms = true;
@@ -617,92 +721,190 @@ class KernelDso : public Dso {
}
if (can_read_kallsyms) {
std::string kallsyms;
- if (!android::base::ReadFileToString("/proc/kallsyms", &kallsyms)) {
- LOG(DEBUG) << "failed to read /proc/kallsyms";
- } else {
- symbols = ReadSymbolsFromKallsyms(kallsyms);
+ if (LoadKernelSymbols(&kallsyms)) {
+ ReadSymbolsFromKallsyms(kallsyms, symbols);
}
}
}
- SortAndFixSymbols(symbols);
- if (!symbols.empty()) {
- symbols.back().len = std::numeric_limits<uint64_t>::max() - symbols.back().addr;
+ }
+#endif // defined(__linux__)
+
+ uint64_t GetKernelStartAddr() {
+ if (!kernel_start_addr_) {
+ ParseKernelStartAddr();
}
- return symbols;
+ return kernel_start_addr_.value();
}
- private:
- std::vector<Symbol> ReadSymbolsFromKallsyms(std::string& kallsyms) {
- std::vector<Symbol> symbols;
- auto symbol_callback = [&](const KernelSymbol& symbol) {
- if (strchr("TtWw", symbol.type) && symbol.addr != 0u) {
- symbols.emplace_back(symbol.name, symbol.addr, 0);
+ uint64_t GetKernelStartOffset() {
+ if (!kernel_start_file_offset_) {
+ ParseKernelStartAddr();
+ }
+ return kernel_start_file_offset_.value();
+ }
+
+ void ParseKernelStartAddr() {
+ kernel_start_addr_ = 0;
+ kernel_start_file_offset_ = 0;
+ if (has_debug_file_) {
+ ElfStatus status;
+ if (auto elf = ElfFile::Open(debug_file_path_, &status); elf) {
+ for (const auto& section : elf->GetSectionHeader()) {
+ if (section.name == ".text") {
+ kernel_start_addr_ = section.vaddr;
+ kernel_start_file_offset_ = section.file_offset;
+ break;
+ }
+ }
}
- return false;
- };
- ProcessKernelSymbols(kallsyms, symbol_callback);
- if (symbols.empty()) {
- LOG(WARNING) << "Symbol addresses in /proc/kallsyms on device are all zero. "
- "`echo 0 >/proc/sys/kernel/kptr_restrict` if possible.";
}
- return symbols;
}
+
+ bool has_debug_file_ = false;
+ bool fix_kernel_address_randomization_ = false;
+ std::optional<uint64_t> kernel_start_addr_;
+ std::optional<uint64_t> kernel_start_file_offset_;
};
class KernelModuleDso : public Dso {
public:
- KernelModuleDso(const std::string& path, const std::string& debug_file_path)
- : Dso(DSO_KERNEL_MODULE, path, debug_file_path) {}
+ KernelModuleDso(const std::string& path, const std::string& debug_file_path,
+ uint64_t memory_start, uint64_t memory_end, Dso* kernel_dso)
+ : Dso(DSO_KERNEL_MODULE, path, debug_file_path),
+ memory_start_(memory_start),
+ memory_end_(memory_end),
+ kernel_dso_(kernel_dso) {}
+
+ void SetMinExecutableVaddr(uint64_t min_vaddr, uint64_t memory_offset) override {
+ min_vaddr_ = min_vaddr;
+ memory_offset_of_min_vaddr_ = memory_offset;
+ }
+
+ void GetMinExecutableVaddr(uint64_t* min_vaddr, uint64_t* memory_offset) override {
+ if (!min_vaddr_) {
+ CalculateMinVaddr();
+ }
+ *min_vaddr = min_vaddr_.value();
+ *memory_offset = memory_offset_of_min_vaddr_.value();
+ }
uint64_t IpToVaddrInFile(uint64_t ip, uint64_t map_start, uint64_t) override {
- return ip - map_start;
+ uint64_t min_vaddr;
+ uint64_t memory_offset;
+ GetMinExecutableVaddr(&min_vaddr, &memory_offset);
+ return ip - map_start - memory_offset + min_vaddr;
}
protected:
- std::vector<Symbol> LoadSymbols() override {
+ std::vector<Symbol> LoadSymbolsImpl() override {
std::vector<Symbol> symbols;
BuildId build_id = GetExpectedBuildId();
auto symbol_callback = [&](const ElfFileSymbol& symbol) {
- if (symbol.is_func || symbol.is_in_text_section) {
+ // We only know how to map ip addrs to symbols in text section.
+ if (symbol.is_in_text_section && (symbol.is_label || symbol.is_func)) {
symbols.emplace_back(symbol.name, symbol.vaddr, symbol.len);
}
};
- ElfStatus status = ParseSymbolsFromElfFile(debug_file_path_, build_id, symbol_callback);
+ ElfStatus status;
+ auto elf = ElfFile::Open(debug_file_path_, &build_id, &status);
+ if (elf) {
+ status = elf->ParseSymbols(symbol_callback);
+ }
ReportReadElfSymbolResult(status, path_, debug_file_path_,
symbols_.empty() ? android::base::WARNING : android::base::DEBUG);
SortAndFixSymbols(symbols);
return symbols;
}
+
+ private:
+ void CalculateMinVaddr() {
+ min_vaddr_ = 0;
+ memory_offset_of_min_vaddr_ = 0;
+
+ // min_vaddr and memory_offset are used to convert an ip addr of a kernel module to its
+ // vaddr_in_file, as shown in IpToVaddrInFile(). When the kernel loads a kernel module, it
+ // puts ALLOC sections (like .plt, .text.ftrace_trampoline, .text) in memory in order. The
+ // text section may not be at the start of the module memory. To do address conversion, we
+ // need to know its relative position in the module memory. There are two ways:
+ // 1. Read the kernel module file to calculate the relative position of .text section. It
+ // is relatively complex and depends on both PLT entries and the kernel version.
+ // 2. Find a module symbol in .text section, get its address in memory from /proc/kallsyms, 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.
+
+ // 1. Select a module symbol in /proc/kallsyms.
+ kernel_dso_->LoadSymbols();
+ const auto& kernel_symbols = kernel_dso_->GetSymbols();
+ auto it = std::lower_bound(kernel_symbols.begin(), kernel_symbols.end(), memory_start_,
+ CompareSymbolToAddr);
+ const Symbol* kernel_symbol = nullptr;
+ while (it != kernel_symbols.end() && it->addr < memory_end_) {
+ if (strlen(it->Name()) > 0 && it->Name()[0] != '$') {
+ kernel_symbol = &*it;
+ break;
+ }
+ ++it;
+ }
+ if (kernel_symbol == nullptr) {
+ return;
+ }
+
+ // 2. Find the symbol in .ko file.
+ std::string symbol_name = kernel_symbol->Name();
+ if (auto pos = symbol_name.rfind(' '); pos != std::string::npos) {
+ symbol_name.resize(pos);
+ }
+ LoadSymbols();
+ for (const auto& symbol : symbols_) {
+ if (symbol_name == symbol.Name()) {
+ min_vaddr_ = symbol.addr;
+ memory_offset_of_min_vaddr_ = kernel_symbol->addr - memory_start_;
+ return;
+ }
+ }
+ }
+
+ uint64_t memory_start_;
+ uint64_t memory_end_;
+ Dso* kernel_dso_;
+ std::optional<uint64_t> min_vaddr_;
+ std::optional<uint64_t> memory_offset_of_min_vaddr_;
+};
+
+class SymbolMapFileDso : public Dso {
+ public:
+ SymbolMapFileDso(const std::string& path) : Dso(DSO_SYMBOL_MAP_FILE, path, path) {}
+
+ uint64_t IpToVaddrInFile(uint64_t ip, uint64_t, uint64_t) override { return ip; }
+
+ protected:
+ std::vector<Symbol> LoadSymbolsImpl() override { return {}; }
};
class UnknownDso : public Dso {
public:
UnknownDso(const std::string& path) : Dso(DSO_UNKNOWN_FILE, path, path) {}
- uint64_t IpToVaddrInFile(uint64_t ip, uint64_t, uint64_t) override {
- return ip;
- }
+ uint64_t IpToVaddrInFile(uint64_t ip, uint64_t, uint64_t) override { return ip; }
protected:
- std::vector<Symbol> LoadSymbols() override {
- return std::vector<Symbol>();
- }
+ std::vector<Symbol> LoadSymbolsImpl() override { return std::vector<Symbol>(); }
};
std::unique_ptr<Dso> Dso::CreateDso(DsoType dso_type, const std::string& dso_path,
bool force_64bit) {
+ BuildId build_id = FindExpectedBuildIdForPath(dso_path);
+ std::string debug_path = debug_elf_file_finder_.FindDebugFile(dso_path, force_64bit, build_id);
switch (dso_type) {
- case DSO_ELF_FILE: {
- BuildId build_id = FindExpectedBuildIdForPath(dso_path);
- return std::unique_ptr<Dso>(new ElfDso(dso_path,
- debug_elf_file_finder_.FindDebugFile(dso_path, force_64bit, build_id)));
- }
+ case DSO_ELF_FILE:
+ return std::unique_ptr<Dso>(new ElfDso(dso_path, debug_path));
case DSO_KERNEL:
- return std::unique_ptr<Dso>(new KernelDso(dso_path, dso_path));
- case DSO_KERNEL_MODULE:
- return std::unique_ptr<Dso>(new KernelModuleDso(dso_path, dso_path));
+ return std::unique_ptr<Dso>(new KernelDso(dso_path, debug_path));
case DSO_DEX_FILE:
return std::unique_ptr<Dso>(new DexFileDso(dso_path, dso_path));
+ case DSO_SYMBOL_MAP_FILE:
+ return std::unique_ptr<Dso>(new SymbolMapFileDso(dso_path));
case DSO_UNKNOWN_FILE:
return std::unique_ptr<Dso>(new UnknownDso(dso_path));
default:
@@ -711,6 +913,30 @@ std::unique_ptr<Dso> Dso::CreateDso(DsoType dso_type, const std::string& dso_pat
return nullptr;
}
+std::unique_ptr<Dso> Dso::CreateDsoWithBuildId(DsoType dso_type, const std::string& dso_path,
+ BuildId& build_id) {
+ std::string debug_path = debug_elf_file_finder_.FindDebugFile(dso_path, false, build_id);
+ switch (dso_type) {
+ case DSO_ELF_FILE:
+ return std::unique_ptr<Dso>(new ElfDso(dso_path, debug_path));
+ case DSO_KERNEL:
+ return std::unique_ptr<Dso>(new KernelDso(dso_path, debug_path));
+ case DSO_KERNEL_MODULE:
+ return std::unique_ptr<Dso>(new KernelModuleDso(dso_path, debug_path, 0, 0, nullptr));
+ default:
+ LOG(FATAL) << "Unexpected dso_type " << static_cast<int>(dso_type);
+ }
+ return nullptr;
+}
+
+std::unique_ptr<Dso> Dso::CreateKernelModuleDso(const std::string& dso_path, uint64_t memory_start,
+ uint64_t memory_end, Dso* kernel_dso) {
+ BuildId build_id = FindExpectedBuildIdForPath(dso_path);
+ std::string debug_path = debug_elf_file_finder_.FindDebugFile(dso_path, false, build_id);
+ return std::unique_ptr<Dso>(
+ new KernelModuleDso(dso_path, debug_path, memory_start, memory_end, kernel_dso));
+}
+
const char* DsoTypeToString(DsoType dso_type) {
switch (dso_type) {
case DSO_KERNEL:
@@ -721,24 +947,20 @@ const char* DsoTypeToString(DsoType dso_type) {
return "dso_elf_file";
case DSO_DEX_FILE:
return "dso_dex_file";
+ case DSO_SYMBOL_MAP_FILE:
+ return "dso_symbol_map_file";
default:
return "unknown";
}
}
bool GetBuildIdFromDsoPath(const std::string& dso_path, BuildId* build_id) {
- auto tuple = SplitUrlInApk(dso_path);
- ElfStatus result;
- if (std::get<0>(tuple)) {
- EmbeddedElf* elf = ApkInspector::FindElfInApkByName(std::get<1>(tuple), std::get<2>(tuple));
- if (elf == nullptr) {
- result = ElfStatus::FILE_NOT_FOUND;
- } else {
- result = GetBuildIdFromEmbeddedElfFile(elf->filepath(), elf->entry_offset(),
- elf->entry_size(), build_id);
- }
- } else {
- result = GetBuildIdFromElfFile(dso_path, build_id);
+ ElfStatus status;
+ auto elf = ElfFile::Open(dso_path, &status);
+ if (status == ElfStatus::NO_ERROR && elf->GetBuildId(build_id) == ElfStatus::NO_ERROR) {
+ return true;
}
- return result == ElfStatus::NO_ERROR;
+ return false;
}
+
+} // namespace simpleperf
diff --git a/simpleperf/dso.h b/simpleperf/dso.h
index fbb46f7b..4bac3e0d 100644
--- a/simpleperf/dso.h
+++ b/simpleperf/dso.h
@@ -18,7 +18,9 @@
#define SIMPLE_PERF_DSO_H_
#include <memory>
+#include <optional>
#include <string>
+#include <string_view>
#include <unordered_map>
#include <vector>
@@ -26,9 +28,10 @@
#include <android-base/logging.h>
#include "build_id.h"
+#include "kallsyms.h"
#include "read_elf.h"
-
+namespace simpleperf {
namespace simpleperf_dso_impl {
// Find elf files with symbol table and debug information.
@@ -38,8 +41,7 @@ class DebugElfFileFinder {
bool SetSymFsDir(const std::string& symfs_dir);
bool AddSymbolDir(const std::string& symbol_dir);
void SetVdsoFile(const std::string& vdso_file, bool is_64bit);
- std::string FindDebugFile(const std::string& dso_path, bool force_64bit,
- BuildId& build_id);
+ std::string FindDebugFile(const std::string& dso_path, bool force_64bit, BuildId& build_id);
// Only for testing
std::string GetPathInSymFsDir(const std::string& path);
@@ -63,10 +65,9 @@ struct Symbol {
const char* Name() const { return name_; }
const char* DemangledName() const;
+ void SetDemangledName(std::string_view name) const;
- bool HasDumpId() const {
- return dump_id_ != UINT_MAX;
- }
+ bool HasDumpId() const { return dump_id_ != UINT_MAX; }
bool GetDumpId(uint32_t* pdump_id) const {
if (!HasDumpId()) {
@@ -84,13 +85,9 @@ struct Symbol {
return id1 < id2;
}
- static bool CompareByAddr(const Symbol* s1, const Symbol* s2) {
- return s1->addr < s2->addr;
- }
+ static bool CompareByAddr(const Symbol* s1, const Symbol* s2) { return s1->addr < s2->addr; }
- static bool CompareValueByAddr(const Symbol& s1, const Symbol& s2) {
- return s1.addr < s2.addr;
- }
+ static bool CompareValueByAddr(const Symbol& s1, const Symbol& s2) { return s1.addr < s2.addr; }
private:
const char* name_;
@@ -105,12 +102,12 @@ enum DsoType {
DSO_KERNEL_MODULE,
DSO_ELF_FILE,
DSO_DEX_FILE, // For files containing dex files, like .vdex files.
+ DSO_SYMBOL_MAP_FILE,
DSO_UNKNOWN_FILE,
+ // DSO_UNKNOWN_FILE is written to the file feature section in recording files. Changing its value
+ // may cause compatibility issue. So put new DsoTypes below.
};
-struct KernelSymbol;
-struct ElfFileSymbol;
-
class Dso {
public:
static void SetDemangle(bool demangle);
@@ -128,17 +125,17 @@ class Dso {
kallsyms_ = std::move(kallsyms);
}
}
- static void ReadKernelSymbolsFromProc() {
- read_kernel_symbols_from_proc_ = true;
- }
- static void SetBuildIds(
- const std::vector<std::pair<std::string, BuildId>>& build_ids);
+ static void SetBuildIds(const std::vector<std::pair<std::string, BuildId>>& build_ids);
static BuildId FindExpectedBuildIdForPath(const std::string& path);
static void SetVdsoFile(const std::string& vdso_file, bool is_64bit);
static std::unique_ptr<Dso> CreateDso(DsoType dso_type, const std::string& dso_path,
bool force_64bit = false);
-
+ static std::unique_ptr<Dso> CreateDsoWithBuildId(DsoType dso_type, const std::string& dso_path,
+ BuildId& build_id);
+ static std::unique_ptr<Dso> CreateKernelModuleDso(const std::string& dso_path,
+ uint64_t memory_start, uint64_t memory_end,
+ Dso* kernel_dso);
virtual ~Dso();
DsoType type() const { return type_; }
@@ -147,12 +144,12 @@ class Dso {
const std::string& Path() const { return path_; }
// Return the path containing symbol table and debug information.
const std::string& GetDebugFilePath() const { return debug_file_path_; }
+ // Return the path beautified for reporting.
+ virtual std::string_view GetReportPath() const { return Path(); }
// Return the file name without directory info.
const std::string& FileName() const { return file_name_; }
- bool HasDumpId() {
- return dump_id_ != UINT_MAX;
- }
+ bool HasDumpId() { return dump_id_ != UINT_MAX; }
bool GetDumpId(uint32_t* pdump_id) {
if (!HasDumpId()) {
@@ -174,22 +171,23 @@ class Dso {
virtual const std::vector<uint64_t>* DexFileOffsets() { return nullptr; }
virtual uint64_t IpToVaddrInFile(uint64_t ip, uint64_t map_start, uint64_t map_pgoff) = 0;
+ virtual std::optional<uint64_t> IpToFileOffset(uint64_t ip, uint64_t map_start,
+ uint64_t map_pgoff);
const Symbol* FindSymbol(uint64_t vaddr_in_dso);
-
- const std::vector<Symbol>& GetSymbols() { return symbols_; }
+ void LoadSymbols();
+ const std::vector<Symbol>& GetSymbols() const { return symbols_; }
void SetSymbols(std::vector<Symbol>* symbols);
// Create a symbol for a virtual address which can't find a corresponding
// symbol in symbol table.
void AddUnknownSymbol(uint64_t vaddr_in_dso, const std::string& name);
- bool IsForJavaMethod();
+ bool IsForJavaMethod() const;
protected:
static bool demangle_;
static std::string vmlinux_;
static std::string kallsyms_;
- static bool read_kernel_symbols_from_proc_;
static std::unordered_map<std::string, BuildId> build_id_map_;
static size_t dso_count_;
static uint32_t g_dump_id_;
@@ -198,8 +196,7 @@ class Dso {
Dso(DsoType type, const std::string& path, const std::string& debug_file_path);
BuildId GetExpectedBuildId();
- void Load();
- virtual std::vector<Symbol> LoadSymbols() = 0;
+ virtual std::vector<Symbol> LoadSymbolsImpl() = 0;
DsoType type_;
// path of the shared library used by the profiled program
@@ -223,4 +220,6 @@ class Dso {
const char* DsoTypeToString(DsoType dso_type);
bool GetBuildIdFromDsoPath(const std::string& dso_path, BuildId* build_id);
+} // namespace simpleperf
+
#endif // SIMPLE_PERF_DSO_H_
diff --git a/simpleperf/dso_test.cpp b/simpleperf/dso_test.cpp
index 77537d46..02958ce6 100644
--- a/simpleperf/dso_test.cpp
+++ b/simpleperf/dso_test.cpp
@@ -20,11 +20,14 @@
#include <android-base/file.h>
#include <android-base/stringprintf.h>
+#include <android-base/test_utils.h>
#include "get_test_data.h"
#include "read_apk.h"
+#include "thread_tree.h"
#include "utils.h"
+using namespace simpleperf;
using namespace simpleperf_dso_impl;
TEST(DebugElfFileFinder, use_build_id_list) {
@@ -133,6 +136,19 @@ TEST(DebugElfFileFinder, find_basename_in_symfs_dir) {
symfs_dir + OS_PATH_SEPARATOR + "elf");
}
+TEST(DebugElfFileFinder, build_id_mismatch) {
+ DebugElfFileFinder finder;
+ finder.SetSymFsDir(GetTestDataDir());
+ CapturedStderr capture;
+ capture.Start();
+ BuildId mismatch_build_id("0c12a384a9f4a3f3659b7171ca615dbec3a81f71");
+ std::string debug_file = finder.FindDebugFile(ELF_FILE, false, mismatch_build_id);
+ capture.Stop();
+ std::string stderr_output = capture.str();
+ ASSERT_EQ(debug_file, ELF_FILE);
+ ASSERT_NE(stderr_output.find("build id mismatch"), std::string::npos);
+}
+
TEST(dso, dex_file_dso) {
#if defined(__linux__)
for (DsoType dso_type : {DSO_DEX_FILE, DSO_ELF_FILE}) {
@@ -195,3 +211,93 @@ TEST(dso, IpToVaddrInFile) {
ASSERT_TRUE(dso);
ASSERT_EQ(0xa5140, dso->IpToVaddrInFile(0xe9201140, 0xe9201000, 0xa5000));
}
+
+TEST(dso, kernel_address_randomization) {
+ // Use ELF_FILE as a fake kernel vmlinux.
+ const std::string vmlinux_path = GetTestData(ELF_FILE);
+ Dso::SetVmlinux(vmlinux_path);
+ std::unique_ptr<Dso> dso = Dso::CreateDso(DSO_KERNEL, DEFAULT_KERNEL_MMAP_NAME);
+ ASSERT_TRUE(dso);
+ ASSERT_EQ(dso->GetDebugFilePath(), vmlinux_path);
+ // When map_start = 0, can't fix kernel address randomization. So vmlinux isn't used.
+ ASSERT_EQ(dso->IpToVaddrInFile(0x800500, 0, 0), 0x800500);
+ ASSERT_FALSE(dso->IpToFileOffset(0x800500, 0, 0));
+ ASSERT_TRUE(dso->FindSymbol(0x400510) == nullptr);
+
+ dso = Dso::CreateDso(DSO_KERNEL, DEFAULT_KERNEL_MMAP_NAME);
+ ASSERT_TRUE(dso);
+ ASSERT_EQ(dso->GetDebugFilePath(), vmlinux_path);
+ // When map_start != 0, can fix kernel address randomization. So vmlinux is used.
+ ASSERT_EQ(dso->IpToVaddrInFile(0x800500, 0x800400, 0), 0x400500);
+ ASSERT_EQ(dso->IpToFileOffset(0x800500, 0x800400, 0).value(), 0x500);
+ const Symbol* symbol = dso->FindSymbol(0x400510);
+ ASSERT_TRUE(symbol != nullptr);
+ ASSERT_STREQ(symbol->Name(), "GlobalFunc");
+}
+
+TEST(dso, find_vmlinux_in_symdirs) {
+ // Create a symdir.
+ TemporaryDir tmpdir;
+ std::string vmlinux_path = std::string(tmpdir.path) + OS_PATH_SEPARATOR + "elf";
+ std::string data;
+ ASSERT_TRUE(android::base::ReadFileToString(GetTestData(ELF_FILE), &data));
+ ASSERT_TRUE(android::base::WriteStringToFile(data, vmlinux_path));
+
+ // Find vmlinux in symbol dirs.
+ Dso::SetVmlinux("");
+ Dso::AddSymbolDir(tmpdir.path);
+ Dso::SetBuildIds({std::make_pair(DEFAULT_KERNEL_MMAP_NAME, BuildId(ELF_FILE_BUILD_ID))});
+ std::unique_ptr<Dso> dso = Dso::CreateDso(DSO_KERNEL, DEFAULT_KERNEL_MMAP_NAME);
+ ASSERT_TRUE(dso);
+ ASSERT_EQ(dso->GetDebugFilePath(), vmlinux_path);
+}
+
+TEST(dso, kernel_module) {
+ // Test finding debug files for kernel modules.
+ Dso::SetSymFsDir(GetTestDataDir());
+ std::vector<std::pair<std::string, BuildId>> build_ids;
+ build_ids.emplace_back(ELF_FILE, BuildId(ELF_FILE_BUILD_ID));
+ Dso::SetBuildIds(build_ids);
+ std::unique_ptr<Dso> kernel_dso = Dso::CreateDso(DSO_KERNEL, DEFAULT_KERNEL_MMAP_NAME);
+ ASSERT_TRUE(kernel_dso);
+ std::unique_ptr<Dso> dso = Dso::CreateKernelModuleDso(ELF_FILE, 0, 0, kernel_dso.get());
+ ASSERT_EQ(dso->GetDebugFilePath(), GetTestData(ELF_FILE));
+}
+
+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;
+ auto module_dso =
+ Dso::CreateKernelModuleDso("fake_module.ko", module_memory_start,
+ module_memory_start + module_memory_size, kernel_dso.get());
+ ASSERT_TRUE(module_dso);
+
+ // Provide symbol info for calculating min vaddr.
+ std::vector<Symbol> kernel_symbols;
+ kernel_symbols.emplace_back("fake_module_function [fake_module]", 0xffffffa9bc7a64e8ULL, 0x60c);
+ kernel_dso->SetSymbols(&kernel_symbols);
+ std::vector<Symbol> module_symbols;
+ module_symbols.emplace_back("fake_module_function", 0x144e8, 0x60c);
+ module_dso->SetSymbols(&module_symbols);
+
+ // Calculate min vaddr.
+ uint64_t min_vaddr;
+ uint64_t memory_offset;
+ module_dso->GetMinExecutableVaddr(&min_vaddr, &memory_offset);
+ ASSERT_EQ(min_vaddr, 0x144e8);
+ ASSERT_EQ(memory_offset, 0x164e8);
+
+ // Use min vaddr in IpToVaddrInFile().
+ ASSERT_EQ(module_dso->IpToVaddrInFile(0xffffffa9bc7a64e8ULL, module_memory_start, 0), 0x144e8);
+}
+
+TEST(dso, symbol_map_file) {
+ auto dso = Dso::CreateDso(DSO_SYMBOL_MAP_FILE, "perf-123.map");
+ ASSERT_TRUE(dso);
+ ASSERT_EQ(DSO_SYMBOL_MAP_FILE, dso->type());
+ ASSERT_EQ(0x12345678, dso->IpToVaddrInFile(0x12345678, 0x0, 0x0));
+ ASSERT_EQ(0x12345678, dso->IpToVaddrInFile(0x12345678, 0xe9201000, 0xa5000));
+}
diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp
index 5b2fcaa8..4c960350 100644
--- a/simpleperf/environment.cpp
+++ b/simpleperf/environment.cpp
@@ -23,6 +23,7 @@
#include <string.h>
#include <sys/resource.h>
#include <sys/utsname.h>
+#include <unistd.h>
#include <limits>
#include <set>
@@ -32,8 +33,8 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
-#include <android-base/strings.h>
#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
#include <procinfo/process.h>
#include <procinfo/process_map.h>
@@ -41,96 +42,35 @@
#include <android-base/properties.h>
#endif
+#include "IOEventLoop.h"
#include "command.h"
#include "event_type.h"
-#include "IOEventLoop.h"
+#include "kallsyms.h"
#include "read_elf.h"
#include "thread_tree.h"
#include "utils.h"
#include "workload.h"
-using namespace simpleperf;
-
-class LineReader {
- public:
- explicit LineReader(FILE* fp) : fp_(fp), buf_(nullptr), bufsize_(0) {
- }
-
- ~LineReader() {
- free(buf_);
- fclose(fp_);
- }
-
- char* ReadLine() {
- if (getline(&buf_, &bufsize_, fp_) != -1) {
- return buf_;
- }
- return nullptr;
- }
-
- size_t MaxLineSize() {
- return bufsize_;
- }
-
- private:
- FILE* fp_;
- char* buf_;
- size_t bufsize_;
-};
+namespace simpleperf {
std::vector<int> GetOnlineCpus() {
std::vector<int> result;
- FILE* fp = fopen("/sys/devices/system/cpu/online", "re");
- if (fp == nullptr) {
+ LineReader reader("/sys/devices/system/cpu/online");
+ if (!reader.Ok()) {
PLOG(ERROR) << "can't open online cpu information";
return result;
}
- LineReader reader(fp);
- char* line;
+ std::string* line;
if ((line = reader.ReadLine()) != nullptr) {
- result = GetCpusFromString(line);
+ if (auto cpus = GetCpusFromString(*line); cpus) {
+ result.assign(cpus->begin(), cpus->end());
+ }
}
CHECK(!result.empty()) << "can't get online cpu information";
return result;
}
-static std::vector<KernelMmap> GetLoadedModules() {
- std::vector<KernelMmap> result;
- FILE* fp = fopen("/proc/modules", "re");
- if (fp == nullptr) {
- // There is no /proc/modules on Android devices, so we don't print error if failed to open it.
- PLOG(DEBUG) << "failed to open file /proc/modules";
- return result;
- }
- LineReader reader(fp);
- char* line;
- while ((line = reader.ReadLine()) != nullptr) {
- // Parse line like: nf_defrag_ipv6 34768 1 nf_conntrack_ipv6, Live 0xffffffffa0fe5000
- char name[reader.MaxLineSize()];
- uint64_t addr;
- uint64_t len;
- if (sscanf(line, "%s%" PRIu64 "%*u%*s%*s 0x%" PRIx64, name, &len, &addr) == 3) {
- KernelMmap map;
- map.name = name;
- map.start_addr = addr;
- map.len = len;
- result.push_back(map);
- }
- }
- bool all_zero = true;
- for (const auto& map : result) {
- if (map.start_addr != 0) {
- all_zero = false;
- }
- }
- if (all_zero) {
- LOG(DEBUG) << "addresses in /proc/modules are all zero, so ignore kernel modules";
- return std::vector<KernelMmap>();
- }
- return result;
-}
-
static void GetAllModuleFiles(const std::string& path,
std::unordered_map<std::string, std::string>* module_file_map) {
for (const auto& name : GetEntriesInDir(path)) {
@@ -180,6 +120,11 @@ void GetKernelAndModuleMmaps(KernelMmap* kernel_mmap, std::vector<KernelMmap>* m
kernel_mmap->name = DEFAULT_KERNEL_MMAP_NAME;
kernel_mmap->start_addr = 0;
kernel_mmap->len = std::numeric_limits<uint64_t>::max();
+ if (uint64_t kstart_addr = GetKernelStartAddress(); kstart_addr != 0) {
+ kernel_mmap->name = std::string(DEFAULT_KERNEL_MMAP_NAME) + "_stext";
+ kernel_mmap->start_addr = kstart_addr;
+ kernel_mmap->len = std::numeric_limits<uint64_t>::max() - kstart_addr;
+ }
kernel_mmap->filepath = kernel_mmap->name;
*module_mmaps = GetModulesInUse();
for (auto& map : *module_mmaps) {
@@ -236,11 +181,10 @@ std::vector<pid_t> GetAllProcesses() {
bool GetThreadMmapsInProcess(pid_t pid, std::vector<ThreadMmap>* thread_mmaps) {
thread_mmaps->clear();
- return android::procinfo::ReadProcessMaps(
- pid, [&](uint64_t start, uint64_t end, uint16_t flags, uint64_t pgoff,
- ino_t, const char* name) {
- thread_mmaps->emplace_back(start, end - start, pgoff, name, flags);
- });
+ return android::procinfo::ReadProcessMaps(pid, [&](const android::procinfo::MapInfo& mapinfo) {
+ thread_mmaps->emplace_back(mapinfo.start, mapinfo.end - mapinfo.start, mapinfo.pgoff,
+ mapinfo.name.c_str(), mapinfo.flags);
+ });
}
bool GetKernelBuildId(BuildId* build_id) {
@@ -251,45 +195,31 @@ bool GetKernelBuildId(BuildId* build_id) {
return result == ElfStatus::NO_ERROR;
}
-bool GetModuleBuildId(const std::string& module_name, BuildId* build_id) {
- std::string notefile = "/sys/module/" + module_name + "/notes/.note.gnu.build-id";
- return GetBuildIdFromNoteFile(notefile, build_id);
-}
-
-bool GetValidThreadsFromThreadString(const std::string& tid_str, std::set<pid_t>* tid_set) {
- std::vector<std::string> strs = android::base::Split(tid_str, ",");
- for (const auto& s : strs) {
- int tid;
- if (!android::base::ParseInt(s.c_str(), &tid, 0)) {
- LOG(ERROR) << "Invalid tid '" << s << "'";
- return false;
- }
- if (!IsDir(android::base::StringPrintf("/proc/%d", tid))) {
- LOG(ERROR) << "Non existing thread '" << tid << "'";
- return false;
- }
- tid_set->insert(tid);
- }
- return true;
+bool GetModuleBuildId(const std::string& module_name, BuildId* build_id,
+ const std::string& sysfs_dir) {
+ std::string notefile = sysfs_dir + "/module/" + module_name + "/notes/.note.gnu.build-id";
+ return GetBuildIdFromNoteFile(notefile, build_id) == ElfStatus::NO_ERROR;
}
/*
- * perf event paranoia level:
- * -1 - not paranoid at all
+ * perf event allow level:
+ * -1 - everything allowed
* 0 - disallow raw tracepoint access for unpriv
* 1 - disallow cpu events for unpriv
* 2 - disallow kernel profiling for unpriv
* 3 - disallow user profiling for unpriv
*/
-static bool ReadPerfEventParanoid(int* value) {
+static const char* perf_event_allow_path = "/proc/sys/kernel/perf_event_paranoid";
+
+static bool ReadPerfEventAllowStatus(int* value) {
std::string s;
- if (!android::base::ReadFileToString("/proc/sys/kernel/perf_event_paranoid", &s)) {
- PLOG(DEBUG) << "failed to read /proc/sys/kernel/perf_event_paranoid";
+ if (!android::base::ReadFileToString(perf_event_allow_path, &s)) {
+ PLOG(DEBUG) << "failed to read " << perf_event_allow_path;
return false;
}
s = android::base::Trim(s);
if (!android::base::ParseInt(s.c_str(), value)) {
- PLOG(ERROR) << "failed to parse /proc/sys/kernel/perf_event_paranoid: " << s;
+ PLOG(ERROR) << "failed to parse " << perf_event_allow_path << ": " << s;
return false;
}
return true;
@@ -306,32 +236,45 @@ bool CanRecordRawData() {
return false;
#else
int value;
- return ReadPerfEventParanoid(&value) && value == -1;
+ return ReadPerfEventAllowStatus(&value) && value == -1;
#endif
}
static const char* GetLimitLevelDescription(int limit_level) {
switch (limit_level) {
- case -1: return "unlimited";
- case 0: return "disallowing raw tracepoint access for unpriv";
- case 1: return "disallowing cpu events for unpriv";
- case 2: return "disallowing kernel profiling for unpriv";
- case 3: return "disallowing user profiling for unpriv";
- default: return "unknown level";
+ case -1:
+ return "unlimited";
+ case 0:
+ return "disallowing raw tracepoint access for unpriv";
+ case 1:
+ return "disallowing cpu events for unpriv";
+ case 2:
+ return "disallowing kernel profiling for unpriv";
+ case 3:
+ return "disallowing user profiling for unpriv";
+ default:
+ return "unknown level";
}
}
bool CheckPerfEventLimit() {
- // Root is not limited by /proc/sys/kernel/perf_event_paranoid. However, the monitored threads
+ // 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_paranoid.
+ // enough permission to create inherited tracepoint events, write -1 to perf_event_allow_path.
// See http://b/62230699.
if (IsRoot()) {
- return android::base::WriteStringToFile("-1", "/proc/sys/kernel/perf_event_paranoid");
+ if (android::base::WriteStringToFile("-1", perf_event_allow_path)) {
+ return true;
+ }
+ // On host, we may not be able to write to perf_event_allow_path (like when running in docker).
+#if defined(__ANDROID__)
+ PLOG(ERROR) << "failed to write -1 to " << perf_event_allow_path;
+ return false;
+#endif
}
int limit_level;
- bool can_read_paranoid = ReadPerfEventParanoid(&limit_level);
- if (can_read_paranoid && limit_level <= 1) {
+ bool can_read_allow_file = ReadPerfEventAllowStatus(&limit_level);
+ if (can_read_allow_file && limit_level <= 1) {
return true;
}
#if defined(__ANDROID__)
@@ -344,26 +287,26 @@ bool CheckPerfEventLimit() {
if (prop_value == "0") {
return true;
}
- // Try to enable perf_event_paranoid by setprop security.perf_harden=0.
+ // Try to enable perf events by setprop security.perf_harden=0.
if (android::base::SetProperty(prop_name, "0")) {
sleep(1);
- if (can_read_paranoid && ReadPerfEventParanoid(&limit_level) && limit_level <= 1) {
+ if (can_read_allow_file && ReadPerfEventAllowStatus(&limit_level) && limit_level <= 1) {
return true;
}
if (android::base::GetProperty(prop_name, "") == "0") {
return true;
}
}
- if (can_read_paranoid) {
- LOG(WARNING) << "/proc/sys/kernel/perf_event_paranoid is " << limit_level
- << ", " << GetLimitLevelDescription(limit_level) << ".";
+ if (can_read_allow_file) {
+ LOG(WARNING) << perf_event_allow_path << " is " << limit_level << ", "
+ << GetLimitLevelDescription(limit_level) << ".";
}
LOG(WARNING) << "Try using `adb shell setprop security.perf_harden 0` to allow profiling.";
return false;
#else
- if (can_read_paranoid) {
- LOG(WARNING) << "/proc/sys/kernel/perf_event_paranoid is " << limit_level
- << ", " << GetLimitLevelDescription(limit_level) << ".";
+ if (can_read_allow_file) {
+ LOG(WARNING) << perf_event_allow_path << " is " << limit_level << ", "
+ << GetLimitLevelDescription(limit_level) << ".";
return false;
}
#endif
@@ -468,44 +411,25 @@ bool SetPerfEventMlockKb(uint64_t mlock_kb) {
return WriteUintToProcFile("/proc/sys/kernel/perf_event_mlock_kb", mlock_kb);
}
-bool CheckKernelSymbolAddresses() {
- const std::string kptr_restrict_file = "/proc/sys/kernel/kptr_restrict";
- std::string s;
- if (!android::base::ReadFileToString(kptr_restrict_file, &s)) {
- PLOG(DEBUG) << "failed to read " << kptr_restrict_file;
- return false;
- }
- s = android::base::Trim(s);
- int value;
- if (!android::base::ParseInt(s.c_str(), &value)) {
- LOG(ERROR) << "failed to parse " << kptr_restrict_file << ": " << s;
- return false;
- }
- // Accessible to everyone?
- if (value == 0) {
- return true;
- }
- // Accessible to root?
- if (value == 1 && IsRoot()) {
- return true;
- }
- // Can we make it accessible to us?
- if (IsRoot() && android::base::WriteStringToFile("1", kptr_restrict_file)) {
- return true;
- }
- LOG(WARNING) << "Access to kernel symbol addresses is restricted. If "
- << "possible, please do `echo 0 >/proc/sys/kernel/kptr_restrict` "
- << "to fix this.";
- return false;
-}
-
ArchType GetMachineArch() {
+#if defined(__i386__)
+ // For 32 bit x86 build, we can't get machine arch by uname().
+ ArchType arch = ARCH_UNSUPPORTED;
+ std::unique_ptr<FILE, decltype(&pclose)> fp(popen("uname -m", "re"), pclose);
+ if (fp) {
+ char machine[40];
+ if (fgets(machine, sizeof(machine), fp.get()) == machine) {
+ arch = GetArchType(android::base::Trim(machine));
+ }
+ }
+#else
utsname uname_buf;
if (TEMP_FAILURE_RETRY(uname(&uname_buf)) != 0) {
PLOG(WARNING) << "uname() failed";
return GetBuildArch();
}
ArchType arch = GetArchType(uname_buf.machine);
+#endif
if (arch != ARCH_UNSUPPORTED) {
return arch;
}
@@ -604,15 +528,18 @@ std::set<pid_t> WaitForAppProcesses(const std::string& package_name) {
}
}
-bool IsAppDebuggable(const std::string& package_name) {
- return Workload::RunCmd({"run-as", package_name, "echo", ">/dev/null", "2>/dev/null"}, false);
-}
-
namespace {
+bool IsAppDebuggable(int user_id, const std::string& package_name) {
+ return Workload::RunCmd({"run-as", package_name, "--user", std::to_string(user_id), "echo",
+ ">/dev/null", "2>/dev/null"},
+ false);
+}
+
class InAppRunner {
public:
- InAppRunner(const std::string& package_name) : package_name_(package_name) {}
+ InAppRunner(int user_id, const std::string& package_name)
+ : user_id_(std::to_string(user_id)), package_name_(package_name) {}
virtual ~InAppRunner() {
if (!tracepoint_file_.empty()) {
unlink(tracepoint_file_.c_str());
@@ -622,9 +549,11 @@ class InAppRunner {
bool RunCmdInApp(const std::string& cmd, const std::vector<std::string>& args,
size_t workload_args_size, const std::string& output_filepath,
bool need_tracepoint_events);
+
protected:
virtual std::vector<std::string> GetPrefixArgs(const std::string& cmd) = 0;
+ const std::string user_id_;
const std::string package_name_;
std::string tracepoint_file_;
};
@@ -643,7 +572,7 @@ bool InAppRunner::RunCmdInApp(const std::string& cmd, const std::vector<std::str
// them in tracepoint_file in shell's context, and pass the path of tracepoint_file to the
// child process using --tracepoint-events option.
const std::string tracepoint_file = "/data/local/tmp/tracepoint_events";
- if (!android::base::WriteStringToFile(GetTracepointEvents(), tracepoint_file)) {
+ if (!EventTypeManager::Instance().WriteTracepointsToFile(tracepoint_file)) {
PLOG(ERROR) << "Failed to store tracepoint events";
return false;
}
@@ -708,8 +637,10 @@ bool InAppRunner::RunCmdInApp(const std::string& cmd, const std::vector<std::str
if (!SignalIsIgnored(SIGHUP)) {
stop_signals.push_back(SIGHUP);
}
- if (!loop.AddSignalEvents(stop_signals,
- [&]() { need_to_stop_child = true; return loop.ExitLoop(); })) {
+ if (!loop.AddSignalEvents(stop_signals, [&]() {
+ need_to_stop_child = true;
+ return loop.ExitLoop();
+ })) {
return false;
}
if (!loop.AddSignalEvent(SIGCHLD, [&]() { return loop.ExitLoop(); })) {
@@ -734,19 +665,31 @@ bool InAppRunner::RunCmdInApp(const std::string& cmd, const std::vector<std::str
class RunAs : public InAppRunner {
public:
- RunAs(const std::string& package_name) : InAppRunner(package_name) {}
+ RunAs(int user_id, const std::string& package_name) : InAppRunner(user_id, package_name) {}
virtual ~RunAs() {
if (simpleperf_copied_in_app_) {
- Workload::RunCmd({"run-as", package_name_, "rm", "-rf", "simpleperf"});
+ Workload::RunCmd({"run-as", package_name_, "--user", user_id_, "rm", "-rf", "simpleperf"});
}
}
bool Prepare() override;
protected:
std::vector<std::string> GetPrefixArgs(const std::string& cmd) {
- return {"run-as", package_name_,
- simpleperf_copied_in_app_ ? "./simpleperf" : simpleperf_path_, cmd,
- "--app", package_name_};
+ std::vector<std::string> args = {"run-as",
+ package_name_,
+ "--user",
+ user_id_,
+ simpleperf_copied_in_app_ ? "./simpleperf" : simpleperf_path_,
+ cmd,
+ "--app",
+ package_name_};
+ if (cmd == "record") {
+ if (simpleperf_copied_in_app_ || GetAndroidVersion() >= kAndroidVersionS) {
+ args.emplace_back("--add-meta-info");
+ args.emplace_back("app_type=debuggable");
+ }
+ }
+ return args;
}
bool simpleperf_copied_in_app_ = false;
@@ -754,10 +697,6 @@ class RunAs : public InAppRunner {
};
bool RunAs::Prepare() {
- // Test if run-as can access the package.
- if (!IsAppDebuggable(package_name_)) {
- return false;
- }
// run-as can't run /data/local/tmp/simpleperf directly. So copy simpleperf binary if needed.
if (!android::base::Readlink("/proc/self/exe", &simpleperf_path_)) {
PLOG(ERROR) << "ReadLink failed";
@@ -770,7 +709,8 @@ bool RunAs::Prepare() {
if (android::base::StartsWith(simpleperf_path_, "/system")) {
return true;
}
- if (!Workload::RunCmd({"run-as", package_name_, "cp", simpleperf_path_, "simpleperf"})) {
+ if (!Workload::RunCmd(
+ {"run-as", package_name_, "--user", user_id_, "cp", simpleperf_path_, "simpleperf"})) {
return false;
}
simpleperf_copied_in_app_ = true;
@@ -779,15 +719,30 @@ bool RunAs::Prepare() {
class SimpleperfAppRunner : public InAppRunner {
public:
- SimpleperfAppRunner(const std::string& package_name) : InAppRunner(package_name) {}
- bool Prepare() override {
- return GetAndroidVersion() >= kAndroidVersionP + 1;
+ SimpleperfAppRunner(int user_id, const std::string& package_name, const std::string app_type)
+ : InAppRunner(user_id, package_name) {
+ // On Android < S, the app type is unknown before running simpleperf_app_runner. Assume it's
+ // profileable.
+ app_type_ = app_type == "unknown" ? "profileable" : app_type;
}
+ bool Prepare() override { return GetAndroidVersion() >= kAndroidVersionQ; }
protected:
std::vector<std::string> GetPrefixArgs(const std::string& cmd) {
- return {"simpleperf_app_runner", package_name_, cmd};
+ std::vector<std::string> args = {"simpleperf_app_runner", package_name_};
+ if (user_id_ != "0") {
+ args.emplace_back("--user");
+ args.emplace_back(user_id_);
+ }
+ args.emplace_back(cmd);
+ if (cmd == "record" && GetAndroidVersion() >= kAndroidVersionS) {
+ args.emplace_back("--add-meta-info");
+ args.emplace_back("app_type=" + app_type_);
+ }
+ return args;
}
+
+ std::string app_type_;
};
} // namespace
@@ -800,20 +755,59 @@ void SetRunInAppToolForTesting(bool run_as, bool simpleperf_app_runner) {
allow_simpleperf_app_runner = simpleperf_app_runner;
}
+static int GetCurrentUserId() {
+ std::unique_ptr<FILE, decltype(&pclose)> fd(popen("am get-current-user", "r"), pclose);
+ if (fd) {
+ char buf[128];
+ if (fgets(buf, sizeof(buf), fd.get()) != nullptr) {
+ int user_id;
+ if (android::base::ParseInt(android::base::Trim(buf), &user_id, 0)) {
+ return user_id;
+ }
+ }
+ }
+ return 0;
+}
+
+std::string GetAppType(const std::string& app_package_name) {
+ if (GetAndroidVersion() < kAndroidVersionS) {
+ return "unknown";
+ }
+ std::string cmd = "simpleperf_app_runner " + app_package_name + " --show-app-type";
+ std::unique_ptr<FILE, decltype(&pclose)> fp(popen(cmd.c_str(), "re"), pclose);
+ if (fp) {
+ char buf[128];
+ if (fgets(buf, sizeof(buf), fp.get()) != nullptr) {
+ return android::base::Trim(buf);
+ }
+ }
+ // Can't get app_type. It means the app doesn't exist.
+ return "not_exist";
+}
+
bool RunInAppContext(const std::string& app_package_name, const std::string& cmd,
const std::vector<std::string>& args, size_t workload_args_size,
const std::string& output_filepath, bool need_tracepoint_events) {
+ int user_id = GetCurrentUserId();
std::unique_ptr<InAppRunner> in_app_runner;
- if (allow_run_as) {
- in_app_runner.reset(new RunAs(app_package_name));
+
+ std::string app_type = GetAppType(app_package_name);
+ if (app_type == "unknown" && IsAppDebuggable(user_id, app_package_name)) {
+ app_type = "debuggable";
+ }
+
+ if (allow_run_as && app_type == "debuggable") {
+ in_app_runner.reset(new RunAs(user_id, app_package_name));
if (!in_app_runner->Prepare()) {
in_app_runner = nullptr;
}
}
if (!in_app_runner && allow_simpleperf_app_runner) {
- in_app_runner.reset(new SimpleperfAppRunner(app_package_name));
- if (!in_app_runner->Prepare()) {
- in_app_runner = nullptr;
+ if (app_type == "debuggable" || app_type == "profileable" || app_type == "unknown") {
+ in_app_runner.reset(new SimpleperfAppRunner(user_id, app_package_name, app_type));
+ if (!in_app_runner->Prepare()) {
+ in_app_runner = nullptr;
+ }
}
}
if (!in_app_runner) {
@@ -838,6 +832,13 @@ void AllowMoreOpenedFiles() {
std::string ScopedTempFiles::tmp_dir_;
std::vector<std::string> ScopedTempFiles::files_to_delete_;
+std::unique_ptr<ScopedTempFiles> ScopedTempFiles::Create(const std::string& tmp_dir) {
+ if (access(tmp_dir.c_str(), W_OK | X_OK) != 0) {
+ return nullptr;
+ }
+ return std::unique_ptr<ScopedTempFiles>(new ScopedTempFiles(tmp_dir));
+}
+
ScopedTempFiles::ScopedTempFiles(const std::string& tmp_dir) {
CHECK(tmp_dir_.empty()); // No other ScopedTempFiles.
tmp_dir_ = tmp_dir;
@@ -854,7 +855,7 @@ ScopedTempFiles::~ScopedTempFiles() {
std::unique_ptr<TemporaryFile> ScopedTempFiles::CreateTempFile(bool delete_in_destructor) {
CHECK(!tmp_dir_.empty());
std::unique_ptr<TemporaryFile> tmp_file(new TemporaryFile(tmp_dir_));
- CHECK_NE(tmp_file->fd, -1);
+ CHECK_NE(tmp_file->fd, -1) << "failed to create tmpfile under " << tmp_dir_;
if (delete_in_destructor) {
tmp_file->DoNotRemove();
files_to_delete_.push_back(tmp_file->path);
@@ -862,6 +863,10 @@ std::unique_ptr<TemporaryFile> ScopedTempFiles::CreateTempFile(bool delete_in_de
return tmp_file;
}
+void ScopedTempFiles::RegisterTempFile(const std::string& path) {
+ files_to_delete_.emplace_back(path);
+}
+
bool SignalIsIgnored(int signo) {
struct sigaction act;
if (sigaction(signo, nullptr, &act) != 0) {
@@ -880,7 +885,10 @@ int GetAndroidVersion() {
static int android_version = -1;
if (android_version == -1) {
android_version = 0;
- std::string s = android::base::GetProperty("ro.build.version.release", "");
+ std::string s = android::base::GetProperty("ro.build.version.codename", "REL");
+ if (s == "REL") {
+ s = android::base::GetProperty("ro.build.version.release", "");
+ }
// The release string can be a list of numbers (like 8.1.0), a character (like Q)
// or many characters (like OMR1).
if (!s.empty()) {
@@ -919,11 +927,9 @@ bool MappedFileOnlyExistInMemory(const char* filename) {
// /dev/*
// //anon: generated by kernel/events/core.c.
// /memfd: created by memfd_create.
- return filename[0] == '\0' ||
- (filename[0] == '[' && strcmp(filename, "[vdso]") != 0) ||
- strncmp(filename, "//", 2) == 0 ||
- strncmp(filename, "/dev/", 5) == 0 ||
- strncmp(filename, "/memfd:", 7) == 0;
+ return filename[0] == '\0' || (filename[0] == '[' && strcmp(filename, "[vdso]") != 0) ||
+ strncmp(filename, "//", 2) == 0 || strncmp(filename, "/dev/", 5) == 0 ||
+ strncmp(filename, "/memfd:", 7) == 0;
}
std::string GetCompleteProcessName(pid_t pid) {
@@ -942,13 +948,46 @@ std::string GetCompleteProcessName(pid_t pid) {
}
const char* GetTraceFsDir() {
- static const char* tracefs_dirs[] = {
- "/sys/kernel/debug/tracing", "/sys/kernel/tracing"
- };
- for (const char* path : tracefs_dirs) {
- if (IsDir(path)) {
- return path;
+ static const char* tracefs_dir = nullptr;
+ if (tracefs_dir == nullptr) {
+ for (const char* path : {"/sys/kernel/debug/tracing", "/sys/kernel/tracing"}) {
+ if (IsDir(path)) {
+ tracefs_dir = path;
+ break;
+ }
+ }
+ }
+ return tracefs_dir;
+}
+
+std::optional<std::pair<int, int>> GetKernelVersion() {
+ utsname uname_buf;
+ int major;
+ int minor;
+ if (TEMP_FAILURE_RETRY(uname(&uname_buf)) != 0 ||
+ sscanf(uname_buf.release, "%d.%d", &major, &minor) != 2) {
+ return std::nullopt;
+ }
+ return std::make_pair(major, minor);
+}
+
+std::optional<uid_t> GetProcessUid(pid_t pid) {
+ std::string status_file = "/proc/" + std::to_string(pid) + "/status";
+ LineReader reader(status_file);
+ if (!reader.Ok()) {
+ return std::nullopt;
+ }
+
+ std::string* line;
+ while ((line = reader.ReadLine()) != nullptr) {
+ if (android::base::StartsWith(*line, "Uid:")) {
+ uid_t uid;
+ if (sscanf(line->data() + strlen("Uid:"), "%u", &uid) == 1) {
+ return uid;
+ }
}
}
- return nullptr;
+ return std::nullopt;
}
+
+} // namespace simpleperf
diff --git a/simpleperf/environment.h b/simpleperf/environment.h
index 533d2987..180b98c3 100644
--- a/simpleperf/environment.h
+++ b/simpleperf/environment.h
@@ -26,8 +26,11 @@
#endif
#include <functional>
+#include <memory>
+#include <optional>
#include <set>
#include <string>
+#include <utility>
#include <vector>
#include <android-base/file.h>
@@ -35,6 +38,8 @@
#include "build_id.h"
#include "perf_regs.h"
+namespace simpleperf {
+
std::vector<int> GetOnlineCpus();
struct KernelMmap {
@@ -62,7 +67,8 @@ bool GetThreadMmapsInProcess(pid_t pid, std::vector<ThreadMmap>* thread_mmaps);
constexpr char DEFAULT_KERNEL_FILENAME_FOR_BUILD_ID[] = "[kernel.kallsyms]";
bool GetKernelBuildId(BuildId* build_id);
-bool GetModuleBuildId(const std::string& module_name, BuildId* build_id);
+bool GetModuleBuildId(const std::string& module_name, BuildId* build_id,
+ const std::string& sysfs_dir = "/sys");
bool IsThreadAlive(pid_t tid);
std::vector<pid_t> GetAllProcesses();
@@ -70,9 +76,6 @@ std::vector<pid_t> GetThreadsInProcess(pid_t pid);
bool ReadThreadNameAndPid(pid_t tid, std::string* comm, pid_t* pid);
bool GetProcessForThread(pid_t tid, pid_t* pid);
bool GetThreadName(pid_t tid, std::string* name);
-
-bool GetValidThreadsFromThreadString(const std::string& tid_str, std::set<pid_t>* tid_set);
-
bool CheckPerfEventLimit();
bool SetPerfEventLimits(uint64_t sample_freq, size_t cpu_percent, uint64_t mlock_kb);
bool GetMaxSampleFrequency(uint64_t* max_sample_freq);
@@ -81,61 +84,73 @@ bool GetCpuTimeMaxPercent(size_t* percent);
bool SetCpuTimeMaxPercent(size_t percent);
bool GetPerfEventMlockKb(uint64_t* mlock_kb);
bool SetPerfEventMlockKb(uint64_t mlock_kb);
-bool CheckKernelSymbolAddresses();
bool CanRecordRawData();
-#if defined(__linux__)
-static inline uint64_t GetSystemClock() {
- timespec ts;
- // Assume clock_gettime() doesn't fail.
- clock_gettime(CLOCK_MONOTONIC, &ts);
- return ts.tv_sec * 1000000000ULL + ts.tv_nsec;
-}
-
-#if !defined(__ANDROID__)
-static inline int gettid() {
- return syscall(__NR_gettid);
-}
-#endif
-#endif
-
ArchType GetMachineArch();
void PrepareVdsoFile();
std::set<pid_t> WaitForAppProcesses(const std::string& package_name);
-bool IsAppDebuggable(const std::string& package_name);
void SetRunInAppToolForTesting(bool run_as, bool simpleperf_app_runner); // for testing only
bool RunInAppContext(const std::string& app_package_name, const std::string& cmd,
const std::vector<std::string>& args, size_t workload_args_size,
const std::string& output_filepath, bool need_tracepoint_events);
+std::string GetAppType(const std::string& app_package_name);
void AllowMoreOpenedFiles();
class ScopedTempFiles {
public:
- ScopedTempFiles(const std::string& tmp_dir);
+ static std::unique_ptr<ScopedTempFiles> Create(const std::string& tmp_dir);
~ScopedTempFiles();
// If delete_in_destructor = true, the temp file will be deleted in the destructor of
// ScopedTempFile. Otherwise, it should be deleted by the caller.
static std::unique_ptr<TemporaryFile> CreateTempFile(bool delete_in_destructor = true);
+ static void RegisterTempFile(const std::string& path);
private:
+ ScopedTempFiles(const std::string& tmp_dir);
+
static std::string tmp_dir_;
static std::vector<std::string> files_to_delete_;
};
bool SignalIsIgnored(int signo);
+
+enum {
+ kAndroidVersionP = 9,
+ kAndroidVersionQ = 10,
+ kAndroidVersionR = 11,
+ kAndroidVersionS = 12,
+};
+
// Return 0 if no android version.
int GetAndroidVersion();
-
-constexpr int kAndroidVersionP = 9;
+std::optional<std::pair<int, int>> GetKernelVersion();
std::string GetHardwareFromCpuInfo(const std::string& cpu_info);
bool MappedFileOnlyExistInMemory(const char* filename);
std::string GetCompleteProcessName(pid_t pid);
-
const char* GetTraceFsDir();
+#if defined(__linux__)
+static inline uint64_t GetSystemClock() {
+ timespec ts;
+ // Assume clock_gettime() doesn't fail.
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return ts.tv_sec * 1000000000ULL + ts.tv_nsec;
+}
+
+#if !defined(__ANDROID__)
+static inline int gettid() {
+ return syscall(__NR_gettid);
+}
+#endif
+
+std::optional<uid_t> GetProcessUid(pid_t pid);
+#endif // defined(__linux__)
+
+} // namespace simpleperf
+
#endif // SIMPLE_PERF_ENVIRONMENT_H_
diff --git a/simpleperf/environment_test.cpp b/simpleperf/environment_test.cpp
index 5f799642..a95caca8 100644
--- a/simpleperf/environment_test.cpp
+++ b/simpleperf/environment_test.cpp
@@ -16,10 +16,18 @@
#include <gtest/gtest.h>
+#include <filesystem>
+
#include <android-base/file.h>
#include "dso.h"
#include "environment.h"
+#include "get_test_data.h"
+#include "test_util.h"
+#include "thread_tree.h"
+
+namespace fs = std::filesystem;
+using namespace simpleperf;
TEST(environment, PrepareVdsoFile) {
std::string content;
@@ -29,16 +37,18 @@ TEST(environment, PrepareVdsoFile) {
return;
}
TemporaryDir tmpdir;
- ScopedTempFiles scoped_temp_files(tmpdir.path);
+ auto scoped_temp_files = ScopedTempFiles::Create(tmpdir.path);
+ ASSERT_TRUE(scoped_temp_files);
PrepareVdsoFile();
- std::unique_ptr<Dso> dso = Dso::CreateDso(DSO_ELF_FILE, "[vdso]",
- sizeof(size_t) == sizeof(uint64_t));
+ std::unique_ptr<Dso> dso =
+ Dso::CreateDso(DSO_ELF_FILE, "[vdso]", sizeof(size_t) == sizeof(uint64_t));
ASSERT_TRUE(dso != nullptr);
ASSERT_NE(dso->GetDebugFilePath(), "[vdso]");
}
TEST(environment, GetHardwareFromCpuInfo) {
- std::string cpu_info = "CPU revision : 10\n\n"
+ std::string cpu_info =
+ "CPU revision : 10\n\n"
"Hardware : Symbol i.MX6 Freeport_Plat Quad/DualLite (Device Tree)\n";
ASSERT_EQ("Symbol i.MX6 Freeport_Plat Quad/DualLite (Device Tree)",
GetHardwareFromCpuInfo(cpu_info));
@@ -90,3 +100,39 @@ TEST(environment, SetPerfEventLimits) {
GTEST_LOG_(INFO) << "This test tests setting properties on Android.";
#endif
}
+
+TEST(environment, GetKernelVersion) {
+ ASSERT_TRUE(GetKernelVersion());
+}
+
+TEST(environment, GetModuleBuildId) {
+ BuildId build_id;
+ fs::path dir(GetTestData("sysfs/module/fake_kernel_module/notes"));
+ ASSERT_TRUE(fs::copy_file(dir / "note.gnu.build-id", dir / ".note.gnu.build-id",
+ fs::copy_options::overwrite_existing));
+ ASSERT_TRUE(GetModuleBuildId("fake_kernel_module", &build_id, GetTestData("sysfs")));
+ ASSERT_EQ(build_id, BuildId("3e0ba155286f3454"));
+}
+
+TEST(environment, GetKernelAndModuleMmaps) {
+ TEST_REQUIRE_ROOT();
+ KernelMmap kernel_mmap;
+ std::vector<KernelMmap> module_mmaps;
+ GetKernelAndModuleMmaps(&kernel_mmap, &module_mmaps);
+ // The kernel map should contain the kernel start address.
+ ASSERT_EQ(kernel_mmap.name, std::string(DEFAULT_KERNEL_MMAP_NAME) + "_stext");
+ ASSERT_GT(kernel_mmap.start_addr, 0);
+}
+
+TEST(environment, GetProcessUid) {
+ std::optional<uid_t> uid = GetProcessUid(getpid());
+ ASSERT_TRUE(uid.has_value());
+ ASSERT_EQ(uid.value(), getuid());
+}
+
+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");
+}
diff --git a/simpleperf/etm_branch_list.proto b/simpleperf/etm_branch_list.proto
new file mode 100644
index 00000000..c66b0d5e
--- /dev/null
+++ b/simpleperf/etm_branch_list.proto
@@ -0,0 +1,66 @@
+/*
+ * 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 ecc08439..21f3c19e 100644
--- a/simpleperf/event_attr.cpp
+++ b/simpleperf/event_attr.cpp
@@ -27,6 +27,8 @@
#include "event_type.h"
#include "utils.h"
+namespace simpleperf {
+
static std::string BitsToString(const std::string& name, uint64_t bits,
const std::vector<std::pair<int, std::string>>& bit_names) {
std::string result;
@@ -87,7 +89,7 @@ perf_event_attr CreateDefaultPerfEventAttr(const EventType& event_type) {
attr.read_format =
PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING | PERF_FORMAT_ID;
attr.sample_type |= PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME | PERF_SAMPLE_PERIOD |
- PERF_SAMPLE_CPU | PERF_SAMPLE_ID;
+ PERF_SAMPLE_CPU | PERF_SAMPLE_ID;
if (attr.type == PERF_TYPE_TRACEPOINT) {
// Tracepoint information are stored in raw data in sample records.
@@ -142,8 +144,8 @@ void DumpPerfEventAttr(const perf_event_attr& attr, size_t indent) {
}
bool GetCommonEventIdPositionsForAttrs(std::vector<perf_event_attr>& attrs,
- size_t* event_id_pos_in_sample_records,
- size_t* event_id_reverse_pos_in_non_sample_records) {
+ 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
// in each record to decide current record should use which attr. So
// we need to determine the event id position in a record here.
@@ -158,7 +160,7 @@ bool GetCommonEventIdPositionsForAttrs(std::vector<perf_event_attr>& attrs,
bool identifier_enabled = true;
bool id_enabled = true;
uint64_t flags_before_id_mask = PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_IP | PERF_SAMPLE_TID |
- PERF_SAMPLE_TIME | PERF_SAMPLE_ADDR;
+ PERF_SAMPLE_TIME | PERF_SAMPLE_ADDR;
uint64_t flags_before_id = sample_types[0] & flags_before_id_mask;
bool flags_before_id_are_the_same = true;
for (auto type : sample_types) {
@@ -214,7 +216,8 @@ bool GetCommonEventIdPositionsForAttrs(std::vector<perf_event_attr>& attrs,
}
*event_id_reverse_pos_in_non_sample_records = pos;
} else {
- LOG(ERROR) << "perf_event_attrs don't have a common event id reverse position in non sample records";
+ LOG(ERROR)
+ << "perf_event_attrs don't have a common event id reverse position in non sample records";
return false;
}
return true;
@@ -229,19 +232,24 @@ bool IsCpuSupported(const perf_event_attr& attr) {
}
std::string GetEventNameByAttr(const perf_event_attr& attr) {
- for (const auto& event_type : GetAllEventTypes()) {
+ std::string name = "unknown";
+ auto callback = [&](const EventType& event_type) {
// An event type uses both type and config value to define itself. But etm event type
// only uses type value (whose config value is used to set etm options).
if (event_type.type == attr.type &&
- (event_type.config == attr.config || IsEtmEventType(event_type.type))) {
- std::string name = event_type.name;
+ (event_type.config == attr.config || event_type.IsEtmEvent())) {
+ name = event_type.name;
if (attr.exclude_user && !attr.exclude_kernel) {
name += ":k";
} else if (attr.exclude_kernel && !attr.exclude_user) {
name += ":u";
}
- return name;
+ return false;
}
- }
- return "unknown";
+ return true;
+ };
+ EventTypeManager::Instance().ForEachType(callback);
+ return name;
}
+
+} // namespace simpleperf
diff --git a/simpleperf/event_attr.h b/simpleperf/event_attr.h
index 969f9f99..9957bf7c 100644
--- a/simpleperf/event_attr.h
+++ b/simpleperf/event_attr.h
@@ -24,6 +24,8 @@
#include "perf_event.h"
+namespace simpleperf {
+
struct EventType;
struct EventAttrWithId {
@@ -42,4 +44,6 @@ bool IsCpuSupported(const perf_event_attr& attr);
// This function is slow for using linear search, so only used when reporting.
std::string GetEventNameByAttr(const perf_event_attr& attr);
+} // namespace simpleperf
+
#endif // SIMPLE_PERF_EVENT_ATTR_H_
diff --git a/simpleperf/event_fd.cpp b/simpleperf/event_fd.cpp
index 0010af9d..0343e156 100644
--- a/simpleperf/event_fd.cpp
+++ b/simpleperf/event_fd.cpp
@@ -16,6 +16,7 @@
#define ATRACE_TAG ATRACE_TAG_ALWAYS
#include "event_fd.h"
+#include <cutils/trace.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
@@ -23,10 +24,9 @@
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/types.h>
+#include <utils/Trace.h>
#include <atomic>
#include <memory>
-#include <cutils/trace.h>
-#include <utils/Trace.h>
#include <android-base/file.h>
#include <android-base/logging.h>
@@ -38,8 +38,10 @@
#include "perf_event.h"
#include "utils.h"
-static int perf_event_open(const perf_event_attr& attr, pid_t pid, int cpu,
- int group_fd, unsigned long flags) { // NOLINT
+namespace simpleperf {
+
+static int perf_event_open(const perf_event_attr& attr, pid_t pid, int cpu, int group_fd,
+ unsigned long flags) { // NOLINT
return syscall(__NR_perf_event_open, &attr, pid, cpu, group_fd, flags);
}
@@ -65,30 +67,25 @@ std::unique_ptr<EventFd> EventFd::OpenEventFile(const perf_event_attr& attr, pid
int perf_event_fd = perf_event_open(real_attr, tid, cpu, group_fd, 0);
if (perf_event_fd == -1) {
if (report_error) {
- PLOG(ERROR) << "open perf_event_file (event " << event_name << ", tid "
- << tid << ", cpu " << cpu << ", group_fd " << group_fd
- << ") failed";
+ PLOG(ERROR) << "open perf_event_file (event " << event_name << ", tid " << tid << ", cpu "
+ << cpu << ", group_fd " << group_fd << ") failed";
} else {
- PLOG(DEBUG) << "open perf_event_file (event " << event_name << ", tid "
- << tid << ", cpu " << cpu << ", group_fd " << group_fd
- << ") failed";
+ PLOG(DEBUG) << "open perf_event_file (event " << event_name << ", tid " << tid << ", cpu "
+ << cpu << ", group_fd " << group_fd << ") failed";
}
return nullptr;
}
if (fcntl(perf_event_fd, F_SETFD, FD_CLOEXEC) == -1) {
if (report_error) {
- PLOG(ERROR) << "fcntl(FD_CLOEXEC) for perf_event_file (event "
- << event_name << ", tid " << tid << ", cpu " << cpu
- << ", group_fd " << group_fd << ") failed";
+ PLOG(ERROR) << "fcntl(FD_CLOEXEC) for perf_event_file (event " << event_name << ", tid "
+ << tid << ", cpu " << cpu << ", group_fd " << group_fd << ") failed";
} else {
- PLOG(DEBUG) << "fcntl(FD_CLOEXEC) for perf_event_file (event "
- << event_name << ", tid " << tid << ", cpu " << cpu
- << ", group_fd " << group_fd << ") failed";
+ PLOG(DEBUG) << "fcntl(FD_CLOEXEC) for perf_event_file (event " << event_name << ", tid "
+ << tid << ", cpu " << cpu << ", group_fd " << group_fd << ") failed";
}
return nullptr;
}
- return std::unique_ptr<EventFd>(
- new EventFd(real_attr, perf_event_fd, event_name, tid, cpu));
+ return std::unique_ptr<EventFd>(new EventFd(real_attr, perf_event_fd, event_name, tid, cpu));
}
EventFd::~EventFd() {
@@ -98,9 +95,8 @@ EventFd::~EventFd() {
}
std::string EventFd::Name() const {
- return android::base::StringPrintf(
- "perf_event_file(event %s, tid %d, cpu %d)", event_name_.c_str(), tid_,
- cpu_);
+ return android::base::StringPrintf("perf_event_file(event %s, tid %d, cpu %d)",
+ event_name_.c_str(), tid_, cpu_);
}
uint64_t EventFd::Id() const {
@@ -145,13 +141,12 @@ bool EventFd::ReadCounter(PerfCounter* counter) {
}
// Trace is always available to systrace if enabled
if (tid_ > 0) {
- ATRACE_INT64(android::base::StringPrintf(
- "%s_tid%d_cpu%d", event_name_.c_str(), tid_,
- cpu_).c_str(), counter->value - last_counter_value_);
+ ATRACE_INT64(
+ android::base::StringPrintf("%s_tid%d_cpu%d", event_name_.c_str(), tid_, cpu_).c_str(),
+ counter->value - last_counter_value_);
} else {
- ATRACE_INT64(android::base::StringPrintf(
- "%s_cpu%d", event_name_.c_str(),
- cpu_).c_str(), counter->value - last_counter_value_);
+ ATRACE_INT64(android::base::StringPrintf("%s_cpu%d", event_name_.c_str(), cpu_).c_str(),
+ counter->value - last_counter_value_);
}
last_counter_value_ = counter->value;
return true;
@@ -161,8 +156,7 @@ bool EventFd::CreateMappedBuffer(size_t mmap_pages, bool report_error) {
CHECK(IsPowerOfTwo(mmap_pages));
size_t page_size = sysconf(_SC_PAGE_SIZE);
size_t mmap_len = (mmap_pages + 1) * page_size;
- void* mmap_addr = mmap(nullptr, mmap_len, PROT_READ | PROT_WRITE, MAP_SHARED,
- perf_event_fd_, 0);
+ void* mmap_addr = mmap(nullptr, mmap_len, PROT_READ | PROT_WRITE, MAP_SHARED, perf_event_fd_, 0);
if (mmap_addr == MAP_FAILED) {
bool is_perm_error = (errno == EPERM);
if (report_error) {
@@ -171,9 +165,8 @@ bool EventFd::CreateMappedBuffer(size_t mmap_pages, bool report_error) {
PLOG(DEBUG) << "mmap(" << mmap_pages << ") failed for " << Name();
}
if (report_error && is_perm_error) {
- LOG(ERROR)
- << "It seems the kernel doesn't allow allocating enough "
- << "buffer for dumping samples, consider decreasing mmap pages(-m).";
+ LOG(ERROR) << "It seems the kernel doesn't allow allocating enough "
+ << "buffer for dumping samples, consider decreasing mmap pages(-m).";
}
return false;
}
@@ -188,12 +181,11 @@ bool EventFd::CreateMappedBuffer(size_t mmap_pages, bool report_error) {
bool EventFd::ShareMappedBuffer(const EventFd& event_fd, bool report_error) {
CHECK(!HasMappedBuffer());
CHECK(event_fd.HasMappedBuffer());
- int result =
- ioctl(perf_event_fd_, PERF_EVENT_IOC_SET_OUTPUT, event_fd.perf_event_fd_);
+ int result = ioctl(perf_event_fd_, PERF_EVENT_IOC_SET_OUTPUT, event_fd.perf_event_fd_);
if (result != 0) {
if (report_error) {
- PLOG(ERROR) << "failed to share mapped buffer of "
- << event_fd.perf_event_fd_ << " with " << perf_event_fd_;
+ PLOG(ERROR) << "failed to share mapped buffer of " << event_fd.perf_event_fd_ << " with "
+ << perf_event_fd_;
}
return false;
}
@@ -320,14 +312,17 @@ void EventFd::DiscardAuxData(size_t discard_size) {
mmap_metadata_page_->aux_tail += discard_size;
}
-bool EventFd::StartPolling(IOEventLoop& loop,
- const std::function<bool()>& callback) {
+bool EventFd::StartPolling(IOEventLoop& loop, const std::function<bool()>& callback) {
ioevent_ref_ = loop.AddReadEvent(perf_event_fd_, callback);
return ioevent_ref_ != nullptr;
}
-bool EventFd::StopPolling() { return IOEventLoop::DelEvent(ioevent_ref_); }
+bool EventFd::StopPolling() {
+ return IOEventLoop::DelEvent(ioevent_ref_);
+}
bool IsEventAttrSupported(const perf_event_attr& attr, const std::string& event_name) {
return EventFd::OpenEventFile(attr, getpid(), -1, nullptr, event_name, false) != nullptr;
}
+
+} // namespace simpleperf
diff --git a/simpleperf/event_fd.h b/simpleperf/event_fd.h
index d687054f..90791a53 100644
--- a/simpleperf/event_fd.h
+++ b/simpleperf/event_fd.h
@@ -28,8 +28,10 @@
#include "IOEventLoop.h"
#include "perf_event.h"
+namespace simpleperf {
+
struct PerfCounter {
- uint64_t value; // The value of the event specified by the perf_event_file.
+ uint64_t value; // The value of the event specified by the perf_event_file.
uint64_t time_enabled; // The enabled time.
uint64_t time_running; // The running time.
uint64_t id; // The id of the perf_event_file.
@@ -107,8 +109,8 @@ class EventFd {
virtual bool StopPolling();
protected:
- EventFd(const perf_event_attr& attr, int perf_event_fd,
- const std::string& event_name, pid_t tid, int cpu)
+ EventFd(const perf_event_attr& attr, int perf_event_fd, const std::string& event_name, pid_t tid,
+ int cpu)
: attr_(attr),
perf_event_fd_(perf_event_fd),
id_(0),
@@ -153,4 +155,6 @@ class EventFd {
bool IsEventAttrSupported(const perf_event_attr& attr, const std::string& event_name);
+} // namespace simpleperf
+
#endif // SIMPLE_PERF_EVENT_FD_H_
diff --git a/simpleperf/event_selection_set.cpp b/simpleperf/event_selection_set.cpp
index 6583bd2d..82d41fd4 100644
--- a/simpleperf/event_selection_set.cpp
+++ b/simpleperf/event_selection_set.cpp
@@ -19,19 +19,25 @@
#include <algorithm>
#include <atomic>
#include <thread>
+#include <unordered_map>
#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
-#include "environment.h"
#include "ETMRecorder.h"
+#include "IOEventLoop.h"
+#include "RecordReadThread.h"
+#include "environment.h"
#include "event_attr.h"
#include "event_type.h"
-#include "IOEventLoop.h"
#include "perf_regs.h"
+#include "tracing.h"
#include "utils.h"
-#include "RecordReadThread.h"
-using namespace simpleperf;
+namespace simpleperf {
+
+using android::base::StringPrintf;
bool IsBranchSamplingSupported() {
const EventType* type = FindEventTypeByName("cpu-cycles");
@@ -50,8 +56,7 @@ bool IsDwarfCallChainSamplingSupported() {
return false;
}
perf_event_attr attr = CreateDefaultPerfEventAttr(*type);
- attr.sample_type |=
- PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
+ attr.sample_type |= PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
attr.exclude_callchain_user = 1;
attr.sample_regs_user = GetSupportedRegMask(GetBuildArch());
attr.sample_stack_user = 8192;
@@ -59,6 +64,11 @@ bool IsDwarfCallChainSamplingSupported() {
}
bool IsDumpingRegsForTracepointEventsSupported() {
+ if (auto version = GetKernelVersion(); version && version.value() >= std::make_pair(4, 2)) {
+ // Kernel >= 4.2 has patch "5b09a094f2 arm64: perf: Fix callchain parse error with kernel
+ // tracepoint events". So no need to test.
+ return true;
+ }
const EventType* event_type = FindEventTypeByName("sched:sched_switch", false);
if (event_type == nullptr) {
return false;
@@ -88,16 +98,20 @@ bool IsDumpingRegsForTracepointEventsSupported() {
done = true;
thread.join();
- std::vector<char> buffer = event_fd->GetAvailableMmapData();
- std::vector<std::unique_ptr<Record>> records =
- ReadRecordsFromBuffer(attr, buffer.data(), buffer.size());
- for (auto& r : records) {
- if (r->type() == PERF_RECORD_SAMPLE) {
- auto& record = *static_cast<SampleRecord*>(r.get());
- if (record.ip_data.ip != 0) {
- return true;
+ // There are small chances that we don't see samples immediately after joining the thread on
+ // cuttlefish, probably due to data synchronization between cpus. To avoid flaky tests, use a
+ // loop to wait for samples.
+ for (int timeout = 0; timeout < 1000; timeout++) {
+ std::vector<char> buffer = event_fd->GetAvailableMmapData();
+ std::vector<std::unique_ptr<Record>> records =
+ ReadRecordsFromBuffer(attr, buffer.data(), buffer.size());
+ for (auto& r : records) {
+ if (r->type() == PERF_RECORD_SAMPLE) {
+ auto& record = *static_cast<SampleRecord*>(r.get());
+ return record.ip_data.ip != 0;
}
}
+ usleep(1);
}
return false;
}
@@ -131,6 +145,23 @@ bool IsMmap2Supported() {
return IsEventAttrSupported(attr, type->name);
}
+std::string AddrFilter::ToString() const {
+ switch (type) {
+ case FILE_RANGE:
+ return StringPrintf("filter 0x%" PRIx64 "/0x%" PRIx64 "@%s", addr, size, file_path.c_str());
+ case AddrFilter::FILE_START:
+ return StringPrintf("start 0x%" PRIx64 "@%s", addr, file_path.c_str());
+ case AddrFilter::FILE_STOP:
+ return StringPrintf("stop 0x%" PRIx64 "@%s", addr, file_path.c_str());
+ case AddrFilter::KERNEL_RANGE:
+ return StringPrintf("filter 0x%" PRIx64 "/0x%" PRIx64, addr, size);
+ case AddrFilter::KERNEL_START:
+ return StringPrintf("start 0x%" PRIx64, addr);
+ case AddrFilter::KERNEL_STOP:
+ return StringPrintf("stop 0x%" PRIx64, addr);
+ }
+}
+
EventSelectionSet::EventSelectionSet(bool for_stat_cmd)
: for_stat_cmd_(for_stat_cmd), loop_(new IOEventLoop) {}
@@ -143,11 +174,9 @@ bool EventSelectionSet::BuildAndCheckEventSelection(const std::string& event_nam
return false;
}
if (for_stat_cmd_) {
- if (event_type->event_type.name == "cpu-clock" ||
- event_type->event_type.name == "task-clock") {
+ if (event_type->event_type.name == "cpu-clock" || event_type->event_type.name == "task-clock") {
if (event_type->exclude_user || event_type->exclude_kernel) {
- LOG(ERROR) << "Modifier u and modifier k used in event type "
- << event_type->event_type.name
+ LOG(ERROR) << "Modifier u and modifier k used in event type " << event_type->event_type.name
<< " are not supported by the kernel.";
return false;
}
@@ -199,8 +228,7 @@ bool EventSelectionSet::BuildAndCheckEventSelection(const std::string& event_nam
// PMU events are provided by kernel, so they should be supported
if (!event_type->event_type.IsPmuEvent() &&
!IsEventAttrSupported(selection->event_attr, selection->event_type_modifier.name)) {
- LOG(ERROR) << "Event type '" << event_type->name
- << "' is not supported on the device";
+ LOG(ERROR) << "Event type '" << event_type->name << "' is not supported on the device";
return false;
}
if (set_default_sample_freq) {
@@ -212,8 +240,7 @@ bool EventSelectionSet::BuildAndCheckEventSelection(const std::string& event_nam
for (const auto& group : groups_) {
for (const auto& sel : group) {
if (sel.event_type_modifier.name == selection->event_type_modifier.name) {
- LOG(ERROR) << "Event type '" << sel.event_type_modifier.name
- << "' appears more than once";
+ LOG(ERROR) << "Event type '" << sel.event_type_modifier.name << "' appears more than once";
return false;
}
}
@@ -225,8 +252,8 @@ bool EventSelectionSet::AddEventType(const std::string& event_name, size_t* grou
return AddEventGroup(std::vector<std::string>(1, event_name), group_id);
}
-bool EventSelectionSet::AddEventGroup(
- const std::vector<std::string>& event_names, size_t* group_id) {
+bool EventSelectionSet::AddEventGroup(const std::vector<std::string>& event_names,
+ size_t* group_id) {
EventSelectionGroup group;
bool first_event = groups_.empty();
bool first_in_group = true;
@@ -270,8 +297,7 @@ std::vector<const EventType*> EventSelectionSet::GetTracepointEvents() const {
std::vector<const EventType*> result;
for (const auto& group : groups_) {
for (const auto& selection : group) {
- if (selection.event_type_modifier.event_type.type ==
- PERF_TYPE_TRACEPOINT) {
+ if (selection.event_type_modifier.event_type.type == PERF_TYPE_TRACEPOINT) {
result.push_back(&selection.event_type_modifier.event_type);
}
}
@@ -305,6 +331,18 @@ std::vector<EventAttrWithId> EventSelectionSet::GetEventAttrWithId() const {
return result;
}
+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& fd : selection.event_fds) {
+ result[fd->Id()] = selection.event_type_modifier.name;
+ }
+ }
+ }
+ return result;
+}
+
// Union the sample type of different event attrs can make reading sample
// records in perf.data easier.
void EventSelectionSet::UnionSampleType() {
@@ -374,11 +412,9 @@ void EventSelectionSet::SetSampleSpeed(size_t group_id, const SampleSpeed& speed
bool EventSelectionSet::SetBranchSampling(uint64_t branch_sample_type) {
if (branch_sample_type != 0 &&
- (branch_sample_type &
- (PERF_SAMPLE_BRANCH_ANY | PERF_SAMPLE_BRANCH_ANY_CALL |
- PERF_SAMPLE_BRANCH_ANY_RETURN | PERF_SAMPLE_BRANCH_IND_CALL)) == 0) {
- LOG(ERROR) << "Invalid branch_sample_type: 0x" << std::hex
- << branch_sample_type;
+ (branch_sample_type & (PERF_SAMPLE_BRANCH_ANY | PERF_SAMPLE_BRANCH_ANY_CALL |
+ PERF_SAMPLE_BRANCH_ANY_RETURN | PERF_SAMPLE_BRANCH_IND_CALL)) == 0) {
+ LOG(ERROR) << "Invalid branch_sample_type: 0x" << std::hex << branch_sample_type;
return false;
}
if (branch_sample_type != 0 && !IsBranchSamplingSupported()) {
@@ -414,12 +450,10 @@ bool EventSelectionSet::EnableDwarfCallChainSampling(uint32_t dump_stack_size) {
}
for (auto& group : groups_) {
for (auto& selection : group) {
- selection.event_attr.sample_type |= PERF_SAMPLE_CALLCHAIN |
- PERF_SAMPLE_REGS_USER |
- PERF_SAMPLE_STACK_USER;
+ selection.event_attr.sample_type |=
+ PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
selection.event_attr.exclude_callchain_user = 1;
- selection.event_attr.sample_regs_user =
- GetSupportedRegMask(GetMachineArch());
+ selection.event_attr.sample_regs_user = GetSupportedRegMask(GetMachineArch());
selection.event_attr.sample_stack_user = dump_stack_size;
}
}
@@ -444,14 +478,7 @@ void EventSelectionSet::SetClockId(int clock_id) {
}
bool EventSelectionSet::NeedKernelSymbol() const {
- for (const auto& group : groups_) {
- for (const auto& selection : group) {
- if (!selection.event_type_modifier.exclude_kernel) {
- return true;
- }
- }
- }
- return false;
+ return !ExcludeKernel();
}
void EventSelectionSet::SetRecordNotExecutableMaps(bool record) {
@@ -463,11 +490,68 @@ bool EventSelectionSet::RecordNotExecutableMaps() const {
return groups_[0][0].event_attr.mmap_data == 1;
}
+void EventSelectionSet::WakeupPerSample() {
+ for (auto& group : groups_) {
+ for (auto& selection : group) {
+ selection.event_attr.watermark = 0;
+ selection.event_attr.wakeup_events = 1;
+ }
+ }
+}
+
+bool EventSelectionSet::SetTracepointFilter(const std::string& filter) {
+ // 1. Find the tracepoint event to set 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 (selection == nullptr) {
+ LOG(ERROR) << "No tracepoint event before filter: " << filter;
+ return false;
+ }
+
+ // 2. Check the format of the filter.
+ bool use_quote = false;
+ // Quotes are needed for string operands in kernel >= 4.19, probably after patch "tracing: Rewrite
+ // filter logic to be simpler and faster".
+ if (auto version = GetKernelVersion(); version && version.value() >= std::make_pair(4, 19)) {
+ use_quote = true;
+ }
+
+ FieldNameSet used_fields;
+ auto adjusted_filter = AdjustTracepointFilter(filter, use_quote, &used_fields);
+ if (!adjusted_filter) {
+ return false;
+ }
+
+ // 3. Check if used fields are available in the tracepoint event.
+ auto& event_type = selection->event_type_modifier.event_type;
+ if (auto opt_fields = GetFieldNamesForTracepointEvent(event_type); opt_fields) {
+ FieldNameSet& fields = opt_fields.value();
+ for (const auto& field : used_fields) {
+ if (fields.find(field) == fields.end()) {
+ LOG(ERROR) << "field name " << field << " used in \"" << filter << "\" doesn't exist in "
+ << event_type.name << ". Available fields are "
+ << android::base::Join(fields, ",");
+ return false;
+ }
+ }
+ }
+
+ // 4. Connect the filter to the event.
+ selection->tracepoint_filter = adjusted_filter.value();
+ 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()) {
+ if (std::find(online_cpus.begin(), online_cpus.end(), cpu) == online_cpus.end()) {
LOG(ERROR) << "cpu " << cpu << " is not online.";
return false;
}
@@ -475,8 +559,7 @@ static bool CheckIfCpusOnline(const std::vector<int>& cpus) {
return true;
}
-bool EventSelectionSet::OpenEventFilesOnGroup(EventSelectionGroup& group,
- pid_t tid, int cpu,
+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
@@ -486,8 +569,8 @@ bool EventSelectionSet::OpenEventFilesOnGroup(EventSelectionGroup& group,
std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(
selection.event_attr, tid, cpu, group_fd, selection.event_type_modifier.name, false);
if (!event_fd) {
- *failed_event_type = selection.event_type_modifier.name;
- return false;
+ *failed_event_type = selection.event_type_modifier.name;
+ return false;
}
LOG(VERBOSE) << "OpenEventFile for " << event_fd->Name();
event_fds.push_back(std::move(event_fd));
@@ -557,32 +640,40 @@ bool EventSelectionSet::OpenEventFiles(const std::vector<int>& cpus) {
}
bool EventSelectionSet::ApplyFilters() {
- if (include_filters_.empty()) {
+ return ApplyAddrFilters() && ApplyTracepointFilters();
+}
+
+bool EventSelectionSet::ApplyAddrFilters() {
+ if (addr_filters_.empty()) {
return true;
}
if (!has_aux_trace_) {
- LOG(ERROR) << "include filters only take effect in cs-etm instruction tracing";
+ LOG(ERROR) << "addr filters only take effect in cs-etm instruction tracing";
return false;
}
- size_t supported_pairs = ETMRecorder::GetInstance().GetAddrFilterPairs();
- if (supported_pairs < include_filters_.size()) {
- LOG(ERROR) << "filter binary count is " << include_filters_.size()
- << ", bigger than maximum supported filters on device, which is " << supported_pairs;
+
+ // Check filter count limit.
+ size_t required_etm_filter_count = 0;
+ for (auto& filter : addr_filters_) {
+ // A range filter needs two etm filters.
+ required_etm_filter_count +=
+ (filter.type == AddrFilter::FILE_RANGE || filter.type == AddrFilter::KERNEL_RANGE) ? 2 : 1;
+ }
+ size_t etm_filter_count = ETMRecorder::GetInstance().GetAddrFilterPairs() * 2;
+ if (etm_filter_count < required_etm_filter_count) {
+ LOG(ERROR) << "needed " << required_etm_filter_count << " etm filters, but only "
+ << etm_filter_count << " filters are available.";
return false;
}
+
std::string filter_str;
- for (auto& binary : include_filters_) {
- std::string path;
- if (!android::base::Realpath(binary, &path)) {
- PLOG(ERROR) << "failed to find include filter binary: " << binary;
- return false;
- }
- uint64_t file_size = GetFileSize(path);
+ for (auto& filter : addr_filters_) {
if (!filter_str.empty()) {
filter_str += ',';
}
- android::base::StringAppendF(&filter_str, "filter 0/%" PRIu64 "@%s", file_size, path.c_str());
+ filter_str += filter.ToString();
}
+
for (auto& group : groups_) {
for (auto& selection : group) {
if (IsEtmEventType(selection.event_type_modifier.event_type.type)) {
@@ -597,6 +688,21 @@ bool EventSelectionSet::ApplyFilters() {
return true;
}
+bool EventSelectionSet::ApplyTracepointFilters() {
+ for (auto& group : groups_) {
+ for (auto& selection : group) {
+ if (!selection.tracepoint_filter.empty()) {
+ for (auto& event_fd : selection.event_fds) {
+ if (!event_fd->SetFilter(selection.tracepoint_filter)) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+ return true;
+}
+
static bool ReadCounter(EventFd* event_fd, CounterInfo* counter) {
if (!event_fd->ReadCounter(&counter->counter)) {
return false;
@@ -631,10 +737,9 @@ 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) {
- 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_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));
return true;
}
@@ -690,6 +795,17 @@ bool EventSelectionSet::FinishReadMmapEventData() {
return true;
}
+void EventSelectionSet::CloseEventFiles() {
+ if (record_read_thread_) {
+ record_read_thread_->StopReadThread();
+ }
+ for (auto& group : groups_) {
+ for (auto& event : group) {
+ event.event_fds.clear();
+ }
+ }
+}
+
bool EventSelectionSet::StopWhenNoMoreTargets(double check_interval_in_sec) {
return loop_->AddPeriodicEvent(SecondToTimeval(check_interval_in_sec),
[&]() { return CheckMonitoredTargets(); });
@@ -735,3 +851,5 @@ bool EventSelectionSet::SetEnableEvents(bool enable) {
}
return true;
}
+
+} // namespace simpleperf
diff --git a/simpleperf/event_selection_set.h b/simpleperf/event_selection_set.h
index 2af14711..7d84b620 100644
--- a/simpleperf/event_selection_set.h
+++ b/simpleperf/event_selection_set.h
@@ -25,13 +25,15 @@
#include <android-base/macros.h>
+#include "IOEventLoop.h"
+#include "RecordReadThread.h"
#include "event_attr.h"
#include "event_fd.h"
#include "event_type.h"
-#include "IOEventLoop.h"
#include "perf_event.h"
#include "record.h"
-#include "RecordReadThread.h"
+
+namespace simpleperf {
constexpr double DEFAULT_PERIOD_TO_CHECK_MONITORED_TARGETS_IN_SEC = 1;
constexpr uint64_t DEFAULT_SAMPLE_FREQ_FOR_NONTRACEPOINT_EVENT = 4000;
@@ -64,6 +66,25 @@ struct SampleSpeed {
}
};
+struct AddrFilter {
+ enum Type {
+ FILE_RANGE,
+ FILE_START,
+ FILE_STOP,
+ KERNEL_RANGE,
+ KERNEL_START,
+ KERNEL_STOP,
+ } type;
+ uint64_t addr;
+ uint64_t size;
+ std::string file_path;
+
+ AddrFilter(AddrFilter::Type type, uint64_t addr, uint64_t size, const std::string& file_path)
+ : type(type), addr(addr), size(size), file_path(file_path) {}
+
+ std::string ToString() const;
+};
+
// EventSelectionSet helps to monitor events. It is used in following steps:
// 1. Create an EventSelectionSet, and add event types to monitor by calling
// AddEventType() or AddEventGroup().
@@ -93,6 +114,7 @@ class EventSelectionSet {
bool ExcludeKernel() const;
bool HasAuxTrace() const { return has_aux_trace_; }
std::vector<EventAttrWithId> GetEventAttrWithId() const;
+ std::unordered_map<uint64_t, std::string> GetEventNamesById() const;
void SetEnableOnExec(bool enable);
bool GetEnableOnExec();
@@ -106,9 +128,9 @@ class EventSelectionSet {
bool NeedKernelSymbol() const;
void SetRecordNotExecutableMaps(bool record);
bool RecordNotExecutableMaps() const;
- void SetIncludeFilters(std::vector<std::string>&& filters) {
- include_filters_ = std::move(filters);
- }
+ void WakeupPerSample();
+ void SetAddrFilters(std::vector<AddrFilter>&& filters) { addr_filters_ = std::move(filters); }
+ bool SetTracepointFilter(const std::string& filter);
template <typename Collection = std::vector<pid_t>>
void AddMonitoredProcesses(const Collection& processes) {
@@ -129,13 +151,9 @@ class EventSelectionSet {
threads_.clear();
}
- bool HasMonitoredTarget() const {
- return !processes_.empty() || !threads_.empty();
- }
+ bool HasMonitoredTarget() const { return !processes_.empty() || !threads_.empty(); }
- IOEventLoop* GetIOEventLoop() {
- return loop_.get();
- }
+ 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.
@@ -147,14 +165,13 @@ class EventSelectionSet {
bool PrepareToReadMmapEventData(const std::function<bool(Record*)>& callback);
bool SyncKernelBuffer();
bool FinishReadMmapEventData();
+ void CloseEventFiles();
- const simpleperf::RecordStat& GetRecordStat() {
- return record_read_thread_->GetStat();
- }
+ const simpleperf::RecordStat& GetRecordStat() { return record_read_thread_->GetStat(); }
// Stop profiling if all monitored processes/threads don't exist.
- bool StopWhenNoMoreTargets(double check_interval_in_sec =
- DEFAULT_PERIOD_TO_CHECK_MONITORED_TARGETS_IN_SEC);
+ bool StopWhenNoMoreTargets(
+ double check_interval_in_sec = DEFAULT_PERIOD_TO_CHECK_MONITORED_TARGETS_IN_SEC);
bool SetEnableEvents(bool enable);
@@ -166,6 +183,7 @@ class EventSelectionSet {
// counters for event files closed for cpu hotplug events
std::vector<CounterInfo> hotplugged_counters;
std::vector<int> allowed_cpus;
+ std::string tracepoint_filter;
};
typedef std::vector<EventSelection> EventSelectionGroup;
@@ -175,6 +193,8 @@ class EventSelectionSet {
bool OpenEventFilesOnGroup(EventSelectionGroup& group, pid_t tid, int cpu,
std::string* failed_event_type);
bool ApplyFilters();
+ bool ApplyAddrFilters();
+ bool ApplyTracepointFilters();
bool ReadMmapEventData(bool with_time_limit);
bool CheckMonitoredTargets();
@@ -192,7 +212,7 @@ class EventSelectionSet {
std::unique_ptr<simpleperf::RecordReadThread> record_read_thread_;
bool has_aux_trace_ = false;
- std::vector<std::string> include_filters_;
+ std::vector<AddrFilter> addr_filters_;
DISALLOW_COPY_AND_ASSIGN(EventSelectionSet);
};
@@ -203,4 +223,6 @@ bool IsDumpingRegsForTracepointEventsSupported();
bool IsSettingClockIdSupported();
bool IsMmap2Supported();
+} // namespace simpleperf
+
#endif // SIMPLE_PERF_EVENT_SELECTION_SET_H_
diff --git a/simpleperf/event_type.cpp b/simpleperf/event_type.cpp
index 71c3d9f1..135e69da 100644
--- a/simpleperf/event_type.cpp
+++ b/simpleperf/event_type.cpp
@@ -28,17 +28,16 @@
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
-#include "environment.h"
#include "ETMRecorder.h"
+#include "environment.h"
#include "event_attr.h"
#include "utils.h"
-using namespace simpleperf;
+namespace simpleperf {
struct EventFormat {
EventFormat(const std::string& name, const std::string& attr, int shift)
- : name(name), attr(attr), shift(shift) {
- }
+ : name(name), attr(attr), shift(shift) {}
std::string name;
std::string attr;
@@ -46,188 +45,411 @@ struct EventFormat {
};
#define EVENT_TYPE_TABLE_ENTRY(name, type, config, description, limited_arch) \
- {name, type, config, description, limited_arch},
+ {name, type, config, description, limited_arch},
-static const std::vector<EventType> static_event_type_array = {
+static const std::set<EventType> builtin_event_types = {
#include "event_type_table.h"
};
-static std::string tracepoint_events;
-static std::set<EventType> g_event_types;
-static uint32_t g_etm_event_type;
+enum class EventFinderType {
+ BUILTIN,
+ TRACEPOINT_STRING,
+ TRACEPOINT_SYSTEM,
+ PMU,
+ ETM,
+ RAW,
+ SCOPED,
+};
-bool SetTracepointEventsFilePath(const std::string& filepath) {
- if (!android::base::ReadFileToString(filepath, &tracepoint_events)) {
- PLOG(ERROR) << "Failed to read " << filepath;
- return false;
+class EventTypeFinder {
+ public:
+ EventTypeFinder(EventFinderType type) : finder_type_(type) {}
+ virtual ~EventTypeFinder() {}
+
+ EventFinderType GetFinderType() const { return finder_type_; }
+
+ const std::set<EventType>& GetTypes() {
+ if (!loaded_) {
+ loaded_ = true;
+ LoadTypes();
+ }
+ return types_;
}
- return true;
-}
-std::string GetTracepointEvents() {
- std::string result;
- for (auto& event : GetAllEventTypes()) {
- if (event.type != PERF_TYPE_TRACEPOINT) {
- continue;
+ virtual const EventType* FindType(const std::string& name) {
+ const auto& types = GetTypes();
+ auto it = types.find(EventType(name, 0, 0, "", ""));
+ if (it != types.end()) {
+ return &*it;
}
- if (!result.empty()) {
- result.push_back('\n');
+ return nullptr;
+ }
+
+ protected:
+ virtual void LoadTypes() = 0;
+
+ const EventFinderType finder_type_;
+ std::set<EventType> types_;
+ bool loaded_ = false;
+};
+
+class BuiltinTypeFinder : public EventTypeFinder {
+ public:
+ BuiltinTypeFinder() : EventTypeFinder(EventFinderType::BUILTIN) {}
+
+ protected:
+ void LoadTypes() override { types_ = std::move(builtin_event_types); }
+};
+
+class TracepointStringFinder : public EventTypeFinder {
+ public:
+ TracepointStringFinder(std::string&& s)
+ : EventTypeFinder(EventFinderType::TRACEPOINT_STRING), s_(std::move(s)) {}
+
+ protected:
+ void LoadTypes() override {
+ for (const auto& line : android::base::Split(s_, "\n")) {
+ std::vector<std::string> items = android::base::Split(line, " ");
+ CHECK_EQ(items.size(), 2u);
+ std::string event_name = items[0];
+ uint64_t id;
+ CHECK(android::base::ParseUint(items[1].c_str(), &id));
+ types_.emplace(event_name, PERF_TYPE_TRACEPOINT, id, "", "");
}
- result += android::base::StringPrintf("%s %" PRIu64, event.name.c_str(), event.config);
}
- return result;
-}
-static std::vector<EventType> GetTracepointEventTypesFromString(const std::string& s) {
- std::vector<EventType> result;
- for (auto& line : android::base::Split(s, "\n")) {
- std::vector<std::string> items = android::base::Split(line, " ");
- CHECK_EQ(items.size(), 2u);
- std::string event_name = items[0];
+ private:
+ const std::string s_;
+};
+
+class TracepointSystemFinder : public EventTypeFinder {
+ public:
+ TracepointSystemFinder() : EventTypeFinder(EventFinderType::TRACEPOINT_SYSTEM) {}
+
+ const EventType* FindType(const std::string& name) override {
+ if (auto it = types_.find(EventType(name, 0, 0, "", "")); it != types_.end()) {
+ return &*it;
+ }
+ std::vector<std::string> strs = android::base::Split(name, ":");
+ if (strs.size() != 2) {
+ return nullptr;
+ }
+ const char* tracefs_dir = GetTraceFsDir();
+ if (tracefs_dir == nullptr) {
+ return nullptr;
+ }
+ std::string path = tracefs_dir + std::string("/events/") + strs[0] + "/" + strs[1] + "/id";
uint64_t id;
- CHECK(android::base::ParseUint(items[1].c_str(), &id));
- result.push_back(EventType(event_name, PERF_TYPE_TRACEPOINT, id, "", ""));
+ if (!ReadEventId(path, &id)) {
+ return nullptr;
+ }
+ auto res = types_.emplace(name, PERF_TYPE_TRACEPOINT, id, "", "");
+ return &*res.first;
}
- return result;
-}
-static std::vector<EventType> GetTracepointEventTypesFromTraceFs() {
- std::vector<EventType> result;
- const char* tracefs_dir = GetTraceFsDir();
- if (tracefs_dir == nullptr) {
+ void RemoveType(const std::string& name) { types_.erase(EventType(name, 0, 0, "", "")); }
+
+ std::string ToString() {
+ std::string result;
+ for (auto& type : GetTypes()) {
+ if (!result.empty()) {
+ result.push_back('\n');
+ }
+ result += android::base::StringPrintf("%s %" PRIu64, type.name.c_str(), type.config);
+ }
return result;
}
- const std::string tracepoint_dirname = tracefs_dir + std::string("/events");
- for (const auto& system_name : GetSubDirs(tracepoint_dirname)) {
- std::string system_path = tracepoint_dirname + "/" + system_name;
- for (const auto& event_name : GetSubDirs(system_path)) {
- std::string id_path = system_path + "/" + event_name + "/id";
- std::string id_content;
- if (!android::base::ReadFileToString(id_path, &id_content)) {
+
+ protected:
+ void LoadTypes() override {
+ const char* tracefs_dir = GetTraceFsDir();
+ if (tracefs_dir == nullptr) {
+ return;
+ }
+ const std::string tracepoint_dirname = tracefs_dir + std::string("/events");
+ for (const auto& system_name : GetSubDirs(tracepoint_dirname)) {
+ std::string system_path = tracepoint_dirname + "/" + system_name;
+ for (const auto& event_name : GetSubDirs(system_path)) {
+ std::string id_path = system_path + "/" + event_name + "/id";
+ uint64_t id;
+ if (ReadEventId(id_path, &id)) {
+ types_.emplace(system_name + ":" + event_name, PERF_TYPE_TRACEPOINT, id, "", "");
+ }
+ }
+ }
+ }
+
+ private:
+ bool ReadEventId(const std::string& id_path, uint64_t* id) {
+ std::string id_content;
+ if (!android::base::ReadFileToString(id_path, &id_content)) {
+ return false;
+ }
+ if (!android::base::ParseUint(android::base::Trim(id_content), id)) {
+ LOG(DEBUG) << "unexpected id '" << id_content << "' in " << id_path;
+ return false;
+ }
+ return true;
+ }
+};
+
+class PMUTypeFinder : public EventTypeFinder {
+ public:
+ PMUTypeFinder() : EventTypeFinder(EventFinderType::PMU) {}
+
+ const EventType* FindType(const std::string& name) override {
+ if (name.find('/') == std::string::npos) {
+ return nullptr;
+ }
+ return EventTypeFinder::FindType(name);
+ }
+
+ protected:
+ void LoadTypes() override {
+ const std::string evtsrc_dirname = "/sys/bus/event_source/devices/";
+ for (const auto& device_name : GetSubDirs(evtsrc_dirname)) {
+ std::string evtdev_path = evtsrc_dirname + device_name;
+ std::string type_path = evtdev_path + "/type";
+ std::string type_content;
+
+ if (!android::base::ReadFileToString(type_path, &type_content)) {
+ LOG(DEBUG) << "cannot read event type: " << device_name;
continue;
}
- char* endptr;
- uint64_t id = strtoull(id_content.c_str(), &endptr, 10);
- if (endptr == id_content.c_str()) {
- LOG(DEBUG) << "unexpected id '" << id_content << "' in " << id_path;
+ uint64_t type_id = strtoull(type_content.c_str(), NULL, 10);
+
+ std::vector<EventFormat> formats = ParseEventFormats(evtdev_path);
+
+ std::string events_dirname = evtdev_path + "/events/";
+ for (const auto& event_name : GetEntriesInDir(events_dirname)) {
+ std::string event_path = events_dirname + event_name;
+ std::string event_content;
+ if (!android::base::ReadFileToString(event_path, &event_content)) {
+ LOG(DEBUG) << "cannot read event content in " << event_name;
+ continue;
+ }
+
+ uint64_t config = MakeEventConfig(event_content, formats);
+ if (config == ~0ULL) {
+ LOG(DEBUG) << "cannot handle config format in " << event_name;
+ continue;
+ }
+ types_.emplace(device_name + "/" + event_name + "/", type_id, config, "", "");
+ }
+ }
+ }
+
+ private:
+ std::vector<EventFormat> ParseEventFormats(const std::string& evtdev_path) {
+ std::vector<EventFormat> v;
+ std::string formats_dirname = evtdev_path + "/format/";
+ for (const auto& format_name : GetEntriesInDir(formats_dirname)) {
+ std::string format_path = formats_dirname + format_name;
+ std::string format_content;
+ if (!android::base::ReadFileToString(format_path, &format_content)) {
+ continue;
+ }
+
+ // format files look like below (currently only 'config' is supported) :
+ // # cat armv8_pmuv3/format/event
+ // config:0-15
+ int shift;
+ if (sscanf(format_content.c_str(), "config:%d", &shift) != 1) {
+ LOG(DEBUG) << "Invalid or unsupported event format: " << format_content;
continue;
}
- result.push_back(EventType(system_name + ":" + event_name, PERF_TYPE_TRACEPOINT, id, "", ""));
+
+ v.emplace_back(EventFormat(format_name, "config", shift));
}
+ return v;
}
- return result;
-}
-static std::vector<EventType> GetTracepointEventTypes() {
- std::vector<EventType> result;
- if (!tracepoint_events.empty()) {
- result = GetTracepointEventTypesFromString(tracepoint_events);
- } else {
- result = GetTracepointEventTypesFromTraceFs();
+ uint64_t MakeEventConfig(const std::string& event_str, std::vector<EventFormat>& formats) {
+ uint64_t config = 0;
+
+ // event files might have multiple terms, but usually have a term like:
+ // # cat armv8_pmuv3/events/cpu_cycles
+ // event=0x011
+ for (auto& s : android::base::Split(event_str, ",")) {
+ auto pos = s.find('=');
+ if (pos == std::string::npos) continue;
+
+ auto format = s.substr(0, pos);
+ long val;
+ if (!android::base::ParseInt(android::base::Trim(s.substr(pos + 1)), &val)) {
+ LOG(DEBUG) << "Invalid event format '" << s << "'";
+ continue;
+ }
+
+ for (auto& f : formats) {
+ if (f.name == format) {
+ if (f.attr != "config") {
+ LOG(DEBUG) << "cannot support other attribute: " << s;
+ return ~0ULL;
+ }
+
+ config |= val << f.shift;
+ break;
+ }
+ }
+ }
+ return config;
}
- std::sort(result.begin(), result.end(),
- [](const EventType& type1, const EventType& type2) { return type1.name < type2.name; });
- return result;
-}
+};
+
+class ETMTypeFinder : public EventTypeFinder {
+ public:
+ ETMTypeFinder() : EventTypeFinder(EventFinderType::ETM) {}
-static std::vector<EventFormat> ParseEventFormats(const std::string& evtdev_path) {
- std::vector<EventFormat> v;
- std::string formats_dirname = evtdev_path + "/format/";
- for (const auto& format_name : GetEntriesInDir(formats_dirname)) {
- std::string format_path = formats_dirname + format_name;
- std::string format_content;
- if (!android::base::ReadFileToString(format_path, &format_content)) {
- continue;
+ const EventType* FindType(const std::string& name) override {
+ if (name != kETMEventName) {
+ return nullptr;
}
+ return EventTypeFinder::FindType(name);
+ }
- // format files look like below (currently only 'config' is supported) :
- // # cat armv8_pmuv3/format/event
- // config:0-15
- int shift;
- if (sscanf(format_content.c_str(), "config:%d", &shift) != 1) {
- LOG(DEBUG) << "Invalid or unsupported event format: " << format_content;
- continue;
+ protected:
+ void LoadTypes() override {
+#if defined(__linux__)
+ std::unique_ptr<EventType> etm_type = ETMRecorder::GetInstance().BuildEventType();
+ if (etm_type) {
+ types_.emplace(std::move(*etm_type));
}
+#endif
+ }
+};
+
+class RawTypeFinder : public EventTypeFinder {
+ public:
+ RawTypeFinder() : EventTypeFinder(EventFinderType::RAW) {}
- v.emplace_back(EventFormat(format_name, "config", shift));
+ const EventType* AddType(EventType&& type) {
+ auto result = types_.emplace(std::move(type));
+ return &*(result.first);
}
- return v;
+
+ protected:
+ void LoadTypes() override {}
+};
+
+class ScopedTypeFinder : public EventTypeFinder {
+ public:
+ ScopedTypeFinder(std::set<EventType>&& types) : EventTypeFinder(EventFinderType::SCOPED) {
+ types_ = std::move(types);
+ }
+
+ protected:
+ void LoadTypes() override {}
+};
+
+EventTypeManager EventTypeManager::instance_;
+
+EventTypeManager::EventTypeManager() {
+ type_finders_.emplace_back(new BuiltinTypeFinder());
+ type_finders_.emplace_back(new TracepointSystemFinder());
+ type_finders_.emplace_back(new PMUTypeFinder());
+ type_finders_.emplace_back(new ETMTypeFinder());
+ type_finders_.emplace_back(new RawTypeFinder());
}
-static uint64_t MakeEventConfig(const std::string& event_str, std::vector<EventFormat>& formats) {
- uint64_t config = 0;
-
- // event files might have multiple terms, but usually have a term like:
- // # cat armv8_pmuv3/events/cpu_cycles
- // event=0x011
- for (auto& s : android::base::Split(event_str, ",")) {
- auto pos = s.find('=');
- if (pos == std::string::npos)
- continue;
-
- auto format = s.substr(0, pos);
- long val;
- if (!android::base::ParseInt(android::base::Trim(s.substr(pos+1)), &val)) {
- LOG(DEBUG) << "Invalid event format '" << s << "'";
- continue;
+EventTypeManager::~EventTypeManager() {}
+
+std::unique_ptr<EventTypeFinder>& EventTypeManager::GetFinder(EventFinderType type) {
+ for (auto& finder : type_finders_) {
+ if (finder->GetFinderType() == type) {
+ return finder;
}
+ }
+ LOG(FATAL) << "Failed to get EventTypeFinder";
+ __builtin_unreachable();
+}
- for (auto& f : formats) {
- if (f.name == format) {
- if (f.attr != "config") {
- LOG(DEBUG) << "cannot support other attribute: " << s;
- return ~0ULL;
- }
+RawTypeFinder& EventTypeManager::GetRawTypeFinder() {
+ return *static_cast<RawTypeFinder*>(GetFinder(EventFinderType::RAW).get());
+}
- config |= val << f.shift;
- break;
+TracepointSystemFinder& EventTypeManager::GetTracepointSystemFinder() {
+ return *static_cast<TracepointSystemFinder*>(GetFinder(EventFinderType::TRACEPOINT_SYSTEM).get());
+}
+
+bool EventTypeManager::ReadTracepointsFromFile(const std::string& filepath) {
+ std::string data;
+ if (!android::base::ReadFileToString(filepath, &data)) {
+ PLOG(ERROR) << "Failed to read " << filepath;
+ return false;
+ }
+ // Replace TracepointSystemFinder with TracepointStringFinder.
+ auto& finder = GetFinder(EventFinderType::TRACEPOINT_SYSTEM);
+ finder.reset(new TracepointStringFinder(std::move(data)));
+ return true;
+}
+
+bool EventTypeManager::WriteTracepointsToFile(const std::string& filepath) {
+ auto& tp_finder = GetTracepointSystemFinder();
+ std::string s = tp_finder.ToString();
+ if (!android::base::WriteStringToFile(s, filepath)) {
+ PLOG(ERROR) << "Failed to store tracepoint events";
+ return false;
+ }
+ return true;
+}
+
+bool EventTypeManager::ForEachType(const std::function<bool(const EventType&)>& callback) {
+ if (scoped_finder_) {
+ for (const auto& type : scoped_finder_->GetTypes()) {
+ if (!callback(type)) {
+ return false;
+ }
+ }
+ } else {
+ for (auto& finder : type_finders_) {
+ for (const auto& type : finder->GetTypes()) {
+ if (!callback(type)) {
+ return false;
+ }
}
}
}
- return config;
+ return true;
}
-static std::vector<EventType> GetPmuEventTypes() {
- std::vector<EventType> result;
- const std::string evtsrc_dirname = "/sys/bus/event_source/devices/";
- for (const auto& device_name : GetSubDirs(evtsrc_dirname)) {
- std::string evtdev_path = evtsrc_dirname + device_name;
- std::string type_path = evtdev_path + "/type";
- std::string type_content;
-
- if (!android::base::ReadFileToString(type_path, &type_content)) {
- LOG(DEBUG) << "cannot read event type: " << device_name;
- continue;
+const EventType* EventTypeManager::FindType(const std::string& name) {
+ if (scoped_finder_) {
+ return scoped_finder_->FindType(name);
+ }
+ for (auto& finder : type_finders_) {
+ if (auto type = finder->FindType(name)) {
+ return type;
}
- uint64_t type_id = strtoull(type_content.c_str(), NULL, 10);
+ }
+ return nullptr;
+}
- std::vector<EventFormat> formats = ParseEventFormats(evtdev_path);
+const EventType* EventTypeManager::AddRawType(const std::string& name) {
+ if (name.empty() || name[0] != 'r') {
+ return nullptr;
+ }
+ errno = 0;
+ char* end;
+ uint64_t config = strtoull(&name[1], &end, 16);
+ if (errno != 0 || *end != '\0') {
+ return nullptr;
+ }
+ auto& raw_finder = GetRawTypeFinder();
+ return raw_finder.AddType(EventType(name, PERF_TYPE_RAW, config, "", ""));
+}
- std::string events_dirname = evtdev_path + "/events/";
- for (const auto& event_name : GetEntriesInDir(events_dirname)) {
- std::string event_path = events_dirname + event_name;
- std::string event_content;
- if (!android::base::ReadFileToString(event_path, &event_content)) {
- LOG(DEBUG) << "cannot read event content in " << event_name;
- continue;
- }
+void EventTypeManager::RemoveProbeType(const std::string& name) {
+ GetTracepointSystemFinder().RemoveType(name);
+}
- uint64_t config = MakeEventConfig(event_content, formats);
- if (config == ~0ULL) {
- LOG(DEBUG) << "cannot handle config format in " << event_name;
- continue;
- }
- result.emplace_back(EventType(device_name + "/" + event_name + "/",
- type_id, config, "", ""));
- }
- }
- return result;
+void EventTypeManager::SetScopedFinder(std::unique_ptr<EventTypeFinder>&& finder) {
+ scoped_finder_ = std::move(finder);
}
std::vector<int> EventType::GetPmuCpumask() {
std::vector<int> empty_result;
- if (!IsPmuEvent())
- return empty_result;
+ if (!IsPmuEvent()) return empty_result;
std::string pmu = name.substr(0, name.find('/'));
std::string cpumask_path = "/sys/bus/event_source/devices/" + pmu + "/cpumask";
@@ -236,7 +458,10 @@ std::vector<int> EventType::GetPmuCpumask() {
LOG(DEBUG) << "cannot read cpumask content in " << pmu;
return empty_result;
}
- return GetCpusFromString(cpumask_content);
+ if (auto cpus = GetCpusFromString(cpumask_content); cpus) {
+ return std::vector<int>(cpus->begin(), cpus->end());
+ }
+ return empty_result;
}
std::string ScopedEventTypes::BuildString(const std::vector<const EventType*>& event_types) {
@@ -245,65 +470,39 @@ std::string ScopedEventTypes::BuildString(const std::vector<const EventType*>& e
if (!result.empty()) {
result.push_back('\n');
}
- result += android::base::StringPrintf("%s,%u,%" PRIu64, type->name.c_str(), type->type,
- type->config);
+ result +=
+ android::base::StringPrintf("%s,%u,%" PRIu64, type->name.c_str(), type->type, type->config);
}
return result;
}
ScopedEventTypes::ScopedEventTypes(const std::string& event_type_str) {
- saved_event_types_ = std::move(g_event_types);
- saved_etm_event_type_ = g_etm_event_type;
- g_event_types.clear();
+ std::set<EventType> event_types;
for (auto& s : android::base::Split(event_type_str, "\n")) {
std::string name = s.substr(0, s.find(','));
uint32_t type;
uint64_t config;
sscanf(s.c_str() + name.size(), ",%u,%" PRIu64, &type, &config);
- if (name == "cs-etm") {
- g_etm_event_type = type;
- }
- g_event_types.emplace(name, type, config, "", "");
+ event_types.emplace(name, type, config, "", "");
}
+ CHECK(EventTypeManager::Instance().GetScopedFinder() == nullptr);
+ EventTypeManager::Instance().SetScopedFinder(
+ std::make_unique<ScopedTypeFinder>(std::move(event_types)));
}
ScopedEventTypes::~ScopedEventTypes() {
- g_event_types = std::move(saved_event_types_);
- g_etm_event_type = saved_etm_event_type_;
-}
-
-const std::set<EventType>& GetAllEventTypes() {
- if (g_event_types.empty()) {
- g_event_types.insert(static_event_type_array.begin(), static_event_type_array.end());
- std::vector<EventType> tracepoint_array = GetTracepointEventTypes();
- g_event_types.insert(tracepoint_array.begin(), tracepoint_array.end());
- std::vector<EventType> pmu_array = GetPmuEventTypes();
- g_event_types.insert(pmu_array.begin(), pmu_array.end());
-#if defined(__linux__)
- std::unique_ptr<EventType> etm_type = ETMRecorder::GetInstance().BuildEventType();
- if (etm_type) {
- g_etm_event_type = etm_type->type;
- g_event_types.emplace(std::move(*etm_type));
- }
-#endif
- }
- return g_event_types;
+ CHECK(EventTypeManager::Instance().GetScopedFinder() != nullptr);
+ EventTypeManager::Instance().SetScopedFinder(nullptr);
}
const EventType* FindEventTypeByName(const std::string& name, bool report_error) {
- const auto& event_types = GetAllEventTypes();
- auto it = event_types.find(EventType(name, 0, 0, "", ""));
- if (it != event_types.end()) {
- return &*it;
- }
- if (!name.empty() && name[0] == 'r') {
- char* end;
- uint64_t config = strtoull(&name[1], &end, 16);
- if (end != &name[1] && *end == '\0') {
- auto result = g_event_types.emplace(name, PERF_TYPE_RAW, config, "", "");
- CHECK(result.second);
- return &*(result.first);
- }
+ const EventType* event_type = EventTypeManager::Instance().FindType(name);
+ if (event_type != nullptr) {
+ return event_type;
+ }
+ event_type = EventTypeManager::Instance().AddRawType(name);
+ if (event_type != nullptr) {
+ return event_type;
}
if (report_error) {
LOG(ERROR) << "Unknown event_type '" << name
@@ -387,5 +586,8 @@ std::unique_ptr<EventTypeAndModifier> ParseEventType(const std::string& event_ty
}
bool IsEtmEventType(uint32_t type) {
- return g_etm_event_type != 0 && type == g_etm_event_type;
+ const EventType* event_type = EventTypeManager::Instance().FindType(kETMEventName);
+ return (event_type != nullptr) && (event_type->type == type);
}
+
+} // namespace simpleperf
diff --git a/simpleperf/event_type.h b/simpleperf/event_type.h
index 359438ec..d2cd0c15 100644
--- a/simpleperf/event_type.h
+++ b/simpleperf/event_type.h
@@ -24,28 +24,32 @@
#include <string>
#include <vector>
+namespace simpleperf {
+
+inline const std::string kETMEventName = "cs-etm";
+
// EventType represents one type of event, like cpu_cycle_event, cache_misses_event.
// The user knows one event type by its name, and the kernel knows one event type by its
// (type, config) pair. EventType connects the two representations, and tells the user if
// the event type is supported by the kernel.
struct EventType {
- EventType(const std::string& name, uint32_t type, uint64_t config,
- const std::string& description, const std::string& limited_arch)
- : name(name), type(type), config(config), description(description),
- limited_arch(limited_arch) {
- }
+ EventType(const std::string& name, uint32_t type, uint64_t config, const std::string& description,
+ const std::string& limited_arch)
+ : name(name),
+ type(type),
+ config(config),
+ description(description),
+ limited_arch(limited_arch) {}
- EventType() : type(0), config(0) {
- }
+ EventType() : type(0), config(0) {}
bool operator<(const EventType& other) const {
return strcasecmp(name.c_str(), other.name.c_str()) < 0;
}
- bool IsPmuEvent() const {
- return name.find('/') != std::string::npos;
- }
+ bool IsPmuEvent() const { return name.find('/') != std::string::npos; }
+ bool IsEtmEvent() const { return name == kETMEventName; }
std::vector<int> GetPmuCpumask();
@@ -56,9 +60,6 @@ struct EventType {
std::string limited_arch;
};
-bool SetTracepointEventsFilePath(const std::string& filepath);
-std::string GetTracepointEvents();
-
// Used to temporarily change event types returned by GetAllEventTypes().
class ScopedEventTypes {
public:
@@ -66,15 +67,8 @@ class ScopedEventTypes {
ScopedEventTypes(const std::string& event_type_str);
~ScopedEventTypes();
-
- private:
- std::set<EventType> saved_event_types_;
- uint32_t saved_etm_event_type_;
};
-const std::set<EventType>& GetAllEventTypes();
-const EventType* FindEventTypeByName(const std::string& name, bool report_error = true);
-
struct EventTypeAndModifier {
std::string name;
EventType event_type;
@@ -92,11 +86,46 @@ struct EventTypeAndModifier {
exclude_hv(false),
exclude_host(false),
exclude_guest(false),
- precise_ip(0) {
- }
+ precise_ip(0) {}
};
+enum class EventFinderType;
+class EventTypeFinder;
+class RawTypeFinder;
+class TracepointSystemFinder;
+
+class EventTypeManager {
+ public:
+ static EventTypeManager& Instance() { return instance_; }
+ ~EventTypeManager();
+
+ bool ReadTracepointsFromFile(const std::string& filepath);
+ bool WriteTracepointsToFile(const std::string& filepath);
+
+ // Iterate through all event types, and stop when callback returns false.
+ bool ForEachType(const std::function<bool(const EventType&)>& callback);
+ const EventType* FindType(const std::string& name);
+ const EventType* AddRawType(const std::string& name);
+ void RemoveProbeType(const std::string& name);
+ const EventTypeFinder* GetScopedFinder() { return scoped_finder_.get(); }
+ void SetScopedFinder(std::unique_ptr<EventTypeFinder>&& finder);
+
+ private:
+ EventTypeManager();
+ std::unique_ptr<EventTypeFinder>& GetFinder(EventFinderType type);
+ RawTypeFinder& GetRawTypeFinder();
+ TracepointSystemFinder& GetTracepointSystemFinder();
+
+ static EventTypeManager instance_;
+
+ std::vector<std::unique_ptr<EventTypeFinder>> type_finders_;
+ std::unique_ptr<EventTypeFinder> scoped_finder_;
+};
+
+const EventType* FindEventTypeByName(const std::string& name, bool report_error = true);
std::unique_ptr<EventTypeAndModifier> ParseEventType(const std::string& event_type_str);
bool IsEtmEventType(uint32_t type);
+} // namespace simpleperf
+
#endif // SIMPLE_PERF_EVENT_H_
diff --git a/simpleperf/event_type_table.h b/simpleperf/event_type_table.h
index d2335adc..d209f0b7 100644
--- a/simpleperf/event_type_table.h
+++ b/simpleperf/event_type_table.h
@@ -2,216 +2,495 @@
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-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-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("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("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("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("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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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")
-
+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/get_test_data.h b/simpleperf/get_test_data.h
index d9e0d988..1ef663b9 100644
--- a/simpleperf/get_test_data.h
+++ b/simpleperf/get_test_data.h
@@ -21,6 +21,8 @@
#include "build_id.h"
+using BuildId = simpleperf::BuildId;
+
std::string GetTestData(const std::string& filename);
const std::string& GetTestDataDir();
@@ -35,7 +37,8 @@ static const std::string PERF_DATA = "perf.data";
// perf_with_multiple_pids_and_tids.data is generated by sampling on two processes, each
// process running two threads.
-static const std::string PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS = "perf_with_multiple_pids_and_tids.data";
+static const std::string PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS =
+ "perf_with_multiple_pids_and_tids.data";
// perf_g_fp.data is generated by sampling on one process running elf using --call-graph fp option.
static const std::string CALLGRAPH_FP_PERF_DATA = "perf_g_fp.data";
@@ -47,7 +50,6 @@ static const std::string PERF_DATA_WITH_MINI_DEBUG_INFO = "perf_with_mini_debug_
static BuildId elf_file_build_id("0b12a384a9f4a3f3659b7171ca615dbec3a81f71");
-
// To generate apk supporting execution on shared libraries in apk:
// 1. Add android:extractNativeLibs=false in AndroidManifest.xml.
// 2. Use `zip -0` to store native libraries in apk without compression.
@@ -84,9 +86,10 @@ static const std::string PERF_DATA_WITH_SYMBOLS = "perf_with_symbols.data";
static const std::string PERF_DATA_WITH_SYMBOLS_FOR_NONZERO_MINVADDR_DSO =
"perf_with_symbols_for_nonzero_minvaddr_dso.data";
-// perf_kmem_slab_callgraph.data is generated by `simpleperf kmem record --slab --call-graph fp -f 100 sleep 0.0001`.
-static const std::string PERF_DATA_WITH_KMEM_SLAB_CALLGRAPH_RECORD = "perf_with_kmem_slab_callgraph.data";
-
+// perf_kmem_slab_callgraph.data is generated by `simpleperf kmem record --slab --call-graph fp -f
+// 100 sleep 0.0001`.
+static const std::string PERF_DATA_WITH_KMEM_SLAB_CALLGRAPH_RECORD =
+ "perf_with_kmem_slab_callgraph.data";
// perf_for_build_id_check.data is generated by recording a process running
// testdata/data/correct_symfs_for_build_id_check/elf_for_build_id_check.
@@ -94,7 +97,8 @@ 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_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");
@@ -103,7 +107,8 @@ static BuildId CHECK_ELF_FILE_BUILD_ID("91b1c10fdd9fe2221dfec525497637f2229bfdbb
static const std::string PERF_DATA_GENERATED_BY_LINUX_PERF = "generated_by_linux_perf.data";
// generated by `simpleperf record -g ls`.
-static const std::string PERF_DATA_MAX_STACK_AND_PERCENT_LIMIT = "perf_test_max_stack_and_percent_limit.data";
+static const std::string PERF_DATA_MAX_STACK_AND_PERCENT_LIMIT =
+ "perf_test_max_stack_and_percent_limit.data";
// generated by `dd if=/dev/zero of=invalid_perf.data bs=1024 count=1`.
static const std::string INVALID_PERF_DATA = "invalid_perf.data";
@@ -111,7 +116,8 @@ static const std::string INVALID_PERF_DATA = "invalid_perf.data";
// generated by recording an app.
static const std::string PERF_DATA_WITH_WRONG_IP_IN_CALLCHAIN = "wrong_ip_callchain_perf.data";
-// generated by `simpleperf record --trace-offcpu --duration 2 -g ./simpleperf_runtest_run_and_sleep64`.
+// generated by `simpleperf record --trace-offcpu --duration 2 -g
+// ./simpleperf_runtest_run_and_sleep64`.
static const std::string PERF_DATA_WITH_TRACE_OFFCPU = "perf_with_trace_offcpu.data";
// generated by `simpleperf record -g --log debug sleep 1`.
@@ -129,13 +135,16 @@ static const std::string PERF_DATA_WITH_BIG_TRACE_DATA = "perf_with_big_trace_da
// generated by `simpleperf record --app com.google.sample.tunnel --duration 1`.
static const std::string PERF_DATA_WITH_APP_PACKAGE_NAME = "perf_with_app_package_name.data";
-static const std::string PERF_DATA_WITH_KERNEL_SYMBOLS_AVAILABLE_TRUE = "perf_with_kernel_symbols_available_true.data";
+static const std::string PERF_DATA_WITH_KERNEL_SYMBOLS_AVAILABLE_TRUE =
+ "perf_with_kernel_symbols_available_true.data";
-static const std::string PERF_DATA_WITH_KERNEL_SYMBOLS_AVAILABLE_FALSE = "perf_with_kernel_symbols_available_false.data";
+static const std::string PERF_DATA_WITH_KERNEL_SYMBOLS_AVAILABLE_FALSE =
+ "perf_with_kernel_symbols_available_false.data";
static const std::string PERF_DATA_WITH_INTERPRETER_FRAMES = "perf_with_interpreter_frames.data";
-static const std::string PERF_DATA_WITH_IP_ZERO_IN_CALLCHAIN = "perf_with_ip_zero_in_callchain.data";
+static const std::string PERF_DATA_WITH_IP_ZERO_IN_CALLCHAIN =
+ "perf_with_ip_zero_in_callchain.data";
// generated by `simpleperf record -e cs-etm:u ./etm_test_loop`
static const std::string PERF_DATA_ETM_TEST_LOOP = "etm/perf.data";
diff --git a/simpleperf/gtest_main.cpp b/simpleperf/gtest_main.cpp
index a6062281..3fce9857 100644
--- a/simpleperf/gtest_main.cpp
+++ b/simpleperf/gtest_main.cpp
@@ -36,6 +36,8 @@
#include "utils.h"
#include "workload.h"
+using namespace simpleperf;
+
static std::string testdata_dir;
#if defined(__ANDROID__)
@@ -58,7 +60,7 @@ class ScopedEnablingPerf {
android::base::SetProperty("security.perf_harden", value);
// Sleep one second to wait for security.perf_harden changing
- // /proc/sys/kernel/perf_event_paranoid.
+ // perf_event_allow_path.
sleep(1);
}
@@ -97,8 +99,7 @@ int main(int argc, char** argv) {
android::base::ScopedLogSeverity severity(log_severity);
#if defined(__ANDROID__)
- // A cts test PerfEventParanoidTest.java is testing if
- // /proc/sys/kernel/perf_event_paranoid is 3, so restore perf_harden
+ // A cts test is testing if perf_event_allow_path is 3, so restore perf_harden
// value after current test to not break that test.
ScopedEnablingPerf scoped_enabling_perf;
#endif
diff --git a/simpleperf/include/simpleperf_profcollect.hpp b/simpleperf/include/simpleperf_profcollect.hpp
new file mode 100644
index 00000000..179d0e1d
--- /dev/null
+++ b/simpleperf/include/simpleperf_profcollect.hpp
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+extern "C" {
+
+bool HasSupport();
+bool Record(const char* event_name, const char* output, float duration);
+bool Inject(const char* traceInput, const char* profileOutput);
+}
diff --git a/simpleperf/kallsyms.cpp b/simpleperf/kallsyms.cpp
new file mode 100644
index 00000000..f9a93589
--- /dev/null
+++ b/simpleperf/kallsyms.cpp
@@ -0,0 +1,283 @@
+/*
+ * 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.
+ */
+#include "kallsyms.h"
+
+#include <inttypes.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+
+#include "environment.h"
+#include "utils.h"
+
+namespace simpleperf {
+
+#if defined(__linux__)
+
+namespace {
+
+const char kKallsymsPath[] = "/proc/kallsyms";
+const char kProcModulesPath[] = "/proc/modules";
+const char kPtrRestrictPath[] = "/proc/sys/kernel/kptr_restrict";
+const char kLowerPtrRestrictAndroidProp[] = "security.lower_kptr_restrict";
+const unsigned int kMinLineTestNonNullSymbols = 10;
+
+// Tries to read the kernel symbol file and ensure that at least some symbol
+// addresses are non-null.
+bool CanReadKernelSymbolAddresses() {
+ LineReader reader(kKallsymsPath);
+ if (!reader.Ok()) {
+ LOG(DEBUG) << "Failed to read " << kKallsymsPath;
+ return false;
+ }
+ auto symbol_callback = [&](const KernelSymbol& symbol) { return (symbol.addr != 0u); };
+ for (unsigned int i = 0; i < kMinLineTestNonNullSymbols; i++) {
+ std::string* line = reader.ReadLine();
+ if (line == nullptr) {
+ return false;
+ }
+ if (ProcessKernelSymbols(*line, symbol_callback)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Define a scope in which access to kallsyms is possible.
+// This is based on the Perfetto implementation.
+class ScopedKptrUnrestrict {
+ public:
+ ScopedKptrUnrestrict(); // Lowers kptr_restrict if necessary.
+ ~ScopedKptrUnrestrict(); // Restores the initial kptr_restrict.
+
+ // Indicates if access to kallsyms should be successful.
+ bool KallsymsAvailable() { return kallsyms_available_; }
+
+ static void ResetWarning() { kernel_address_warning_printed_ = false; }
+
+ private:
+ bool WriteKptrRestrict(const std::string& value);
+ void PrintWarning();
+
+ bool restore_property_ = false;
+ bool restore_restrict_value_ = false;
+ std::string saved_restrict_value_;
+ bool kallsyms_available_ = false;
+
+ static bool kernel_address_warning_printed_;
+};
+
+bool ScopedKptrUnrestrict::kernel_address_warning_printed_ = false;
+
+ScopedKptrUnrestrict::ScopedKptrUnrestrict() {
+ if (CanReadKernelSymbolAddresses()) {
+ // Everything seems to work (e.g., we are running as root and kptr_restrict
+ // is < 2). Don't touching anything.
+ kallsyms_available_ = true;
+ return;
+ }
+
+ if (GetAndroidVersion() >= 12 && IsRoot()) {
+ // Enable kernel addresses by setting property.
+ if (!android::base::SetProperty(kLowerPtrRestrictAndroidProp, "1")) {
+ LOG(DEBUG) << "Unable to set " << kLowerPtrRestrictAndroidProp << " to 1.";
+ PrintWarning();
+ return;
+ }
+ restore_property_ = true;
+ // Init takes some time to react to the property change.
+ // Unfortunately, we cannot read kptr_restrict because of SELinux. Instead,
+ // we detect this by reading the initial lines of kallsyms and checking
+ // that they are non-zero. This loop waits for at most 250ms (50 * 5ms).
+ for (int attempt = 1; attempt <= 50; ++attempt) {
+ usleep(5000);
+ if (CanReadKernelSymbolAddresses()) {
+ kallsyms_available_ = true;
+ return;
+ }
+ }
+ LOG(DEBUG) << "kallsyms addresses are still masked after setting "
+ << kLowerPtrRestrictAndroidProp;
+ PrintWarning();
+ return;
+ }
+
+ // Otherwise, read the kptr_restrict value and lower it if needed.
+ if (!android::base::ReadFileToString(kPtrRestrictPath, &saved_restrict_value_)) {
+ LOG(DEBUG) << "Failed to read " << kPtrRestrictPath;
+ PrintWarning();
+ return;
+ }
+
+ // Progressively lower kptr_restrict until we can read kallsyms.
+ for (int value = atoi(saved_restrict_value_.c_str()); value > 0; --value) {
+ if (!WriteKptrRestrict(std::to_string(value))) {
+ break;
+ }
+ restore_restrict_value_ = true;
+ if (CanReadKernelSymbolAddresses()) {
+ kallsyms_available_ = true;
+ return;
+ }
+ }
+ PrintWarning();
+}
+
+ScopedKptrUnrestrict::~ScopedKptrUnrestrict() {
+ if (restore_property_) {
+ android::base::SetProperty(kLowerPtrRestrictAndroidProp, "0");
+ }
+ if (restore_restrict_value_) {
+ WriteKptrRestrict(saved_restrict_value_);
+ }
+}
+
+bool ScopedKptrUnrestrict::WriteKptrRestrict(const std::string& value) {
+ if (!android::base::WriteStringToFile(value, kPtrRestrictPath)) {
+ LOG(DEBUG) << "Failed to set " << kPtrRestrictPath << " to " << value;
+ return false;
+ }
+ return true;
+}
+
+void ScopedKptrUnrestrict::PrintWarning() {
+ if (!kernel_address_warning_printed_) {
+ kernel_address_warning_printed_ = true;
+ LOG(WARNING) << "Access to kernel symbol addresses is restricted. If "
+ << "possible, please do `echo 0 >/proc/sys/kernel/kptr_restrict` "
+ << "to fix this.";
+ }
+}
+
+} // namespace
+
+std::vector<KernelMmap> GetLoadedModules() {
+ ScopedKptrUnrestrict kptr_unrestrict;
+ if (!kptr_unrestrict.KallsymsAvailable()) return {};
+ std::vector<KernelMmap> result;
+ LineReader reader(kProcModulesPath);
+ if (!reader.Ok()) {
+ // There is no /proc/modules on Android devices, so we don't print error if failed to open it.
+ PLOG(DEBUG) << "failed to open file /proc/modules";
+ return result;
+ }
+ std::string* line;
+ std::string name_buf;
+ while ((line = reader.ReadLine()) != nullptr) {
+ // Parse line like: nf_defrag_ipv6 34768 1 nf_conntrack_ipv6, Live 0xffffffffa0fe5000
+ name_buf.resize(line->size());
+ char* name = name_buf.data();
+ uint64_t addr;
+ uint64_t len;
+ if (sscanf(line->data(), "%s%" PRIu64 "%*u%*s%*s 0x%" PRIx64, name, &len, &addr) == 3) {
+ KernelMmap map;
+ map.name = name;
+ map.start_addr = addr;
+ map.len = len;
+ result.push_back(map);
+ }
+ }
+ bool all_zero = true;
+ for (const auto& map : result) {
+ if (map.start_addr != 0) {
+ all_zero = false;
+ }
+ }
+ if (all_zero) {
+ LOG(DEBUG) << "addresses in /proc/modules are all zero, so ignore kernel modules";
+ return std::vector<KernelMmap>();
+ }
+ return result;
+}
+
+uint64_t GetKernelStartAddress() {
+ ScopedKptrUnrestrict kptr_unrestrict;
+ if (!kptr_unrestrict.KallsymsAvailable()) return 0;
+ LineReader reader(kKallsymsPath);
+ if (!reader.Ok()) {
+ return 0;
+ }
+ std::string* line;
+ while ((line = reader.ReadLine()) != nullptr) {
+ if (strstr(line->data(), "_stext") != nullptr) {
+ uint64_t addr;
+ if (sscanf(line->data(), "%" PRIx64, &addr) == 1) {
+ return addr;
+ }
+ }
+ }
+ return 0;
+}
+
+bool LoadKernelSymbols(std::string* kallsyms) {
+ ScopedKptrUnrestrict kptr_unrestrict;
+ if (kptr_unrestrict.KallsymsAvailable()) {
+ return android::base::ReadFileToString(kKallsymsPath, kallsyms);
+ }
+ return false;
+}
+
+void ResetKernelAddressWarning() {
+ ScopedKptrUnrestrict::ResetWarning();
+}
+
+#endif // defined(__linux__)
+
+bool ProcessKernelSymbols(std::string& symbol_data,
+ const std::function<bool(const KernelSymbol&)>& callback) {
+ char* p = &symbol_data[0];
+ char* data_end = p + symbol_data.size();
+ while (p < data_end) {
+ char* line_end = strchr(p, '\n');
+ if (line_end != nullptr) {
+ *line_end = '\0';
+ }
+ size_t line_size = (line_end != nullptr) ? (line_end - p) : (data_end - p);
+ // Parse line like: ffffffffa005c4e4 d __warned.41698 [libsas]
+ char name[line_size];
+ char module[line_size];
+ strcpy(module, "");
+
+ KernelSymbol symbol;
+ int ret = sscanf(p, "%" PRIx64 " %c %s%s", &symbol.addr, &symbol.type, name, module);
+ if (line_end != nullptr) {
+ *line_end = '\n';
+ p = line_end + 1;
+ } else {
+ p = data_end;
+ }
+ if (ret >= 3) {
+ symbol.name = name;
+ size_t module_len = strlen(module);
+ if (module_len > 2 && module[0] == '[' && module[module_len - 1] == ']') {
+ module[module_len - 1] = '\0';
+ symbol.module = &module[1];
+ } else {
+ symbol.module = nullptr;
+ }
+
+ if (callback(symbol)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+} // namespace simpleperf
diff --git a/simpleperf/kallsyms.h b/simpleperf/kallsyms.h
new file mode 100644
index 00000000..33016e6d
--- /dev/null
+++ b/simpleperf/kallsyms.h
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+#ifndef SIMPLE_PERF_KALLSYMS_H_
+#define SIMPLE_PERF_KALLSYMS_H_
+
+#include <string>
+
+#include "environment.h"
+
+namespace simpleperf {
+
+struct KernelSymbol {
+ uint64_t addr;
+ char type;
+ const char* name;
+ const char* module; // If nullptr, the symbol is not in a kernel module.
+};
+
+// Parses symbol_data as the content of /proc/kallsyms, calling the callback for
+// each symbol that is found. Stops the parsing if the callback returns true.
+bool ProcessKernelSymbols(std::string& symbol_data,
+ const std::function<bool(const KernelSymbol&)>& callback);
+
+#if defined(__linux__)
+
+// Returns the list of currently loaded kernel modules.
+std::vector<KernelMmap> GetLoadedModules();
+
+// Returns the start address of the kernel. It uses /proc/kallsyms to find this
+// address. Returns 0 if unknown.
+uint64_t GetKernelStartAddress();
+
+// Loads the /proc/kallsyms file, requesting access if required. The value of
+// kptr_restrict might be modified during the process. Its original value will
+// be restored. This usually requires root privileges.
+bool LoadKernelSymbols(std::string* kallsyms);
+
+// only for testing
+void ResetKernelAddressWarning();
+
+#endif // defined(__linux__)
+
+} // namespace simpleperf
+
+#endif // SIMPLE_PERF_KALLSYMS_H_
diff --git a/simpleperf/kallsyms_test.cpp b/simpleperf/kallsyms_test.cpp
new file mode 100644
index 00000000..d45aeaa6
--- /dev/null
+++ b/simpleperf/kallsyms_test.cpp
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include <android-base/test_utils.h>
+
+#include "get_test_data.h"
+#include "kallsyms.h"
+#include "test_util.h"
+
+using namespace simpleperf;
+
+static bool ModulesMatch(const char* p, const char* q) {
+ if (p == nullptr && q == nullptr) {
+ return true;
+ }
+ if (p != nullptr && q != nullptr) {
+ return strcmp(p, q) == 0;
+ }
+ return false;
+}
+
+static bool KernelSymbolsMatch(const KernelSymbol& sym1, const KernelSymbol& sym2) {
+ return sym1.addr == sym2.addr && sym1.type == sym2.type && strcmp(sym1.name, sym2.name) == 0 &&
+ ModulesMatch(sym1.module, sym2.module);
+}
+
+TEST(kallsyms, ProcessKernelSymbols) {
+ std::string data =
+ "ffffffffa005c4e4 d __warned.41698 [libsas]\n"
+ "aaaaaaaaaaaaaaaa T _text\n"
+ "cccccccccccccccc c ccccc\n";
+ KernelSymbol expected_symbol;
+ expected_symbol.addr = 0xffffffffa005c4e4ULL;
+ expected_symbol.type = 'd';
+ expected_symbol.name = "__warned.41698";
+ expected_symbol.module = "libsas";
+ ASSERT_TRUE(ProcessKernelSymbols(
+ data, std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol)));
+
+ expected_symbol.addr = 0xaaaaaaaaaaaaaaaaULL;
+ expected_symbol.type = 'T';
+ expected_symbol.name = "_text";
+ expected_symbol.module = nullptr;
+ ASSERT_TRUE(ProcessKernelSymbols(
+ data, std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol)));
+
+ expected_symbol.name = "non_existent_symbol";
+ ASSERT_FALSE(ProcessKernelSymbols(
+ data, std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol)));
+}
+
+#if defined(__ANDROID__)
+TEST(kallsyms, GetKernelStartAddress) {
+ TEST_REQUIRE_ROOT();
+ ASSERT_NE(GetKernelStartAddress(), 0u);
+}
+
+TEST(kallsyms, LoadKernelSymbols) {
+ TEST_REQUIRE_ROOT();
+ std::string kallsyms;
+ ASSERT_TRUE(LoadKernelSymbols(&kallsyms));
+}
+
+TEST(kallsyms, print_warning) {
+ TEST_REQUIRE_NON_ROOT();
+ const std::string warning_msg = "Access to kernel symbol addresses is restricted.";
+ CapturedStderr capture;
+
+ // Call each function requiring kernel addresses once. Check if the warning is printed.
+ ResetKernelAddressWarning();
+ ASSERT_EQ(0, GetKernelStartAddress());
+ capture.Stop();
+ ASSERT_NE(capture.str().find(warning_msg), std::string::npos);
+
+ capture.Reset();
+ capture.Start();
+ ResetKernelAddressWarning();
+ std::string kallsyms;
+ ASSERT_FALSE(LoadKernelSymbols(&kallsyms));
+ capture.Stop();
+ ASSERT_NE(capture.str().find(warning_msg), std::string::npos);
+
+ capture.Reset();
+ capture.Start();
+ ResetKernelAddressWarning();
+ ASSERT_TRUE(GetLoadedModules().empty());
+ capture.Stop();
+ ASSERT_NE(capture.str().find(warning_msg), std::string::npos);
+
+ // Call functions requiring kernel addresses more than once.
+ // Check if the kernel address warning is only printed once.
+ capture.Reset();
+ capture.Start();
+ ResetKernelAddressWarning();
+ for (int i = 0; i < 2; i++) {
+ ASSERT_EQ(0, GetKernelStartAddress());
+ ASSERT_FALSE(LoadKernelSymbols(&kallsyms));
+ ASSERT_TRUE(GetLoadedModules().empty());
+ }
+ capture.Stop();
+ std::string output = capture.str();
+ auto pos = output.find(warning_msg);
+ ASSERT_NE(pos, std::string::npos);
+ ASSERT_EQ(output.find(warning_msg, pos + warning_msg.size()), std::string::npos);
+}
+#endif // defined(__ANDROID__)
diff --git a/simpleperf/main.cpp b/simpleperf/main.cpp
index 49d25481..21c5351e 100644
--- a/simpleperf/main.cpp
+++ b/simpleperf/main.cpp
@@ -21,17 +21,19 @@
#include "command.h"
#include "environment.h"
+using namespace simpleperf;
+
#if defined(__ANDROID__)
bool AndroidSecurityCheck() {
// Simpleperf can be executed by the shell, or by apps themselves. To avoid malicious apps
// exploiting perf_event_open interface via simpleperf, simpleperf needs proof that the user
// is expecting simpleperf to be ran:
- // 1) On Android < R, perf_event_open is secured by perf_event_paranoid, which is controlled
+ // 1) On Android < R, perf_event_open is secured by perf_event_allow_path, which is controlled
// by security.perf_harden property. perf_event_open syscall can be used only after user setting
// security.perf_harden to 0 in shell. So we don't need to check security.perf_harden explicitly.
// 2) On Android R, perf_event_open may be controlled by selinux instead of
- // perf_event_paranoid. So we need to check security.perf_harden explicitly. If simpleperf is
+ // perf_event_allow_path. So we need to check security.perf_harden explicitly. If simpleperf is
// running via shell, we already know the origin of the request is the user, so set the property
// ourselves for convenience. When started by the app, we won't have the permission to set the
// property, so the user will need to prove this intent by setting it manually via shell.
diff --git a/simpleperf/nonlinux_support/nonlinux_support.cpp b/simpleperf/nonlinux_support/nonlinux_support.cpp
index deb3b1d5..2268e301 100644
--- a/simpleperf/nonlinux_support/nonlinux_support.cpp
+++ b/simpleperf/nonlinux_support/nonlinux_support.cpp
@@ -17,9 +17,11 @@
// Add fake functions to build successfully on darwin.
#include <android-base/logging.h>
-#include "read_dex_file.h"
-#include "environment.h"
#include "OfflineUnwinder.h"
+#include "environment.h"
+#include "read_dex_file.h"
+
+namespace simpleperf {
bool GetKernelBuildId(BuildId*) {
return false;
@@ -30,12 +32,12 @@ bool CanRecordRawData() {
}
bool ReadSymbolsFromDexFileInMemory(void*, uint64_t, const std::vector<uint64_t>&,
- std::vector<DexFileSymbol>*) {
+ const std::function<void(DexFileSymbol*)>&) {
return true;
}
bool ReadSymbolsFromDexFile(const std::string&, const std::vector<uint64_t>&,
- std::vector<DexFileSymbol>*) {
+ const std::function<void(DexFileSymbol*)>&) {
return true;
}
@@ -43,9 +45,7 @@ const char* GetTraceFsDir() {
return nullptr;
}
-namespace simpleperf {
-
-class DummyOfflineUnwinder : public OfflineUnwinder {
+class NoOpOfflineUnwinder : public OfflineUnwinder {
public:
bool UnwindCallChain(const ThreadEntry&, const RegSet&, const char*, size_t,
std::vector<uint64_t>*, std::vector<uint64_t>*) override {
@@ -54,7 +54,7 @@ class DummyOfflineUnwinder : public OfflineUnwinder {
};
std::unique_ptr<OfflineUnwinder> OfflineUnwinder::Create(bool) {
- return std::unique_ptr<OfflineUnwinder>(new DummyOfflineUnwinder);
+ return std::unique_ptr<OfflineUnwinder>(new NoOpOfflineUnwinder);
}
} // namespace simpleperf \ No newline at end of file
diff --git a/simpleperf/perf_regs.cpp b/simpleperf/perf_regs.cpp
index c238f075..cccd136f 100644
--- a/simpleperf/perf_regs.cpp
+++ b/simpleperf/perf_regs.cpp
@@ -18,15 +18,16 @@
#include <string.h>
-#include <unordered_map>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#include <unordered_map>
#include "perf_event.h"
+namespace simpleperf {
+
ArchType ScopedCurrentArch::current_arch = ARCH_UNSUPPORTED;
-ArchType ScopedCurrentArch::current_arch32 = ARCH_UNSUPPORTED;
ArchType GetArchType(const std::string& arch) {
if (arch == "x86" || arch == "i686") {
@@ -59,6 +60,13 @@ ArchType GetArchForAbi(ArchType machine_arch, int abi) {
if (machine_arch == ARCH_ARM64) {
return ARCH_ARM;
}
+ } else if (abi == PERF_SAMPLE_REGS_ABI_64) {
+ if (machine_arch == ARCH_X86_32) {
+ return ARCH_X86_64;
+ }
+ if (machine_arch == ARCH_ARM) {
+ return ARCH_ARM64;
+ }
}
return machine_arch;
}
@@ -83,7 +91,7 @@ uint64_t GetSupportedRegMask(ArchType arch) {
switch (arch) {
case ARCH_X86_32:
return ((1ULL << PERF_REG_X86_32_MAX) - 1) & ~(1ULL << PERF_REG_X86_DS) &
- ~(1ULL << PERF_REG_X86_ES) & ~(1ULL << PERF_REG_X86_FS) & ~(1ULL << PERF_REG_X86_GS);
+ ~(1ULL << PERF_REG_X86_ES) & ~(1ULL << PERF_REG_X86_FS) & ~(1ULL << PERF_REG_X86_GS);
case ARCH_X86_64:
return (((1ULL << PERF_REG_X86_64_MAX) - 1) & ~(1ULL << PERF_REG_X86_DS) &
~(1ULL << PERF_REG_X86_ES) & ~(1ULL << PERF_REG_X86_FS) & ~(1ULL << PERF_REG_X86_GS));
@@ -112,7 +120,9 @@ static std::unordered_map<size_t, std::string> arm_reg_map = {
};
static std::unordered_map<size_t, std::string> arm64_reg_map = {
- {PERF_REG_ARM64_LR, "lr"}, {PERF_REG_ARM64_SP, "sp"}, {PERF_REG_ARM64_PC, "pc"},
+ {PERF_REG_ARM64_LR, "lr"},
+ {PERF_REG_ARM64_SP, "sp"},
+ {PERF_REG_ARM64_PC, "pc"},
};
std::string GetRegName(size_t regno, ArchType arch) {
@@ -151,10 +161,8 @@ std::string GetRegName(size_t regno, ArchType arch) {
}
}
-RegSet::RegSet(int abi, uint64_t valid_mask, const uint64_t* valid_regs)
- : valid_mask(valid_mask) {
- arch = (abi == PERF_SAMPLE_REGS_ABI_32) ? ScopedCurrentArch::GetCurrentArch32()
- : ScopedCurrentArch::GetCurrentArch();
+RegSet::RegSet(int abi, uint64_t valid_mask, const uint64_t* valid_regs) : valid_mask(valid_mask) {
+ arch = GetArchForAbi(ScopedCurrentArch::GetCurrentArch(), abi);
memset(data, 0, sizeof(data));
for (int i = 0, j = 0; i < 64; ++i) {
if ((valid_mask >> i) & 1) {
@@ -215,3 +223,5 @@ bool RegSet::GetIpRegValue(uint64_t* value) const {
}
return GetRegValue(regno, value);
}
+
+} // namespace simpleperf
diff --git a/simpleperf/perf_regs.h b/simpleperf/perf_regs.h
index 86a12d0c..17e28a65 100644
--- a/simpleperf/perf_regs.h
+++ b/simpleperf/perf_regs.h
@@ -18,13 +18,13 @@
#define SIMPLE_PERF_PERF_REGS_H_
#if defined(USE_BIONIC_UAPI_HEADERS)
-#include <uapi/asm-x86/asm/perf_regs.h>
#include <uapi/asm-arm/asm/perf_regs.h>
+#include <uapi/asm-x86/asm/perf_regs.h>
#define perf_event_arm_regs perf_event_arm64_regs
#include <uapi/asm-arm64/asm/perf_regs.h>
#else
-#include <asm-x86/asm/perf_regs.h>
#include <asm-arm/asm/perf_regs.h>
+#include <asm-x86/asm/perf_regs.h>
#define perf_event_arm_regs perf_event_arm64_regs
#include <asm-arm64/asm/perf_regs.h>
#endif
@@ -35,6 +35,8 @@
#include "perf_event.h"
+namespace simpleperf {
+
enum ArchType {
ARCH_X86_32,
ARCH_X86_64,
@@ -67,23 +69,15 @@ class ScopedCurrentArch {
public:
explicit ScopedCurrentArch(ArchType arch) : saved_arch(current_arch) {
current_arch = arch;
- current_arch32 = GetArchForAbi(arch, PERF_SAMPLE_REGS_ABI_32);
}
~ScopedCurrentArch() {
current_arch = saved_arch;
- current_arch32 = GetArchForAbi(saved_arch, PERF_SAMPLE_REGS_ABI_32);
- }
- static ArchType GetCurrentArch() {
- return current_arch;
- }
- static ArchType GetCurrentArch32() {
- return current_arch32;
}
+ static ArchType GetCurrentArch() { return current_arch; }
private:
ArchType saved_arch;
static ArchType current_arch;
- static ArchType current_arch32;
};
struct RegSet {
@@ -100,4 +94,6 @@ struct RegSet {
bool GetIpRegValue(uint64_t* value) const;
};
+} // namespace simpleperf
+
#endif // SIMPLE_PERF_PERF_REGS_H_
diff --git a/simpleperf/perf_regs_test.cpp b/simpleperf/perf_regs_test.cpp
new file mode 100644
index 00000000..fc75cca3
--- /dev/null
+++ b/simpleperf/perf_regs_test.cpp
@@ -0,0 +1,37 @@
+/*
+ * 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 "perf_regs.h"
+
+#include <gtest/gtest.h>
+
+using namespace simpleperf;
+
+TEST(RegSet, arch) {
+ ArchType arch_pairs[2][2] = {
+ {ARCH_X86_32, ARCH_X86_64},
+ {ARCH_ARM, ARCH_ARM64},
+ };
+ for (ArchType* arch_pair : arch_pairs) {
+ for (size_t i = 0; i < 2; i++) {
+ ScopedCurrentArch scoped_arch(arch_pair[i]);
+ RegSet reg32(PERF_SAMPLE_REGS_ABI_32, 0, nullptr);
+ ASSERT_EQ(reg32.arch, arch_pair[0]) << i;
+ RegSet reg64(PERF_SAMPLE_REGS_ABI_64, 0, nullptr);
+ ASSERT_EQ(reg64.arch, arch_pair[1]) << i;
+ }
+ }
+}
diff --git a/simpleperf/profcollect.cpp b/simpleperf/profcollect.cpp
new file mode 100644
index 00000000..15d213c2
--- /dev/null
+++ b/simpleperf/profcollect.cpp
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#include <include/simpleperf_profcollect.hpp>
+
+#include "ETMRecorder.h"
+#include "command.h"
+#include "event_attr.h"
+#include "event_fd.h"
+#include "event_type.h"
+
+using namespace simpleperf;
+
+bool HasSupport() {
+ if (!ETMRecorder::GetInstance().CheckEtmSupport()) {
+ return false;
+ }
+ const EventType* type = FindEventTypeByName("cs-etm", false);
+ if (type == nullptr) {
+ return false;
+ }
+ return IsEventAttrSupported(CreateDefaultPerfEventAttr(*type), type->name);
+}
+
+bool Record(const char* event_name, const char* output, float duration) {
+ 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 Inject(const char* traceInput, const char* profileOutput) {
+ auto injectCmd = CreateCommandInstance("inject");
+ std::vector<std::string> args;
+ args.insert(args.end(), {"-i", traceInput});
+ args.insert(args.end(), {"-o", profileOutput});
+ args.insert(args.end(), {"--output", "branch-list"});
+ args.emplace_back("--exclude-perf");
+ return injectCmd->Run(args);
+}
diff --git a/simpleperf/read_apk.cpp b/simpleperf/read_apk.cpp
index 651a2412..f5838397 100644
--- a/simpleperf/read_apk.cpp
+++ b/simpleperf/read_apk.cpp
@@ -33,6 +33,8 @@
#include "read_elf.h"
#include "utils.h"
+namespace simpleperf {
+
std::unordered_map<std::string, ApkInspector::ApkNode> ApkInspector::embedded_elf_cache_;
EmbeddedElf* ApkInspector::FindElfInApkByOffset(const std::string& apk_path, uint64_t file_offset) {
@@ -81,8 +83,7 @@ std::unique_ptr<EmbeddedElf> ApkInspector::FindElfInApkByOffsetWithoutCache(
ZipEntry found_entry;
std::string found_entry_name;
bool result = ahelper->IterateEntries([&](ZipEntry& entry, const std::string& name) {
- if (entry.method == kCompressStored &&
- file_offset >= static_cast<uint64_t>(entry.offset) &&
+ if (entry.method == kCompressStored && file_offset >= static_cast<uint64_t>(entry.offset) &&
file_offset < static_cast<uint64_t>(entry.offset) + entry.uncompressed_length) {
found = true;
found_entry = entry;
@@ -100,9 +101,8 @@ std::unique_ptr<EmbeddedElf> ApkInspector::FindElfInApkByOffsetWithoutCache(
// Omit files that are not ELF files.
return nullptr;
}
- return std::unique_ptr<EmbeddedElf>(new EmbeddedElf(apk_path, found_entry_name,
- found_entry.offset,
- found_entry.uncompressed_length));
+ return std::unique_ptr<EmbeddedElf>(new EmbeddedElf(
+ apk_path, found_entry_name, found_entry.offset, found_entry.uncompressed_length));
}
std::unique_ptr<EmbeddedElf> ApkInspector::FindElfInApkByNameWithoutCache(
@@ -118,11 +118,12 @@ std::unique_ptr<EmbeddedElf> ApkInspector::FindElfInApkByNameWithoutCache(
if (zentry.method != kCompressStored || zentry.compressed_length != zentry.uncompressed_length) {
return nullptr;
}
- return std::unique_ptr<EmbeddedElf>(new EmbeddedElf(apk_path, entry_name, zentry.offset,
- zentry.uncompressed_length));
+ return std::unique_ptr<EmbeddedElf>(
+ new EmbeddedElf(apk_path, entry_name, zentry.offset, zentry.uncompressed_length));
}
-// Refer file in apk in compliance with http://developer.android.com/reference/java/net/JarURLConnection.html.
+// Refer file in apk in compliance with
+// http://developer.android.com/reference/java/net/JarURLConnection.html.
std::string GetUrlInApk(const std::string& apk_path, const std::string& elf_filename) {
return apk_path + "!/" + elf_filename;
}
@@ -166,3 +167,5 @@ bool ParseExtractedInMemoryPath(const std::string& path, std::string* zip_path,
}
return false;
}
+
+} // namespace simpleperf
diff --git a/simpleperf/read_apk.h b/simpleperf/read_apk.h
index c37366b9..baee2874 100644
--- a/simpleperf/read_apk.h
+++ b/simpleperf/read_apk.h
@@ -26,31 +26,25 @@
#include "read_elf.h"
+namespace simpleperf {
+
// Container for info an on ELF file embedded into an APK file
class EmbeddedElf {
public:
- EmbeddedElf()
- : entry_offset_(0)
- , entry_size_(0)
- {
- }
-
- EmbeddedElf(const std::string& filepath,
- const std::string& entry_name,
- uint64_t entry_offset,
+ EmbeddedElf() : entry_offset_(0), entry_size_(0) {}
+
+ EmbeddedElf(const std::string& filepath, const std::string& entry_name, uint64_t entry_offset,
size_t entry_size)
- : filepath_(filepath)
- , entry_name_(entry_name)
- , entry_offset_(entry_offset)
- , entry_size_(entry_size)
- {
- }
+ : filepath_(filepath),
+ entry_name_(entry_name),
+ entry_offset_(entry_offset),
+ entry_size_(entry_size) {}
// Path to APK file
- const std::string &filepath() const { return filepath_; }
+ const std::string& filepath() const { return filepath_; }
// Entry name within zip archive
- const std::string &entry_name() const { return entry_name_; }
+ const std::string& entry_name() const { return entry_name_; }
// Offset of zip entry from start of containing APK file
uint64_t entry_offset() const { return entry_offset_; }
@@ -59,10 +53,10 @@ class EmbeddedElf {
uint32_t entry_size() const { return entry_size_; }
private:
- std::string filepath_; // containing APK path
- std::string entry_name_; // name of entry in zip index of embedded elf file
- uint64_t entry_offset_; // offset of ELF from start of containing APK file
- uint32_t entry_size_; // size of ELF file in zip
+ std::string filepath_; // containing APK path
+ std::string entry_name_; // name of entry in zip index of embedded elf file
+ uint64_t entry_offset_; // offset of ELF from start of containing APK file
+ uint32_t entry_size_; // size of ELF file in zip
};
// APK inspector helper class
@@ -75,8 +69,8 @@ class ApkInspector {
private:
static std::unique_ptr<EmbeddedElf> FindElfInApkByOffsetWithoutCache(const std::string& apk_path,
uint64_t file_offset);
- static std::unique_ptr<EmbeddedElf> FindElfInApkByNameWithoutCache(
- const std::string& apk_path, const std::string& entry_name);
+ static std::unique_ptr<EmbeddedElf> FindElfInApkByNameWithoutCache(const std::string& apk_path,
+ const std::string& entry_name);
struct ApkNode {
// Map from entry_offset to EmbeddedElf.
@@ -95,4 +89,6 @@ std::tuple<bool, std::string, std::string> SplitUrlInApk(const std::string& path
bool ParseExtractedInMemoryPath(const std::string& path, std::string* zip_path,
std::string* entry_name);
+} // namespace simpleperf
+
#endif // SIMPLE_PERF_READ_APK_H_
diff --git a/simpleperf/read_apk_test.cpp b/simpleperf/read_apk_test.cpp
index d9665ac6..e4dd71f5 100644
--- a/simpleperf/read_apk_test.cpp
+++ b/simpleperf/read_apk_test.cpp
@@ -20,14 +20,16 @@
#include "get_test_data.h"
#include "test_util.h"
+using namespace simpleperf;
+
TEST(read_apk, FindElfInApkByOffset) {
ApkInspector inspector;
ASSERT_TRUE(inspector.FindElfInApkByOffset("/dev/null", 0) == nullptr);
ASSERT_TRUE(inspector.FindElfInApkByOffset(GetTestData(APK_FILE), 0) == nullptr);
// Test if we can read the EmbeddedElf using an offset inside its [offset, offset+size] range
// in the apk file.
- EmbeddedElf* ee = inspector.FindElfInApkByOffset(GetTestData(APK_FILE),
- NATIVELIB_OFFSET_IN_APK + NATIVELIB_SIZE_IN_APK / 2);
+ EmbeddedElf* ee = inspector.FindElfInApkByOffset(
+ GetTestData(APK_FILE), NATIVELIB_OFFSET_IN_APK + NATIVELIB_SIZE_IN_APK / 2);
ASSERT_TRUE(ee != nullptr);
ASSERT_EQ(NATIVELIB_IN_APK, ee->entry_name());
ASSERT_EQ(NATIVELIB_OFFSET_IN_APK, ee->entry_offset());
@@ -46,27 +48,35 @@ TEST(read_apk, FindElfInApkByName) {
TEST(read_apk, ParseExtractedInMemoryPath) {
std::string zip_path;
std::string entry_name;
- ASSERT_TRUE(ParseExtractedInMemoryPath("[anon:dalvik-classes.dex extracted in memory from "
+ ASSERT_TRUE(ParseExtractedInMemoryPath(
+ "[anon:dalvik-classes.dex extracted in memory from "
"/data/app/com.example.simpleperf.simpleperfexamplepurejava-HZK6bPs3Z9SDT3a-tqmasA==/"
- "base.apk]", &zip_path, &entry_name));
- ASSERT_EQ(zip_path, "/data/app/com.example.simpleperf.simpleperfexamplepurejava"
+ "base.apk]",
+ &zip_path, &entry_name));
+ ASSERT_EQ(zip_path,
+ "/data/app/com.example.simpleperf.simpleperfexamplepurejava"
"-HZK6bPs3Z9SDT3a-tqmasA==/base.apk");
ASSERT_EQ(entry_name, "classes.dex");
- ASSERT_FALSE(ParseExtractedInMemoryPath("[anon:dalvik-thread local mark stack]",
- &zip_path, &entry_name));
- ASSERT_TRUE(ParseExtractedInMemoryPath("/dev/ashmem/dalvik-classes.dex extracted in memory from "
+ ASSERT_FALSE(
+ ParseExtractedInMemoryPath("[anon:dalvik-thread local mark stack]", &zip_path, &entry_name));
+ ASSERT_TRUE(ParseExtractedInMemoryPath(
+ "/dev/ashmem/dalvik-classes.dex extracted in memory from "
"/data/app/com.example.simpleperf.simpleperfexamplepurejava-HZK6bPs3Z9SDT3a-tqmasA==/base.apk"
- " (deleted)", &zip_path, &entry_name));
- ASSERT_EQ(zip_path, "/data/app/com.example.simpleperf.simpleperfexamplepurejava"
+ " (deleted)",
+ &zip_path, &entry_name));
+ ASSERT_EQ(zip_path,
+ "/data/app/com.example.simpleperf.simpleperfexamplepurejava"
"-HZK6bPs3Z9SDT3a-tqmasA==/base.apk");
ASSERT_EQ(entry_name, "classes.dex");
ASSERT_FALSE(ParseExtractedInMemoryPath("/dev/ashmem/dalvik-thread local mark stack (deleted)",
&zip_path, &entry_name));
// Parse multidex file.
- ASSERT_TRUE(ParseExtractedInMemoryPath("/dev/ashmem/dalvik-classes2.dex extracted in memory from "
+ ASSERT_TRUE(ParseExtractedInMemoryPath(
+ "/dev/ashmem/dalvik-classes2.dex extracted in memory from "
"/data/app/getxml.test.com.testgetxml-knxI11ZXLT-OVBs9X9bSkw==/base.apk!classes2.dex "
- "(deleted)", &zip_path, &entry_name));
+ "(deleted)",
+ &zip_path, &entry_name));
ASSERT_EQ(zip_path, "/data/app/getxml.test.com.testgetxml-knxI11ZXLT-OVBs9X9bSkw==/base.apk");
ASSERT_EQ(entry_name, "classes2.dex");
}
diff --git a/simpleperf/read_dex_file.cpp b/simpleperf/read_dex_file.cpp
index c9eed8fb..13ca403f 100644
--- a/simpleperf/read_dex_file.cpp
+++ b/simpleperf/read_dex_file.cpp
@@ -25,31 +25,32 @@
#include <vector>
#include <android-base/logging.h>
+#include <android-base/mapped_file.h>
#include <android-base/unique_fd.h>
#include <art_api/dex_file_support.h>
+#include "utils.h"
+
+namespace simpleperf {
+
static bool ReadSymbols(
- const std::vector<uint64_t>& dex_file_offsets, std::vector<DexFileSymbol>* symbols,
- const std::function<std::unique_ptr<art_api::dex::DexFile>(uint64_t offset)>& open_file_cb) {
- for (uint64_t offset : dex_file_offsets) {
- std::unique_ptr<art_api::dex::DexFile> dex_file = open_file_cb(offset);
+ const std::vector<uint64_t>& dex_file_offsets,
+ const std::function<std::unique_ptr<art_api::dex::DexFile>(uint64_t offset)>& open_file_cb,
+ const std::function<void(DexFileSymbol*)>& symbol_cb) {
+ for (uint64_t dex_offset : dex_file_offsets) {
+ std::unique_ptr<art_api::dex::DexFile> dex_file = open_file_cb(dex_offset);
if (dex_file == nullptr) {
return false;
}
- std::vector<art_api::dex::MethodInfo> file_syms = dex_file->GetAllMethodInfos(false);
-
- // Adjust offsets to be from the start of the combined file.
- for (art_api::dex::MethodInfo& sym : file_syms) {
- sym.offset += offset;
- }
-
- if (symbols->empty()) {
- *symbols = std::move(file_syms);
- } else {
- symbols->reserve(symbols->size() + file_syms.size());
- std::move(std::begin(file_syms), std::end(file_syms), std::back_inserter(*symbols));
- }
+ auto callback = [&](const art_api::dex::DexFile::Method& method) {
+ size_t name_size, code_size;
+ const char* name = method.GetQualifiedName(/*with_params=*/false, &name_size);
+ size_t offset = method.GetCodeOffset(&code_size);
+ DexFileSymbol symbol{std::string_view(name, name_size), dex_offset + offset, code_size};
+ symbol_cb(&symbol);
+ };
+ dex_file->ForEachMethod(callback);
}
return true;
@@ -57,42 +58,44 @@ static bool ReadSymbols(
bool ReadSymbolsFromDexFileInMemory(void* addr, uint64_t size,
const std::vector<uint64_t>& dex_file_offsets,
- std::vector<DexFileSymbol>* symbols) {
+ const std::function<void(DexFileSymbol*)>& symbol_callback) {
return ReadSymbols(
- dex_file_offsets, symbols, [&](uint64_t offset) -> std::unique_ptr<art_api::dex::DexFile> {
+ dex_file_offsets,
+ [&](uint64_t offset) -> std::unique_ptr<art_api::dex::DexFile> {
size_t max_file_size;
if (__builtin_sub_overflow(size, offset, &max_file_size)) {
return nullptr;
}
uint8_t* file_addr = static_cast<uint8_t*>(addr) + offset;
- std::string error_msg;
- std::unique_ptr<art_api::dex::DexFile> dex_file =
- art_api::dex::DexFile::OpenFromMemory(file_addr, &max_file_size, "", &error_msg);
+ std::unique_ptr<art_api::dex::DexFile> dex_file;
+ art_api::dex::DexFile::Error error_msg =
+ art_api::dex::DexFile::Create(file_addr, max_file_size, nullptr, "", &dex_file);
if (dex_file == nullptr) {
- LOG(WARNING) << "Failed to read dex file symbols: " << error_msg;
+ LOG(WARNING) << "Failed to read dex file symbols: " << error_msg.ToString();
return nullptr;
}
return dex_file;
- });
+ },
+ symbol_callback);
}
bool ReadSymbolsFromDexFile(const std::string& file_path,
const std::vector<uint64_t>& dex_file_offsets,
- std::vector<DexFileSymbol>* symbols) {
+ const std::function<void(DexFileSymbol*)>& symbol_callback) {
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(file_path.c_str(), O_RDONLY | O_CLOEXEC)));
if (fd == -1) {
return false;
}
- return ReadSymbols(
- dex_file_offsets, symbols, [&](uint64_t offset) -> std::unique_ptr<art_api::dex::DexFile> {
- std::string error_msg;
- std::unique_ptr<art_api::dex::DexFile> dex_file =
- art_api::dex::DexFile::OpenFromFd(fd, offset, file_path, &error_msg);
- if (dex_file == nullptr) {
- LOG(WARNING) << "Failed to read dex file symbols from '" << file_path
- << "': " << error_msg;
- return nullptr;
- }
- return dex_file;
- });
+ size_t file_size = GetFileSize(file_path);
+ if (file_size == 0) {
+ return false;
+ }
+ std::unique_ptr<android::base::MappedFile> map;
+ map = android::base::MappedFile::FromFd(fd, 0, file_size, PROT_READ);
+ if (map == nullptr) {
+ return false;
+ }
+ return ReadSymbolsFromDexFileInMemory(map->data(), file_size, dex_file_offsets, symbol_callback);
}
+
+} // namespace simpleperf
diff --git a/simpleperf/read_dex_file.h b/simpleperf/read_dex_file.h
index 4563b3d0..b4298fab 100644
--- a/simpleperf/read_dex_file.h
+++ b/simpleperf/read_dex_file.h
@@ -26,21 +26,21 @@
#include <art_api/dex_file_support.h>
#endif
-#ifndef NO_LIBDEXFILE_SUPPORT
-typedef art_api::dex::MethodInfo DexFileSymbol;
-#else
+namespace simpleperf {
+
struct DexFileSymbol {
- uint64_t offset;
- uint64_t len;
- std::string name;
+ std::string_view name;
+ uint64_t addr;
+ uint64_t size;
};
-#endif
bool ReadSymbolsFromDexFileInMemory(void* addr, uint64_t size,
const std::vector<uint64_t>& dex_file_offsets,
- std::vector<DexFileSymbol>* symbols);
+ const std::function<void(DexFileSymbol*)>& symbol_callback);
bool ReadSymbolsFromDexFile(const std::string& file_path,
const std::vector<uint64_t>& dex_file_offsets,
- std::vector<DexFileSymbol>* symbols);
+ const std::function<void(DexFileSymbol*)>& symbol_callback);
+
+} // namespace simpleperf
#endif // SIMPLE_PERF_READ_DEX_FILE_H_
diff --git a/simpleperf/read_dex_file_test.cpp b/simpleperf/read_dex_file_test.cpp
index 7e727689..843a964c 100644
--- a/simpleperf/read_dex_file_test.cpp
+++ b/simpleperf/read_dex_file_test.cpp
@@ -20,18 +20,24 @@
#include <algorithm>
+#include "dso.h"
#include "get_test_data.h"
#include "test_util.h"
#include "utils.h"
+using namespace simpleperf;
+
TEST(read_dex_file, smoke) {
- std::vector<DexFileSymbol> symbols;
- ASSERT_TRUE(ReadSymbolsFromDexFile(GetTestData("base.vdex"), {0x28}, &symbols));
+ std::vector<Symbol> symbols;
+ auto symbol_callback = [&](DexFileSymbol* symbol) {
+ symbols.emplace_back(symbol->name, symbol->addr, symbol->size);
+ };
+ ASSERT_TRUE(ReadSymbolsFromDexFile(GetTestData("base.vdex"), {0x28}, symbol_callback));
ASSERT_EQ(12435u, symbols.size());
- DexFileSymbol target;
- target.offset = 0x6c77e;
- target.len = 0x16;
- target.name = art_api::dex::DexString(
- "com.example.simpleperf.simpleperfexamplewithnative.MixActivity$1.run");
- ASSERT_NE(std::find(symbols.begin(), symbols.end(), target), symbols.end());
+ auto it = std::find_if(symbols.begin(), symbols.end(),
+ [](const Symbol& symbol) { return symbol.addr == 0x6c77e; });
+ ASSERT_NE(it, symbols.end());
+ ASSERT_EQ(it->addr, 0x6c77e);
+ ASSERT_EQ(it->len, 0x16);
+ ASSERT_STREQ(it->Name(), "com.example.simpleperf.simpleperfexamplewithnative.MixActivity$1.run");
}
diff --git a/simpleperf/read_elf.cpp b/simpleperf/read_elf.cpp
index 822d1e3e..db3d242a 100644
--- a/simpleperf/read_elf.cpp
+++ b/simpleperf/read_elf.cpp
@@ -38,12 +38,13 @@
#pragma clang diagnostic pop
+#include "JITDebugReader.h"
#include "utils.h"
-#define ELF_NOTE_GNU "GNU"
-#define NT_GNU_BUILD_ID 3
+namespace simpleperf {
-using namespace simpleperf;
+const static char* ELF_NOTE_GNU = "GNU";
+const static int NT_GNU_BUILD_ID = 3;
std::ostream& operator<<(std::ostream& os, const ElfStatus& status) {
switch (status) {
@@ -129,36 +130,15 @@ ElfStatus GetBuildIdFromNoteFile(const std::string& filename, BuildId* build_id)
return ElfStatus::NO_ERROR;
}
-template <class ELFT>
-ElfStatus GetBuildIdFromELFFile(const llvm::object::ELFObjectFile<ELFT>* elf, BuildId* build_id) {
- llvm::StringRef data = elf->getData();
- const char* binary_start = data.data();
- const char* binary_end = data.data() + data.size();
- for (auto it = elf->section_begin(); it != elf->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;
- }
- if (data.data() < binary_start || data.data() + data.size() > binary_end) {
- return ElfStatus::NO_BUILD_ID;
- }
- if (GetBuildIdFromNoteSection(data.data(), data.size(), build_id)) {
- return ElfStatus::NO_ERROR;
- }
- }
- }
- return ElfStatus::NO_BUILD_ID;
+bool IsArmMappingSymbol(const char* name) {
+ // Mapping symbols in arm, which are described in "ELF for ARM Architecture" and
+ // "ELF for ARM 64-bit Architecture". The regular expression to match mapping symbol
+ // is ^\$(a|d|t|x)(\..*)?$
+ return name[0] == '$' && strchr("adtx", name[1]) != nullptr &&
+ (name[2] == '\0' || name[2] == '.');
}
-static ElfStatus GetBuildIdFromObjectFile(llvm::object::ObjectFile* obj, BuildId* build_id) {
- if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(obj)) {
- return GetBuildIdFromELFFile(elf, build_id);
- } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(obj)) {
- return GetBuildIdFromELFFile(elf, build_id);
- }
- return ElfStatus::FILE_MALFORMED;
-}
+namespace {
struct BinaryWrapper {
std::unique_ptr<llvm::MemoryBuffer> buffer;
@@ -217,50 +197,8 @@ static ElfStatus OpenObjectFileInMemory(const char* data, size_t size, BinaryWra
return ElfStatus::NO_ERROR;
}
-ElfStatus GetBuildIdFromElfFile(const std::string& filename, BuildId* build_id) {
- return GetBuildIdFromEmbeddedElfFile(filename, 0, 0, build_id);
-}
-
-ElfStatus GetBuildIdFromEmbeddedElfFile(const std::string& filename, uint64_t file_offset,
- uint32_t file_size, BuildId* build_id) {
- BinaryWrapper wrapper;
- ElfStatus result = OpenObjectFile(filename, file_offset, file_size, &wrapper);
- if (result != ElfStatus::NO_ERROR) {
- return result;
- }
- return GetBuildIdFromObjectFile(wrapper.obj, build_id);
-}
-
-template <class ELFT>
-ElfStatus ReadSectionFromELFFile(const llvm::object::ELFObjectFile<ELFT>* elf, const std::string& section_name,
- std::string* content) {
- for (llvm::object::section_iterator it = elf->section_begin(); it != elf->section_end(); ++it) {
- llvm::StringRef name;
- if (it->getName(name) || name != section_name) {
- continue;
- }
- llvm::StringRef data;
- std::error_code err = it->getContents(data);
- if (err) {
- return ElfStatus::READ_FAILED;
- }
- *content = data;
- return ElfStatus::NO_ERROR;
- }
- return ElfStatus::SECTION_NOT_FOUND;
-}
-
-bool IsArmMappingSymbol(const char* name) {
- // Mapping symbols in arm, which are described in "ELF for ARM Architecture" and
- // "ELF for ARM 64-bit Architecture". The regular expression to match mapping symbol
- // is ^\$(a|d|t|x)(\..*)?$
- return name[0] == '$' && strchr("adtx", name[1]) != nullptr && (name[2] == '\0' || name[2] == '.');
-}
-
-void ReadSymbolTable(llvm::object::symbol_iterator sym_begin,
- llvm::object::symbol_iterator sym_end,
- const std::function<void(const ElfFileSymbol&)>& callback,
- bool is_arm,
+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) {
for (; sym_begin != sym_end; ++sym_begin) {
ElfFileSymbol symbol;
@@ -351,8 +289,8 @@ void AddSymbolForPltSection(const llvm::object::ELFObjectFile<ELFT>* elf,
}
template <class ELFT>
-void CheckSymbolSections(const llvm::object::ELFObjectFile<ELFT>* elf,
- bool* has_symtab, bool* has_dynsym) {
+void CheckSymbolSections(const llvm::object::ELFObjectFile<ELFT>* elf, bool* has_symtab,
+ bool* has_dynsym) {
*has_symtab = false;
*has_dynsym = false;
for (auto it = elf->section_begin(); it != elf->section_end(); ++it) {
@@ -370,226 +308,197 @@ void CheckSymbolSections(const llvm::object::ELFObjectFile<ELFT>* elf,
}
}
-template <class ELFT>
-ElfStatus ParseSymbolsFromELFFile(const llvm::object::ELFObjectFile<ELFT>* elf,
- const std::function<void(const ElfFileSymbol&)>& callback) {
- auto machine = elf->getELFFile()->getHeader()->e_machine;
- bool is_arm = (machine == llvm::ELF::EM_ARM || machine == llvm::ELF::EM_AARCH64);
- AddSymbolForPltSection(elf, callback);
- // Some applications deliberately ship elf files with broken section tables.
- // So check the existence of .symtab section and .dynsym section before reading symbols.
- bool has_symtab;
- bool has_dynsym;
- CheckSymbolSections(elf, &has_symtab, &has_dynsym);
- if (has_symtab && elf->symbol_begin() != elf->symbol_end()) {
- ReadSymbolTable(elf->symbol_begin(), elf->symbol_end(), callback, is_arm, elf->section_end());
- return ElfStatus::NO_ERROR;
- } else if (has_dynsym &&
- elf->dynamic_symbol_begin()->getRawDataRefImpl() != llvm::object::DataRefImpl()) {
- ReadSymbolTable(elf->dynamic_symbol_begin(), elf->dynamic_symbol_end(), callback, is_arm,
- elf->section_end());
- }
- std::string debugdata;
- ElfStatus result = ReadSectionFromELFFile(elf, ".gnu_debugdata", &debugdata);
- if (result == ElfStatus::SECTION_NOT_FOUND) {
- return ElfStatus::NO_SYMBOL_TABLE;
- } else if (result == ElfStatus::NO_ERROR) {
- std::string decompressed_data;
- if (XzDecompress(debugdata, &decompressed_data)) {
- BinaryWrapper wrapper;
- result = OpenObjectFileInMemory(decompressed_data.data(), decompressed_data.size(),
- &wrapper);
- if (result == ElfStatus::NO_ERROR) {
- if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(wrapper.obj)) {
- return ParseSymbolsFromELFFile(elf, callback);
- } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(wrapper.obj)) {
- return ParseSymbolsFromELFFile(elf, callback);
- } else {
- return ElfStatus::FILE_MALFORMED;
- }
- }
- }
- }
- return result;
-}
+template <typename T>
+class ElfFileImpl {};
-ElfStatus MatchBuildId(llvm::object::ObjectFile* obj, const BuildId& expected_build_id) {
- if (expected_build_id.IsEmpty()) {
- return ElfStatus::NO_ERROR;
- }
- BuildId real_build_id;
- ElfStatus result = GetBuildIdFromObjectFile(obj, &real_build_id);
- if (result != ElfStatus::NO_ERROR) {
- return result;
- }
- if (expected_build_id != real_build_id) {
- return ElfStatus::BUILD_ID_MISMATCH;
+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()) {}
+
+ bool Is64Bit() override { return elf_->getHeader()->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];
+ segments[i].vaddr = phdr.p_vaddr;
+ segments[i].file_offset = phdr.p_offset;
+ segments[i].file_size = phdr.p_filesz;
+ segments[i].is_executable =
+ (phdr.p_type == llvm::ELF::PT_LOAD) && (phdr.p_flags & llvm::ELF::PF_X);
+ segments[i].is_load = (phdr.p_type == llvm::ELF::PT_LOAD);
+ }
+ return segments;
}
- return ElfStatus::NO_ERROR;
-}
-
-ElfStatus ParseSymbolsFromElfFile(const std::string& filename,
- const BuildId& expected_build_id,
- const std::function<void(const ElfFileSymbol&)>& callback) {
- return ParseSymbolsFromEmbeddedElfFile(filename, 0, 0, expected_build_id, callback);
-}
-ElfStatus ParseSymbolsFromEmbeddedElfFile(const std::string& filename, uint64_t file_offset,
- uint32_t file_size, const BuildId& expected_build_id,
- const std::function<void(const ElfFileSymbol&)>& callback) {
- BinaryWrapper wrapper;
- ElfStatus result = OpenObjectFile(filename, file_offset, file_size, &wrapper);
- if (result != ElfStatus::NO_ERROR) {
- return result;
- }
- result = MatchBuildId(wrapper.obj, expected_build_id);
- if (result != ElfStatus::NO_ERROR) {
- return result;
- }
- if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(wrapper.obj)) {
- return ParseSymbolsFromELFFile(elf, callback);
- } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(wrapper.obj)) {
- return ParseSymbolsFromELFFile(elf, callback);
+ std::vector<ElfSection> GetSectionHeader() override {
+ auto section_headers_or_err = elf_->sections();
+ if (!section_headers_or_err) {
+ return {};
+ }
+ const auto& section_headers = section_headers_or_err.get();
+ 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) {
+ sections[i].name = name.get();
+ }
+ sections[i].vaddr = shdr.sh_addr;
+ sections[i].file_offset = shdr.sh_offset;
+ sections[i].size = shdr.sh_size;
+ }
+ return sections;
+ }
+
+ ElfStatus GetBuildId(BuildId* build_id) override {
+ llvm::StringRef data = elf_obj_->getData();
+ const char* binary_start = data.data();
+ const char* binary_end = data.data() + data.size();
+ 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;
+ }
+ if (data.data() < binary_start || data.data() + data.size() > binary_end) {
+ return ElfStatus::NO_BUILD_ID;
+ }
+ if (GetBuildIdFromNoteSection(data.data(), data.size(), build_id)) {
+ return ElfStatus::NO_ERROR;
+ }
+ }
+ }
+ return ElfStatus::NO_BUILD_ID;
}
- return ElfStatus::FILE_MALFORMED;
-}
-ElfStatus ParseSymbolsFromElfFileInMemory(const char* data, size_t size,
- const std::function<void(const ElfFileSymbol&)>& callback) {
- BinaryWrapper wrapper;
- ElfStatus result = OpenObjectFileInMemory(data, size, &wrapper);
- if (result != ElfStatus::NO_ERROR) {
+ ElfStatus ParseSymbols(const ParseSymbolCallback& callback) override {
+ auto machine = elf_->getHeader()->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.
+ // So check the existence of .symtab section and .dynsym section before reading symbols.
+ bool has_symtab;
+ bool has_dynsym;
+ CheckSymbolSections(elf_obj_, &has_symtab, &has_dynsym);
+ if (has_symtab && elf_obj_->symbol_begin() != elf_obj_->symbol_end()) {
+ ReadSymbolTable(elf_obj_->symbol_begin(), elf_obj_->symbol_end(), callback, is_arm,
+ elf_obj_->section_end());
+ return ElfStatus::NO_ERROR;
+ } else if (has_dynsym && elf_obj_->dynamic_symbol_begin()->getRawDataRefImpl() !=
+ llvm::object::DataRefImpl()) {
+ ReadSymbolTable(elf_obj_->dynamic_symbol_begin(), elf_obj_->dynamic_symbol_end(), callback,
+ is_arm, elf_obj_->section_end());
+ }
+ std::string debugdata;
+ ElfStatus result = ReadSection(".gnu_debugdata", &debugdata);
+ if (result == ElfStatus::SECTION_NOT_FOUND) {
+ return ElfStatus::NO_SYMBOL_TABLE;
+ } else if (result == ElfStatus::NO_ERROR) {
+ std::string decompressed_data;
+ if (XzDecompress(debugdata, &decompressed_data)) {
+ auto debugdata_elf =
+ ElfFile::Open(decompressed_data.data(), decompressed_data.size(), &result);
+ if (debugdata_elf) {
+ return debugdata_elf->ParseSymbols(callback);
+ }
+ }
+ }
return result;
}
- if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(wrapper.obj)) {
- return ParseSymbolsFromELFFile(elf, callback);
- } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(wrapper.obj)) {
- return ParseSymbolsFromELFFile(elf, callback);
- }
- return ElfStatus::FILE_MALFORMED;
-}
-
-template <class ELFT>
-ElfStatus ParseDynamicSymbolsFromELFFile(const llvm::object::ELFObjectFile<ELFT>* elf,
- const std::function<void(const ElfFileSymbol&)>& callback) {
- auto machine = elf->getELFFile()->getHeader()->e_machine;
- bool is_arm = (machine == llvm::ELF::EM_ARM || machine == llvm::ELF::EM_AARCH64);
- ReadSymbolTable(elf->dynamic_symbol_begin(), elf->dynamic_symbol_end(), callback, is_arm,
- elf->section_end());
- return ElfStatus::NO_ERROR;
-}
-ElfStatus ParseDynamicSymbolsFromElfFile(const std::string& filename,
- const std::function<void(const ElfFileSymbol&)>& callback) {
- BinaryWrapper wrapper;
- ElfStatus result = OpenObjectFile(filename, 0, 0, &wrapper);
- if (result != ElfStatus::NO_ERROR) {
- return result;
+ void ParseDynamicSymbols(const ParseSymbolCallback& callback) override {
+ auto machine = elf_->getHeader()->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());
}
- if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(wrapper.obj)) {
- return ParseDynamicSymbolsFromELFFile(elf, callback);
- } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(wrapper.obj)) {
- return ParseDynamicSymbolsFromELFFile(elf, callback);
- }
- return ElfStatus::FILE_MALFORMED;
-}
-template <class ELFT>
-ElfStatus ReadMinExecutableVirtualAddress(const llvm::object::ELFFile<ELFT>* elf,
- uint64_t* p_vaddr,
- 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;
- has_vaddr = true;
+ 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) {
+ continue;
}
+ llvm::StringRef data;
+ std::error_code err = it->getContents(data);
+ if (err) {
+ return ElfStatus::READ_FAILED;
+ }
+ *content = data;
+ return ElfStatus::NO_ERROR;
}
+ return ElfStatus::SECTION_NOT_FOUND;
+ }
+
+ 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;
+ has_vaddr = true;
+ }
+ }
+ }
+ if (!has_vaddr) {
+ // JIT symfiles don't have program headers.
+ min_addr = 0;
+ *file_offset = 0;
+ }
+ return min_addr;
}
- if (!has_vaddr) {
- // JIT symfiles don't have program headers.
- min_addr = 0;
- *file_offset = 0;
- }
- *p_vaddr = min_addr;
- return ElfStatus::NO_ERROR;
-}
-
-ElfStatus ReadMinExecutableVirtualAddressFromElfFile(const std::string& filename,
- const BuildId& expected_build_id,
- uint64_t* min_vaddr,
- uint64_t* file_offset_of_min_vaddr) {
- return ReadMinExecutableVirtualAddressFromEmbeddedElfFile(filename, 0, 0, expected_build_id,
- min_vaddr, file_offset_of_min_vaddr);
-}
-
-ElfStatus ReadMinExecutableVirtualAddressFromEmbeddedElfFile(const std::string& filename,
- uint64_t file_offset,
- uint32_t file_size,
- const BuildId& expected_build_id,
- uint64_t* min_vaddr,
- uint64_t* file_offset_of_min_vaddr) {
- BinaryWrapper wrapper;
- ElfStatus result = OpenObjectFile(filename, file_offset, file_size, &wrapper);
- if (result != ElfStatus::NO_ERROR) {
- return result;
- }
- result = MatchBuildId(wrapper.obj, expected_build_id);
- if (result != ElfStatus::NO_ERROR) {
- return result;
- }
- if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(wrapper.obj)) {
- return ReadMinExecutableVirtualAddress(elf->getELFFile(), min_vaddr, file_offset_of_min_vaddr);
- } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(wrapper.obj)) {
- return ReadMinExecutableVirtualAddress(elf->getELFFile(), min_vaddr, file_offset_of_min_vaddr);
- }
- return ElfStatus::FILE_MALFORMED;
-}
-
-ElfStatus ReadSectionFromElfFile(const std::string& filename, const std::string& section_name,
- std::string* content) {
- BinaryWrapper wrapper;
- ElfStatus result = OpenObjectFile(filename, 0, 0, &wrapper);
- if (result != ElfStatus::NO_ERROR) {
- return result;
- }
- if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(wrapper.obj)) {
- return ReadSectionFromELFFile(elf, section_name, content);
- } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(wrapper.obj)) {
- return ReadSectionFromELFFile(elf, section_name, content);
- } else {
- return ElfStatus::FILE_MALFORMED;
- }
-}
-
-namespace {
-template <typename T>
-class ElfFileImpl {};
-
-template <typename ELFT>
-class ElfFileImpl<llvm::object::ELFFile<ELFT>> : public ElfFile {
- public:
- ElfFileImpl(BinaryWrapper&& wrapper, const llvm::object::ELFFile<ELFT>* elf)
- : wrapper_(std::move(wrapper)), elf_(elf) {}
-
- llvm::MemoryBuffer* GetMemoryBuffer() override {
- return wrapper_.buffer.get();
+ 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;
+ return true;
+ }
+ }
+ return false;
}
private:
BinaryWrapper wrapper_;
+ const llvm::object::ELFObjectFile<ELFT>* elf_obj_;
const llvm::object::ELFFile<ELFT>* elf_;
};
+std::unique_ptr<ElfFile> CreateElfFileImpl(BinaryWrapper&& wrapper, ElfStatus* status) {
+ if (auto obj = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(wrapper.obj)) {
+ 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)) {
+ return std::unique_ptr<ElfFile>(
+ new ElfFileImpl<llvm::object::ELF64LEObjectFile>(std::move(wrapper), obj));
+ }
+ *status = ElfStatus::FILE_MALFORMED;
+ return nullptr;
+}
+
} // namespace
-namespace simpleperf {
+std::unique_ptr<ElfFile> ElfFile::Open(const std::string& filename) {
+ ElfStatus status;
+ auto elf = Open(filename, &status);
+ if (!elf) {
+ LOG(ERROR) << "failed to open " << filename << ": " << status;
+ }
+ return elf;
+}
-std::unique_ptr<ElfFile> ElfFile::Open(const std::string& filename, ElfStatus* status) {
+std::unique_ptr<ElfFile> ElfFile::Open(const std::string& filename,
+ const BuildId* expected_build_id, ElfStatus* status) {
BinaryWrapper wrapper;
auto tuple = SplitUrlInApk(filename);
if (std::get<0>(tuple)) {
@@ -599,23 +508,64 @@ std::unique_ptr<ElfFile> ElfFile::Open(const std::string& filename, ElfStatus* s
} else {
*status = OpenObjectFile(elf->filepath(), elf->entry_offset(), elf->entry_size(), &wrapper);
}
+ } else if (JITDebugReader::IsPathInJITSymFile(filename)) {
+ size_t colon_pos = filename.rfind(':');
+ CHECK_NE(colon_pos, std::string::npos);
+ // path generated by JITDebugReader: app_jit_cache:<file_start>-<file_end>
+ uint64_t file_start;
+ uint64_t file_end;
+ if (sscanf(filename.data() + colon_pos, ":%" PRIu64 "-%" PRIu64, &file_start, &file_end) != 2) {
+ *status = ElfStatus::FILE_NOT_FOUND;
+ return nullptr;
+ }
+ *status =
+ OpenObjectFile(filename.substr(0, colon_pos), file_start, file_end - file_start, &wrapper);
} else {
*status = OpenObjectFile(filename, 0, 0, &wrapper);
}
- if (*status == ElfStatus::NO_ERROR) {
- if (auto obj = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(wrapper.obj)) {
- using elf_t = std::decay_t<decltype(*obj->getELFFile())>;
- return std::unique_ptr<ElfFile>(
- new ElfFileImpl<elf_t>(std::move(wrapper), obj->getELFFile()));
+ if (*status != ElfStatus::NO_ERROR) {
+ return nullptr;
+ }
+ auto elf = CreateElfFileImpl(std::move(wrapper), status);
+ if (elf && expected_build_id != nullptr && !expected_build_id->IsEmpty()) {
+ BuildId real_build_id;
+ *status = elf->GetBuildId(&real_build_id);
+ if (*status != ElfStatus::NO_ERROR) {
+ return nullptr;
}
- if (auto obj = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(wrapper.obj)) {
- using elf_t = std::decay_t<decltype(*obj->getELFFile())>;
- return std::unique_ptr<ElfFile>(
- new ElfFileImpl<elf_t>(std::move(wrapper), obj->getELFFile()));
+ if (*expected_build_id != real_build_id) {
+ *status = ElfStatus::BUILD_ID_MISMATCH;
+ return nullptr;
}
- *status = ElfStatus::FILE_MALFORMED;
}
+ return elf;
+}
+
+std::unique_ptr<ElfFile> ElfFile::Open(const char* data, size_t size, ElfStatus* status) {
+ BinaryWrapper wrapper;
+ *status = OpenObjectFileInMemory(data, size, &wrapper);
+ if (*status != ElfStatus::NO_ERROR) {
+ return nullptr;
+ }
+ return CreateElfFileImpl(std::move(wrapper), status);
+}
+
+} // namespace simpleperf
+
+// LLVM libraries uses ncurses library, but that isn't needed by simpleperf.
+// So support a naive implementation to avoid depending on ncurses.
+__attribute__((weak)) extern "C" int setupterm(char*, int, int*) {
+ return -1;
+}
+
+__attribute__((weak)) extern "C" struct term* set_curterm(struct term*) {
return nullptr;
}
-} // namespace simpleperf \ No newline at end of file
+__attribute__((weak)) extern "C" int del_curterm(struct term*) {
+ return -1;
+}
+
+__attribute__((weak)) extern "C" int tigetnum(char*) {
+ return -1;
+}
diff --git a/simpleperf/read_elf.h b/simpleperf/read_elf.h
index f289ee17..3ade27fa 100644
--- a/simpleperf/read_elf.h
+++ b/simpleperf/read_elf.h
@@ -22,11 +22,17 @@
#include <string>
#include "build_id.h"
+namespace llvm {
+class MemoryBuffer;
+}
+
+namespace simpleperf {
+
// Read ELF functions are called in different situations, so it is hard to
// decide whether to report error or not. So read ELF functions don't report
// error when something wrong happens, instead they return ElfStatus, which
// identifies different errors met while reading elf file.
-enum ElfStatus {
+enum class ElfStatus {
NO_ERROR,
FILE_NOT_FOUND,
READ_FAILED,
@@ -40,9 +46,6 @@ enum ElfStatus {
std::ostream& operator<<(std::ostream& os, const ElfStatus& status);
ElfStatus GetBuildIdFromNoteFile(const std::string& filename, BuildId* build_id);
-ElfStatus GetBuildIdFromElfFile(const std::string& filename, BuildId* build_id);
-ElfStatus GetBuildIdFromEmbeddedElfFile(const std::string& filename, uint64_t file_offset,
- uint32_t file_size, BuildId* build_id);
// The symbol prefix used to indicate that the symbol belongs to android linker.
static const std::string linker_prefix = "__dl_";
@@ -55,57 +58,60 @@ struct ElfFileSymbol {
bool is_in_text_section;
std::string name;
- ElfFileSymbol() : vaddr(0), len(0), is_func(false), is_label(false), is_in_text_section(false) {
- }
+ ElfFileSymbol() : vaddr(0), len(0), is_func(false), is_label(false), is_in_text_section(false) {}
};
-ElfStatus ParseSymbolsFromElfFile(const std::string& filename,
- const BuildId& expected_build_id,
- const std::function<void(const ElfFileSymbol&)>& callback);
-ElfStatus ParseSymbolsFromEmbeddedElfFile(const std::string& filename, uint64_t file_offset,
- uint32_t file_size, const BuildId& expected_build_id,
- const std::function<void(const ElfFileSymbol&)>& callback);
-ElfStatus ParseSymbolsFromElfFileInMemory(const char* data, size_t size,
- const std::function<void(const ElfFileSymbol&)>& callback);
-ElfStatus ParseDynamicSymbolsFromElfFile(const std::string& filename,
- const std::function<void(const ElfFileSymbol&)>& callback);
-
-ElfStatus ReadMinExecutableVirtualAddressFromElfFile(const std::string& filename,
- const BuildId& expected_build_id,
- uint64_t* min_addr,
- uint64_t* file_offset_of_min_vaddr);
-ElfStatus ReadMinExecutableVirtualAddressFromEmbeddedElfFile(const std::string& filename,
- uint64_t file_offset,
- uint32_t file_size,
- const BuildId& expected_build_id,
- uint64_t* min_vaddr,
- uint64_t* file_offset_of_min_vaddr);
-
-ElfStatus ReadSectionFromElfFile(const std::string& filename, const std::string& section_name,
- std::string* content);
-
-namespace llvm {
-class MemoryBuffer;
-}
+struct ElfSegment {
+ uint64_t vaddr = 0;
+ uint64_t file_offset = 0;
+ uint64_t file_size = 0;
+ bool is_executable = false;
+ bool is_load = false;
+};
-namespace simpleperf {
+struct ElfSection {
+ std::string name;
+ uint64_t vaddr = 0;
+ uint64_t file_offset = 0;
+ uint64_t size = 0;
+};
class ElfFile {
public:
- static std::unique_ptr<ElfFile> Open(const std::string& filename, ElfStatus* status);
+ // Report error instead of returning status.
+ static std::unique_ptr<ElfFile> Open(const std::string& filename);
+ static std::unique_ptr<ElfFile> Open(const std::string& filename, ElfStatus* status) {
+ return Open(filename, nullptr, status);
+ }
+
+ static std::unique_ptr<ElfFile> Open(const std::string& filename,
+ const BuildId* expected_build_id, ElfStatus* status);
+ static std::unique_ptr<ElfFile> Open(const char* data, size_t size, ElfStatus* status);
virtual ~ElfFile() {}
+ virtual bool Is64Bit() = 0;
virtual llvm::MemoryBuffer* GetMemoryBuffer() = 0;
+ virtual std::vector<ElfSegment> GetProgramHeader() = 0;
+ virtual std::vector<ElfSection> GetSectionHeader() = 0;
+ virtual ElfStatus GetBuildId(BuildId* build_id) = 0;
+
+ using ParseSymbolCallback = std::function<void(const ElfFileSymbol&)>;
+ virtual ElfStatus ParseSymbols(const ParseSymbolCallback& callback) = 0;
+ virtual void ParseDynamicSymbols(const ParseSymbolCallback& callback) = 0;
+
+ virtual ElfStatus ReadSection(const std::string& section_name, std::string* content) = 0;
+ virtual uint64_t ReadMinExecutableVaddr(uint64_t* file_offset_of_min_vaddr) = 0;
+ virtual bool VaddrToOff(uint64_t vaddr, uint64_t* file_offset) = 0;
protected:
ElfFile() {}
};
-} // namespace simpleperf
-
bool IsArmMappingSymbol(const char* name);
ElfStatus IsValidElfFile(int fd, uint64_t file_offset = 0);
bool IsValidElfFileMagic(const char* buf, size_t buf_size);
bool GetBuildIdFromNoteSection(const char* section, size_t section_size, BuildId* build_id);
+} // namespace simpleperf
+
#endif // SIMPLE_PERF_READ_ELF_H_
diff --git a/simpleperf/read_elf_test.cpp b/simpleperf/read_elf_test.cpp
index cc5aa52b..e2a2cd88 100644
--- a/simpleperf/read_elf_test.cpp
+++ b/simpleperf/read_elf_test.cpp
@@ -23,6 +23,7 @@
#include <android-base/file.h>
#include "get_test_data.h"
+#include "read_apk.h"
#include "test_util.h"
#include "utils.h"
@@ -63,14 +64,20 @@ TEST(read_elf, GetBuildIdFromNoteSection) {
TEST(read_elf, GetBuildIdFromElfFile) {
BuildId build_id;
- ASSERT_EQ(ElfStatus::NO_ERROR, GetBuildIdFromElfFile(GetTestData(ELF_FILE), &build_id));
+ ElfStatus status;
+ auto elf = ElfFile::Open(GetTestData(ELF_FILE), &status);
+ ASSERT_EQ(status, ElfStatus::NO_ERROR);
+ ASSERT_EQ(ElfStatus::NO_ERROR, elf->GetBuildId(&build_id));
ASSERT_EQ(build_id, BuildId(elf_file_build_id));
}
TEST(read_elf, GetBuildIdFromEmbeddedElfFile) {
BuildId build_id;
- ASSERT_EQ(ElfStatus::NO_ERROR, GetBuildIdFromEmbeddedElfFile(GetTestData(APK_FILE), NATIVELIB_OFFSET_IN_APK,
- NATIVELIB_SIZE_IN_APK, &build_id));
+ ElfStatus status;
+ std::string path = GetUrlInApk(APK_FILE, NATIVELIB_IN_APK);
+ auto elf = ElfFile::Open(GetTestData(path), &status);
+ ASSERT_EQ(status, ElfStatus::NO_ERROR);
+ ASSERT_EQ(ElfStatus::NO_ERROR, elf->GetBuildId(&build_id));
ASSERT_EQ(build_id, native_lib_build_id);
}
@@ -98,37 +105,60 @@ void CheckElfFileSymbols(const std::map<std::string, ElfFileSymbol>& symbols) {
TEST(read_elf, parse_symbols_from_elf_file_with_correct_build_id) {
std::map<std::string, ElfFileSymbol> symbols;
- ASSERT_EQ(ElfStatus::NO_ERROR, ParseSymbolsFromElfFile(GetTestData(ELF_FILE), elf_file_build_id,
- std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
+ ElfStatus status;
+ auto elf = ElfFile::Open(GetTestData(ELF_FILE), &elf_file_build_id, &status);
+ ASSERT_EQ(ElfStatus::NO_ERROR, status);
+ ASSERT_EQ(ElfStatus::NO_ERROR,
+ elf->ParseSymbols(std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
CheckElfFileSymbols(symbols);
}
TEST(read_elf, parse_symbols_from_elf_file_without_build_id) {
std::map<std::string, ElfFileSymbol> symbols;
- ASSERT_EQ(ElfStatus::NO_ERROR, ParseSymbolsFromElfFile(GetTestData(ELF_FILE), BuildId(),
- std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
+ ElfStatus status;
+ // Test no build_id.
+ auto elf = ElfFile::Open(GetTestData(ELF_FILE), &status);
+ ASSERT_EQ(ElfStatus::NO_ERROR, status);
+ ASSERT_EQ(ElfStatus::NO_ERROR,
+ elf->ParseSymbols(std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
+ CheckElfFileSymbols(symbols);
+
+ // Test empty build id.
+ symbols.clear();
+ BuildId build_id;
+ elf = ElfFile::Open(GetTestData(ELF_FILE), &build_id, &status);
+ ASSERT_EQ(ElfStatus::NO_ERROR, status);
+ ASSERT_EQ(ElfStatus::NO_ERROR,
+ elf->ParseSymbols(std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
CheckElfFileSymbols(symbols);
}
TEST(read_elf, parse_symbols_from_elf_file_with_wrong_build_id) {
BuildId build_id("01010101010101010101");
std::map<std::string, ElfFileSymbol> symbols;
- ASSERT_EQ(ElfStatus::BUILD_ID_MISMATCH, ParseSymbolsFromElfFile(GetTestData(ELF_FILE), build_id,
- std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
+ ElfStatus status;
+ auto elf = ElfFile::Open(GetTestData(ELF_FILE), &build_id, &status);
+ ASSERT_EQ(ElfStatus::BUILD_ID_MISMATCH, status);
}
TEST(read_elf, ParseSymbolsFromEmbeddedElfFile) {
std::map<std::string, ElfFileSymbol> symbols;
- ASSERT_EQ(ElfStatus::NO_SYMBOL_TABLE, ParseSymbolsFromEmbeddedElfFile(GetTestData(APK_FILE), NATIVELIB_OFFSET_IN_APK,
- NATIVELIB_SIZE_IN_APK, native_lib_build_id,
- std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
+ ElfStatus status;
+ std::string path = GetUrlInApk(APK_FILE, NATIVELIB_IN_APK);
+ auto elf = ElfFile::Open(GetTestData(path), &native_lib_build_id, &status);
+ ASSERT_EQ(status, ElfStatus::NO_ERROR);
+ ASSERT_EQ(ElfStatus::NO_SYMBOL_TABLE,
+ elf->ParseSymbols(std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
CheckElfFileSymbols(symbols);
}
TEST(read_elf, ParseSymbolFromMiniDebugInfoElfFile) {
std::map<std::string, ElfFileSymbol> symbols;
- ASSERT_EQ(ElfStatus::NO_ERROR, ParseSymbolsFromElfFile(GetTestData(ELF_FILE_WITH_MINI_DEBUG_INFO), BuildId(),
- std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
+ ElfStatus status;
+ auto elf = ElfFile::Open(GetTestData(ELF_FILE_WITH_MINI_DEBUG_INFO), &status);
+ ASSERT_EQ(ElfStatus::NO_ERROR, status);
+ ASSERT_EQ(ElfStatus::NO_ERROR,
+ elf->ParseSymbols(std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
CheckFunctionSymbols(symbols);
}
@@ -155,32 +185,38 @@ TEST(read_elf, ElfFile_Open) {
TEST(read_elf, check_symbol_for_plt_section) {
std::map<std::string, ElfFileSymbol> symbols;
- ASSERT_EQ(ElfStatus::NO_ERROR, ParseSymbolsFromElfFile(GetTestData(ELF_FILE), BuildId(),
- std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
+ ElfStatus status;
+ auto elf = ElfFile::Open(GetTestData(ELF_FILE), &status);
+ ASSERT_EQ(ElfStatus::NO_ERROR, status);
+ ASSERT_EQ(ElfStatus::NO_ERROR,
+ elf->ParseSymbols(std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
ASSERT_NE(symbols.find("@plt"), symbols.end());
}
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;
+ ElfStatus status;
+ auto elf = ElfFile::Open(elf_path, &status);
+ ASSERT_EQ(ElfStatus::NO_ERROR, status);
ASSERT_EQ(ElfStatus::NO_SYMBOL_TABLE,
- ParseSymbolsFromElfFile(elf_path, BuildId(),
- std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
+ elf->ParseSymbols(std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
+
BuildId build_id;
- ASSERT_EQ(ElfStatus::NO_BUILD_ID, GetBuildIdFromElfFile(elf_path, &build_id));
- uint64_t min_vaddr;
+ ASSERT_EQ(ElfStatus::NO_BUILD_ID, elf->GetBuildId(&build_id));
+
uint64_t file_offset_of_min_vaddr;
- ASSERT_EQ(ElfStatus::NO_ERROR, ReadMinExecutableVirtualAddressFromElfFile(
- elf_path, BuildId(), &min_vaddr, &file_offset_of_min_vaddr));
+ uint64_t min_vaddr = elf->ReadMinExecutableVaddr(&file_offset_of_min_vaddr);
ASSERT_EQ(min_vaddr, 0u);
ASSERT_EQ(file_offset_of_min_vaddr, 0u);
}
-TEST(read_elf, ReadMinExecutableVirtualAddressFromElfFile) {
- uint64_t min_vaddr;
+TEST(read_elf, ReadMinExecutableVaddr) {
+ ElfStatus status;
+ auto elf = ElfFile::Open(GetTestData("libc.so"), &status);
+ ASSERT_EQ(status, ElfStatus::NO_ERROR);
uint64_t file_offset_of_min_vaddr;
- ASSERT_EQ(ElfStatus::NO_ERROR, ReadMinExecutableVirtualAddressFromElfFile(
- GetTestData("libc.so"), BuildId(), &min_vaddr, &file_offset_of_min_vaddr));
+ uint64_t min_vaddr = elf->ReadMinExecutableVaddr(&file_offset_of_min_vaddr);
ASSERT_EQ(min_vaddr, 0x29000u);
ASSERT_EQ(file_offset_of_min_vaddr, 0x29000u);
}
@@ -194,7 +230,30 @@ TEST(read_elf, NoUndefinedSymbol) {
}
};
- ASSERT_EQ(ElfStatus::NO_ERROR,
- ParseSymbolsFromElfFile(GetTestData("libc.so"), BuildId(), parse_symbol));
+ ElfStatus status;
+ auto elf = ElfFile::Open(GetTestData("libc.so"), &status);
+ ASSERT_EQ(status, ElfStatus::NO_ERROR);
+ ASSERT_EQ(ElfStatus::NO_ERROR, elf->ParseSymbols(parse_symbol));
ASSERT_FALSE(has_dlerror);
-} \ No newline at end of file
+}
+
+TEST(read_elf, VaddrToOff) {
+ auto elf = ElfFile::Open(GetTestData(ELF_FILE));
+ ASSERT_TRUE(elf != nullptr);
+ uint64_t off;
+ ASSERT_TRUE(elf->VaddrToOff(0x400200, &off));
+ ASSERT_EQ(off, 0x200);
+ ASSERT_FALSE(elf->VaddrToOff(0x300200, &off));
+ ASSERT_FALSE(elf->VaddrToOff(0x420000, &off));
+}
+
+TEST(read_elf, GetSectionHeader) {
+ auto elf = ElfFile::Open(GetTestData(ELF_FILE));
+ ASSERT_TRUE(elf != nullptr);
+ std::vector<ElfSection> sections = elf->GetSectionHeader();
+ ASSERT_EQ(sections.size(), 30);
+ ASSERT_EQ(sections[13].name, ".text");
+ ASSERT_EQ(sections[13].vaddr, 0x400400);
+ ASSERT_EQ(sections[13].file_offset, 0x400);
+ ASSERT_EQ(sections[13].size, 0x1b2);
+}
diff --git a/simpleperf/read_symbol_map.cpp b/simpleperf/read_symbol_map.cpp
new file mode 100644
index 00000000..15d59e06
--- /dev/null
+++ b/simpleperf/read_symbol_map.cpp
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+
+#include "read_symbol_map.h"
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include <algorithm>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "dso.h"
+
+namespace simpleperf {
+namespace {
+
+std::optional<std::string_view> ConsumeWord(std::string_view& content_ref) {
+ size_t begin = content_ref.find_first_not_of(" \t");
+ if (begin == content_ref.npos) {
+ return {};
+ }
+
+ size_t end = content_ref.find_first_of(" \t", begin + 1);
+ if (end == content_ref.npos) {
+ end = content_ref.size();
+ }
+
+ auto res = content_ref.substr(begin, end - begin);
+ content_ref.remove_prefix(end);
+ return res;
+}
+
+std::optional<uint64_t> ConsumeUInt(std::string_view& content_ref) {
+ auto word = ConsumeWord(content_ref);
+ if (!word) {
+ return {};
+ }
+
+ errno = 0;
+ const char* start = word.value().data();
+ char* stop;
+ auto res = strtoull(start, &stop, 0);
+ if (errno != 0 || stop - start != word.value().size()) {
+ return {};
+ }
+
+ return res;
+}
+
+void ReadSymbol(std::string_view content, std::vector<Symbol>* symbols) {
+ auto addr = ConsumeUInt(content);
+ if (!addr) {
+ return;
+ }
+
+ auto size = ConsumeUInt(content);
+ if (!size) {
+ return;
+ }
+
+ auto name = ConsumeWord(content);
+ if (!name) {
+ return;
+ }
+
+ if (ConsumeWord(content)) {
+ return;
+ }
+
+ symbols->emplace_back(name.value(), addr.value(), size.value());
+}
+
+} // namespace
+
+std::vector<Symbol> ReadSymbolMapFromString(const std::string& content) {
+ std::vector<Symbol> symbols;
+
+ for (size_t begin = 0;;) {
+ size_t end = content.find_first_of("\n\r", begin);
+
+ if (end == content.npos) {
+ ReadSymbol({content.c_str() + begin, content.size() - begin}, &symbols);
+ std::sort(symbols.begin(), symbols.end(), Symbol::CompareValueByAddr);
+ return symbols;
+ }
+
+ ReadSymbol({content.c_str() + begin, end - begin}, &symbols);
+ begin = end + 1;
+ }
+}
+
+} // namespace simpleperf
diff --git a/simpleperf/read_symbol_map.h b/simpleperf/read_symbol_map.h
new file mode 100644
index 00000000..dea22eb5
--- /dev/null
+++ b/simpleperf/read_symbol_map.h
@@ -0,0 +1,38 @@
+/*
+ * 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>
+
+#include <android-base/file.h>
+
+#include "dso.h"
+
+namespace simpleperf {
+
+std::vector<Symbol> ReadSymbolMapFromString(const std::string& content);
+
+inline std::vector<Symbol> ReadSymbolMapFromFile(const std::string& path) {
+ std::string content;
+ if (android::base::ReadFileToString(path, &content, /* follow_symlinks = */ true)) {
+ return ReadSymbolMapFromString(content);
+ }
+ return {};
+}
+
+} // namespace simpleperf
diff --git a/simpleperf/read_symbol_map_test.cpp b/simpleperf/read_symbol_map_test.cpp
new file mode 100644
index 00000000..234bea8c
--- /dev/null
+++ b/simpleperf/read_symbol_map_test.cpp
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#include "read_symbol_map.h"
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "dso.h"
+
+using namespace simpleperf;
+
+TEST(read_symbol_map, smoke) {
+ std::string content(
+ "\n" // skip
+ " 0x2000 0x20 two \n"
+ "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
+ "0x3000 48 three \n");
+
+ auto symbols = ReadSymbolMapFromString(content);
+
+ ASSERT_EQ(3u, symbols.size());
+
+ ASSERT_EQ(0x1000, symbols[0].addr);
+ ASSERT_EQ(0x10, symbols[0].len);
+ ASSERT_STREQ("one", symbols[0].Name());
+
+ ASSERT_EQ(0x2000, symbols[1].addr);
+ ASSERT_EQ(0x20, symbols[1].len);
+ ASSERT_STREQ("two", symbols[1].Name());
+
+ ASSERT_EQ(0x3000, symbols[2].addr);
+ ASSERT_EQ(0x30, symbols[2].len);
+ ASSERT_STREQ("three", symbols[2].Name());
+}
diff --git a/simpleperf/record.cpp b/simpleperf/record.cpp
index 3ce99acc..4e8a8822 100644
--- a/simpleperf/record.cpp
+++ b/simpleperf/record.cpp
@@ -23,13 +23,13 @@
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
-#include "dso.h"
#include "OfflineUnwinder.h"
+#include "dso.h"
#include "perf_regs.h"
#include "tracing.h"
#include "utils.h"
-using namespace simpleperf;
+namespace simpleperf {
static std::string RecordTypeToString(int record_type) {
static std::unordered_map<int, std::string> record_type_names = {
@@ -69,7 +69,9 @@ void MoveToBinaryFormat(const RecordHeader& data, char*& p) {
data.MoveToBinaryFormat(p);
}
-SampleId::SampleId() { memset(this, 0, sizeof(SampleId)); }
+SampleId::SampleId() {
+ memset(this, 0, sizeof(SampleId));
+}
// Return sample_id size in binary format.
size_t SampleId::CreateContent(const perf_event_attr& attr, uint64_t event_id) {
@@ -80,8 +82,7 @@ size_t SampleId::CreateContent(const perf_event_attr& attr, uint64_t event_id) {
return Size();
}
-void SampleId::ReadFromBinaryFormat(const perf_event_attr& attr, const char* p,
- const char* end) {
+void SampleId::ReadFromBinaryFormat(const perf_event_attr& attr, const char* p, const char* end) {
sample_id_all = attr.sample_id_all;
sample_type = attr.sample_type;
if (sample_id_all) {
@@ -133,8 +134,7 @@ void SampleId::WriteToBinaryFormat(char*& p) const {
void SampleId::Dump(size_t indent) const {
if (sample_id_all) {
if (sample_type & PERF_SAMPLE_TID) {
- PrintIndented(indent, "sample_id: pid %u, tid %u\n", tid_data.pid,
- tid_data.tid);
+ PrintIndented(indent, "sample_id: pid %u, tid %u\n", tid_data.pid, tid_data.tid);
}
if (sample_type & PERF_SAMPLE_TIME) {
PrintIndented(indent, "sample_id: time %" PRId64 "\n", time_data.time);
@@ -143,12 +143,10 @@ void SampleId::Dump(size_t indent) const {
PrintIndented(indent, "sample_id: id %" PRId64 "\n", id_data.id);
}
if (sample_type & PERF_SAMPLE_STREAM_ID) {
- PrintIndented(indent, "sample_id: stream_id %" PRId64 "\n",
- stream_id_data.stream_id);
+ PrintIndented(indent, "sample_id: stream_id %" PRId64 "\n", stream_id_data.stream_id);
}
if (sample_type & PERF_SAMPLE_CPU) {
- PrintIndented(indent, "sample_id: cpu %u, res %u\n", cpu_data.cpu,
- cpu_data.res);
+ PrintIndented(indent, "sample_id: cpu %u, res %u\n", cpu_data.cpu, cpu_data.res);
}
}
}
@@ -194,9 +192,15 @@ void Record::Dump(size_t indent) const {
sample_id.Dump(indent + 1);
}
-uint64_t Record::Timestamp() const { return sample_id.time_data.time; }
-uint32_t Record::Cpu() const { return sample_id.cpu_data.cpu; }
-uint64_t Record::Id() const { return sample_id.id_data.id; }
+uint64_t Record::Timestamp() const {
+ return sample_id.time_data.time;
+}
+uint32_t Record::Cpu() const {
+ return sample_id.cpu_data.cpu;
+}
+uint64_t Record::Id() const {
+ return sample_id.id_data.id;
+}
void Record::UpdateBinary(char* new_binary) {
if (own_binary_) {
@@ -217,12 +221,10 @@ MmapRecord::MmapRecord(const perf_event_attr& attr, char* p) : Record(p) {
sample_id.ReadFromBinaryFormat(attr, p, end);
}
-MmapRecord::MmapRecord(const perf_event_attr& attr, bool in_kernel,
- uint32_t pid, uint32_t tid, uint64_t addr, uint64_t len,
- uint64_t pgoff, const std::string& filename,
+MmapRecord::MmapRecord(const perf_event_attr& attr, bool in_kernel, uint32_t pid, uint32_t tid,
+ uint64_t addr, uint64_t len, uint64_t pgoff, const std::string& filename,
uint64_t event_id, uint64_t time) {
- SetTypeAndMisc(PERF_RECORD_MMAP,
- in_kernel ? PERF_RECORD_MISC_KERNEL : PERF_RECORD_MISC_USER);
+ SetTypeAndMisc(PERF_RECORD_MMAP, in_kernel ? PERF_RECORD_MISC_KERNEL : PERF_RECORD_MISC_USER);
sample_id.CreateContent(attr, event_id);
sample_id.time_data.time = time;
MmapRecordDataType data;
@@ -234,10 +236,8 @@ MmapRecord::MmapRecord(const perf_event_attr& attr, bool in_kernel,
SetDataAndFilename(data, filename);
}
-void MmapRecord::SetDataAndFilename(const MmapRecordDataType& data,
- const std::string& filename) {
- SetSize(header_size() + sizeof(data) + Align(filename.size() + 1, 8) +
- sample_id.Size());
+void MmapRecord::SetDataAndFilename(const MmapRecordDataType& data, const std::string& filename) {
+ SetSize(header_size() + sizeof(data) + Align(filename.size() + 1, 8) + sample_id.Size());
char* new_binary = new char[size()];
char* p = new_binary;
MoveToBinaryFormat(header, p);
@@ -251,11 +251,9 @@ void MmapRecord::SetDataAndFilename(const MmapRecordDataType& data,
}
void MmapRecord::DumpData(size_t indent) const {
- PrintIndented(indent,
- "pid %u, tid %u, addr 0x%" PRIx64 ", len 0x%" PRIx64 "\n",
- data->pid, data->tid, data->addr, data->len);
- PrintIndented(indent, "pgoff 0x%" PRIx64 ", filename %s\n", data->pgoff,
- filename);
+ PrintIndented(indent, "pid %u, tid %u, addr 0x%" PRIx64 ", len 0x%" PRIx64 "\n", data->pid,
+ data->tid, data->addr, data->len);
+ PrintIndented(indent, "pgoff 0x%" PRIx64 ", filename %s\n", data->pgoff, filename);
}
Mmap2Record::Mmap2Record(const perf_event_attr& attr, char* p) : Record(p) {
@@ -285,10 +283,8 @@ Mmap2Record::Mmap2Record(const perf_event_attr& attr, bool in_kernel, uint32_t p
SetDataAndFilename(data, filename);
}
-void Mmap2Record::SetDataAndFilename(const Mmap2RecordDataType& data,
- const std::string& filename) {
- SetSize(header_size() + sizeof(data) + Align(filename.size() + 1, 8) +
- sample_id.Size());
+void Mmap2Record::SetDataAndFilename(const Mmap2RecordDataType& data, const std::string& filename) {
+ SetSize(header_size() + sizeof(data) + Align(filename.size() + 1, 8) + sample_id.Size());
char* new_binary = new char[size()];
char* p = new_binary;
MoveToBinaryFormat(header, p);
@@ -302,15 +298,12 @@ void Mmap2Record::SetDataAndFilename(const Mmap2RecordDataType& data,
}
void Mmap2Record::DumpData(size_t indent) const {
- PrintIndented(indent,
- "pid %u, tid %u, addr 0x%" PRIx64 ", len 0x%" PRIx64 "\n",
- data->pid, data->tid, data->addr, data->len);
- PrintIndented(indent, "pgoff 0x%" PRIx64 ", maj %u, min %u, ino %" PRId64
- ", ino_generation %" PRIu64 "\n",
- data->pgoff, data->maj, data->min, data->ino,
- data->ino_generation);
- PrintIndented(indent, "prot %u, flags %u, filename %s\n", data->prot,
- data->flags, filename);
+ PrintIndented(indent, "pid %u, tid %u, addr 0x%" PRIx64 ", len 0x%" PRIx64 "\n", data->pid,
+ data->tid, data->addr, data->len);
+ PrintIndented(
+ indent, "pgoff 0x%" PRIx64 ", maj %u, min %u, ino %" PRId64 ", ino_generation %" PRIu64 "\n",
+ data->pgoff, data->maj, data->min, data->ino, data->ino_generation);
+ PrintIndented(indent, "prot %u, flags %u, filename %s\n", data->prot, data->flags, filename);
}
CommRecord::CommRecord(const perf_event_attr& attr, char* p) : Record(p) {
@@ -332,8 +325,7 @@ CommRecord::CommRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid,
data.tid = tid;
size_t sample_id_size = sample_id.CreateContent(attr, event_id);
sample_id.time_data.time = time;
- SetSize(header_size() + sizeof(data) + Align(comm.size() + 1, 8) +
- sample_id_size);
+ SetSize(header_size() + sizeof(data) + Align(comm.size() + 1, 8) + sample_id_size);
char* new_binary = new char[size()];
char* p = new_binary;
MoveToBinaryFormat(header, p);
@@ -370,12 +362,10 @@ void CommRecord::SetCommandName(const std::string& name) {
}
void CommRecord::DumpData(size_t indent) const {
- PrintIndented(indent, "pid %u, tid %u, comm %s\n", data->pid, data->tid,
- comm);
+ PrintIndented(indent, "pid %u, tid %u, comm %s\n", data->pid, data->tid, comm);
}
-ExitOrForkRecord::ExitOrForkRecord(const perf_event_attr& attr, char* p)
- : Record(p) {
+ExitOrForkRecord::ExitOrForkRecord(const perf_event_attr& attr, char* p) : Record(p) {
const char* end = p + size();
p += header_size();
data = reinterpret_cast<const ExitOrForkRecordDataType*>(p);
@@ -385,12 +375,12 @@ ExitOrForkRecord::ExitOrForkRecord(const perf_event_attr& attr, char* p)
}
void ExitOrForkRecord::DumpData(size_t indent) const {
- PrintIndented(indent, "pid %u, ppid %u, tid %u, ptid %u\n", data->pid,
- data->ppid, data->tid, data->ptid);
+ PrintIndented(indent, "pid %u, ppid %u, tid %u, ptid %u\n", data->pid, data->ppid, data->tid,
+ data->ptid);
}
-ForkRecord::ForkRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid,
- uint32_t ppid, uint32_t ptid, uint64_t event_id) {
+ForkRecord::ForkRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid, uint32_t ppid,
+ uint32_t ptid, uint64_t event_id) {
SetTypeAndMisc(PERF_RECORD_FORK, 0);
ExitOrForkRecordDataType data;
data.pid = pid;
@@ -500,17 +490,16 @@ SampleRecord::SampleRecord(const perf_event_attr& attr, char* p) : Record(p) {
}
}
-SampleRecord::SampleRecord(const perf_event_attr& attr, uint64_t id,
- uint64_t ip, uint32_t pid, uint32_t tid,
- uint64_t time, uint32_t cpu, uint64_t period,
+SampleRecord::SampleRecord(const perf_event_attr& attr, uint64_t id, uint64_t ip, uint32_t pid,
+ uint32_t tid, uint64_t time, uint32_t cpu, uint64_t period,
const std::vector<uint64_t>& ips, const std::vector<char>& stack,
uint64_t dyn_stack_size) {
SetTypeAndMisc(PERF_RECORD_SAMPLE, PERF_RECORD_MISC_USER);
sample_type = attr.sample_type;
- CHECK_EQ(0u, sample_type & ~(PERF_SAMPLE_IP | PERF_SAMPLE_TID
- | PERF_SAMPLE_TIME | PERF_SAMPLE_ID | PERF_SAMPLE_CPU
- | PERF_SAMPLE_PERIOD | PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_REGS_USER
- | PERF_SAMPLE_STACK_USER));
+ CHECK_EQ(0u,
+ sample_type & ~(PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME | PERF_SAMPLE_ID |
+ PERF_SAMPLE_CPU | PERF_SAMPLE_PERIOD | PERF_SAMPLE_CALLCHAIN |
+ PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER));
ip_data.ip = ip;
tid_data.pid = pid;
tid_data.tid = tid;
@@ -601,8 +590,8 @@ SampleRecord::SampleRecord(const perf_event_attr& attr, uint64_t id,
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 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;
BuildBinaryWithNewCallChain(new_size, ips);
}
@@ -662,8 +651,8 @@ void SampleRecord::UpdateUserCallChain(const std::vector<uint64_t>& user_ips) {
// Callchain isn't changed.
return;
}
- size_t new_size = size() + (kernel_ip_count + 1 + user_ips.size() - callchain_data.ip_nr) *
- sizeof(uint64_t);
+ size_t new_size =
+ size() + (kernel_ip_count + 1 + user_ips.size() - callchain_data.ip_nr) * sizeof(uint64_t);
callchain_data.ip_nr = kernel_ip_count;
BuildBinaryWithNewCallChain(new_size, user_ips);
}
@@ -716,7 +705,7 @@ void SampleRecord::BuildBinaryWithNewCallChain(uint32_t new_size,
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;
+ << "record time " << time_data.time;
if (new_binary != binary_) {
UpdateBinary(new_binary);
}
@@ -763,29 +752,27 @@ void SampleRecord::DumpData(size_t indent) const {
}
}
if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
- PrintIndented(indent, "branch_stack nr=%" PRIu64 "\n",
- branch_stack_data.stack_nr);
+ PrintIndented(indent, "branch_stack nr=%" PRIu64 "\n", branch_stack_data.stack_nr);
for (uint64_t i = 0; i < branch_stack_data.stack_nr; ++i) {
auto& item = branch_stack_data.stack[i];
- PrintIndented(indent + 1, "from 0x%" PRIx64 ", to 0x%" PRIx64
- ", flags 0x%" PRIx64 "\n",
+ PrintIndented(indent + 1, "from 0x%" PRIx64 ", to 0x%" PRIx64 ", flags 0x%" PRIx64 "\n",
item.from, item.to, item.flags);
}
}
if (sample_type & PERF_SAMPLE_REGS_USER) {
PrintIndented(indent, "user regs: abi=%" PRId64 "\n", regs_user_data.abi);
- for (size_t i = 0, pos = 0; i < 64; ++i) {
- if ((regs_user_data.reg_mask >> i) & 1) {
- PrintIndented(
- indent + 1, "reg (%s) 0x%016" PRIx64 "\n",
- GetRegName(i, ScopedCurrentArch::GetCurrentArch()).c_str(),
- regs_user_data.regs[pos++]);
+ RegSet regs(regs_user_data.abi, regs_user_data.reg_mask, regs_user_data.regs);
+ for (size_t i = 0; i < 64; ++i) {
+ uint64_t value;
+ if (regs.GetRegValue(i, &value)) {
+ PrintIndented(indent + 1, "reg (%s) 0x%016" PRIx64 "\n", GetRegName(i, regs.arch).c_str(),
+ value);
}
}
}
if (sample_type & PERF_SAMPLE_STACK_USER) {
- PrintIndented(indent, "user stack: size %zu dyn_size %" PRIu64 "\n",
- stack_user_data.size, stack_user_data.dyn_size);
+ PrintIndented(indent, "user stack: size %zu dyn_size %" PRIu64 "\n", stack_user_data.size,
+ stack_user_data.dyn_size);
const uint64_t* p = reinterpret_cast<const uint64_t*>(stack_user_data.data);
const uint64_t* end = p + (stack_user_data.size / sizeof(uint64_t));
while (p < end) {
@@ -799,16 +786,22 @@ void SampleRecord::DumpData(size_t indent) const {
}
}
-uint64_t SampleRecord::Timestamp() const { return time_data.time; }
-uint32_t SampleRecord::Cpu() const { return cpu_data.cpu; }
-uint64_t SampleRecord::Id() const { return id_data.id; }
+uint64_t SampleRecord::Timestamp() const {
+ return time_data.time;
+}
+uint32_t SampleRecord::Cpu() const {
+ return cpu_data.cpu;
+}
+uint64_t SampleRecord::Id() const {
+ return id_data.id;
+}
void SampleRecord::AdjustCallChainGeneratedByKernel() {
// The kernel stores return addrs in the callchain, but we want the addrs of call instructions
// along the callchain.
uint64_t* ips = callchain_data.ips;
- uint64_t context = header.misc == PERF_RECORD_MISC_KERNEL ? PERF_CONTEXT_KERNEL
- : PERF_CONTEXT_USER;
+ uint64_t context =
+ header.misc == PERF_RECORD_MISC_KERNEL ? PERF_CONTEXT_KERNEL : PERF_CONTEXT_USER;
bool first_frame = true;
for (size_t i = 0; i < callchain_data.ip_nr; ++i) {
if (ips[i] < PERF_CONTEXT_MAX) {
@@ -821,7 +814,7 @@ void SampleRecord::AdjustCallChainGeneratedByKernel() {
} else {
// Here we want to change the return addr to the addr of the previous instruction. We
// don't need to find the exact start addr of the previous instruction. A location in
- // [start_addr_of_call_inst, start_addr_of_next_inst) is enough.
+ // [start_addr_of_call_inst, start_addr_of_next_inst) is enough.
#if defined(__arm__) || defined(__aarch64__)
// If we are built for arm/aarch64, this may be a callchain of thumb code. For thumb code,
// the real instruction addr is (ip & ~1), and ip - 2 can used to hit the address range
@@ -852,7 +845,7 @@ std::vector<uint64_t> SampleRecord::GetCallChain(size_t* kernel_ip_count) const
if (ip >= PERF_CONTEXT_MAX) {
switch (ip) {
case PERF_CONTEXT_KERNEL:
- CHECK(in_kernel) << "User space callchain followed by kernel callchain.";
+ in_kernel = true;
break;
case PERF_CONTEXT_USER:
in_kernel = false;
@@ -908,18 +901,17 @@ void BuildIdRecord::DumpData(size_t indent) const {
PrintIndented(indent, "filename %s\n", filename);
}
-BuildIdRecord::BuildIdRecord(bool in_kernel, pid_t pid, const BuildId& build_id,
+BuildIdRecord::BuildIdRecord(bool in_kernel, uint32_t pid, const BuildId& build_id,
const std::string& filename) {
- SetTypeAndMisc(PERF_RECORD_BUILD_ID,
- in_kernel ? PERF_RECORD_MISC_KERNEL : PERF_RECORD_MISC_USER);
+ SetTypeAndMisc(PERF_RECORD_BUILD_ID, in_kernel ? PERF_RECORD_MISC_KERNEL : PERF_RECORD_MISC_USER);
this->pid = pid;
this->build_id = build_id;
- SetSize(header_size() + sizeof(pid) + Align(build_id.Size(), 8) +
+ SetSize(header_size() + sizeof(this->pid) + Align(build_id.Size(), 8) +
Align(filename.size() + 1, 64));
char* new_binary = new char[size()];
char* p = new_binary;
MoveToBinaryFormat(header, p);
- MoveToBinaryFormat(pid, p);
+ MoveToBinaryFormat(this->pid, p);
memcpy(p, build_id.Data(), build_id.Size());
p += Align(build_id.Size(), 8);
this->filename = p;
@@ -1021,8 +1013,7 @@ KernelSymbolRecord::KernelSymbolRecord(char* p) : Record(p) {
}
void KernelSymbolRecord::DumpData(size_t indent) const {
- PrintIndented(indent, "kallsyms: %s\n",
- std::string(kallsyms, kallsyms + kallsyms_size).c_str());
+ PrintIndented(indent, "kallsyms: %s\n", std::string(kallsyms, kallsyms + kallsyms_size).c_str());
}
KernelSymbolRecord::KernelSymbolRecord(const std::string& kallsyms) {
@@ -1049,8 +1040,8 @@ DsoRecord::DsoRecord(char* p) : Record(p) {
CHECK_EQ(p, end);
}
-DsoRecord::DsoRecord(uint64_t dso_type, uint64_t dso_id,
- const std::string& dso_name, uint64_t min_vaddr) {
+DsoRecord::DsoRecord(uint64_t dso_type, uint64_t dso_id, const std::string& dso_name,
+ uint64_t min_vaddr) {
SetTypeAndMisc(SIMPLE_PERF_RECORD_DSO, 0);
this->dso_type = dso_type;
this->dso_id = dso_id;
@@ -1086,8 +1077,7 @@ SymbolRecord::SymbolRecord(char* p) : Record(p) {
CHECK_EQ(p, end);
}
-SymbolRecord::SymbolRecord(uint64_t addr, uint64_t len, const std::string& name,
- uint64_t dso_id) {
+SymbolRecord::SymbolRecord(uint64_t addr, uint64_t len, const std::string& name, uint64_t dso_id) {
SetTypeAndMisc(SIMPLE_PERF_RECORD_SYMBOL, 0);
this->addr = addr;
this->len = len;
@@ -1163,10 +1153,8 @@ EventIdRecord::EventIdRecord(const std::vector<uint64_t>& data) {
void EventIdRecord::DumpData(size_t indent) const {
PrintIndented(indent, "count: %" PRIu64 "\n", count);
for (size_t i = 0; i < count; ++i) {
- PrintIndented(indent, "attr_id[%" PRIu64 "]: %" PRIu64 "\n", i,
- data[i].attr_id);
- PrintIndented(indent, "event_id[%" PRIu64 "]: %" PRIu64 "\n", i,
- data[i].event_id);
+ PrintIndented(indent, "attr_id[%" PRIu64 "]: %" PRIu64 "\n", i, data[i].attr_id);
+ PrintIndented(indent, "event_id[%" PRIu64 "]: %" PRIu64 "\n", i, data[i].event_id);
}
}
@@ -1214,10 +1202,18 @@ CallChainRecord::CallChainRecord(pid_t pid, pid_t tid, CallChainJoiner::ChainTyp
void CallChainRecord::DumpData(size_t indent) const {
const char* type_name = "";
switch (chain_type) {
- case CallChainJoiner::ORIGINAL_OFFLINE: type_name = "ORIGINAL_OFFLINE"; break;
- case CallChainJoiner::ORIGINAL_REMOTE: type_name = "ORIGINAL_REMOTE"; break;
- case CallChainJoiner::JOINED_OFFLINE: type_name = "JOINED_OFFLINE"; break;
- case CallChainJoiner::JOINED_REMOTE: type_name = "JOINED_REMOTE"; break;
+ case CallChainJoiner::ORIGINAL_OFFLINE:
+ type_name = "ORIGINAL_OFFLINE";
+ break;
+ case CallChainJoiner::ORIGINAL_REMOTE:
+ type_name = "ORIGINAL_REMOTE";
+ break;
+ case CallChainJoiner::JOINED_OFFLINE:
+ type_name = "JOINED_OFFLINE";
+ break;
+ case CallChainJoiner::JOINED_REMOTE:
+ type_name = "JOINED_REMOTE";
+ break;
}
PrintIndented(indent, "pid %u\n", pid);
PrintIndented(indent, "tid %u\n", tid);
@@ -1234,57 +1230,120 @@ UnwindingResultRecord::UnwindingResultRecord(char* p) : Record(p) {
p += header_size();
MoveFromBinaryFormat(time, p);
MoveFromBinaryFormat(unwinding_result.used_time, p);
- uint64_t stop_reason;
- MoveFromBinaryFormat(stop_reason, p);
- unwinding_result.stop_reason = static_cast<decltype(unwinding_result.stop_reason)>(stop_reason);
- MoveFromBinaryFormat(unwinding_result.stop_info, p);
+ MoveFromBinaryFormat(unwinding_result.error_code, p);
+ MoveFromBinaryFormat(unwinding_result.error_addr, p);
MoveFromBinaryFormat(unwinding_result.stack_start, p);
MoveFromBinaryFormat(unwinding_result.stack_end, p);
- CHECK_EQ(p, end);
+
+ // regs_user_data
+ MoveFromBinaryFormat(regs_user_data.abi, p);
+ MoveFromBinaryFormat(regs_user_data.reg_mask, p);
+ size_t bit_nr = __builtin_popcountll(regs_user_data.reg_mask);
+ regs_user_data.reg_nr = bit_nr;
+ regs_user_data.regs = reinterpret_cast<uint64_t*>(p);
+ p += bit_nr * sizeof(uint64_t);
+
+ // stack_user_data
+ MoveFromBinaryFormat(stack_user_data.size, p);
+ if (stack_user_data.size == 0) {
+ stack_user_data.dyn_size = 0;
+ } else {
+ stack_user_data.data = p;
+ p += stack_user_data.size;
+ MoveFromBinaryFormat(stack_user_data.dyn_size, p);
+ }
+
+ // callchain
+ if (p < end) {
+ MoveFromBinaryFormat(callchain.length, p);
+ callchain.ips = reinterpret_cast<uint64_t*>(p);
+ p += callchain.length * sizeof(uint64_t);
+ callchain.sps = reinterpret_cast<uint64_t*>(p);
+ p += callchain.length * sizeof(uint64_t);
+ }
+ CHECK_LE(p, end);
}
-UnwindingResultRecord::UnwindingResultRecord(uint64_t time,
- const UnwindingResult& unwinding_result) {
+UnwindingResultRecord::UnwindingResultRecord(uint64_t time, const UnwindingResult& unwinding_result,
+ const PerfSampleRegsUserType& regs_user_data,
+ const PerfSampleStackUserType& stack_user_data,
+ const std::vector<uint64_t>& ips,
+ const std::vector<uint64_t>& sps) {
SetTypeAndMisc(SIMPLE_PERF_RECORD_UNWINDING_RESULT, 0);
- SetSize(header_size() + 6 * sizeof(uint64_t));
+ uint32_t size = header_size() + 6 * sizeof(uint64_t);
+ size += (2 + regs_user_data.reg_nr) * sizeof(uint64_t);
+ size +=
+ stack_user_data.size == 0 ? sizeof(uint64_t) : (2 * sizeof(uint64_t) + stack_user_data.size);
+ CHECK_EQ(ips.size(), sps.size());
+ size += (1 + ips.size() * 2) * sizeof(uint64_t);
+ SetSize(size);
this->time = time;
this->unwinding_result = unwinding_result;
- char* new_binary = new char[size()];
+ char* new_binary = new char[size];
char* p = new_binary;
MoveToBinaryFormat(header, p);
MoveToBinaryFormat(this->time, p);
MoveToBinaryFormat(unwinding_result.used_time, p);
- uint64_t stop_reason = unwinding_result.stop_reason;
- MoveToBinaryFormat(stop_reason, p);
- MoveToBinaryFormat(unwinding_result.stop_info, p);
+ MoveToBinaryFormat(unwinding_result.error_code, p);
+ MoveToBinaryFormat(unwinding_result.error_addr, p);
MoveToBinaryFormat(unwinding_result.stack_start, p);
MoveToBinaryFormat(unwinding_result.stack_end, p);
+ MoveToBinaryFormat(regs_user_data.abi, p);
+ MoveToBinaryFormat(regs_user_data.reg_mask, p);
+ if (regs_user_data.reg_nr > 0) {
+ MoveToBinaryFormat(regs_user_data.regs, regs_user_data.reg_nr, p);
+ }
+ MoveToBinaryFormat(stack_user_data.size, p);
+ if (stack_user_data.size > 0) {
+ MoveToBinaryFormat(stack_user_data.data, stack_user_data.size, p);
+ MoveToBinaryFormat(stack_user_data.dyn_size, p);
+ }
+ MoveToBinaryFormat(static_cast<uint64_t>(ips.size()), p);
+ MoveToBinaryFormat(ips.data(), ips.size(), p);
+ MoveToBinaryFormat(sps.data(), sps.size(), p);
+ CHECK_EQ(p, new_binary + size);
UpdateBinary(new_binary);
}
void UnwindingResultRecord::DumpData(size_t indent) const {
PrintIndented(indent, "time %" PRIu64 "\n", time);
PrintIndented(indent, "used_time %" PRIu64 "\n", unwinding_result.used_time);
- static std::unordered_map<int, std::string> map = {
- {UnwindingResult::UNKNOWN_REASON, "UNKNOWN_REASON"},
- {UnwindingResult::EXCEED_MAX_FRAMES_LIMIT, "EXCEED_MAX_FRAME_LIMIT"},
- {UnwindingResult::ACCESS_REG_FAILED, "ACCESS_REG_FAILED"},
- {UnwindingResult::ACCESS_STACK_FAILED, "ACCESS_STACK_FAILED"},
- {UnwindingResult::ACCESS_MEM_FAILED, "ACCESS_MEM_FAILED"},
- {UnwindingResult::FIND_PROC_INFO_FAILED, "FIND_PROC_INFO_FAILED"},
- {UnwindingResult::EXECUTE_DWARF_INSTRUCTION_FAILED, "EXECUTE_DWARF_INSTRUCTION_FAILED"},
- {UnwindingResult::DIFFERENT_ARCH, "DIFFERENT_ARCH"},
- {UnwindingResult::MAP_MISSING, "MAP_MISSING"},
- };
- PrintIndented(indent, "stop_reason %s\n", map[unwinding_result.stop_reason].c_str());
- if (unwinding_result.stop_reason == UnwindingResult::ACCESS_REG_FAILED) {
- PrintIndented(indent, "regno %" PRIu64 "\n", unwinding_result.stop_info);
- } else if (unwinding_result.stop_reason == UnwindingResult::ACCESS_STACK_FAILED ||
- unwinding_result.stop_reason == UnwindingResult::ACCESS_MEM_FAILED) {
- PrintIndented(indent, "addr 0x%" PRIx64 "\n", unwinding_result.stop_info);
- }
+ PrintIndented(indent, "error_code %" PRIu64 "\n", unwinding_result.error_code);
+ PrintIndented(indent, "error_addr 0x%" PRIx64 "\n", unwinding_result.error_addr);
PrintIndented(indent, "stack_start 0x%" PRIx64 "\n", unwinding_result.stack_start);
PrintIndented(indent, "stack_end 0x%" PRIx64 "\n", unwinding_result.stack_end);
+ if (regs_user_data.reg_nr > 0) {
+ PrintIndented(indent, "user regs: abi=%" PRId64 "\n", regs_user_data.abi);
+ RegSet regs(regs_user_data.abi, regs_user_data.reg_mask, regs_user_data.regs);
+ for (size_t i = 0; i < 64; ++i) {
+ uint64_t value;
+ if (regs.GetRegValue(i, &value)) {
+ PrintIndented(indent + 1, "reg (%s) 0x%016" PRIx64 "\n", GetRegName(i, regs.arch).c_str(),
+ value);
+ }
+ }
+ }
+ if (stack_user_data.size > 0) {
+ PrintIndented(indent, "user stack: size %zu dyn_size %" PRIu64 "\n", stack_user_data.size,
+ stack_user_data.dyn_size);
+ const uint64_t* p = reinterpret_cast<const uint64_t*>(stack_user_data.data);
+ const uint64_t* end = p + (stack_user_data.size / sizeof(uint64_t));
+ while (p < end) {
+ PrintIndented(indent + 1, "");
+ for (size_t i = 0; i < 4 && p < end; ++i, ++p) {
+ printf(" %016" PRIx64, *p);
+ }
+ printf("\n");
+ }
+ printf("\n");
+ }
+ if (callchain.length > 0) {
+ PrintIndented(indent, "callchain length=%" PRIu64 ":\n", callchain.length);
+ for (uint64_t i = 0; i < callchain.length; i++) {
+ PrintIndented(indent + 1, "ip_%" PRIu64 ": 0x%" PRIx64 "\n", i + 1, callchain.ips[i]);
+ PrintIndented(indent + 1, "sp_%" PRIu64 ": 0x%" PRIx64 "\n", i + 1, callchain.sps[i]);
+ }
+ }
}
UnknownRecord::UnknownRecord(char* p) : Record(p) {
@@ -1337,8 +1396,8 @@ std::unique_ptr<Record> ReadRecordFromBuffer(const perf_event_attr& attr, uint32
}
}
-std::unique_ptr<Record> ReadRecordFromOwnedBuffer(const perf_event_attr& attr,
- uint32_t type, char* p) {
+std::unique_ptr<Record> ReadRecordFromOwnedBuffer(const perf_event_attr& attr, uint32_t type,
+ char* p) {
std::unique_ptr<Record> record = ReadRecordFromBuffer(attr, type, p);
if (record != nullptr) {
record->OwnBinary();
@@ -1348,8 +1407,8 @@ std::unique_ptr<Record> ReadRecordFromOwnedBuffer(const perf_event_attr& attr,
return record;
}
-std::vector<std::unique_ptr<Record>> ReadRecordsFromBuffer(
- const perf_event_attr& attr, char* buf, size_t buf_size) {
+std::vector<std::unique_ptr<Record>> ReadRecordsFromBuffer(const perf_event_attr& attr, char* buf,
+ size_t buf_size) {
std::vector<std::unique_ptr<Record>> result;
char* p = buf;
char* end = buf + buf_size;
@@ -1367,3 +1426,5 @@ std::unique_ptr<Record> ReadRecordFromBuffer(const perf_event_attr& attr, char*
auto header = reinterpret_cast<const perf_event_header*>(p);
return ReadRecordFromBuffer(attr, header->type, p);
}
+
+} // namespace simpleperf
diff --git a/simpleperf/record.h b/simpleperf/record.h
index 163d5208..0ba8d4aa 100644
--- a/simpleperf/record.h
+++ b/simpleperf/record.h
@@ -27,11 +27,13 @@
#include <android-base/logging.h>
-#include "build_id.h"
#include "CallChainJoiner.h"
#include "OfflineUnwinder.h"
+#include "build_id.h"
#include "perf_event.h"
+namespace simpleperf {
+
enum user_record_type {
PERF_RECORD_USER_DEFINED_TYPE_START = 64,
PERF_RECORD_ATTR = 64,
@@ -66,9 +68,8 @@ struct simpleperf_record_header {
uint16_t size0;
};
-static_assert(
- sizeof(simpleperf_record_header) == sizeof(perf_event_header),
- "simpleperf_record_header should have the same size as perf_event_header");
+static_assert(sizeof(simpleperf_record_header) == sizeof(perf_event_header),
+ "simpleperf_record_header should have the same size as perf_event_header");
struct PerfSampleIpType {
uint64_t ip;
@@ -184,12 +185,11 @@ struct SampleId {
bool sample_id_all;
uint64_t sample_type;
- PerfSampleTidType tid_data; // Valid if sample_id_all && PERF_SAMPLE_TID.
- PerfSampleTimeType time_data; // Valid if sample_id_all && PERF_SAMPLE_TIME.
- PerfSampleIdType id_data; // Valid if sample_id_all && PERF_SAMPLE_ID.
- PerfSampleStreamIdType
- stream_id_data; // Valid if sample_id_all && PERF_SAMPLE_STREAM_ID.
- PerfSampleCpuType cpu_data; // Valid if sample_id_all && PERF_SAMPLE_CPU.
+ PerfSampleTidType tid_data; // Valid if sample_id_all && PERF_SAMPLE_TID.
+ PerfSampleTimeType time_data; // Valid if sample_id_all && PERF_SAMPLE_TIME.
+ PerfSampleIdType id_data; // Valid if sample_id_all && PERF_SAMPLE_ID.
+ PerfSampleStreamIdType stream_id_data; // Valid if sample_id_all && PERF_SAMPLE_STREAM_ID.
+ PerfSampleCpuType cpu_data; // Valid if sample_id_all && PERF_SAMPLE_CPU.
SampleId();
@@ -197,8 +197,7 @@ struct SampleId {
size_t CreateContent(const perf_event_attr& attr, uint64_t event_id);
// Parse sample_id from binary format in the buffer pointed by p.
- void ReadFromBinaryFormat(const perf_event_attr& attr, const char* p,
- const char* end);
+ void ReadFromBinaryFormat(const perf_event_attr& attr, const char* p, const char* end);
// Write the binary format of sample_id to the buffer pointed by p.
void WriteToBinaryFormat(char*& p) const;
@@ -238,8 +237,8 @@ struct Record {
static uint32_t header_size() { return sizeof(perf_event_header); }
bool InKernel() const {
- return (header.misc & PERF_RECORD_MISC_CPUMODE_MASK) ==
- PERF_RECORD_MISC_KERNEL;
+ uint16_t cpumode = header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+ return cpumode == PERF_RECORD_MISC_KERNEL || cpumode == PERF_RECORD_MISC_GUEST_KERNEL;
}
void SetTypeAndMisc(uint32_t type, uint16_t misc) {
@@ -280,12 +279,11 @@ struct MmapRecord : public Record {
MmapRecord(const perf_event_attr& attr, char* p);
- MmapRecord(const perf_event_attr& attr, bool in_kernel, uint32_t pid,
- uint32_t tid, uint64_t addr, uint64_t len, uint64_t pgoff,
- const std::string& filename, uint64_t event_id, uint64_t time = 0);
+ MmapRecord(const perf_event_attr& attr, bool in_kernel, uint32_t pid, uint32_t tid, uint64_t addr,
+ uint64_t len, uint64_t pgoff, const std::string& filename, uint64_t event_id,
+ uint64_t time = 0);
- void SetDataAndFilename(const MmapRecordDataType& data,
- const std::string& filename);
+ void SetDataAndFilename(const MmapRecordDataType& data, const std::string& filename);
protected:
void DumpData(size_t indent) const override;
@@ -311,8 +309,7 @@ struct Mmap2Record : public Record {
uint64_t addr, uint64_t len, uint64_t pgoff, uint32_t prot,
const std::string& filename, uint64_t event_id, uint64_t time = 0);
- void SetDataAndFilename(const Mmap2RecordDataType& data,
- const std::string& filename);
+ void SetDataAndFilename(const Mmap2RecordDataType& data, const std::string& filename);
protected:
void DumpData(size_t indent) const override;
@@ -327,8 +324,8 @@ struct CommRecord : public Record {
CommRecord(const perf_event_attr& attr, char* p);
- CommRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid,
- const std::string& comm, uint64_t event_id, uint64_t time);
+ CommRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid, const std::string& comm,
+ uint64_t event_id, uint64_t time);
void SetCommandName(const std::string& name);
@@ -353,16 +350,14 @@ struct ExitOrForkRecord : public Record {
};
struct ExitRecord : public ExitOrForkRecord {
- ExitRecord(const perf_event_attr& attr, char* p)
- : ExitOrForkRecord(attr, p) {}
+ ExitRecord(const perf_event_attr& attr, char* p) : ExitOrForkRecord(attr, p) {}
};
struct ForkRecord : public ExitOrForkRecord {
- ForkRecord(const perf_event_attr& attr, char* p)
- : ExitOrForkRecord(attr, p) {}
+ ForkRecord(const perf_event_attr& attr, char* p) : ExitOrForkRecord(attr, p) {}
- ForkRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid,
- uint32_t ppid, uint32_t ptid, uint64_t event_id);
+ ForkRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid, uint32_t ppid, uint32_t ptid,
+ uint64_t event_id);
};
struct LostRecord : public Record {
@@ -388,17 +383,15 @@ struct SampleRecord : public Record {
PerfSampleCpuType cpu_data; // Valid if PERF_SAMPLE_CPU.
PerfSamplePeriodType period_data; // Valid if PERF_SAMPLE_PERIOD.
- PerfSampleCallChainType callchain_data; // Valid if PERF_SAMPLE_CALLCHAIN.
- PerfSampleRawType raw_data; // Valid if PERF_SAMPLE_RAW.
- PerfSampleBranchStackType
- branch_stack_data; // Valid if PERF_SAMPLE_BRANCH_STACK.
- PerfSampleRegsUserType regs_user_data; // Valid if PERF_SAMPLE_REGS_USER.
- PerfSampleStackUserType stack_user_data; // Valid if PERF_SAMPLE_STACK_USER.
+ PerfSampleCallChainType callchain_data; // Valid if PERF_SAMPLE_CALLCHAIN.
+ PerfSampleRawType raw_data; // Valid if PERF_SAMPLE_RAW.
+ PerfSampleBranchStackType branch_stack_data; // Valid if PERF_SAMPLE_BRANCH_STACK.
+ PerfSampleRegsUserType regs_user_data; // Valid if PERF_SAMPLE_REGS_USER.
+ PerfSampleStackUserType stack_user_data; // Valid if PERF_SAMPLE_STACK_USER.
SampleRecord(const perf_event_attr& attr, char* p);
- SampleRecord(const perf_event_attr& attr, uint64_t id, uint64_t ip,
- uint32_t pid, uint32_t tid, uint64_t time, uint32_t cpu,
- uint64_t period, const std::vector<uint64_t>& ips,
+ SampleRecord(const perf_event_attr& attr, uint64_t id, uint64_t ip, uint32_t pid, uint32_t tid,
+ uint64_t time, uint32_t cpu, uint64_t period, const std::vector<uint64_t>& ips,
const std::vector<char>& stack, uint64_t dyn_stack_size);
void ReplaceRegAndStackWithCallChain(const std::vector<uint64_t>& ips);
@@ -430,7 +423,7 @@ struct AuxRecord : public Record {
uint64_t aux_offset;
uint64_t aux_size;
uint64_t flags;
- }* data;
+ } * data;
AuxRecord(const perf_event_attr& attr, char* p);
@@ -447,8 +440,7 @@ struct BuildIdRecord : public Record {
explicit BuildIdRecord(char* p);
- BuildIdRecord(bool in_kernel, pid_t pid, const BuildId& build_id,
- const std::string& filename);
+ BuildIdRecord(bool in_kernel, uint32_t pid, const BuildId& build_id, const std::string& filename);
protected:
void DumpData(size_t indent) const override;
@@ -479,7 +471,7 @@ struct AuxTraceInfoRecord : public Record {
uint32_t pmu_type;
uint64_t snapshot;
ETM4Info etm4_info[0];
- }* data;
+ } * data;
explicit AuxTraceInfoRecord(char* p);
AuxTraceInfoRecord(const DataType& data, const std::vector<ETM4Info>& etm4_info);
@@ -534,8 +526,7 @@ struct DsoRecord : public Record {
explicit DsoRecord(char* p);
- DsoRecord(uint64_t dso_type, uint64_t dso_id, const std::string& dso_name,
- uint64_t min_vaddr);
+ DsoRecord(uint64_t dso_type, uint64_t dso_id, const std::string& dso_name, uint64_t min_vaddr);
protected:
void DumpData(size_t indent) const override;
@@ -549,8 +540,7 @@ struct SymbolRecord : public Record {
explicit SymbolRecord(char* p);
- SymbolRecord(uint64_t addr, uint64_t len, const std::string& name,
- uint64_t dso_id);
+ SymbolRecord(uint64_t addr, uint64_t len, const std::string& name, uint64_t dso_id);
protected:
void DumpData(size_t indent) const override;
@@ -597,9 +587,7 @@ struct CallChainRecord : public Record {
CallChainRecord(pid_t pid, pid_t tid, simpleperf::CallChainJoiner::ChainType type, uint64_t time,
const std::vector<uint64_t>& ips, const std::vector<uint64_t>& sps);
- uint64_t Timestamp() const override {
- return time;
- }
+ uint64_t Timestamp() const override { return time; }
protected:
void DumpData(size_t indent) const override;
@@ -607,15 +595,24 @@ struct CallChainRecord : public Record {
struct UnwindingResultRecord : public Record {
uint64_t time;
- simpleperf::UnwindingResult unwinding_result;
+ UnwindingResult unwinding_result;
+ PerfSampleRegsUserType regs_user_data;
+ PerfSampleStackUserType stack_user_data;
+
+ struct CallChain {
+ uint64_t length = 0;
+ uint64_t* ips = nullptr;
+ uint64_t* sps = nullptr;
+ } callchain;
explicit UnwindingResultRecord(char* p);
- UnwindingResultRecord(uint64_t time, const simpleperf::UnwindingResult& unwinding_result);
+ UnwindingResultRecord(uint64_t time, const simpleperf::UnwindingResult& unwinding_result,
+ const PerfSampleRegsUserType& regs_user_data,
+ const PerfSampleStackUserType& stack_user_data,
+ const std::vector<uint64_t>& ips, const std::vector<uint64_t>& sps);
- uint64_t Timestamp() const override {
- return time;
- }
+ uint64_t Timestamp() const override { return time; }
protected:
void DumpData(size_t indent) const override;
@@ -637,16 +634,18 @@ struct UnknownRecord : public Record {
std::unique_ptr<Record> ReadRecordFromBuffer(const perf_event_attr& attr, uint32_t type, char* p);
// Read record from the buffer pointed by [p]. And the record owns the buffer.
-std::unique_ptr<Record> ReadRecordFromOwnedBuffer(const perf_event_attr& attr,
- uint32_t type, char* p);
+std::unique_ptr<Record> ReadRecordFromOwnedBuffer(const perf_event_attr& attr, uint32_t type,
+ char* p);
// Read records from the buffer pointed by [buf]. None of the records own
// the buffer.
-std::vector<std::unique_ptr<Record>> ReadRecordsFromBuffer(
- const perf_event_attr& attr, char* buf, size_t buf_size);
+std::vector<std::unique_ptr<Record>> ReadRecordsFromBuffer(const perf_event_attr& attr, char* buf,
+ size_t buf_size);
// Read one record from the buffer pointed by [p]. But the record doesn't
// own the buffer.
std::unique_ptr<Record> ReadRecordFromBuffer(const perf_event_attr& attr, char* p);
+} // namespace simpleperf
+
#endif // SIMPLE_PERF_RECORD_H_
diff --git a/simpleperf/record_equal_test.h b/simpleperf/record_equal_test.h
index a939740b..8e27e1ea 100644
--- a/simpleperf/record_equal_test.h
+++ b/simpleperf/record_equal_test.h
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+namespace simpleperf {
+
static void CheckMmapRecordDataEqual(const MmapRecord& r1, const MmapRecord& r2) {
ASSERT_EQ(0, memcmp(r1.data, r2.data, sizeof(*r1.data)));
ASSERT_STREQ(r1.filename, r2.filename);
@@ -69,8 +71,8 @@ static void CheckSampleRecordDataEqual(const SampleRecord& r1, const SampleRecor
ASSERT_EQ(r1.stack_user_data.size, r2.stack_user_data.size);
if (r1.stack_user_data.size > 0) {
ASSERT_EQ(r1.stack_user_data.dyn_size, r2.stack_user_data.dyn_size);
- ASSERT_EQ(0, memcmp(r1.stack_user_data.data, r2.stack_user_data.data,
- r1.stack_user_data.size));
+ ASSERT_EQ(0,
+ memcmp(r1.stack_user_data.data, r2.stack_user_data.data, r1.stack_user_data.size));
}
}
}
@@ -86,11 +88,15 @@ static void CheckRecordEqual(const Record& r1, const Record& r2) {
}
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));
+ CheckMmapRecordDataEqual(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));
+ CheckCommRecordDataEqual(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));
}
}
+
+} // namespace simpleperf
diff --git a/simpleperf/record_file.h b/simpleperf/record_file.h
index a9fa1c32..76f70436 100644
--- a/simpleperf/record_file.h
+++ b/simpleperf/record_file.h
@@ -22,6 +22,7 @@
#include <functional>
#include <map>
#include <memory>
+#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
@@ -36,6 +37,29 @@
#include "record_file_format.h"
#include "thread_tree.h"
+namespace simpleperf {
+
+struct FileFeature {
+ std::string path;
+ DsoType type;
+ uint64_t min_vaddr;
+ uint64_t file_offset_of_min_vaddr;
+ std::vector<Symbol> symbols; // used for reading symbols
+ std::vector<const Symbol*> symbol_ptrs; // used for writing symbols
+ std::vector<uint64_t> dex_file_offsets;
+
+ FileFeature() {}
+
+ DISALLOW_COPY_AND_ASSIGN(FileFeature);
+};
+
+struct DebugUnwindFile {
+ std::string path;
+ uint64_t size;
+};
+
+using DebugUnwindFeature = std::vector<DebugUnwindFile>;
+
// RecordFileWriter writes to a perf record file, like perf.data.
// User should call RecordFileWriter::Close() to finish writing the file, otherwise the file will
// be removed in RecordFileWriter::~RecordFileWriter().
@@ -43,10 +67,13 @@ class RecordFileWriter {
public:
static std::unique_ptr<RecordFileWriter> CreateInstance(const std::string& filename);
+ // If own_fp = true, close fp when we finish writing.
+ RecordFileWriter(const std::string& filename, FILE* fp, bool own_fp);
~RecordFileWriter();
bool WriteAttrSection(const std::vector<EventAttrWithId>& attr_ids);
bool WriteRecord(const Record& record);
+ bool WriteData(const void* buf, size_t len);
uint64_t GetDataSectionSize() const { return data_section_size_; }
bool ReadDataSection(const std::function<void(const Record*)>& callback);
@@ -57,35 +84,30 @@ class RecordFileWriter {
bool WriteCmdlineFeature(const std::vector<std::string>& cmdline);
bool WriteBranchStackFeature();
bool WriteAuxTraceFeature(const std::vector<uint64_t>& auxtrace_offset);
- bool WriteFileFeatures(const std::vector<Dso*>& files);
+ bool WriteFileFeatures(const std::vector<Dso*>& dsos);
+ bool WriteFileFeature(const FileFeature& file);
bool WriteMetaInfoFeature(const std::unordered_map<std::string, std::string>& info_map);
- bool WriteFeature(int feature, const std::vector<char>& data);
+ bool WriteDebugUnwindFeature(const DebugUnwindFeature& debug_unwind);
+ bool WriteFeature(int feature, const char* data, size_t size);
bool EndWriteFeatures();
bool Close();
private:
- RecordFileWriter(const std::string& filename, FILE* fp);
void GetHitModulesInBuffer(const char* p, const char* end,
std::vector<std::string>* hit_kernel_modules,
std::vector<std::string>* hit_user_files);
bool WriteFileHeader();
- bool WriteData(const void* buf, size_t len);
bool Write(const void* buf, size_t len);
bool Read(void* buf, size_t len);
bool GetFilePos(uint64_t* file_pos);
bool WriteStringWithLength(const std::string& s);
- bool WriteFileFeature(const std::string& file_path,
- uint32_t file_type,
- uint64_t min_vaddr,
- uint64_t file_offset_of_min_vaddr,
- const std::vector<const Symbol*>& symbols,
- const std::vector<uint64_t>* dex_file_offsets);
bool WriteFeatureBegin(int feature);
bool WriteFeatureEnd(int feature);
const std::string filename_;
FILE* record_fp_;
+ bool own_fp_;
perf_event_attr event_attr_;
uint64_t attr_section_offset_;
@@ -107,9 +129,7 @@ class RecordFileReader {
~RecordFileReader();
- const PerfFileFormat::FileHeader& FileHeader() const {
- return header_;
- }
+ const PerfFileFormat::FileHeader& FileHeader() const { return header_; }
std::vector<EventAttrWithId> AttrSection() const {
std::vector<EventAttrWithId> result(file_attrs_.size());
@@ -120,6 +140,8 @@ class RecordFileReader {
return result;
}
+ const std::unordered_map<uint64_t, size_t>& EventIdMap() const { return event_id_to_attr_map_; }
+
const std::map<int, PerfFileFormat::SectionDesc>& FeatureSectionDescriptors() const {
return feature_section_descriptors_;
}
@@ -127,6 +149,7 @@ class RecordFileReader {
return feature_section_descriptors_.find(feature) != feature_section_descriptors_.end();
}
bool ReadFeatureSection(int feature, std::vector<char>* data);
+ bool ReadFeatureSection(int feature, std::string* data);
// There are two ways to read records in data section: one is by calling
// ReadDataSection(), and [callback] is called for each Record. the other
@@ -134,6 +157,7 @@ class RecordFileReader {
// If sorted is true, sort records before passing them to callback function.
bool ReadDataSection(const std::function<bool(std::unique_ptr<Record>)>& callback);
+ bool ReadAtOffset(uint64_t offset, void* buf, size_t len);
// Read next record. If read successfully, set [record] and return true.
// If there is no more records, set [record] to nullptr and return true.
@@ -152,11 +176,10 @@ class RecordFileReader {
// call, and is updated to point to the next file information. Return true
// if read successfully, and return false if there is no more file
// information.
- bool ReadFileFeature(size_t& read_pos, std::string* file_path, uint32_t* file_type,
- uint64_t* min_vaddr, uint64_t* file_offset_of_min_vaddr,
- std::vector<Symbol>* symbols, std::vector<uint64_t>* dex_file_offsets);
+ bool ReadFileFeature(size_t& read_pos, FileFeature* file);
const std::unordered_map<std::string, std::string>& GetMetaInfoFeature() { return meta_info_; }
+ std::optional<DebugUnwindFeature> ReadDebugUnwindFeature();
void LoadBuildIdAndFileFeatures(ThreadTree& thread_tree);
@@ -177,7 +200,6 @@ class RecordFileReader {
void UseRecordingEnvironment();
std::unique_ptr<Record> ReadRecord();
bool Read(void* buf, size_t len);
- bool ReadAtOffset(uint64_t offset, void* buf, size_t len);
void ProcessEventIdRecord(const EventIdRecord& r);
bool BuildAuxDataLocation();
@@ -214,4 +236,8 @@ class RecordFileReader {
DISALLOW_COPY_AND_ASSIGN(RecordFileReader);
};
+bool IsPerfDataFile(const std::string& filename);
+
+} // namespace simpleperf
+
#endif // SIMPLE_PERF_RECORD_FILE_H_
diff --git a/simpleperf/record_file.proto b/simpleperf/record_file.proto
new file mode 100644
index 00000000..752c1f6b
--- /dev/null
+++ b/simpleperf/record_file.proto
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+// message types used in perf.data.
+
+syntax = "proto3";
+
+package simpleperf.proto;
+
+
+message DebugUnwindFeature {
+ message File {
+ string path = 1;
+ uint64 size = 2;
+ }
+
+ repeated File file = 1;
+}
diff --git a/simpleperf/record_file_format.h b/simpleperf/record_file_format.h
index 4606abcc..07a489c8 100644
--- a/simpleperf/record_file_format.h
+++ b/simpleperf/record_file_format.h
@@ -53,6 +53,7 @@ file feature section:
uint32_t dex_file_offset_count; // Only when file_type = DSO_DEX_FILE
uint64_t dex_file_offsets[dex_file_offset_count]; // Only when file_type = DSO_DEX_FILE
uint64_t file_offset_of_min_vaddr; // Only when file_type = DSO_ELF_FILE
+ uint64_t memory_offset_of_min_vaddr; // Only when file_type = DSO_KERNEL_MODULE
};
meta_info feature section:
@@ -65,8 +66,18 @@ meta_info feature section:
keys in meta_info feature section include:
simpleperf_version,
+debug_unwind feature section:
+ message DebugUnwindSection from record_file.proto
+
+debug_unwind_file feature section:
+ data for file 1
+ data for file 2
+ ...
+
+ The file list is stored in debug_unwind feature section.
*/
+namespace simpleperf {
namespace PerfFileFormat {
enum {
@@ -95,6 +106,8 @@ enum {
FEAT_SIMPLEPERF_START = 128,
FEAT_FILE = FEAT_SIMPLEPERF_START,
FEAT_META_INFO,
+ FEAT_DEBUG_UNWIND,
+ FEAT_DEBUG_UNWIND_FILE,
FEAT_MAX_NUM = 256,
};
@@ -124,5 +137,6 @@ struct FileAttr {
};
} // namespace PerfFileFormat
+} // namespace simpleperf
#endif // SIMPLE_PERF_RECORD_FILE_FORMAT_H_
diff --git a/simpleperf/record_file_reader.cpp b/simpleperf/record_file_reader.cpp
index b17086a3..bba96830 100644
--- a/simpleperf/record_file_reader.cpp
+++ b/simpleperf/record_file_reader.cpp
@@ -25,8 +25,11 @@
#include "event_attr.h"
#include "record.h"
+#include "system/extras/simpleperf/record_file.pb.h"
#include "utils.h"
+namespace simpleperf {
+
using namespace PerfFileFormat;
namespace PerfFileFormat {
@@ -52,6 +55,8 @@ static const std::map<int, std::string> feature_name_map = {
{FEAT_AUXTRACE, "auxtrace"},
{FEAT_FILE, "file"},
{FEAT_META_INFO, "meta_info"},
+ {FEAT_DEBUG_UNWIND, "debug_unwind"},
+ {FEAT_DEBUG_UNWIND_FILE, "debug_unwind_file"},
};
std::string GetFeatureName(int feature_id) {
@@ -68,7 +73,7 @@ int GetFeatureId(const std::string& feature_name) {
return -1;
}
-} // namespace PerfFileFormat
+} // namespace PerfFileFormat
std::unique_ptr<RecordFileReader> RecordFileReader::CreateInstance(const std::string& filename) {
std::string mode = std::string("rb") + CLOSE_ON_EXEC_MODE;
@@ -87,9 +92,11 @@ std::unique_ptr<RecordFileReader> RecordFileReader::CreateInstance(const std::st
}
RecordFileReader::RecordFileReader(const std::string& filename, FILE* fp)
- : filename_(filename), record_fp_(fp), event_id_pos_in_sample_records_(0),
- event_id_reverse_pos_in_non_sample_records_(0), read_record_size_(0) {
-}
+ : filename_(filename),
+ record_fp_(fp),
+ event_id_pos_in_sample_records_(0),
+ event_id_reverse_pos_in_non_sample_records_(0),
+ read_record_size_(0) {}
RecordFileReader::~RecordFileReader() {
if (record_fp_ != nullptr) {
@@ -122,7 +129,7 @@ bool RecordFileReader::ReadAttrSection() {
size_t attr_count = header_.attrs.size / header_.attr_size;
if (header_.attr_size != sizeof(FileAttr)) {
LOG(DEBUG) << "attr size (" << header_.attr_size << ") in " << filename_
- << " doesn't match expected size (" << sizeof(FileAttr) << ")";
+ << " doesn't match expected size (" << sizeof(FileAttr) << ")";
}
if (attr_count == 0) {
LOG(ERROR) << "no attr in file " << filename_;
@@ -153,7 +160,7 @@ bool RecordFileReader::ReadAttrSection() {
attrs.push_back(file_attr.attr);
}
if (!GetCommonEventIdPositionsForAttrs(attrs, &event_id_pos_in_sample_records_,
- &event_id_reverse_pos_in_non_sample_records_)) {
+ &event_id_reverse_pos_in_non_sample_records_)) {
return false;
}
}
@@ -214,7 +221,9 @@ void RecordFileReader::UseRecordingEnvironment() {
}
auto& meta_info = GetMetaInfoFeature();
if (auto it = meta_info.find("event_type_info"); it != meta_info.end()) {
- scoped_event_types_.reset(new ScopedEventTypes(it->second));
+ if (EventTypeManager::Instance().GetScopedFinder() == nullptr) {
+ scoped_event_types_.reset(new ScopedEventTypes(it->second));
+ }
}
}
@@ -308,7 +317,8 @@ std::unique_ptr<Record> RecordFileReader::ReadRecord() {
} else {
if (header.size > event_id_reverse_pos_in_non_sample_records_) {
has_event_id = true;
- event_id = *reinterpret_cast<uint64_t*>(p.get() + header.size - event_id_reverse_pos_in_non_sample_records_);
+ event_id = *reinterpret_cast<uint64_t*>(p.get() + header.size -
+ event_id_reverse_pos_in_non_sample_records_);
}
}
if (has_event_id) {
@@ -379,6 +389,23 @@ bool RecordFileReader::ReadFeatureSection(int feature, std::vector<char>* data)
return true;
}
+bool RecordFileReader::ReadFeatureSection(int feature, std::string* data) {
+ const std::map<int, SectionDesc>& section_map = FeatureSectionDescriptors();
+ auto it = section_map.find(feature);
+ if (it == section_map.end()) {
+ return false;
+ }
+ SectionDesc section = it->second;
+ data->resize(section.size);
+ if (section.size == 0) {
+ return true;
+ }
+ if (!ReadAtOffset(section.offset, data->data(), data->size())) {
+ return false;
+ }
+ return true;
+}
+
std::vector<std::string> RecordFileReader::ReadCmdlineFeature() {
std::vector<char> buf;
if (!ReadFeatureSection(FEAT_CMDLINE, &buf)) {
@@ -444,6 +471,10 @@ std::vector<uint64_t> RecordFileReader::ReadAuxTraceFeature() {
std::vector<uint64_t> auxtrace_offset;
const char* p = buf.data();
const char* end = buf.data() + buf.size();
+ if (buf.size() / sizeof(uint64_t) % 2 == 1) {
+ // Recording files generated by linux perf contain an extra uint64 field. Skip it here.
+ p += sizeof(uint64_t);
+ }
while (p < end) {
uint64_t offset;
uint64_t size;
@@ -455,13 +486,7 @@ std::vector<uint64_t> RecordFileReader::ReadAuxTraceFeature() {
return auxtrace_offset;
}
-bool RecordFileReader::ReadFileFeature(size_t& read_pos,
- std::string* file_path,
- uint32_t* file_type,
- uint64_t* min_vaddr,
- uint64_t* file_offset_of_min_vaddr,
- std::vector<Symbol>* symbols,
- std::vector<uint64_t>* dex_file_offsets) {
+bool RecordFileReader::ReadFileFeature(size_t& read_pos, FileFeature* file) {
auto it = feature_section_descriptors_.find(FEAT_FILE);
if (it == feature_section_descriptors_.end()) {
return false;
@@ -485,14 +510,21 @@ bool RecordFileReader::ReadFileFeature(size_t& read_pos,
}
read_pos += 4 + size;
const char* p = buf.data();
- *file_path = p;
- p += file_path->size() + 1;
- MoveFromBinaryFormat(*file_type, p);
- MoveFromBinaryFormat(*min_vaddr, p);
+ file->path = p;
+ p += file->path.size() + 1;
+ uint32_t file_type;
+ MoveFromBinaryFormat(file_type, p);
+ if (file_type > DSO_UNKNOWN_FILE) {
+ LOG(ERROR) << "unknown file type for " << file->path
+ << " in file feature section: " << file_type;
+ return false;
+ }
+ file->type = static_cast<DsoType>(file_type);
+ MoveFromBinaryFormat(file->min_vaddr, p);
uint32_t symbol_count;
MoveFromBinaryFormat(symbol_count, p);
- symbols->clear();
- symbols->reserve(symbol_count);
+ file->symbols.clear();
+ file->symbols.reserve(symbol_count);
for (uint32_t i = 0; i < symbol_count; ++i) {
uint64_t start_vaddr;
uint32_t len;
@@ -500,20 +532,22 @@ bool RecordFileReader::ReadFileFeature(size_t& read_pos,
MoveFromBinaryFormat(len, p);
std::string name = p;
p += name.size() + 1;
- symbols->emplace_back(name, start_vaddr, len);
+ file->symbols.emplace_back(name, start_vaddr, len);
}
- dex_file_offsets->clear();
- if (*file_type == static_cast<uint32_t>(DSO_DEX_FILE)) {
+ file->dex_file_offsets.clear();
+ if (file->type == DSO_DEX_FILE) {
uint32_t offset_count;
MoveFromBinaryFormat(offset_count, p);
- dex_file_offsets->resize(offset_count);
- MoveFromBinaryFormat(dex_file_offsets->data(), offset_count, p);
+ file->dex_file_offsets.resize(offset_count);
+ MoveFromBinaryFormat(file->dex_file_offsets.data(), offset_count, p);
}
- *file_offset_of_min_vaddr = std::numeric_limits<uint64_t>::max();
- if (*file_type == DSO_ELF_FILE && static_cast<size_t>(p - buf.data()) < size) {
- MoveFromBinaryFormat(*file_offset_of_min_vaddr, p);
+ file->file_offset_of_min_vaddr = std::numeric_limits<uint64_t>::max();
+ if ((file->type == DSO_ELF_FILE || file->type == DSO_KERNEL_MODULE) &&
+ static_cast<size_t>(p - buf.data()) < size) {
+ MoveFromBinaryFormat(file->file_offset_of_min_vaddr, p);
}
- CHECK_EQ(size, static_cast<size_t>(p - buf.data()));
+ CHECK_EQ(size, static_cast<size_t>(p - buf.data()))
+ << "file " << file->path << ", type " << file->type;
return true;
}
@@ -536,6 +570,24 @@ bool RecordFileReader::ReadMetaInfoFeature() {
return true;
}
+std::optional<DebugUnwindFeature> RecordFileReader::ReadDebugUnwindFeature() {
+ if (feature_section_descriptors_.count(FEAT_DEBUG_UNWIND)) {
+ std::string s;
+ if (!ReadFeatureSection(FEAT_DEBUG_UNWIND, &s)) {
+ return std::nullopt;
+ }
+ proto::DebugUnwindFeature proto_debug_unwind;
+ proto_debug_unwind.ParseFromString(s);
+ DebugUnwindFeature debug_unwind(proto_debug_unwind.file_size());
+ for (size_t i = 0; i < proto_debug_unwind.file_size(); i++) {
+ debug_unwind[i].path = proto_debug_unwind.file(i).path();
+ debug_unwind[i].size = proto_debug_unwind.file(i).size();
+ }
+ return debug_unwind;
+ }
+ return std::nullopt;
+}
+
void RecordFileReader::LoadBuildIdAndFileFeatures(ThreadTree& thread_tree) {
std::vector<BuildIdRecord> records = ReadBuildIdFeature();
std::vector<std::pair<std::string, BuildId>> build_ids;
@@ -545,17 +597,10 @@ void RecordFileReader::LoadBuildIdAndFileFeatures(ThreadTree& thread_tree) {
Dso::SetBuildIds(build_ids);
if (HasFeature(PerfFileFormat::FEAT_FILE)) {
- std::string file_path;
- uint32_t file_type;
- uint64_t min_vaddr;
- uint64_t file_offset_of_min_vaddr;
- std::vector<Symbol> symbols;
- std::vector<uint64_t> dex_file_offsets;
+ FileFeature file_feature;
size_t read_pos = 0;
- while (ReadFileFeature(read_pos, &file_path, &file_type, &min_vaddr, &file_offset_of_min_vaddr,
- &symbols, &dex_file_offsets)) {
- thread_tree.AddDsoInfo(file_path, file_type, min_vaddr, file_offset_of_min_vaddr, &symbols,
- dex_file_offsets);
+ while (ReadFileFeature(read_pos, &file_feature)) {
+ thread_tree.AddDsoInfo(file_feature);
}
}
}
@@ -624,3 +669,15 @@ std::vector<std::unique_ptr<Record>> RecordFileReader::DataSection() {
});
return records;
}
+
+bool IsPerfDataFile(const std::string& filename) {
+ auto fd = FileHelper::OpenReadOnly(filename);
+ if (fd.ok()) {
+ PerfFileFormat::FileHeader header;
+ return android::base::ReadFully(fd, &header, sizeof(header)) &&
+ memcmp(header.magic, PERF_MAGIC, sizeof(header.magic)) == 0;
+ }
+ return false;
+}
+
+} // namespace simpleperf
diff --git a/simpleperf/record_file_test.cpp b/simpleperf/record_file_test.cpp
index 76d15ec7..20bacaa9 100644
--- a/simpleperf/record_file_test.cpp
+++ b/simpleperf/record_file_test.cpp
@@ -27,13 +27,17 @@
#include "event_type.h"
#include "record.h"
#include "record_file.h"
+#include "utils.h"
#include "record_equal_test.h"
-using namespace PerfFileFormat;
+using namespace simpleperf;
+using namespace simpleperf::PerfFileFormat;
class RecordFileTest : public ::testing::Test {
protected:
+ void SetUp() override { close(tmpfile_.release()); }
+
void AddEventType(const std::string& event_type_str) {
std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType(event_type_str);
ASSERT_TRUE(event_type_modifier != nullptr);
@@ -61,8 +65,8 @@ 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, "mmap_record_example", attr_ids_[0].ids[0]);
+ 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));
// Write feature section.
@@ -146,3 +150,33 @@ TEST_F(RecordFileTest, write_meta_info_feature_section) {
ASSERT_TRUE(reader != nullptr);
ASSERT_EQ(reader->GetMetaInfoFeature(), info_map);
}
+
+TEST_F(RecordFileTest, write_debug_unwind_feature_section) {
+ // Write to a record file.
+ std::unique_ptr<RecordFileWriter> writer = RecordFileWriter::CreateInstance(tmpfile_.path);
+ ASSERT_TRUE(writer != nullptr);
+ AddEventType("cpu-cycles");
+ ASSERT_TRUE(writer->WriteAttrSection(attr_ids_));
+
+ // Write debug_unwind feature section.
+ ASSERT_TRUE(writer->BeginWriteFeatures(1));
+ DebugUnwindFeature debug_unwind(2);
+ debug_unwind[0].path = "file1";
+ debug_unwind[0].size = 1000;
+ debug_unwind[1].path = "file2";
+ debug_unwind[1].size = 2000;
+ ASSERT_TRUE(writer->WriteDebugUnwindFeature(debug_unwind));
+ ASSERT_TRUE(writer->EndWriteFeatures());
+ ASSERT_TRUE(writer->Close());
+
+ // Read from a record file.
+ std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile_.path);
+ ASSERT_TRUE(reader != nullptr);
+ std::optional<DebugUnwindFeature> opt_debug_unwind = reader->ReadDebugUnwindFeature();
+ ASSERT_TRUE(opt_debug_unwind.has_value());
+ ASSERT_EQ(opt_debug_unwind.value().size(), debug_unwind.size());
+ for (size_t i = 0; i < debug_unwind.size(); i++) {
+ ASSERT_EQ(opt_debug_unwind.value()[i].path, debug_unwind[i].path);
+ ASSERT_EQ(opt_debug_unwind.value()[i].size, debug_unwind[i].size);
+ }
+}
diff --git a/simpleperf/record_file_writer.cpp b/simpleperf/record_file_writer.cpp
index f718df49..9fe2f70c 100644
--- a/simpleperf/record_file_writer.cpp
+++ b/simpleperf/record_file_writer.cpp
@@ -18,8 +18,8 @@
#include <fcntl.h>
#include <string.h>
-#include <sys/mman.h>
#include <unistd.h>
+
#include <algorithm>
#include <set>
#include <string>
@@ -33,8 +33,11 @@
#include "event_attr.h"
#include "perf_event.h"
#include "record.h"
+#include "system/extras/simpleperf/record_file.pb.h"
#include "utils.h"
+namespace simpleperf {
+
using namespace PerfFileFormat;
std::unique_ptr<RecordFileWriter> RecordFileWriter::CreateInstance(const std::string& filename) {
@@ -50,22 +53,22 @@ std::unique_ptr<RecordFileWriter> RecordFileWriter::CreateInstance(const std::st
return nullptr;
}
- return std::unique_ptr<RecordFileWriter>(new RecordFileWriter(filename, fp));
+ return std::unique_ptr<RecordFileWriter>(new RecordFileWriter(filename, fp, true));
}
-RecordFileWriter::RecordFileWriter(const std::string& filename, FILE* fp)
+RecordFileWriter::RecordFileWriter(const std::string& filename, FILE* fp, bool own_fp)
: filename_(filename),
record_fp_(fp),
+ own_fp_(own_fp),
attr_section_offset_(0),
attr_section_size_(0),
data_section_offset_(0),
data_section_size_(0),
feature_section_offset_(0),
- feature_count_(0) {
-}
+ feature_count_(0) {}
RecordFileWriter::~RecordFileWriter() {
- if (record_fp_ != nullptr) {
+ if (record_fp_ != nullptr && own_fp_) {
fclose(record_fp_);
unlink(filename_.c_str());
}
@@ -316,83 +319,88 @@ bool RecordFileWriter::WriteAuxTraceFeature(const std::vector<uint64_t>& auxtrac
data.push_back(offset);
data.push_back(AuxTraceRecord::Size());
}
- return WriteFeatureBegin(FEAT_AUXTRACE) && Write(data.data(), data.size() * sizeof(uint64_t)) &&
- WriteFeatureEnd(FEAT_AUXTRACE);
+ return WriteFeature(FEAT_AUXTRACE, reinterpret_cast<char*>(data.data()),
+ data.size() * sizeof(uint64_t));
}
-bool RecordFileWriter::WriteFileFeatures(const std::vector<Dso*>& files) {
- for (Dso* dso : files) {
+bool RecordFileWriter::WriteFileFeatures(const std::vector<Dso*>& dsos) {
+ for (Dso* dso : dsos) {
// Always want to dump dex file offsets for DSO_DEX_FILE type.
if (!dso->HasDumpId() && dso->type() != DSO_DEX_FILE) {
continue;
}
- uint32_t dso_type = dso->type();
- uint64_t min_vaddr;
- uint64_t file_offset_of_min_vaddr;
- dso->GetMinExecutableVaddr(&min_vaddr, &file_offset_of_min_vaddr);
+ FileFeature file;
+ file.path = dso->Path();
+ file.type = dso->type();
+ dso->GetMinExecutableVaddr(&file.min_vaddr, &file.file_offset_of_min_vaddr);
// Dumping all symbols in hit files takes too much space, so only dump
// needed symbols.
const std::vector<Symbol>& symbols = dso->GetSymbols();
- std::vector<const Symbol*> dump_symbols;
for (const auto& sym : symbols) {
if (sym.HasDumpId()) {
- dump_symbols.push_back(&sym);
+ file.symbol_ptrs.emplace_back(&sym);
}
}
- std::sort(dump_symbols.begin(), dump_symbols.end(), Symbol::CompareByAddr);
+ std::sort(file.symbol_ptrs.begin(), file.symbol_ptrs.end(), Symbol::CompareByAddr);
- const std::vector<uint64_t>* dex_file_offsets = dso->DexFileOffsets();
- if (!WriteFileFeature(dso->Path(), dso_type, min_vaddr, file_offset_of_min_vaddr,
- dump_symbols, dex_file_offsets)) {
+ if (const auto dex_file_offsets = dso->DexFileOffsets(); dex_file_offsets != nullptr) {
+ file.dex_file_offsets = *dex_file_offsets;
+ }
+ if (!WriteFileFeature(file)) {
return false;
}
}
return true;
}
-bool RecordFileWriter::WriteFileFeature(const std::string& file_path,
- uint32_t file_type,
- uint64_t min_vaddr,
- uint64_t file_offset_of_min_vaddr,
- const std::vector<const Symbol*>& symbols,
- const std::vector<uint64_t>* dex_file_offsets) {
- uint32_t size = file_path.size() + 1 + sizeof(uint32_t) * 2 +
- sizeof(uint64_t) + symbols.size() * (sizeof(uint64_t) + sizeof(uint32_t));
- for (const auto& symbol : symbols) {
+bool RecordFileWriter::WriteFileFeature(const FileFeature& file) {
+ uint32_t symbol_count = file.symbols.size() + file.symbol_ptrs.size();
+ uint32_t size = file.path.size() + 1 + sizeof(uint32_t) * 2 + sizeof(uint64_t) +
+ symbol_count * (sizeof(uint64_t) + sizeof(uint32_t));
+ for (const auto& symbol : file.symbols) {
+ size += strlen(symbol.Name()) + 1;
+ }
+ for (const auto& symbol : file.symbol_ptrs) {
size += strlen(symbol->Name()) + 1;
}
- if (dex_file_offsets != nullptr) {
- size += sizeof(uint32_t) + sizeof(uint64_t) * dex_file_offsets->size();
+ if (file.type == DSO_DEX_FILE) {
+ size += sizeof(uint32_t) + sizeof(uint64_t) * file.dex_file_offsets.size();
}
- if (file_type == DSO_ELF_FILE) {
+ if (file.type == DSO_ELF_FILE || file.type == DSO_KERNEL_MODULE) {
size += sizeof(uint64_t);
}
std::vector<char> buf(sizeof(uint32_t) + size);
char* p = buf.data();
MoveToBinaryFormat(size, p);
- MoveToBinaryFormat(file_path.c_str(), file_path.size() + 1, p);
- MoveToBinaryFormat(file_type, p);
- MoveToBinaryFormat(min_vaddr, p);
- uint32_t symbol_count = static_cast<uint32_t>(symbols.size());
+ MoveToBinaryFormat(file.path.c_str(), file.path.size() + 1, p);
+ MoveToBinaryFormat(static_cast<uint32_t>(file.type), p);
+ MoveToBinaryFormat(file.min_vaddr, p);
MoveToBinaryFormat(symbol_count, p);
- for (const auto& symbol : symbols) {
+
+ auto write_symbol = [&](const Symbol* symbol) {
MoveToBinaryFormat(symbol->addr, p);
uint32_t len = symbol->len;
MoveToBinaryFormat(len, p);
MoveToBinaryFormat(symbol->Name(), strlen(symbol->Name()) + 1, p);
+ };
+ for (const auto& symbol : file.symbols) {
+ write_symbol(&symbol);
}
- if (dex_file_offsets != nullptr) {
- uint32_t offset_count = dex_file_offsets->size();
+ for (const auto& symbol : file.symbol_ptrs) {
+ write_symbol(symbol);
+ }
+ if (file.type == DSO_DEX_FILE) {
+ uint32_t offset_count = file.dex_file_offsets.size();
MoveToBinaryFormat(offset_count, p);
- MoveToBinaryFormat(dex_file_offsets->data(), offset_count, p);
+ MoveToBinaryFormat(file.dex_file_offsets.data(), offset_count, p);
}
- if (file_type == DSO_ELF_FILE) {
- MoveToBinaryFormat(file_offset_of_min_vaddr, p);
+ if (file.type == DSO_ELF_FILE || file.type == DSO_KERNEL_MODULE) {
+ MoveToBinaryFormat(file.file_offset_of_min_vaddr, p);
}
CHECK_EQ(buf.size(), static_cast<size_t>(p - buf.data()));
- return WriteFeature(FEAT_FILE, buf);
+ return WriteFeature(FEAT_FILE, buf.data(), buf.size());
}
bool RecordFileWriter::WriteMetaInfoFeature(
@@ -408,11 +416,27 @@ bool RecordFileWriter::WriteMetaInfoFeature(
MoveToBinaryFormat(pair.first.c_str(), pair.first.size() + 1, p);
MoveToBinaryFormat(pair.second.c_str(), pair.second.size() + 1, p);
}
- return WriteFeature(FEAT_META_INFO, buf);
+ return WriteFeature(FEAT_META_INFO, buf.data(), buf.size());
+}
+
+bool RecordFileWriter::WriteDebugUnwindFeature(const DebugUnwindFeature& debug_unwind) {
+ GOOGLE_PROTOBUF_VERIFY_VERSION;
+ proto::DebugUnwindFeature proto_debug_unwind;
+ for (auto& file : debug_unwind) {
+ auto proto_file = proto_debug_unwind.add_file();
+ proto_file->set_path(file.path);
+ proto_file->set_size(file.size);
+ }
+ std::string s;
+ if (!proto_debug_unwind.SerializeToString(&s)) {
+ LOG(ERROR) << "SerializeToString() failed";
+ return false;
+ }
+ return WriteFeature(FEAT_DEBUG_UNWIND, s.data(), s.size());
}
-bool RecordFileWriter::WriteFeature(int feature, const std::vector<char>& data) {
- return WriteFeatureBegin(feature) && Write(data.data(), data.size()) && WriteFeatureEnd(feature);
+bool RecordFileWriter::WriteFeature(int feature, const char* data, size_t size) {
+ return WriteFeatureBegin(feature) && Write(data, size) && WriteFeatureEnd(feature);
}
bool RecordFileWriter::WriteFeatureBegin(int feature) {
@@ -491,10 +515,12 @@ bool RecordFileWriter::Close() {
result = false;
}
- if (fclose(record_fp_) != 0) {
+ if (own_fp_ && fclose(record_fp_) != 0) {
PLOG(ERROR) << "failed to close record file '" << filename_ << "'";
result = false;
}
record_fp_ = nullptr;
return result;
}
+
+} // namespace simpleperf
diff --git a/simpleperf/record_lib_interface.cpp b/simpleperf/record_lib_interface.cpp
index dbe49480..b6b892e0 100644
--- a/simpleperf/record_lib_interface.cpp
+++ b/simpleperf/record_lib_interface.cpp
@@ -37,12 +37,14 @@ std::vector<std::string> GetAllEvents() {
if (!CheckPerfEventLimit()) {
return result;
}
- for (auto& type : GetAllEventTypes()) {
+ auto callback = [&](const EventType& type) {
perf_event_attr attr = CreateDefaultPerfEventAttr(type);
if (IsEventAttrSupported(attr, type.name)) {
result.push_back(type.name);
}
- }
+ return true;
+ };
+ EventTypeManager::Instance().ForEachType(callback);
return result;
}
@@ -177,8 +179,8 @@ bool PerfEventSetForCounting::ReadRawCounters(std::vector<Counter>* counters) {
counters->resize(s.size());
for (size_t i = 0; i < s.size(); ++i) {
CountersInfo& info = s[i];
- std::string name = info.event_modifier.empty() ? info.event_name :
- info.event_name + ":" + info.event_modifier;
+ std::string name =
+ info.event_modifier.empty() ? info.event_name : info.event_name + ":" + info.event_modifier;
CHECK_EQ(name, event_names_[i]);
Counter& sum = (*counters)[i];
sum.event = name;
diff --git a/simpleperf/record_lib_test.cpp b/simpleperf/record_lib_test.cpp
index 5fdaea44..4c1b7e87 100644
--- a/simpleperf/record_lib_test.cpp
+++ b/simpleperf/record_lib_test.cpp
@@ -37,8 +37,8 @@ static void DoSomeWork() {
}
TEST(counter, add_event) {
- std::unique_ptr<PerfEventSet> perf(PerfEventSet::CreateInstance(
- PerfEventSet::Type::kPerfForCounting));
+ std::unique_ptr<PerfEventSet> perf(
+ PerfEventSet::CreateInstance(PerfEventSet::Type::kPerfForCounting));
ASSERT_TRUE(perf);
ASSERT_TRUE(perf->AddEvent("cpu-cycles"));
ASSERT_TRUE(perf->AddEvent("cpu-cycles:u"));
@@ -63,8 +63,8 @@ TEST(counter, add_event) {
TEST(counter, different_targets) {
auto test_function = [](std::function<void(PerfEventSet*)> set_target_func) {
- std::unique_ptr<PerfEventSet> perf(PerfEventSet::CreateInstance(
- PerfEventSet::Type::kPerfForCounting));
+ std::unique_ptr<PerfEventSet> perf(
+ PerfEventSet::CreateInstance(PerfEventSet::Type::kPerfForCounting));
ASSERT_TRUE(perf);
ASSERT_TRUE(perf->AddEvent("cpu-cycles"));
set_target_func(perf.get());
@@ -81,21 +81,16 @@ TEST(counter, different_targets) {
ASSERT_GT(counters[0].time_running_in_ns, 0u);
ASSERT_LE(counters[0].time_running_in_ns, counters[0].time_enabled_in_ns);
};
- test_function([](PerfEventSet* perf) {
- ASSERT_TRUE(perf->MonitorCurrentProcess());
- });
- test_function([](PerfEventSet* perf) {
- ASSERT_TRUE(perf->MonitorCurrentThread());
- });
- test_function([](PerfEventSet* perf) {
- ASSERT_TRUE(perf->MonitorThreadsInCurrentProcess({getpid()}));
- });
+ test_function([](PerfEventSet* perf) { ASSERT_TRUE(perf->MonitorCurrentProcess()); });
+ test_function([](PerfEventSet* perf) { ASSERT_TRUE(perf->MonitorCurrentThread()); });
+ test_function(
+ [](PerfEventSet* perf) { ASSERT_TRUE(perf->MonitorThreadsInCurrentProcess({getpid()})); });
}
TEST(counter, start_stop_multiple_times) {
const size_t TEST_COUNT = 10;
- std::unique_ptr<PerfEventSet> perf(PerfEventSet::CreateInstance(
- PerfEventSet::Type::kPerfForCounting));
+ std::unique_ptr<PerfEventSet> perf(
+ PerfEventSet::CreateInstance(PerfEventSet::Type::kPerfForCounting));
ASSERT_TRUE(perf);
ASSERT_TRUE(perf->AddEvent("cpu-cycles"));
ASSERT_TRUE(perf->MonitorCurrentProcess());
@@ -122,8 +117,8 @@ TEST(counter, start_stop_multiple_times) {
}
TEST(counter, no_change_after_stop) {
- std::unique_ptr<PerfEventSet> perf(PerfEventSet::CreateInstance(
- PerfEventSet::Type::kPerfForCounting));
+ std::unique_ptr<PerfEventSet> perf(
+ PerfEventSet::CreateInstance(PerfEventSet::Type::kPerfForCounting));
ASSERT_TRUE(perf);
ASSERT_TRUE(perf->AddEvent("cpu-cycles"));
ASSERT_TRUE(perf->MonitorCurrentProcess());
diff --git a/simpleperf/record_test.cpp b/simpleperf/record_test.cpp
index 954acfaa..6d65a980 100644
--- a/simpleperf/record_test.cpp
+++ b/simpleperf/record_test.cpp
@@ -21,6 +21,8 @@
#include "record.h"
#include "record_equal_test.h"
+using namespace simpleperf;
+
class RecordTest : public ::testing::Test {
protected:
virtual void SetUp() {
@@ -41,8 +43,7 @@ class RecordTest : public ::testing::Test {
};
TEST_F(RecordTest, MmapRecordMatchBinary) {
- MmapRecord record(event_attr, true, 1, 2, 0x1000, 0x2000, 0x3000,
- "MmapRecord", 0);
+ MmapRecord record(event_attr, true, 1, 2, 0x1000, 0x2000, 0x3000, "MmapRecord", 0);
CheckRecordMatchBinary(record);
}
@@ -52,9 +53,8 @@ TEST_F(RecordTest, CommRecordMatchBinary) {
}
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;
+ 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;
SampleRecord record(event_attr, 1, 2, 3, 4, 5, 6, 7, {8, 9, 10}, {}, 0);
CheckRecordMatchBinary(record);
}
@@ -95,13 +95,13 @@ TEST_F(RecordTest, SampleRecord_exclude_kernel_callchain) {
ASSERT_TRUE(r7.ExcludeKernelCallChain());
CheckRecordEqual(r7, SampleRecord(event_attr, 0, 3, 0, 0, 0, 0, 0,
{PERF_CONTEXT_USER, PERF_CONTEXT_USER, PERF_CONTEXT_USER,
- PERF_CONTEXT_USER, 3, 4}, {}, 0));
+ PERF_CONTEXT_USER, 3, 4},
+ {}, 0));
}
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);
+ 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});
@@ -127,7 +127,8 @@ TEST_F(RecordTest, SampleRecord_AdjustCallChainGeneratedByKernel) {
uint64_t adjustValue = (GetBuildArch() == ARCH_ARM || GetBuildArch() == ARCH_ARM64) ? 2 : 1;
SampleRecord expected(event_attr, 0, 1, 2, 3, 4, 5, 6,
{1, 5 - adjustValue, PERF_CONTEXT_KERNEL, PERF_CONTEXT_USER,
- 6 - adjustValue, PERF_CONTEXT_USER}, {}, 0);
+ 6 - adjustValue, PERF_CONTEXT_USER},
+ {}, 0);
expected.header.misc = PERF_RECORD_MISC_KERNEL;
CheckRecordEqual(r, expected);
}
diff --git a/simpleperf/report_lib_interface.cpp b/simpleperf/report_lib_interface.cpp
index 70a274f4..d6a4bc09 100644
--- a/simpleperf/report_lib_interface.cpp
+++ b/simpleperf/report_lib_interface.cpp
@@ -17,24 +17,22 @@
#include <memory>
#include <utility>
-#include <android-base/logging.h>
#include <android-base/file.h>
+#include <android-base/logging.h>
#include <android-base/strings.h>
+#include "JITDebugReader.h"
#include "dso.h"
#include "event_attr.h"
#include "event_type.h"
#include "record_file.h"
+#include "report_utils.h"
#include "thread_tree.h"
#include "tracing.h"
#include "utils.h"
-class ReportLib;
-
extern "C" {
-#define EXPORT __attribute__((visibility("default")))
-
struct Sample {
uint64_t ip;
uint32_t pid;
@@ -52,6 +50,7 @@ struct TracingFieldFormat {
uint32_t elem_size;
uint32_t elem_count;
uint32_t is_signed;
+ uint32_t is_dynamic;
};
struct TracingDataFormat {
@@ -95,30 +94,9 @@ struct FeatureSection {
uint32_t data_size;
};
-// Create a new instance,
-// pass the instance to the other functions below.
-ReportLib* CreateReportLib() EXPORT;
-void DestroyReportLib(ReportLib* report_lib) EXPORT;
+} // extern "C"
-// Set log severity, different levels are:
-// verbose, debug, info, warning, error, fatal.
-bool SetLogSeverity(ReportLib* report_lib, const char* log_level) EXPORT;
-bool SetSymfs(ReportLib* report_lib, const char* symfs_dir) EXPORT;
-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;
-void MergeJavaMethods(ReportLib* report_lib, bool merge) EXPORT;
-
-Sample* GetNextSample(ReportLib* report_lib) EXPORT;
-Event* GetEventOfCurrentSample(ReportLib* report_lib) EXPORT;
-SymbolEntry* GetSymbolOfCurrentSample(ReportLib* report_lib) EXPORT;
-CallChain* GetCallChainOfCurrentSample(ReportLib* report_lib) EXPORT;
-const char* GetTracingDataOfCurrentSample(ReportLib* report_lib) EXPORT;
-
-const char* GetBuildIdForPath(ReportLib* report_lib, const char* path) EXPORT;
-FeatureSection* GetFeatureSection(ReportLib* report_lib, const char* feature_name) EXPORT;
-}
+namespace simpleperf {
struct EventInfo {
perf_event_attr attr;
@@ -134,13 +112,11 @@ struct EventInfo {
class ReportLib {
public:
ReportLib()
- : log_severity_(
- new android::base::ScopedLogSeverity(android::base::INFO)),
+ : log_severity_(new android::base::ScopedLogSeverity(android::base::INFO)),
record_filename_("perf.data"),
current_thread_(nullptr),
trace_offcpu_(false),
- show_art_frames_(false) {
- }
+ callchain_report_builder_(thread_tree_) {}
bool SetLogSeverity(const char* log_level);
@@ -154,8 +130,14 @@ class ReportLib {
bool SetKallsymsFile(const char* kallsyms_file);
void ShowIpForUnknownSymbol() { thread_tree_.ShowIpForUnknownSymbol(); }
- void ShowArtFrames(bool show) { show_art_frames_ = show; }
- void MergeJavaMethods(bool merge) { merge_java_methods_ = merge; }
+ void ShowArtFrames(bool show) {
+ bool remove_art_frame = !show;
+ callchain_report_builder_.SetRemoveArtFrame(remove_art_frame);
+ }
+ void MergeJavaMethods(bool merge) { callchain_report_builder_.SetConvertJITFrame(merge); }
+ bool AddProguardMappingFile(const char* mapping_file) {
+ return callchain_report_builder_.AddProguardMappingFile(mapping_file);
+ }
Sample* GetNextSample();
Event* GetEventOfCurrentSample() { return &current_event_; }
@@ -193,10 +175,7 @@ class ReportLib {
std::unordered_map<pid_t, std::unique_ptr<SampleRecord>> next_sample_cache_;
FeatureSection feature_section_;
std::vector<char> feature_section_data_;
- bool show_art_frames_;
- bool merge_java_methods_ = true;
- // Map from a java method name to it's dex file, start_addr and len.
- std::unordered_map<std::string, std::tuple<Dso*, uint64_t, uint64_t>> java_methods_;
+ CallChainReportBuilder callchain_report_builder_;
std::unique_ptr<Tracing> tracing_;
};
@@ -232,15 +211,6 @@ bool ReportLib::OpenRecordFileIfNecessary() {
if (auto it = meta_info.find("trace_offcpu"); it != meta_info.end()) {
trace_offcpu_ = it->second == "true";
}
- if (merge_java_methods_) {
- for (Dso* dso : thread_tree_.GetAllDsos()) {
- if (dso->type() == DSO_DEX_FILE) {
- for (auto& symbol : dso->GetSymbols()) {
- java_methods_[symbol.Name()] = std::make_tuple(dso, symbol.addr, symbol.len);
- }
- }
- }
- }
}
return true;
}
@@ -295,8 +265,8 @@ void ReportLib::SetCurrentSample() {
current_sample_.in_kernel = r.InKernel();
current_sample_.cpu = r.cpu_data.cpu;
if (trace_offcpu_) {
- uint64_t next_time = std::max(next_sample_cache_[r.tid_data.tid]->time_data.time,
- r.time_data.time + 1);
+ uint64_t next_time =
+ std::max(next_sample_cache_[r.tid_data.tid]->time_data.time, r.time_data.time + 1);
current_sample_.period = next_time - r.time_data.time;
} else {
current_sample_.period = r.period_data.period;
@@ -304,62 +274,23 @@ void ReportLib::SetCurrentSample() {
size_t kernel_ip_count;
std::vector<uint64_t> ips = r.GetCallChain(&kernel_ip_count);
- std::vector<std::pair<uint64_t, const MapEntry*>> ip_maps;
- bool near_java_method = false;
- auto is_map_for_interpreter = [](const MapEntry* map) {
- return android::base::EndsWith(map->dso->Path(), "/libart.so") ||
- android::base::EndsWith(map->dso->Path(), "/libartd.so");
- };
- for (size_t i = 0; i < ips.size(); ++i) {
- const MapEntry* map = thread_tree_.FindMap(current_thread_, ips[i], i < kernel_ip_count);
- if (!show_art_frames_) {
- // Remove interpreter frames both before and after the Java frame.
- if (map->dso->IsForJavaMethod()) {
- near_java_method = true;
- while (!ip_maps.empty() && is_map_for_interpreter(ip_maps.back().second)) {
- ip_maps.pop_back();
- }
- } else if (is_map_for_interpreter(map)){
- if (near_java_method) {
- continue;
- }
- } else {
- near_java_method = false;
- }
- }
- ip_maps.push_back(std::make_pair(ips[i], map));
- }
- for (auto& pair : ip_maps) {
- uint64_t ip = pair.first;
- const MapEntry* map = pair.second;
- uint64_t vaddr_in_file;
- const Symbol* symbol = thread_tree_.FindSymbol(map, ip, &vaddr_in_file);
- CallChainEntry entry;
- entry.ip = ip;
- entry.symbol.dso_name = map->dso->Path().c_str();
- entry.symbol.vaddr_in_file = vaddr_in_file;
- entry.symbol.symbol_name = symbol->DemangledName();
- entry.symbol.symbol_addr = symbol->addr;
- entry.symbol.symbol_len = symbol->len;
- entry.symbol.mapping = AddMapping(*map);
-
- if (merge_java_methods_ && map->dso->type() == DSO_ELF_FILE && map->dso->IsForJavaMethod()) {
- // This is a jitted java method, merge it with the interpreted java method having the same
- // name if possible. Otherwise, merge it with other jitted java methods having the same name
- // by assigning a common dso_name.
- if (auto it = java_methods_.find(entry.symbol.symbol_name); it != java_methods_.end()) {
- entry.symbol.dso_name = std::get<0>(it->second)->Path().c_str();
- entry.symbol.symbol_addr = std::get<1>(it->second);
- entry.symbol.symbol_len = std::get<2>(it->second);
- // Not enough info to map an offset in a jitted method to an offset in a dex file. So just
- // use the symbol_addr.
- entry.symbol.vaddr_in_file = entry.symbol.symbol_addr;
- } else {
- entry.symbol.dso_name = "[JIT cache]";
- }
+ std::vector<CallChainReportEntry> report_entries =
+ callchain_report_builder_.Build(current_thread_, ips, kernel_ip_count);
+
+ for (const auto& report_entry : report_entries) {
+ callchain_entries_.resize(callchain_entries_.size() + 1);
+ CallChainEntry& entry = callchain_entries_.back();
+ entry.ip = report_entry.ip;
+ if (report_entry.dso_name != nullptr) {
+ entry.symbol.dso_name = report_entry.dso_name;
+ } else {
+ entry.symbol.dso_name = report_entry.dso->GetReportPath().data();
}
-
- callchain_entries_.push_back(entry);
+ entry.symbol.vaddr_in_file = report_entry.vaddr_in_file;
+ entry.symbol.symbol_name = report_entry.symbol->DemangledName();
+ entry.symbol.symbol_addr = report_entry.symbol->addr;
+ entry.symbol.symbol_len = report_entry.symbol->len;
+ entry.symbol.mapping = AddMapping(*report_entry.map);
}
current_sample_.ip = callchain_entries_[0].ip;
current_symbol_ = &(callchain_entries_[0].symbol);
@@ -409,6 +340,7 @@ void ReportLib::CreateEvents() {
field.elem_size = format.fields[i].elem_size;
field.elem_count = format.fields[i].elem_count;
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;
@@ -462,6 +394,40 @@ FeatureSection* ReportLib::GetFeatureSection(const char* feature_name) {
return &feature_section_;
}
+} // namespace simpleperf
+
+using ReportLib = simpleperf::ReportLib;
+
+extern "C" {
+
+#define EXPORT __attribute__((visibility("default")))
+
+// Create a new instance,
+// pass the instance to the other functions below.
+ReportLib* CreateReportLib() EXPORT;
+void DestroyReportLib(ReportLib* report_lib) EXPORT;
+
+// Set log severity, different levels are:
+// verbose, debug, info, warning, error, fatal.
+bool SetLogSeverity(ReportLib* report_lib, const char* log_level) EXPORT;
+bool SetSymfs(ReportLib* report_lib, const char* symfs_dir) EXPORT;
+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;
+void MergeJavaMethods(ReportLib* report_lib, bool merge) EXPORT;
+bool AddProguardMappingFile(ReportLib* report_lib, const char* mapping_file) EXPORT;
+
+Sample* GetNextSample(ReportLib* report_lib) EXPORT;
+Event* GetEventOfCurrentSample(ReportLib* report_lib) EXPORT;
+SymbolEntry* GetSymbolOfCurrentSample(ReportLib* report_lib) EXPORT;
+CallChain* GetCallChainOfCurrentSample(ReportLib* report_lib) EXPORT;
+const char* GetTracingDataOfCurrentSample(ReportLib* report_lib) EXPORT;
+
+const char* GetBuildIdForPath(ReportLib* report_lib, const char* path) EXPORT;
+FeatureSection* GetFeatureSection(ReportLib* report_lib, const char* feature_name) EXPORT;
+}
+
// Exported methods working with a client created instance
ReportLib* CreateReportLib() {
return new ReportLib();
@@ -499,6 +465,10 @@ bool SetKallsymsFile(ReportLib* report_lib, const char* kallsyms_file) {
return report_lib->SetKallsymsFile(kallsyms_file);
}
+bool AddProguardMappingFile(ReportLib* report_lib, const char* mapping_file) {
+ return report_lib->AddProguardMappingFile(mapping_file);
+}
+
Sample* GetNextSample(ReportLib* report_lib) {
return report_lib->GetNextSample();
}
diff --git a/simpleperf/report_utils.cpp b/simpleperf/report_utils.cpp
new file mode 100644
index 00000000..a9468006
--- /dev/null
+++ b/simpleperf/report_utils.cpp
@@ -0,0 +1,240 @@
+/*
+ * 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.
+ */
+
+#include "report_utils.h"
+
+#include <android-base/strings.h>
+
+#include "JITDebugReader.h"
+#include "utils.h"
+
+namespace simpleperf {
+
+static bool IsArtEntry(const CallChainReportEntry& entry, bool* is_jni_trampoline) {
+ if (entry.execution_type == CallChainExecutionType::NATIVE_METHOD) {
+ if (android::base::EndsWith(entry.dso->Path(), "/libart.so") ||
+ android::base::EndsWith(entry.dso->Path(), "/libartd.so")) {
+ *is_jni_trampoline = false;
+ return true;
+ }
+ if (strcmp(entry.symbol->Name(), "art_jni_trampoline") == 0) {
+ // art_jni_trampoline is a trampoline used to call jni methods in art runtime.
+ // We want to hide it when hiding art frames.
+ *is_jni_trampoline = true;
+ return true;
+ }
+ }
+ return false;
+};
+
+bool CallChainReportBuilder::AddProguardMappingFile(std::string_view mapping_file) {
+ // The mapping file format is described in
+ // https://www.guardsquare.com/en/products/proguard/manual/retrace.
+ LineReader reader(mapping_file);
+ if (!reader.Ok()) {
+ PLOG(ERROR) << "failed to read " << mapping_file;
+ return false;
+ }
+ ProguardMappingClass* cur_class = nullptr;
+ std::string* line;
+ while ((line = reader.ReadLine()) != nullptr) {
+ std::string_view s = *line;
+ if (s.empty() || s[0] == '#') {
+ continue;
+ }
+ auto arrow_pos = s.find(" -> ");
+ if (arrow_pos == s.npos) {
+ continue;
+ }
+ auto arrow_end_pos = arrow_pos + strlen(" -> ");
+
+ if (s[0] != ' ') {
+ // Match line "original_classname -> obfuscated_classname:".
+ if (auto colon_pos = s.find(':', arrow_end_pos); colon_pos != s.npos) {
+ std::string_view original_classname = s.substr(0, arrow_pos);
+ std::string obfuscated_classname(s.substr(arrow_end_pos, colon_pos - arrow_end_pos));
+ cur_class = &proguard_class_map_[obfuscated_classname];
+ cur_class->original_classname = original_classname;
+ }
+ } else if (cur_class != nullptr) {
+ // Match line "... [original_classname.]original_methodname(...)... ->
+ // obfuscated_methodname".
+ if (auto left_brace_pos = s.rfind('(', arrow_pos); left_brace_pos != s.npos) {
+ if (auto space_pos = s.rfind(' ', left_brace_pos); space_pos != s.npos) {
+ auto original_methodname = s.substr(space_pos + 1, left_brace_pos - space_pos - 1);
+ if (android::base::StartsWith(original_methodname, cur_class->original_classname)) {
+ original_methodname.remove_prefix(cur_class->original_classname.size() + 1);
+ }
+ std::string obfuscated_methodname(s.substr(arrow_end_pos));
+ cur_class->method_map[obfuscated_methodname] = original_methodname;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+std::vector<CallChainReportEntry> CallChainReportBuilder::Build(const ThreadEntry* thread,
+ const std::vector<uint64_t>& ips,
+ size_t kernel_ip_count) {
+ std::vector<CallChainReportEntry> result;
+ result.reserve(ips.size());
+ 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;
+ uint64_t vaddr_in_file;
+ const Symbol* symbol = thread_tree_.FindSymbol(map, ips[i], &vaddr_in_file, &dso);
+ CallChainExecutionType execution_type = CallChainExecutionType::NATIVE_METHOD;
+ if (dso->IsForJavaMethod()) {
+ if (dso->type() == DSO_DEX_FILE) {
+ execution_type = CallChainExecutionType::INTERPRETED_JVM_METHOD;
+ } else {
+ execution_type = CallChainExecutionType::JIT_JVM_METHOD;
+ }
+ }
+ result.resize(result.size() + 1);
+ auto& entry = result.back();
+ entry.ip = ips[i];
+ entry.symbol = symbol;
+ entry.dso = dso;
+ entry.vaddr_in_file = vaddr_in_file;
+ entry.map = map;
+ 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 (convert_jit_frame_) {
+ ConvertJITFrame(result);
+ }
+ if (!proguard_class_map_.empty()) {
+ DeObfuscateJavaMethods(result);
+ }
+ return result;
+}
+
+void CallChainReportBuilder::MarkArtFrame(std::vector<CallChainReportEntry>& callchain) {
+ // Mark art methods before or after a JVM method.
+ bool near_java_method = false;
+ bool is_jni_trampoline = false;
+ std::vector<size_t> jni_trampoline_positions;
+ for (size_t i = 0; i < callchain.size(); ++i) {
+ auto& entry = callchain[i];
+ if (entry.execution_type == CallChainExecutionType::INTERPRETED_JVM_METHOD ||
+ entry.execution_type == CallChainExecutionType::JIT_JVM_METHOD) {
+ near_java_method = true;
+
+ // Mark art frames before this entry.
+ for (int j = static_cast<int>(i) - 1; j >= 0; j--) {
+ if (!IsArtEntry(callchain[j], &is_jni_trampoline)) {
+ break;
+ }
+ callchain[j].execution_type = CallChainExecutionType::ART_METHOD;
+ if (is_jni_trampoline) {
+ jni_trampoline_positions.push_back(j);
+ }
+ }
+ } else if (near_java_method && IsArtEntry(entry, &is_jni_trampoline)) {
+ entry.execution_type = CallChainExecutionType::ART_METHOD;
+ if (is_jni_trampoline) {
+ jni_trampoline_positions.push_back(i);
+ }
+ } else {
+ near_java_method = false;
+ }
+ }
+ // Functions called by art_jni_trampoline are jni methods. And we don't want to hide them.
+ for (auto i : jni_trampoline_positions) {
+ if (i > 0 && callchain[i - 1].execution_type == CallChainExecutionType::ART_METHOD) {
+ callchain[i - 1].execution_type = CallChainExecutionType::NATIVE_METHOD;
+ }
+ }
+}
+
+void CallChainReportBuilder::ConvertJITFrame(std::vector<CallChainReportEntry>& callchain) {
+ CollectJavaMethods();
+ for (size_t i = 0; i < callchain.size();) {
+ auto& entry = callchain[i];
+ if (entry.dso->IsForJavaMethod() && entry.dso->type() == DSO_ELF_FILE) {
+ // 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(entry.symbol->Name()); 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));
+ }
+ }
+ }
+ }
+}
+
+void CallChainReportBuilder::DeObfuscateJavaMethods(std::vector<CallChainReportEntry>& callchain) {
+ for (auto& entry : callchain) {
+ if (entry.execution_type != CallChainExecutionType::JIT_JVM_METHOD &&
+ entry.execution_type != CallChainExecutionType::INTERPRETED_JVM_METHOD) {
+ continue;
+ }
+ std::string_view name = entry.symbol->DemangledName();
+ if (auto split_pos = name.rfind('.'); split_pos != name.npos) {
+ std::string obfuscated_classname(name.substr(0, split_pos));
+ if (auto it = proguard_class_map_.find(obfuscated_classname);
+ it != proguard_class_map_.end()) {
+ const ProguardMappingClass& proguard_class = it->second;
+ std::string obfuscated_methodname(name.substr(split_pos + 1));
+ if (auto method_it = proguard_class.method_map.find(obfuscated_methodname);
+ method_it != proguard_class.method_map.end()) {
+ std::string new_symbol_name = proguard_class.original_classname + "." + method_it->second;
+ entry.symbol->SetDemangledName(new_symbol_name);
+ }
+ }
+ }
+ }
+}
+
+} // namespace simpleperf
diff --git a/simpleperf/report_utils.h b/simpleperf/report_utils.h
new file mode 100644
index 00000000..e4cc23d0
--- /dev/null
+++ b/simpleperf/report_utils.h
@@ -0,0 +1,90 @@
+/*
+ * 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 <inttypes.h>
+
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <vector>
+
+#include "dso.h"
+#include "thread_tree.h"
+
+namespace simpleperf {
+
+enum class CallChainExecutionType {
+ NATIVE_METHOD,
+ INTERPRETED_JVM_METHOD,
+ JIT_JVM_METHOD,
+ // ART methods near interpreted/JIT JVM methods. They're shown only when RemoveArtFrame = false.
+ ART_METHOD,
+};
+
+struct CallChainReportEntry {
+ uint64_t ip = 0;
+ const Symbol* symbol = nullptr;
+ Dso* dso = nullptr;
+ const char* dso_name = nullptr;
+ uint64_t vaddr_in_file = 0;
+ const MapEntry* map = nullptr;
+ CallChainExecutionType execution_type = CallChainExecutionType::NATIVE_METHOD;
+};
+
+class CallChainReportBuilder {
+ public:
+ CallChainReportBuilder(ThreadTree& thread_tree) : thread_tree_(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; }
+ // 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; }
+ // Add proguard mapping.txt to de-obfuscate minified symbols.
+ bool AddProguardMappingFile(std::string_view mapping_file);
+ 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) {}
+ };
+
+ struct ProguardMappingClass {
+ std::string original_classname;
+ // Map from minified method names to original method names.
+ std::unordered_map<std::string, std::string> method_map;
+ };
+
+ 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 convert_jit_frame_ = true;
+ bool java_method_initialized_ = false;
+ std::unordered_map<std::string, JavaMethod> java_method_map_;
+ // Map from minified class names to ProguardMappingClass.
+ std::unordered_map<std::string, ProguardMappingClass> proguard_class_map_;
+};
+
+} // namespace simpleperf
diff --git a/simpleperf/report_utils_test.cpp b/simpleperf/report_utils_test.cpp
new file mode 100644
index 00000000..cc246357
--- /dev/null
+++ b/simpleperf/report_utils_test.cpp
@@ -0,0 +1,312 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include <string>
+#include <vector>
+
+#include "record_file.h"
+#include "report_utils.h"
+#include "thread_tree.h"
+
+using namespace simpleperf;
+
+class CallChainReportBuilderTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ // To test different options for CallChainReportBuilder, we create a fake thread, add fake
+ // libraries used by the thread, and provide fake symbols in each library. We need four
+ // types of libraries: native, interpreter, jit cache and dex file.
+ thread_tree.SetThreadName(1, 1, "thread1");
+ thread = thread_tree.FindThread(1);
+
+ // Add symbol info for the native library.
+ FileFeature file;
+ file.path = fake_native_lib_path;
+ file.type = DSO_ELF_FILE;
+ file.min_vaddr = file.file_offset_of_min_vaddr = 0;
+ file.symbols = {
+ Symbol("native_func1", 0x0, 0x100),
+ Symbol("art_jni_trampoline", 0x100, 0x100),
+ };
+ thread_tree.AddDsoInfo(file);
+
+ // Add symbol info for the interpreter library.
+ file.path = fake_interpreter_path;
+ file.type = DSO_ELF_FILE;
+ file.min_vaddr = file.file_offset_of_min_vaddr = 0;
+ file.symbols = {
+ Symbol("art_func1", 0x0, 0x100),
+ Symbol("art_func2", 0x100, 0x100),
+ Symbol("_ZN3artL13Method_invokeEP7_JNIEnvP8_jobjectS3_P13_jobjectArray", 0x200, 0x100),
+ };
+ thread_tree.AddDsoInfo(file);
+
+ // Add symbol info for the dex file.
+ file.path = fake_dex_file_path;
+ file.type = DSO_DEX_FILE;
+ file.min_vaddr = file.file_offset_of_min_vaddr = 0;
+ file.symbols = {
+ Symbol("java_method1", 0x0, 0x100),
+ Symbol("java_method2", 0x100, 0x100),
+ Symbol("obfuscated_class.obfuscated_java_method", 0x200, 0x100),
+ };
+ thread_tree.AddDsoInfo(file);
+
+ // Add symbol info for the jit cache.
+ file.path = fake_jit_cache_path;
+ file.type = DSO_ELF_FILE;
+ file.min_vaddr = file.file_offset_of_min_vaddr = 0;
+ file.symbols = {
+ Symbol("java_method2", 0x3000, 0x100),
+ Symbol("java_method3", 0x3100, 0x100),
+ Symbol("obfuscated_class.obfuscated_java_method2", 0x3200, 0x100),
+ };
+ thread_tree.AddDsoInfo(file);
+
+ // Add map layout for libraries used in the thread:
+ // 0x0000 - 0x1000 is mapped to the native library.
+ // 0x1000 - 0x2000 is mapped to the interpreter library.
+ // 0x2000 - 0x3000 is mapped to the dex file.
+ // 0x3000 - 0x4000 is mapped to the jit cache.
+ thread_tree.AddThreadMap(1, 1, 0x0, 0x1000, 0x0, fake_native_lib_path);
+ thread_tree.AddThreadMap(1, 1, 0x1000, 0x1000, 0x0, fake_interpreter_path);
+ thread_tree.AddThreadMap(1, 1, 0x2000, 0x1000, 0x0, fake_dex_file_path);
+ thread_tree.AddThreadMap(1, 1, 0x3000, 0x1000, 0x0, fake_jit_cache_path,
+ map_flags::PROT_JIT_SYMFILE_MAP);
+ }
+
+ ThreadTree thread_tree;
+ const ThreadEntry* thread;
+ const std::string fake_native_lib_path = "fake_dir/fake_native_lib.so";
+ const std::string fake_interpreter_path = "fake_dir/libart.so";
+ const std::string fake_dex_file_path = "fake_dir/framework.jar";
+ const std::string fake_jit_cache_path = "fake_jit_app_cache:0";
+
+ const std::vector<uint64_t> fake_ips = {
+ 0x1000, // art_func1
+ 0x1100, // art_func2
+ 0x2000, // java_method1 in dex file
+ 0x1000, // art_func1
+ 0x1100, // art_func2
+ 0x3000, // java_method2 in jit cache
+ 0x1000, // art_func1
+ 0x1100, // art_func2
+ };
+};
+
+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
+ // converted to a dex frame.
+ CallChainReportBuilder builder(thread_tree);
+ 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);
+}
+
+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
+ // converted to a dex frame.
+ CallChainReportBuilder builder(thread_tree);
+ builder.SetConvertJITFrame(false);
+ 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_jit_cache_path);
+ ASSERT_EQ(entries[1].vaddr_in_file, 0x3000);
+ ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
+}
+
+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
+ // converted to a dex frame.
+ CallChainReportBuilder builder(thread_tree);
+ builder.SetRemoveArtFrame(false);
+ std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
+ ASSERT_EQ(entries.size(), 8);
+ for (size_t i : {0, 3, 6}) {
+ ASSERT_EQ(entries[i].ip, 0x1000);
+ ASSERT_STREQ(entries[i].symbol->Name(), "art_func1");
+ ASSERT_EQ(entries[i].dso->Path(), fake_interpreter_path);
+ ASSERT_EQ(entries[i].vaddr_in_file, 0);
+ ASSERT_EQ(entries[i].execution_type, CallChainExecutionType::ART_METHOD);
+ ASSERT_EQ(entries[i + 1].ip, 0x1100);
+ ASSERT_STREQ(entries[i + 1].symbol->Name(), "art_func2");
+ ASSERT_EQ(entries[i + 1].dso->Path(), fake_interpreter_path);
+ ASSERT_EQ(entries[i + 1].vaddr_in_file, 0x100);
+ ASSERT_EQ(entries[i + 1].execution_type, CallChainExecutionType::ART_METHOD);
+ }
+ ASSERT_EQ(entries[2].ip, 0x2000);
+ ASSERT_STREQ(entries[2].symbol->Name(), "java_method1");
+ ASSERT_EQ(entries[2].dso->Path(), fake_dex_file_path);
+ ASSERT_EQ(entries[2].vaddr_in_file, 0);
+ ASSERT_EQ(entries[2].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
+ ASSERT_EQ(entries[5].ip, 0x3000);
+ ASSERT_STREQ(entries[5].symbol->Name(), "java_method2");
+ ASSERT_EQ(entries[5].dso->Path(), fake_dex_file_path);
+ ASSERT_EQ(entries[5].vaddr_in_file, 0x100);
+ ASSERT_EQ(entries[5].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
+}
+
+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.
+ std::vector<uint64_t> fake_ips = {
+ 0x3000, // java_method2 in jit cache
+ 0x1000, // art_func1
+ 0x1100, // art_func2
+ 0x2100, // java_method2 in dex file
+ 0x1000, // art_func1
+ };
+ CallChainReportBuilder builder(thread_tree);
+ std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
+ ASSERT_EQ(entries.size(), 1);
+ ASSERT_EQ(entries[0].ip, 0x2100);
+ ASSERT_STREQ(entries[0].symbol->Name(), "java_method2");
+ ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
+ ASSERT_EQ(entries[0].vaddr_in_file, 0x100);
+ ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
+}
+
+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.
+ std::vector<uint64_t> fake_ips = {
+ 0x1000, // art_func1
+ 0x0, // native_func1
+ 0x2000, // java_method1 in dex file
+ 0x0, // native_func1
+ 0x1000, // art_func1
+ };
+ CallChainReportBuilder builder(thread_tree);
+ std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
+ ASSERT_EQ(entries.size(), 5);
+ for (size_t i : {0, 4}) {
+ ASSERT_EQ(entries[i].ip, 0x1000);
+ ASSERT_STREQ(entries[i].symbol->Name(), "art_func1");
+ ASSERT_EQ(entries[i].dso->Path(), fake_interpreter_path);
+ ASSERT_EQ(entries[i].vaddr_in_file, 0);
+ ASSERT_EQ(entries[i].execution_type, CallChainExecutionType::NATIVE_METHOD);
+ }
+ for (size_t i : {1, 3}) {
+ ASSERT_EQ(entries[i].ip, 0x0);
+ ASSERT_STREQ(entries[i].symbol->Name(), "native_func1");
+ ASSERT_EQ(entries[i].dso->Path(), fake_native_lib_path);
+ ASSERT_EQ(entries[i].vaddr_in_file, 0);
+ ASSERT_EQ(entries[i].execution_type, CallChainExecutionType::NATIVE_METHOD);
+ }
+
+ ASSERT_EQ(entries[2].ip, 0x2000);
+ ASSERT_STREQ(entries[2].symbol->Name(), "java_method1");
+ ASSERT_EQ(entries[2].dso->Path(), fake_dex_file_path);
+ ASSERT_EQ(entries[2].vaddr_in_file, 0x0);
+ ASSERT_EQ(entries[2].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
+}
+
+TEST_F(CallChainReportBuilderTest, keep_art_jni_method) {
+ // Test option: remove_art_frame = true.
+ // The callchain should remove art_jni_trampoline, but keep jni methods.
+ std::vector<uint64_t> fake_ips = {
+ 0x1200, // art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*)
+ 0x100, // art_jni_trampoline
+ 0x2000, // java_method1 in dex file
+ 0x1200, // art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*)
+ 0x100, // art_jni_trampoline
+ };
+ CallChainReportBuilder builder(thread_tree);
+ std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
+ ASSERT_EQ(entries.size(), 3);
+ for (size_t i : {0, 2}) {
+ ASSERT_EQ(entries[i].ip, 0x1200);
+ ASSERT_STREQ(entries[i].symbol->DemangledName(),
+ "art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*)");
+ ASSERT_EQ(entries[i].dso->Path(), fake_interpreter_path);
+ ASSERT_EQ(entries[i].vaddr_in_file, 0x200);
+ ASSERT_EQ(entries[i].execution_type, CallChainExecutionType::NATIVE_METHOD);
+ }
+ ASSERT_EQ(entries[1].ip, 0x2000);
+ ASSERT_STREQ(entries[1].symbol->Name(), "java_method1");
+ ASSERT_EQ(entries[1].dso->Path(), fake_dex_file_path);
+ ASSERT_EQ(entries[1].vaddr_in_file, 0x0);
+ ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
+}
+
+TEST_F(CallChainReportBuilderTest, add_proguard_mapping_file) {
+ std::vector<uint64_t> fake_ips = {
+ 0x2200, // 2200, // obfuscated_class.obfuscated_java_method
+ 0x3200, // 3200, // obfuscated_class.obfuscated_java_method2
+ };
+ CallChainReportBuilder builder(thread_tree);
+ // Symbol names aren't changed when not given proguard mapping files.
+ std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
+ ASSERT_EQ(entries.size(), 2);
+ ASSERT_EQ(entries[0].ip, 0x2200);
+ ASSERT_STREQ(entries[0].symbol->DemangledName(), "obfuscated_class.obfuscated_java_method");
+ ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
+ ASSERT_EQ(entries[0].vaddr_in_file, 0x200);
+ ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
+ ASSERT_EQ(entries[1].ip, 0x3200);
+ ASSERT_STREQ(entries[1].symbol->DemangledName(), "obfuscated_class.obfuscated_java_method2");
+ ASSERT_EQ(entries[1].dso->Path(), fake_jit_cache_path);
+ ASSERT_EQ(entries[1].vaddr_in_file, 0x3200);
+ ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
+
+ // Symbol names are changed when given a proguard mapping file.
+ TemporaryFile tmpfile;
+ close(tmpfile.release());
+ ASSERT_TRUE(android::base::WriteStringToFile(
+ "android.support.v4.app.RemoteActionCompatParcelizer -> obfuscated_class:\n"
+ " 13:13:androidx.core.app.RemoteActionCompat read(androidx.versionedparcelable.Versioned"
+ "Parcel) -> obfuscated_java_method\n"
+ " 13:13:androidx.core.app.RemoteActionCompat "
+ "android.support.v4.app.RemoteActionCompatParcelizer.read2(androidx.versionedparcelable."
+ "VersionedParcel) -> obfuscated_java_method2",
+ tmpfile.path));
+ builder.AddProguardMappingFile(tmpfile.path);
+ entries = builder.Build(thread, fake_ips, 0);
+ ASSERT_EQ(entries.size(), 2);
+ ASSERT_EQ(entries[0].ip, 0x2200);
+ ASSERT_STREQ(entries[0].symbol->DemangledName(),
+ "android.support.v4.app.RemoteActionCompatParcelizer.read");
+ ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
+ ASSERT_EQ(entries[0].vaddr_in_file, 0x200);
+ ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
+ ASSERT_EQ(entries[1].ip, 0x3200);
+ ASSERT_STREQ(entries[1].symbol->DemangledName(),
+ "android.support.v4.app.RemoteActionCompatParcelizer.read2");
+ ASSERT_EQ(entries[1].dso->Path(), fake_jit_cache_path);
+ ASSERT_EQ(entries[1].vaddr_in_file, 0x3200);
+ ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
+}
diff --git a/simpleperf/runtest/Android.bp b/simpleperf/runtest/Android.bp
index c50d7e0a..dee4ffd8 100644
--- a/simpleperf/runtest/Android.bp
+++ b/simpleperf/runtest/Android.bp
@@ -14,6 +14,15 @@
// 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_simpleperf_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_extras_simpleperf_license"],
+}
+
cc_defaults {
name: "simpleperf_runtest_defaults",
host_supported: true,
diff --git a/simpleperf/runtest/comm_change.cpp b/simpleperf/runtest/comm_change.cpp
index cdcb2bf4..cba59954 100644
--- a/simpleperf/runtest/comm_change.cpp
+++ b/simpleperf/runtest/comm_change.cpp
@@ -12,9 +12,9 @@ int main() {
// doesn't exit before we attach to it. This scheme also allows simpleperf to control
// how long to profile.
while (true) {
- prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("RUN_COMM1"), 0, 0, 0); // NOLINT
+ prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("RUN_COMM1"), 0, 0, 0); // NOLINT
Function1();
- prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("RUN_COMM2"), 0, 0, 0); // NOLINT
+ prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("RUN_COMM2"), 0, 0, 0); // NOLINT
Function1();
}
return 0;
diff --git a/simpleperf/rust/lib.rs b/simpleperf/rust/lib.rs
new file mode 100644
index 00000000..05202efd
--- /dev/null
+++ b/simpleperf/rust/lib.rs
@@ -0,0 +1,66 @@
+//
+// 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 simpleperf etm operations required
+//! by profcollect.
+
+use std::ffi::CString;
+use std::path::Path;
+use std::time::Duration;
+
+fn path_to_cstr(path: &Path) -> CString {
+ CString::new(path.to_str().unwrap()).unwrap()
+}
+
+/// Returns whether the system has support for simpleperf etm.
+pub fn has_support() -> bool {
+ unsafe { simpleperf_profcollect_bindgen::HasSupport() }
+}
+
+/// 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,
+}
+
+/// 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();
+
+ unsafe {
+ simpleperf_profcollect_bindgen::Record(event_name.as_ptr(), trace_file.as_ptr(), duration);
+ }
+}
+
+/// Translate ETM trace to profile.
+pub fn process(trace_path: &Path, profile_path: &Path) {
+ let trace_path = path_to_cstr(trace_path);
+ let profile_path = path_to_cstr(profile_path);
+
+ unsafe {
+ simpleperf_profcollect_bindgen::Inject(trace_path.as_ptr(), profile_path.as_ptr());
+ }
+}
diff --git a/simpleperf/sample_tree.h b/simpleperf/sample_tree.h
index f57fe786..4d5152fb 100644
--- a/simpleperf/sample_tree.h
+++ b/simpleperf/sample_tree.h
@@ -19,15 +19,15 @@
#include <unordered_map>
-#include "callchain.h"
#include "OfflineUnwinder.h"
-#include "perf_regs.h"
-#include "record.h"
#include "SampleComparator.h"
#include "SampleDisplayer.h"
+#include "callchain.h"
+#include "perf_regs.h"
+#include "record.h"
#include "thread_tree.h"
-using namespace simpleperf;
+namespace simpleperf {
// A SampleTree is a collection of samples. A profiling report is mainly about
// constructing a SampleTree and display it. There are three steps involved:
@@ -66,12 +66,9 @@ class SampleTreeBuilder {
virtual ~SampleTreeBuilder() {}
- void SetBranchSampleOption(bool use_branch_address) {
- use_branch_address_ = use_branch_address;
- }
+ void SetBranchSampleOption(bool use_branch_address) { use_branch_address_ = use_branch_address; }
- void SetCallChainSampleOptions(bool accumulate_callchain,
- bool build_callchain,
+ void SetCallChainSampleOptions(bool accumulate_callchain, bool build_callchain,
bool use_caller_as_callchain_root) {
accumulate_callchain_ = accumulate_callchain;
build_callchain_ = build_callchain;
@@ -81,6 +78,8 @@ class SampleTreeBuilder {
}
}
+ OfflineUnwinder* GetUnwinder() { return offline_unwinder_.get(); }
+
void ProcessSampleRecord(const SampleRecord& r) {
if (use_branch_address_ && (r.sample_type & PERF_SAMPLE_BRANCH_STACK)) {
for (uint64_t i = 0; i < r.branch_stack_data.stack_nr; ++i) {
@@ -100,15 +99,13 @@ class SampleTreeBuilder {
if (accumulate_callchain_) {
std::vector<uint64_t> ips;
if (r.sample_type & PERF_SAMPLE_CALLCHAIN) {
- ips.insert(ips.end(), r.callchain_data.ips,
- r.callchain_data.ips + r.callchain_data.ip_nr);
+ ips.insert(ips.end(), r.callchain_data.ips, r.callchain_data.ips + r.callchain_data.ip_nr);
}
const ThreadEntry* thread = GetThreadOfSample(sample);
// Use stack_user_data.data.size() instead of stack_user_data.dyn_size, to
// make up for the missing kernel patch in N9. See b/22612370.
if (thread != nullptr && (r.sample_type & PERF_SAMPLE_REGS_USER) &&
- (r.regs_user_data.reg_mask != 0) &&
- (r.sample_type & PERF_SAMPLE_STACK_USER) &&
+ (r.regs_user_data.reg_mask != 0) && (r.sample_type & PERF_SAMPLE_STACK_USER) &&
(r.GetValidStackSize() > 0)) {
RegSet regs(r.regs_user_data.abi, r.regs_user_data.reg_mask, r.regs_user_data.regs);
std::vector<uint64_t> user_ips;
@@ -186,8 +183,7 @@ class SampleTreeBuilder {
protected:
virtual EntryT* CreateSample(const SampleRecord& r, bool in_kernel,
AccumulateInfoT* acc_info) = 0;
- virtual EntryT* CreateBranchSample(const SampleRecord& r,
- const BranchStackItemType& item) = 0;
+ virtual EntryT* CreateBranchSample(const SampleRecord& r, const BranchStackItemType& item) = 0;
virtual EntryT* CreateCallChainSample(const ThreadEntry* thread, const EntryT* sample,
uint64_t ip, bool in_kernel,
const std::vector<EntryT*>& callchain,
@@ -239,22 +235,19 @@ class SampleTreeBuilder {
if (it != sample_set_.end()) {
EntryT* sample = *it;
// Process only once for recursive function call.
- if (std::find(callchain.begin(), callchain.end(), sample) !=
- callchain.end()) {
+ if (std::find(callchain.begin(), callchain.end(), sample) != callchain.end()) {
return sample;
}
}
return InsertSample(std::move(sample));
}
- void InsertCallChainForSample(EntryT* sample,
- const std::vector<EntryT*>& callchain,
+ void InsertCallChainForSample(EntryT* sample, const std::vector<EntryT*>& callchain,
const AccumulateInfoT& acc_info) {
uint64_t period = GetPeriodForCallChain(acc_info);
- sample->callchain.AddCallChain(
- callchain, period, [&](const EntryT* s1, const EntryT* s2) {
- return sample_comparator_.IsSameSample(s1, s2);
- });
+ sample->callchain.AddCallChain(callchain, period, [&](const EntryT* s1, const EntryT* s2) {
+ return sample_comparator_.IsSameSample(s1, s2);
+ });
}
void AddCallChainDuplicateInfo() {
@@ -308,8 +301,7 @@ class SampleTreeBuilder {
template <typename EntryT>
class SampleTreeSorter {
public:
- explicit SampleTreeSorter(SampleComparator<EntryT> comparator)
- : comparator_(comparator) {}
+ explicit SampleTreeSorter(SampleComparator<EntryT> comparator) : comparator_(comparator) {}
virtual ~SampleTreeSorter() {}
@@ -320,9 +312,8 @@ class SampleTreeSorter {
}
}
if (!comparator_.empty()) {
- std::sort(v.begin(), v.end(), [this](const EntryT* s1, const EntryT* s2) {
- return comparator_(s1, s2);
- });
+ std::sort(v.begin(), v.end(),
+ [this](const EntryT* s1, const EntryT* s2) { return comparator_(s1, s2); });
}
}
@@ -336,13 +327,11 @@ class SampleTreeSorter {
template <typename EntryT, typename InfoT>
class SampleTreeDisplayer {
public:
- explicit SampleTreeDisplayer(SampleDisplayer<EntryT, InfoT> displayer)
- : displayer_(displayer) {}
+ explicit SampleTreeDisplayer(SampleDisplayer<EntryT, InfoT> displayer) : displayer_(displayer) {}
virtual ~SampleTreeDisplayer() {}
- void DisplaySamples(FILE* fp, const std::vector<EntryT*>& samples,
- const InfoT* info) {
+ void DisplaySamples(FILE* fp, const std::vector<EntryT*>& samples, const InfoT* info) {
displayer_.SetInfo(info);
for (const auto& sample : samples) {
displayer_.AdjustWidth(sample);
@@ -357,4 +346,6 @@ class SampleTreeDisplayer {
SampleDisplayer<EntryT, InfoT> displayer_;
};
+} // namespace simpleperf
+
#endif // SIMPLE_PERF_SAMPLE_TREE_H_
diff --git a/simpleperf/sample_tree_test.cpp b/simpleperf/sample_tree_test.cpp
index 4123b0e6..bee187d7 100644
--- a/simpleperf/sample_tree_test.cpp
+++ b/simpleperf/sample_tree_test.cpp
@@ -19,6 +19,8 @@
#include "sample_tree.h"
#include "thread_tree.h"
+using namespace simpleperf;
+
namespace {
struct SampleEntry {
@@ -29,9 +31,8 @@ struct SampleEntry {
uint64_t map_start_addr;
size_t sample_count;
- SampleEntry(int pid, int tid, const char* thread_comm,
- const std::string& dso_name, uint64_t map_start_addr,
- size_t sample_count = 1u)
+ SampleEntry(int pid, int tid, const char* thread_comm, const std::string& dso_name,
+ uint64_t map_start_addr, size_t sample_count = 1u)
: pid(pid),
tid(tid),
thread_comm(thread_comm),
@@ -64,26 +65,20 @@ class TestSampleTreeBuilder : public SampleTreeBuilder<SampleEntry, int> {
void AddSample(int pid, int tid, uint64_t ip, bool in_kernel) {
const ThreadEntry* thread = thread_tree_->FindThreadOrNew(pid, tid);
const MapEntry* map = thread_tree_->FindMap(thread, ip, in_kernel);
- InsertSample(std::unique_ptr<SampleEntry>(new SampleEntry(
- pid, tid, thread->comm, map->dso->Path(), map->start_addr)));
+ InsertSample(std::unique_ptr<SampleEntry>(
+ new SampleEntry(pid, tid, thread->comm, map->dso->Path(), map->start_addr)));
}
protected:
- SampleEntry* CreateSample(const SampleRecord&, bool, int*) override {
- return nullptr;
- }
- SampleEntry* CreateBranchSample(const SampleRecord&,
- const BranchStackItemType&) override {
+ SampleEntry* CreateSample(const SampleRecord&, bool, int*) override { return nullptr; }
+ SampleEntry* CreateBranchSample(const SampleRecord&, const BranchStackItemType&) override {
return nullptr;
};
SampleEntry* CreateCallChainSample(const ThreadEntry*, const SampleEntry*, uint64_t, bool,
- const std::vector<SampleEntry*>&,
- const int&) override {
- return nullptr;
- }
- const ThreadEntry* GetThreadOfSample(SampleEntry*) override {
+ const std::vector<SampleEntry*>&, const int&) override {
return nullptr;
}
+ const ThreadEntry* GetThreadOfSample(SampleEntry*) override { return nullptr; }
uint64_t GetPeriodForCallChain(const int&) override { return 0; }
void MergeSample(SampleEntry* sample1, SampleEntry* sample2) override {
sample1->sample_count += sample2->sample_count;
@@ -93,8 +88,7 @@ class TestSampleTreeBuilder : public SampleTreeBuilder<SampleEntry, int> {
ThreadTree* thread_tree_;
};
-static void SampleMatchExpectation(const SampleEntry& sample,
- const SampleEntry& expected,
+static void SampleMatchExpectation(const SampleEntry& sample, const SampleEntry& expected,
bool* has_error) {
*has_error = true;
ASSERT_EQ(expected.pid, sample.pid);
@@ -115,7 +109,7 @@ static void CheckSamples(const std::vector<SampleEntry*>& samples,
ASSERT_FALSE(has_error) << "Error matching sample at pos " << i;
}
}
-}
+} // namespace
class SampleTreeTest : public testing::Test {
protected:
@@ -214,14 +208,14 @@ TEST(sample_tree, overlapped_map) {
ThreadTree thread_tree;
TestSampleTreeBuilder sample_tree_builder(&thread_tree);
thread_tree.SetThreadName(1, 1, "thread1");
- thread_tree.AddThreadMap(1, 1, 1, 10, 0, "map1"); // Add map 1.
- sample_tree_builder.AddSample(1, 1, 5, false); // Hit map 1.
- thread_tree.AddThreadMap(1, 1, 5, 20, 0, "map2"); // Add map 2.
- sample_tree_builder.AddSample(1, 1, 6, false); // Hit map 2.
- sample_tree_builder.AddSample(1, 1, 4, false); // Hit map 1.
- thread_tree.AddThreadMap(1, 1, 2, 7, 0, "map3"); // Add map 3.
- sample_tree_builder.AddSample(1, 1, 7, false); // Hit map 3.
- sample_tree_builder.AddSample(1, 1, 10, false); // Hit map 2.
+ thread_tree.AddThreadMap(1, 1, 1, 10, 0, "map1"); // Add map 1.
+ sample_tree_builder.AddSample(1, 1, 5, false); // Hit map 1.
+ thread_tree.AddThreadMap(1, 1, 5, 20, 0, "map2"); // Add map 2.
+ sample_tree_builder.AddSample(1, 1, 6, false); // Hit map 2.
+ sample_tree_builder.AddSample(1, 1, 4, false); // Hit map 1.
+ thread_tree.AddThreadMap(1, 1, 2, 7, 0, "map3"); // Add map 3.
+ sample_tree_builder.AddSample(1, 1, 7, false); // Hit map 3.
+ sample_tree_builder.AddSample(1, 1, 10, false); // Hit map 2.
std::vector<SampleEntry> expected_samples = {
SampleEntry(1, 1, "thread1", "map1", 1, 2),
diff --git a/simpleperf/scripts/annotate.py b/simpleperf/scripts/annotate.py
index fb80bfec..1970297f 100755
--- a/simpleperf/scripts/annotate.py
+++ b/simpleperf/scripts/annotate.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2016 The Android Open Source Project
#
@@ -25,8 +25,10 @@ import os.path
import shutil
from simpleperf_report_lib import ReportLib
-from utils import log_info, log_warning, log_exit
-from utils import Addr2Nearestline, extant_dir, flatten_arg_list, is_windows, SourceFileSearcher
+from simpleperf_utils import (
+ Addr2Nearestline, BinaryFinder, extant_dir, flatten_arg_list, is_windows, log_exit, log_info,
+ log_warning, ReadElf, SourceFileSearcher)
+
class SourceLine(object):
def __init__(self, file_id, function, line):
@@ -50,11 +52,13 @@ class SourceLine(object):
class Addr2Line(object):
"""collect information of how to map [dso_name, vaddr] to [source_file:line].
"""
+
def __init__(self, ndk_path, binary_cache_path, source_dirs):
- self.addr2line = Addr2Nearestline(ndk_path, binary_cache_path, True)
+ binary_finder = BinaryFinder(binary_cache_path, ReadElf(ndk_path))
+ self.addr2line = Addr2Nearestline(ndk_path, binary_finder, True)
self.source_searcher = SourceFileSearcher(source_dirs)
- def add_addr(self, dso_path, func_addr, addr):
+ def add_addr(self, dso_path: str, build_id: str, func_addr: int, addr: int):
self.addr2line.add_addr(dso_path, func_addr, addr)
def convert_addrs_to_lines(self):
@@ -85,11 +89,11 @@ class Period(object):
running that line and functions called by that line. Same thing applies
when it is used for a function, a source file, or a binary.
"""
+
def __init__(self, period=0, acc_period=0):
self.period = period
self.acc_period = acc_period
-
def __iadd__(self, other):
self.period += other.period
self.acc_period += other.acc_period
@@ -98,17 +102,18 @@ class Period(object):
class DsoPeriod(object):
"""Period for each shared library"""
+
def __init__(self, dso_name):
self.dso_name = dso_name
self.period = Period()
-
def add_period(self, period):
self.period += period
class FilePeriod(object):
"""Period for each source file"""
+
def __init__(self, file_id):
self.file = file_id
self.period = Period()
@@ -117,18 +122,15 @@ class FilePeriod(object):
# Period for each function in the source file.
self.function_dict = {}
-
def add_period(self, period):
self.period += period
-
def add_line_period(self, line, period):
a = self.line_dict.get(line)
if a is None:
self.line_dict[line] = a = Period()
a += period
-
def add_function_period(self, function_name, function_start_line, period):
a = self.function_dict.get(function_name)
if not a:
@@ -140,6 +142,7 @@ class FilePeriod(object):
class SourceFileAnnotator(object):
"""group code for annotating source files"""
+
def __init__(self, config):
# check config variables
config_names = ['perf_data_list', 'source_dirs', 'comm_filters',
@@ -175,13 +178,11 @@ class SourceFileAnnotator(object):
shutil.rmtree(output_dir)
os.makedirs(output_dir)
-
self.addr2line = Addr2Line(self.config['ndk_path'], symfs_dir, config.get('source_dirs'))
self.period = 0
self.dso_periods = {}
self.file_periods = {}
-
def annotate(self):
self._collect_addrs()
self._convert_addrs_to_lines()
@@ -189,7 +190,6 @@ class SourceFileAnnotator(object):
self._write_summary()
self._annotate_files()
-
def _collect_addrs(self):
"""Read perf.data, collect all addresses we need to convert to
source file:line.
@@ -215,12 +215,12 @@ class SourceFileAnnotator(object):
symbols.append(callchain.entries[i].symbol)
for symbol in symbols:
if self._filter_symbol(symbol):
- self.addr2line.add_addr(symbol.dso_name, symbol.symbol_addr,
+ build_id = lib.GetBuildIdForPath(symbol.dso_name)
+ self.addr2line.add_addr(symbol.dso_name, build_id, symbol.symbol_addr,
symbol.vaddr_in_file)
- self.addr2line.add_addr(symbol.dso_name, symbol.symbol_addr,
+ self.addr2line.add_addr(symbol.dso_name, build_id, symbol.symbol_addr,
symbol.symbol_addr)
-
def _filter_sample(self, sample):
"""Return true if the sample can be used."""
if self.comm_filter:
@@ -234,17 +234,14 @@ class SourceFileAnnotator(object):
return False
return True
-
def _filter_symbol(self, symbol):
if not self.dso_filter or symbol.dso_name in self.dso_filter:
return True
return False
-
def _convert_addrs_to_lines(self):
self.addr2line.convert_addrs_to_lines()
-
def _generate_periods(self):
"""read perf.data, collect Period for all types:
binaries, source files, functions, lines.
@@ -265,7 +262,6 @@ class SourceFileAnnotator(object):
continue
self._generate_periods_for_sample(lib, sample)
-
def _generate_periods_for_sample(self, lib, sample):
symbols = []
symbols.append(lib.GetSymbolOfCurrentSample())
@@ -310,7 +306,6 @@ class SourceFileAnnotator(object):
if is_sample_used:
self.period += sample.period
-
def _add_dso_period(self, dso_name, period, used_dso_dict):
if dso_name not in used_dso_dict:
used_dso_dict[dso_name] = True
@@ -319,7 +314,6 @@ class SourceFileAnnotator(object):
dso_period = self.dso_periods[dso_name] = DsoPeriod(dso_name)
dso_period.add_period(period)
-
def _add_file_period(self, source, period, used_file_dict):
if source.file_key not in used_file_dict:
used_file_dict[source.file_key] = True
@@ -328,21 +322,18 @@ class SourceFileAnnotator(object):
file_period = self.file_periods[source.file] = FilePeriod(source.file)
file_period.add_period(period)
-
def _add_line_period(self, source, period, used_line_dict):
if source.line_key not in used_line_dict:
used_line_dict[source.line_key] = True
file_period = self.file_periods[source.file]
file_period.add_line_period(source.line, period)
-
def _add_function_period(self, source, period, used_function_dict):
if source.function_key not in used_function_dict:
used_function_dict[source.function_key] = True
file_period = self.file_periods[source.file]
file_period.add_function_period(source.function, source.line, period)
-
def _write_summary(self):
summary = os.path.join(self.config['annotate_dest_dir'], 'summary')
with open(summary, 'w') as f:
@@ -375,12 +366,10 @@ class SourceFileAnnotator(object):
f.write('\tline %d: %s\n' % (
line, self._get_percentage_str(file_period.line_dict[line])))
-
def _get_percentage_str(self, period, short=False):
s = 'acc_p: %f%%, p: %f%%' if short else 'accumulated_period: %f%%, period: %f%%'
return s % self._get_percentage(period)
-
def _get_percentage(self, period):
if self.period == 0:
return (0, 0)
@@ -388,7 +377,6 @@ class SourceFileAnnotator(object):
p = 100.0 * period.period / self.period
return (acc_p, p)
-
def _annotate_files(self):
"""Annotate Source files: add acc_period/period for each source file.
1. Annotate java source files, which have $JAVA_SRC_ROOT prefix.
@@ -409,7 +397,6 @@ class SourceFileAnnotator(object):
is_java = from_path.endswith('.java')
self._annotate_file(from_path, to_path, self.file_periods[key], is_java)
-
def _annotate_file(self, from_path, to_path, file_period, is_java):
"""Annotate a source file.
@@ -457,6 +444,7 @@ class SourceFileAnnotator(object):
wf.write(annotate)
wf.write(lines[line-1])
+
def main():
parser = argparse.ArgumentParser(description="""
Annotate source files based on profiling data. It reads line information from binary_cache
@@ -492,5 +480,6 @@ def main():
annotator.annotate()
log_info('annotate finish successfully, please check result in annotated_files/.')
+
if __name__ == '__main__':
main()
diff --git a/simpleperf/scripts/api_profiler.py b/simpleperf/scripts/api_profiler.py
index 424de698..d39b8e3d 100755
--- a/simpleperf/scripts/api_profiler.py
+++ b/simpleperf/scripts/api_profiler.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2019 The Android Open Source Project
#
@@ -33,7 +33,8 @@ import os.path
import shutil
import zipfile
-from utils import AdbHelper, get_target_binary_path, log_exit, log_info, remove
+from simpleperf_utils import AdbHelper, get_target_binary_path, log_exit, log_info, remove
+
def prepare_recording(args):
adb = AdbHelper()
@@ -41,6 +42,7 @@ def prepare_recording(args):
upload_simpleperf_to_device(adb)
run_simpleperf_prepare_cmd(adb)
+
def enable_profiling_on_device(adb, args):
android_version = adb.get_android_version()
if android_version >= 10:
@@ -49,12 +51,14 @@ def enable_profiling_on_device(adb, args):
adb.set_property('debug.perf_event_mlock_kb', str(args.max_memory_in_kb[0]))
adb.set_property('security.perf_harden', '0')
+
def upload_simpleperf_to_device(adb):
device_arch = adb.get_device_arch()
simpleperf_binary = get_target_binary_path(device_arch, 'simpleperf')
adb.check_run(['push', simpleperf_binary, '/data/local/tmp'])
adb.check_run(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf'])
+
def run_simpleperf_prepare_cmd(adb):
adb.check_run(['shell', '/data/local/tmp/simpleperf', 'api-prepare'])
@@ -66,6 +70,7 @@ def collect_data(args):
download_recording_data(adb, args)
unzip_recording_data(args)
+
def download_recording_data(adb, args):
""" download recording data to simpleperf_data.zip."""
upload_simpleperf_to_device(adb)
@@ -74,6 +79,7 @@ def download_recording_data(adb, args):
adb.check_run(['pull', '/data/local/tmp/simpleperf_data.zip', args.out_dir])
adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/simpleperf_data'])
+
def unzip_recording_data(args):
zip_file_path = os.path.join(args.out_dir, 'simpleperf_data.zip')
with zipfile.ZipFile(zip_file_path, 'r') as zip_fh:
@@ -84,10 +90,12 @@ def unzip_recording_data(args):
zip_fh.extract(name, args.out_dir)
remove(zip_file_path)
+
class ArgumentHelpFormatter(argparse.ArgumentDefaultsHelpFormatter,
argparse.RawDescriptionHelpFormatter):
pass
+
def main():
parser = argparse.ArgumentParser(description=__doc__,
formatter_class=ArgumentHelpFormatter)
@@ -113,5 +121,6 @@ def main():
args = parser.parse_args()
args.func(args)
+
if __name__ == '__main__':
main()
diff --git a/simpleperf/scripts/app_profiler.py b/simpleperf/scripts/app_profiler.py
index d6e7ff33..38b3602c 100755
--- a/simpleperf/scripts/app_profiler.py
+++ b/simpleperf/scripts/app_profiler.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2016 The Android Open Source Project
#
@@ -29,13 +29,16 @@ import subprocess
import sys
import time
-from utils import AdbHelper, bytes_to_str, extant_dir, get_script_dir, get_target_binary_path
-from utils import log_debug, log_info, log_exit, ReadElf, remove, set_log_level, str_to_bytes
+from simpleperf_utils import (
+ AdbHelper, bytes_to_str, extant_dir, get_script_dir, get_target_binary_path, log_debug,
+ log_info, log_exit, ReadElf, remove, set_log_level, str_to_bytes)
NATIVE_LIBS_DIR_ON_DEVICE = '/data/local/tmp/native_libs/'
+
class HostElfEntry(object):
"""Represent a native lib on host in NativeLibDownloader."""
+
def __init__(self, path, name, score):
self.path = path
self.name = name
@@ -55,6 +58,7 @@ class NativeLibDownloader(object):
2. Check the available native libs in /data/local/tmp/native_libs on device.
3. Sync native libs on device.
"""
+
def __init__(self, ndk_path, device_arch, adb):
self.adb = adb
self.readelf = ReadElf(ndk_path)
@@ -169,8 +173,7 @@ class NativeLibDownloader(object):
target = self.dir_on_device + entry.name
# Skip download if we have a file with the same name and size on device.
- result, output = self.adb.run_and_return_output(
- ['shell', 'ls', '-l', target], log_output=False, log_stderr=False)
+ result, output = self.adb.run_and_return_output(['shell', 'ls', '-l', target])
if result:
items = output.split()
if len(items) > 5:
@@ -185,6 +188,7 @@ class NativeLibDownloader(object):
class ProfilerBase(object):
"""Base class of all Profilers."""
+
def __init__(self, args):
self.args = args
self.adb = AdbHelper(enable_switch_to_root=not args.disable_adb_root)
@@ -230,7 +234,7 @@ class ProfilerBase(object):
"""Start simpleperf reocrd 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, '>/dev/null', '2>&1']):
+ if self.adb.run(['shell', 'ls', NATIVE_LIBS_DIR_ON_DEVICE]):
args += ['--symfs', NATIVE_LIBS_DIR_ON_DEVICE]
args += ['--log', self.args.log]
args += target_args
@@ -284,6 +288,7 @@ class ProfilerBase(object):
class AppProfiler(ProfilerBase):
"""Profile an Android app."""
+
def prepare(self):
super(AppProfiler, self).prepare()
if self.args.compile_java_code:
@@ -320,7 +325,7 @@ class AppProfiler(ProfilerBase):
adb_args = ['shell', 'cd /data/data/' + self.args.app + ' && ' + (' '.join(args))]
else:
adb_args = ['shell', 'run-as', self.args.app] + args
- return self.adb.run_and_return_output(adb_args, log_output=False)
+ return self.adb.run_and_return_output(adb_args)
def start(self):
if self.args.activity or self.args.test:
@@ -350,32 +355,44 @@ class AppProfiler(ProfilerBase):
class NativeProgramProfiler(ProfilerBase):
"""Profile a native program."""
+
def start(self):
- pid = int(self.adb.check_run_and_return_output(['shell', 'pidof',
- self.args.native_program]))
- self.start_profiling(['-p', str(pid)])
+ log_info('Waiting for native process %s' % self.args.native_program)
+ while True:
+ (result, pid) = self.adb.run_and_return_output(['shell', 'pidof',
+ self.args.native_program])
+ if not result:
+ # Wait for 1 millisecond.
+ time.sleep(0.001)
+ else:
+ self.start_profiling(['-p', str(int(pid))])
+ break
class NativeCommandProfiler(ProfilerBase):
"""Profile running a native command."""
+
def start(self):
self.start_profiling([self.args.cmd])
class NativeProcessProfiler(ProfilerBase):
"""Profile processes given their pids."""
+
def start(self):
self.start_profiling(['-p', ','.join(self.args.pid)])
class NativeThreadProfiler(ProfilerBase):
"""Profile threads given their tids."""
+
def start(self):
self.start_profiling(['-t', ','.join(self.args.tid)])
class SystemWideProfiler(ProfilerBase):
"""Profile system wide."""
+
def start(self):
self.start_profiling(['-a'])
@@ -385,7 +402,7 @@ def main():
formatter_class=argparse.RawDescriptionHelpFormatter)
target_group = parser.add_argument_group(title='Select profiling target'
- ).add_mutually_exclusive_group(required=True)
+ ).add_mutually_exclusive_group(required=True)
target_group.add_argument('-p', '--app', help="""Profile an Android app, given the package name.
Like `-p com.example.android.myapp`.""")
@@ -477,5 +494,6 @@ def main():
profiler = SystemWideProfiler(args)
profiler.profile()
+
if __name__ == '__main__':
main()
diff --git a/simpleperf/scripts/bin/android/arm/simpleperf b/simpleperf/scripts/bin/android/arm/simpleperf
index 9a29c7f8..45250944 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 cbc1da5c..52dcf00c 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/x86/simpleperf b/simpleperf/scripts/bin/android/x86/simpleperf
index a6119599..49ae7dc6 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 0470a5d5..809a9ef6 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 cbe96228..7d95bbea 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 b056f29d..757dd276 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 480d2890..a3f0539a 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 372d42e2..bb7fe549 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/libsimpleperf_report.dll b/simpleperf/scripts/bin/windows/x86/libsimpleperf_report.dll
deleted file mode 100755
index 33326d3e..00000000
--- a/simpleperf/scripts/bin/windows/x86/libsimpleperf_report.dll
+++ /dev/null
Binary files differ
diff --git a/simpleperf/scripts/bin/windows/x86/libwinpthread-1.dll b/simpleperf/scripts/bin/windows/x86/libwinpthread-1.dll
deleted file mode 100755
index a41127ab..00000000
--- a/simpleperf/scripts/bin/windows/x86/libwinpthread-1.dll
+++ /dev/null
Binary files differ
diff --git a/simpleperf/scripts/bin/windows/x86/simpleperf.exe b/simpleperf/scripts/bin/windows/x86/simpleperf.exe
deleted file mode 100755
index daee828f..00000000
--- a/simpleperf/scripts/bin/windows/x86/simpleperf.exe
+++ /dev/null
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
index 176e8b6a..77f14cf6 100755
--- a/simpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dll
+++ b/simpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dll
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
index 5a12ce3c..221fac1a 100755
--- a/simpleperf/scripts/bin/windows/x86_64/libwinpthread-1.dll
+++ b/simpleperf/scripts/bin/windows/x86_64/libwinpthread-1.dll
Binary files differ
diff --git a/simpleperf/scripts/bin/windows/x86_64/simpleperf.exe b/simpleperf/scripts/bin/windows/x86_64/simpleperf.exe
index 89dbc9b0..b1621112 100755
--- a/simpleperf/scripts/bin/windows/x86_64/simpleperf.exe
+++ b/simpleperf/scripts/bin/windows/x86_64/simpleperf.exe
Binary files differ
diff --git a/simpleperf/scripts/binary_cache_builder.py b/simpleperf/scripts/binary_cache_builder.py
index 33de341e..362b8941 100755
--- a/simpleperf/scripts/binary_cache_builder.py
+++ b/simpleperf/scripts/binary_cache_builder.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2016 The Android Open Source Project
#
@@ -21,20 +21,26 @@
from __future__ import print_function
import argparse
+from dataclasses import dataclass
import os
import os.path
+from pathlib import Path
import shutil
+from typing import List, Optional, Union
from simpleperf_report_lib import ReportLib
-from utils import AdbHelper, extant_dir, extant_file, flatten_arg_list, log_info, log_warning
-from utils import ReadElf, set_log_level
+from simpleperf_utils import (AdbHelper, extant_dir, extant_file, flatten_arg_list, log_info,
+ log_warning, ReadElf, set_log_level, str_to_bytes)
+
def is_jit_symfile(dso_name):
return dso_name.split('/')[-1].startswith('TemporaryFile')
+
class BinaryCacheBuilder(object):
"""Collect all binaries needed by perf.data in binary_cache."""
- def __init__(self, ndk_path, disable_adb_root):
+
+ def __init__(self, ndk_path: Optional[str], disable_adb_root: bool):
self.adb = AdbHelper(enable_switch_to_root=not disable_adb_root)
self.readelf = ReadElf(ndk_path)
self.binary_cache_dir = 'binary_cache'
@@ -42,15 +48,15 @@ class BinaryCacheBuilder(object):
os.makedirs(self.binary_cache_dir)
self.binaries = {}
-
- def build_binary_cache(self, perf_data_path, symfs_dirs):
- self._collect_used_binaries(perf_data_path)
+ def build_binary_cache(self, perf_data_path: str, symfs_dirs: List[Union[Path, str]]):
+ self.collect_used_binaries(perf_data_path)
self.copy_binaries_from_symfs_dirs(symfs_dirs)
- self.pull_binaries_from_device()
- self._pull_kernel_symbols()
-
+ if self.adb.is_device_available():
+ self.pull_binaries_from_device()
+ self._pull_kernel_symbols()
+ self.create_build_id_list()
- def _collect_used_binaries(self, perf_data_path):
+ def collect_used_binaries(self, perf_data_path):
"""read perf.data, collect all used binaries and their build id (if available)."""
# A dict mapping from binary name to build_id
binaries = {}
@@ -72,11 +78,11 @@ class BinaryCacheBuilder(object):
if dso_name not in binaries:
if is_jit_symfile(dso_name):
continue
- binaries[dso_name] = lib.GetBuildIdForPath(dso_name)
+ name = 'vmlinux' if dso_name == '[kernel.kallsyms]' else dso_name
+ binaries[name] = lib.GetBuildIdForPath(dso_name)
self.binaries = binaries
-
- def copy_binaries_from_symfs_dirs(self, symfs_dirs):
+ def copy_binaries_from_symfs_dirs(self, symfs_dirs: List[Union[Path, str]]):
"""collect all files in symfs_dirs."""
if not symfs_dirs:
return
@@ -113,7 +119,6 @@ class BinaryCacheBuilder(object):
expected_build_id, binary)
break
-
def _copy_to_binary_cache(self, from_path, expected_build_id, target_file):
if target_file[0] == '/':
target_file = target_file[1:]
@@ -128,7 +133,6 @@ class BinaryCacheBuilder(object):
log_info('copy to binary_cache: %s to %s' % (from_path, target_file))
shutil.copy(from_path, target_file)
-
def _need_to_copy(self, source_file, target_file, expected_build_id):
if not os.path.isfile(target_file):
return True
@@ -137,7 +141,6 @@ class BinaryCacheBuilder(object):
return self._get_file_stripped_level(source_file) < self._get_file_stripped_level(
target_file)
-
def _get_file_stripped_level(self, file_path):
"""Return stripped level of an ELF file. Larger value means more stripped."""
sections = self.readelf.get_sections(file_path)
@@ -147,7 +150,6 @@ class BinaryCacheBuilder(object):
return 1
return 2
-
def pull_binaries_from_device(self):
"""pull binaries needed in perf.data to binary_cache."""
for binary in self.binaries:
@@ -159,7 +161,6 @@ class BinaryCacheBuilder(object):
binary_cache_file = os.path.join(self.binary_cache_dir, binary_cache_file)
self._check_and_pull_binary(binary, build_id, binary_cache_file)
-
def _check_and_pull_binary(self, binary, expected_build_id, binary_cache_file):
"""If the binary_cache_file exists and has the expected_build_id, there
is no need to pull the binary from device. Otherwise, pull it.
@@ -182,12 +183,10 @@ class BinaryCacheBuilder(object):
else:
log_info('use current file in binary_cache: %s' % binary_cache_file)
-
def _read_build_id(self, file_path):
"""read build id of a binary on host."""
return self.readelf.get_build_id(file_path)
-
def _pull_file_from_device(self, device_path, host_path):
if self.adb.run(['pull', device_path, host_path]):
return True
@@ -201,7 +200,6 @@ class BinaryCacheBuilder(object):
log_warning('failed to pull %s from device' % device_path)
return False
-
def _pull_kernel_symbols(self):
file_path = os.path.join(self.binary_cache_dir, 'kallsyms')
if os.path.isfile(file_path):
@@ -210,6 +208,21 @@ class BinaryCacheBuilder(object):
self.adb.run(['shell', 'echo', '0', '>/proc/sys/kernel/kptr_restrict'])
self.adb.run(['pull', '/proc/kallsyms', file_path])
+ def create_build_id_list(self):
+ """ Create build_id_list. So report scripts can find a binary by its build_id instead of
+ path.
+ """
+ build_id_list_path = os.path.join(self.binary_cache_dir, 'build_id_list')
+ with open(build_id_list_path, 'wb') as fh:
+ for root, _, files in os.walk(self.binary_cache_dir):
+ for filename in files:
+ path = os.path.join(root, filename)
+ relative_path = path[len(self.binary_cache_dir) + 1:]
+ build_id = self._read_build_id(path)
+ if build_id:
+ line = f'{build_id}={relative_path}\n'
+ fh.write(str_to_bytes(line))
+
def main():
parser = argparse.ArgumentParser(description="""
diff --git a/simpleperf/scripts/debug_unwind_reporter.py b/simpleperf/scripts/debug_unwind_reporter.py
index 864003a9..238c9acd 100755
--- a/simpleperf/scripts/debug_unwind_reporter.py
+++ b/simpleperf/scripts/debug_unwind_reporter.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2017 The Android Open Source Project
#
@@ -15,457 +15,257 @@
# limitations under the License.
#
-"""debug_unwind_reporter.py: report dwarf unwinding results generated by debug-unwind cmd.
- Below is an example using debug_unwind_reporter.py:
- 1. Record with "-g --no-unwind" option on device.
- simpleperf record -g --no-unwind --app com.google.sample.tunnel --duration 10
- 2. Use debug-unwind cmd to unwind samples in perf.data on device.
- simpleperf debug-unwind
- 3. Pull perf.data.debug on host, and report it with this script.
- python debug_unwind_reporter.py
-
- It reports below items:
- 1. time used for offline dwarf unwinding for each sample.
- 2. mem consumption before and after unwinding samples.
- 3. For samples that don't have complete callchains, report their regs, stack data,
- unwinding failure info, callchains. Samples having the same failure reason
- in the same function are only reported once.
-
- It can be used to:
- 1. show unwinding failures.
- 2. show performance and memory consumption change caused by simpleperf/unwinder changes.
-"""
-
-from __future__ import print_function
-import argparse
-import bisect
-import collections
-import copy
-import re
-import subprocess
-
-from utils import bytes_to_str, log_exit, log_fatal, get_host_binary_path
-
-
-class MapEntry(object):
-
- def __init__(self, start, end, filename):
- self.start = start
- self.end = end
- self.filename = filename
+"""debug_unwind_reporter.py: report failed dwarf unwinding cases generated by debug-unwind cmd.
- def __lt__(self, other):
- return self.start < other.start
+Below is an example using debug_unwind_reporter.py:
+1. Record with "-g --keep-failed-unwinding-debug-info" option on device.
+ $ simpleperf record -g --keep-failed-unwinding-debug-info --app com.google.sample.tunnel \\
+ --duration 10
+ The generated perf.data can be used for normal reporting. But it also contains stack data
+ and binaries for debugging failed unwinding cases.
-class ProcessMaps(object):
+2. Generate report with debug-unwind cmd.
+ $ simpleperf debug-unwind -i perf.data --generate-report -o report.txt
+ The report contains details for each failed unwinding case. It is usually too long to
+ parse manually. That's why we need debug_unwind_reporter.py.
- def __init__(self):
- self.process_maps = {} # map from pid to a sorted list of MapEntry.
-
- def add(self, pid, map_entry):
- old_list = self.process_maps.get(pid, [])
- new_list = []
- map_entry_used = False
- for entry in old_list:
- if entry.end <= map_entry.start:
- new_list.append(entry)
- elif entry.start < map_entry.start:
- entry.end = map_entry.start
- new_list.append(entry)
- else:
- if not map_entry_used:
- new_list.append(map_entry)
- map_entry_used = True
- if entry.start >= map_entry.end:
- new_list.append(entry)
- elif entry.end > map_entry.end:
- entry.start = map_entry.end
- new_list.append(entry)
- if not map_entry_used:
- new_list.append(map_entry)
- self.process_maps[pid] = new_list
-
- def fork_pid(self, pid, ppid):
- if pid == ppid:
- return
- entry_list = self.process_maps.get(ppid, [])
- self.process_maps[pid] = copy.deepcopy(entry_list)
-
- def find(self, pid, addr):
- key = MapEntry(addr, addr, '')
- entry_list = self.process_maps.get(pid, [])
- pos = bisect.bisect_right(entry_list, key)
- if pos > 0 and entry_list[pos - 1].end > addr:
- return entry_list[pos - 1]
- return None
-
- def show(self):
- for pid in sorted(self.process_maps):
- print(' pid %d' % pid)
- for entry in self.process_maps[pid]:
- print(' map [%x-%x] %s' %
- (entry.start, entry.end, entry.filename))
-
-
-class UnwindingTimes(object):
-
- def __init__(self):
- self.total_time = 0
- self.count = 0
- self.max_time = 0
-
- def add_time(self, used_time):
- self.total_time += used_time
- self.count += 1
- self.max_time = max(self.max_time, used_time)
+3. Use debug_unwind_reporter.py to parse the report.
+ $ simpleperf debug-unwind -i report.txt --summary
+ $ simpleperf debug-unwind -i report.txt --include-error-code 1
+ ...
+"""
+import argparse
+from collections import Counter, defaultdict
+from simpleperf_utils import ArgParseFormatter
+from texttable import Texttable
+from typing import Dict, Iterator, List
-class UnwindingMemConsumption(object):
+class CallChainNode:
def __init__(self):
- self.before_unwinding = None
- self.after_unwinding = None
-
-
-class CallChainNode(object):
-
- """ Representing a node in a call chain."""
-
- def __init__(self, ip, sp, filename, vaddr_in_file, function_name, map_start_addr,
- map_end_addr):
- self.ip = ip
- self.sp = sp
- self.filename = filename
- self.vaddr_in_file = vaddr_in_file
- self.function_name = function_name
- self.map_start_addr = map_start_addr
- self.map_end_addr = map_end_addr
+ self.dso = ''
+ self.symbol = ''
+
+
+class Sample:
+ """ A failed unwinding case """
+
+ def __init__(self, raw_lines: List[str]):
+ self.raw_lines = raw_lines
+ self.sample_time = 0
+ self.error_code = 0
+ self.callchain: List[CallChainNode] = []
+ self.parse()
+
+ def parse(self):
+ for line in self.raw_lines:
+ key, value = line.split(': ', 1)
+ if key == 'sample_time':
+ self.sample_time = int(value)
+ elif key == 'unwinding_error_code':
+ self.error_code = int(value)
+ elif key.startswith('dso'):
+ callchain_id = int(key.rsplit('_', 1)[1])
+ self._get_callchain_node(callchain_id).dso = value
+ elif key.startswith('symbol'):
+ callchain_id = int(key.rsplit('_', 1)[1])
+ self._get_callchain_node(callchain_id).symbol = value
+
+ def _get_callchain_node(self, callchain_id: int) -> CallChainNode:
+ callchain_id -= 1
+ if callchain_id == len(self.callchain):
+ self.callchain.append(CallChainNode())
+ return self.callchain[callchain_id]
+
+
+class SampleFilter:
+ def match(self, sample: Sample) -> bool:
+ raise Exception('unimplemented')
+
+
+class CompleteCallChainFilter(SampleFilter):
+ def match(self, sample: Sample) -> bool:
+ for node in sample.callchain:
+ if node.dso.endswith('libc.so') and (node.symbol in ('__libc_init', '__start_thread')):
+ return True
+ return False
-class SampleResult(object):
+class ErrorCodeFilter(SampleFilter):
+ def __init__(self, error_code: List[int]):
+ self.error_code = set(error_code)
- """ Unwinding result per sample. """
+ def match(self, sample: Sample) -> bool:
+ return sample.error_code in self.error_code
- def __init__(self, pid, tid, unwinding_result, callchain, sample_record):
- self.pid = pid
- self.tid = tid
- self.unwinding_result = unwinding_result
- self.callchain = callchain
- self.sample_record = sample_record
- def show(self):
- print(' pid: %d' % self.pid)
- print(' tid: %d' % self.tid)
- for key, value in self.unwinding_result.items():
- print(' %s: %s' % (key, value))
- for i, node in enumerate(self.callchain):
- print(' node %d: ip 0x%x, sp 0x%x, %s (%s[+%x]), map [%x-%x]' % (
- i, node.ip, node.sp, node.function_name, node.filename, node.vaddr_in_file,
- node.map_start_addr, node.map_end_addr))
- if self.sample_record:
- print(' original sample record:')
- for line in self.sample_record:
- print(' %s' % line)
+class EndDsoFilter(SampleFilter):
+ def __init__(self, end_dso: List[str]):
+ self.end_dso = set(end_dso)
+ def match(self, sample: Sample) -> bool:
+ return sample.callchain[-1].dso in self.end_dso
-class FunctionResult(object):
- """ Unwinding result per function. """
+class EndSymbolFilter(SampleFilter):
+ def __init__(self, end_symbol: List[str]):
+ self.end_symbol = set(end_symbol)
- def __init__(self):
- # Map from Unwinding result reason to [SampleResult].
- self.sample_results = {}
+ def match(self, sample: Sample) -> bool:
+ return sample.callchain[-1].symbol in self.end_symbol
- def add_sample_result(self, sample_result):
- stop_reason = sample_result.unwinding_result['stop_reason']
- result_list = self.sample_results.get(stop_reason)
- if not result_list:
- result_list = self.sample_results[stop_reason] = []
- for result in result_list:
- if result.callchain[-1].vaddr_in_file == sample_result.callchain[-1].vaddr_in_file:
- # This sample_result duplicates with an existing one.
- return
- # We don't want to store too many sample results for a function.
- if len(result_list) < 10:
- result_list.append(sample_result)
- def show(self):
- for stop_reason in sorted(self.sample_results):
- for sample_result in self.sample_results[stop_reason]:
- sample_result.show()
+class SampleTimeFilter(SampleFilter):
+ def __init__(self, sample_time: List[int]):
+ self.sample_time = set(sample_time)
+ def match(self, sample: Sample) -> bool:
+ return sample.sample_time in self.sample_time
-class FileResult(object):
-
- """ Unwinding result per shared library. """
+class ReportInput:
def __init__(self):
- self.function_results = {} # Map from function_name to FunctionResult.
-
- def add_sample_result(self, sample_result):
- function_name = sample_result.callchain[-1].function_name
- function_result = self.function_results.get(function_name)
- if not function_result:
- function_result = self.function_results[
- function_name] = FunctionResult()
- function_result.add_sample_result(sample_result)
-
- def show(self):
- for function_name in sorted(self.function_results):
- print(' function %s' % function_name)
- self.function_results[function_name].show()
- print('\n')
-
-
-class UnwindingResultErrorReport(object):
-
- """ Report time used for unwinding and unwinding result errors. """
-
- def __init__(self, omit_callchains_fixed_by_joiner):
- self.omit_callchains_fixed_by_joiner = omit_callchains_fixed_by_joiner
- self.process_maps = ProcessMaps()
- self.unwinding_times = UnwindingTimes()
- self.mem_stat = UnwindingMemConsumption()
- self.file_results = {} # map from filename to FileResult.
-
- def add_sample_result(self, sample_result, joined_record):
- self.unwinding_times.add_time(int(sample_result.unwinding_result['used_time']))
- if self.should_omit(sample_result, joined_record):
- return
- filename = sample_result.callchain[-1].filename
- file_result = self.file_results.get(filename)
- if not file_result:
- file_result = self.file_results[filename] = FileResult()
- file_result.add_sample_result(sample_result)
-
- def add_mem_stat(self, name, mem_str):
- # mem_str is like VmPeak:202464 kB;VmSize:183456 kB;VmHWM:98256 kB;VmRSS:33680 kB.
- items = []
- for p in mem_str.split(';'):
- key, value = p.split(':')
- items.append((key, value))
- if name == 'debug_unwind_mem_before':
- self.mem_stat.before_unwinding = items
- else:
- self.mem_stat.after_unwinding = items
-
- def should_omit(self, sample_result, joined_record):
- # 1. Can't unwind code generated in memory.
- for name in ['/dev/ashmem/dalvik-jit-code-cache', '[anon:dalvik-jit-code-cache]', '//anon']:
- if name in sample_result.callchain[-1].filename:
- return True
- # 2. Don't report complete callchains, which can reach __libc_init or __start_thread in
- # libc.so.
- def is_callchain_complete(callchain):
- for node in callchain:
- if (node.filename.endswith('libc.so') and
- node.function_name in ['__libc_init', '__start_thread']):
- return True
- return False
- if is_callchain_complete(sample_result.callchain):
- return True
- # 3. Omit callchains made complete by callchain joiner.
- if self.omit_callchains_fixed_by_joiner and is_callchain_complete(joined_record.callchain):
- return True
- return False
-
- def show(self):
- print('Unwinding time info:')
- print(' total time: %f ms' % (self.unwinding_times.total_time / 1e6))
- print(' unwinding count: %d' % self.unwinding_times.count)
- if self.unwinding_times.count > 0:
- print(' average time: %f us' % (
- self.unwinding_times.total_time / 1e3 / self.unwinding_times.count))
- print(' max time: %f us' % (self.unwinding_times.max_time / 1e3))
- print('Unwinding mem info:')
- for items in zip(self.mem_stat.before_unwinding, self.mem_stat.after_unwinding):
- assert items[0][0] == items[1][0]
- print(' %s: %s -> %s' % (items[0][0], items[0][1], items[1][1]))
- print('Process maps:')
- self.process_maps.show()
- for filename in sorted(self.file_results):
- print('filename %s' % filename)
- self.file_results[filename].show()
- print('\n')
-
-
-class CallChainRecord(object):
- """ Store data of a callchain record. """
-
+ self.exclude_filters: List[SampleFilter] = []
+ self.include_filters: List[SampleFilter] = []
+
+ def set_filters(self, args: argparse.Namespace):
+ if not args.show_callchain_fixed_by_joiner:
+ self.exclude_filters.append(CompleteCallChainFilter())
+ if args.exclude_error_code:
+ self.exclude_filters.append(ErrorCodeFilter(args.exclude_error_code))
+ if args.exclude_end_dso:
+ self.exclude_filters.append(EndDsoFilter(args.exclude_end_dso))
+ if args.exclude_end_symbol:
+ self.exclude_filters.append(EndSymbolFilter(args.exclude_end_symbol))
+ if args.exclude_sample_time:
+ self.exclude_filters.append(SampleTimeFilter(args.exclude_sample_time))
+
+ if args.include_error_code:
+ self.include_filters.append(ErrorCodeFilter(args.include_error_code))
+ if args.include_end_dso:
+ self.include_filters.append(EndDsoFilter(args.include_end_dso))
+ if args.include_end_symbol:
+ self.include_filters.append(EndSymbolFilter(args.include_end_symbol))
+ if args.include_sample_time:
+ self.include_filters.append(SampleTimeFilter(args.include_sample_time))
+
+ def get_samples(self, input_file: str) -> Iterator[Sample]:
+ sample_lines: List[str] = []
+ in_sample = False
+ with open(input_file, 'r') as fh:
+ for line in fh.readlines():
+ line = line.rstrip()
+ if line.startswith('sample_time:'):
+ in_sample = True
+ elif not line:
+ if in_sample:
+ in_sample = False
+ sample = Sample(sample_lines)
+ sample_lines = []
+ if self.filter_sample(sample):
+ yield sample
+ if in_sample:
+ sample_lines.append(line)
+
+ def filter_sample(self, sample: Sample) -> bool:
+ """ Return true if the input sample passes filters. """
+ for exclude_filter in self.exclude_filters:
+ if exclude_filter.match(sample):
+ return False
+ for include_filter in self.include_filters:
+ if not include_filter.match(sample):
+ return False
+ return True
+
+
+class ReportOutput:
+ def report(self, sample: Sample):
+ pass
+
+ def end_report(self):
+ pass
+
+
+class ReportOutputDetails(ReportOutput):
+ def report(self, sample: Sample):
+ for line in sample.raw_lines:
+ print(line)
+ print()
+
+
+class ReportOutputSummary(ReportOutput):
def __init__(self):
- self.pid = None
- self.tid = None
- self.callchain = []
-
-def parse_sample_record(lines, i):
- """ Read the lines belong to a SampleRecord."""
- if i == len(lines) or not lines[i].startswith('record sample:'):
- log_fatal('unexpected dump output near line %d' % i)
- start_line = i
- i += 1
- while i < len(lines) and (not lines[i] or lines[i].startswith(' ')):
- i += 1
- return i, lines[start_line:i]
-
-def parse_callchain_record(lines, i, chain_type, process_maps):
- if i == len(lines) or not lines[i].startswith('record callchain:'):
- log_fatal('unexpected dump output near line %d' % i)
- i += 1
- record = CallChainRecord()
- ips = []
- sps = []
- function_names = []
- filenames = []
- vaddr_in_files = []
- map_start_addrs = []
- map_end_addrs = []
- in_callchain = False
- while i < len(lines) and not lines[i].startswith('record'):
- line = lines[i].strip()
- items = line.split()
- if not items:
- i += 1
- continue
- if items[0] == 'pid' and len(items) == 2:
- record.pid = int(items[1])
- elif items[0] == 'tid' and len(items) == 2:
- record.tid = int(items[1])
- elif items[0] == 'chain_type' and len(items) == 2:
- if items[1] != chain_type:
- log_fatal('unexpected dump output near line %d' % i)
- elif items[0] == 'ip':
- m = re.search(r'ip\s+0x(\w+),\s+sp\s+0x(\w+)$', line)
- if m:
- ips.append(int(m.group(1), 16))
- sps.append(int(m.group(2), 16))
- elif items[0] == 'callchain:':
- in_callchain = True
- elif in_callchain:
- # "dalvik-jit-code-cache (deleted)[+346c] ([anon:dalvik-jit-code-cache]
- # (deleted)[+346c])"
- if re.search(r'\)\[\+\w+\]\)$', line):
- break_pos = line.rfind('(', 0, line.rfind('('))
- else:
- break_pos = line.rfind('(')
- if break_pos > 0:
- m = re.match(r'(.*)\[\+(\w+)\]\)', line[break_pos + 1:])
- if m:
- function_names.append(line[:break_pos].strip())
- filenames.append(m.group(1))
- vaddr_in_files.append(int(m.group(2), 16))
- i += 1
-
- for ip in ips:
- map_entry = process_maps.find(record.pid, ip)
- if map_entry:
- map_start_addrs.append(map_entry.start)
- map_end_addrs.append(map_entry.end)
- else:
- map_start_addrs.append(0)
- map_end_addrs.append(0)
- n = len(ips)
- if (None in [record.pid, record.tid] or n == 0 or len(sps) != n or
- len(function_names) != n or len(filenames) != n or len(vaddr_in_files) != n or
- len(map_start_addrs) != n or len(map_end_addrs) != n):
- log_fatal('unexpected dump output near line %d' % i)
- for j in range(n):
- record.callchain.append(CallChainNode(ips[j], sps[j], filenames[j], vaddr_in_files[j],
- function_names[j], map_start_addrs[j],
- map_end_addrs[j]))
- return i, record
-
-
-def build_unwinding_result_report(args):
- simpleperf_path = get_host_binary_path('simpleperf')
- proc = subprocess.Popen([simpleperf_path, 'dump', args.record_file[0]],
- stdout=subprocess.PIPE)
- (stdoutdata, _) = proc.communicate()
- stdoutdata = bytes_to_str(stdoutdata)
- if 'debug_unwind = true' not in stdoutdata:
- log_exit("Can't parse unwinding result. Because " +
- "%s was not generated by the debug-unwind cmd." % args.record_file[0])
- unwinding_report = UnwindingResultErrorReport(args.omit_callchains_fixed_by_joiner)
- process_maps = unwinding_report.process_maps
- lines = stdoutdata.split('\n')
- i = 0
- while i < len(lines):
- if lines[i].startswith('record mmap:') or lines[i].startswith('record mmap2:'):
- i += 1
- pid = None
- start = None
- end = None
- filename = None
- while i < len(lines) and not lines[i].startswith('record'):
- if lines[i].startswith(' pid'):
- m = re.search(r'pid\s+(\d+).+addr\s+0x(\w+).+len\s+0x(\w+)', lines[i])
- if m:
- pid = int(m.group(1))
- start = int(m.group(2), 16)
- end = start + int(m.group(3), 16)
- elif 'filename' in lines[i]:
- pos = lines[i].find('filename') + len('filename')
- filename = lines[i][pos:].strip()
- i += 1
- if None in [pid, start, end, filename]:
- log_fatal('unexpected dump output near line %d' % i)
- process_maps.add(pid, MapEntry(start, end, filename))
- elif lines[i].startswith('record unwinding_result:'):
- i += 1
- unwinding_result = collections.OrderedDict()
- while i < len(lines) and not lines[i].startswith('record'):
- strs = (lines[i].strip()).split()
- if len(strs) == 2:
- unwinding_result[strs[0]] = strs[1]
- i += 1
- for key in ['time', 'used_time', 'stop_reason']:
- if key not in unwinding_result:
- log_fatal('unexpected dump output near line %d' % i)
-
- i, sample_record = parse_sample_record(lines, i)
- i, original_record = parse_callchain_record(lines, i, 'ORIGINAL_OFFLINE', process_maps)
- i, joined_record = parse_callchain_record(lines, i, 'JOINED_OFFLINE', process_maps)
- if args.omit_sample:
- sample_record = []
- sample_result = SampleResult(original_record.pid, original_record.tid,
- unwinding_result, original_record.callchain,
- sample_record)
- unwinding_report.add_sample_result(sample_result, joined_record)
- elif lines[i].startswith('record fork:'):
- i += 1
- pid = None
- ppid = None
- while i < len(lines) and not lines[i].startswith('record'):
- if lines[i].startswith(' pid'):
- m = re.search(r'pid\s+(\w+),\s+ppid\s+(\w+)', lines[i])
- if m:
- pid = int(m.group(1))
- ppid = int(m.group(2))
- i += 1
- if None in [pid, ppid]:
- log_fatal('unexpected dump output near line %d' % i)
- process_maps.fork_pid(pid, ppid)
- elif lines[i].startswith(' debug_unwind_mem'):
- items = lines[i].strip().split(' = ')
- if len(items) == 2:
- unwinding_report.add_mem_stat(items[0], items[1])
- i += 1
- else:
- i += 1
- return unwinding_report
+ self.error_code_counter = Counter()
+ self.symbol_counters: Dict[int, Counter] = defaultdict(Counter)
+
+ def report(self, sample: Sample):
+ symbol_key = (sample.callchain[-1].dso, sample.callchain[-1].symbol)
+ self.symbol_counters[sample.error_code][symbol_key] += 1
+ self.error_code_counter[sample.error_code] += 1
+
+ def end_report(self):
+ self.draw_error_code_table()
+ self.draw_symbol_table()
+
+ def draw_error_code_table(self):
+ table = Texttable()
+ table.set_cols_align(['l', 'c'])
+ table.add_row(['Count', 'Error Code'])
+ for error_code, count in self.error_code_counter.most_common():
+ table.add_row([count, error_code])
+ print(table.draw())
+
+ def draw_symbol_table(self):
+ table = Texttable()
+ table.set_cols_align(['l', 'c', 'l', 'l'])
+ table.add_row(['Count', 'Error Code', 'Dso', 'Symbol'])
+ for error_code, _ in self.error_code_counter.most_common():
+ symbol_counter = self.symbol_counters[error_code]
+ for symbol_key, count in symbol_counter.most_common():
+ dso, symbol = symbol_key
+ table.add_row([count, error_code, dso, symbol])
+ print(table.draw())
+
+
+def get_args() -> argparse.Namespace:
+ parser = argparse.ArgumentParser(description=__doc__, formatter_class=ArgParseFormatter)
+ parser.add_argument('-i', '--input-file', required=True,
+ help='report file generated by debug-unwind cmd')
+ parser.add_argument(
+ '--show-callchain-fixed-by-joiner', action='store_true',
+ help="""By default, we don't show failed unwinding cases fixed by callchain joiner.
+ Use this option to show them.""")
+ parser.add_argument('--summary', action='store_true',
+ help='show summary instead of case details')
+ parser.add_argument('--exclude-error-code', metavar='error_code', type=int, nargs='+',
+ help='exclude cases with selected error code')
+ parser.add_argument('--exclude-end-dso', metavar='dso', nargs='+',
+ help='exclude cases ending at selected binary')
+ parser.add_argument('--exclude-end-symbol', metavar='symbol', nargs='+',
+ help='exclude cases ending at selected symbol')
+ parser.add_argument('--exclude-sample-time', metavar='time', type=int,
+ nargs='+', help='exclude cases with selected sample time')
+ parser.add_argument('--include-error-code', metavar='error_code', type=int,
+ nargs='+', help='include cases with selected error code')
+ parser.add_argument('--include-end-dso', metavar='dso', nargs='+',
+ help='include cases ending at selected binary')
+ parser.add_argument('--include-end-symbol', metavar='symbol', nargs='+',
+ help='include cases ending at selected symbol')
+ parser.add_argument('--include-sample-time', metavar='time', type=int,
+ nargs='+', help='include cases with selected sample time')
+ return parser.parse_args()
def main():
- parser = argparse.ArgumentParser(
- description='Report dwarf unwinding results generated by the debug-unwind cmd.')
- parser.add_argument('-i', '--record_file', nargs=1, default=['perf.data.debug'], help="""
- Set profiling data to report. Default is perf.data.debug.""")
- parser.add_argument('--omit-callchains-fixed-by-joiner', action='store_true', help="""
- Don't show incomplete callchains fixed by callchain joiner.""")
- parser.add_argument('--omit-sample', action='store_true', help="""Don't show original sample
- records.""")
- args = parser.parse_args()
- report = build_unwinding_result_report(args)
- report.show()
+ args = get_args()
+ report_input = ReportInput()
+ report_input.set_filters(args)
+ report_output = ReportOutputSummary() if args.summary else ReportOutputDetails()
+ for sample in report_input.get_samples(args.input_file):
+ report_output.report(sample)
+ report_output.end_report()
+
if __name__ == '__main__':
main()
diff --git a/simpleperf/scripts/inferno.bat b/simpleperf/scripts/inferno.bat
index 5818f986..1a8efa49 100755
--- a/simpleperf/scripts/inferno.bat
+++ b/simpleperf/scripts/inferno.bat
@@ -1,2 +1,2 @@
-set PYTHONPATH=%PYTHONPATH%;%~dp0
-python -m inferno.inferno %*
+set SCRIPTPATH=%~dp0
+python3 %SCRIPTPATH%inferno\inferno.py %*
diff --git a/simpleperf/scripts/inferno.sh b/simpleperf/scripts/inferno.sh
index d30ee317..f6ab0c69 100755
--- a/simpleperf/scripts/inferno.sh
+++ b/simpleperf/scripts/inferno.sh
@@ -1,4 +1,3 @@
#!/bin/bash
SCRIPTPATH=$(dirname "$0")
-export PYTHONPATH=$SCRIPTPATH:$PYTHONPATH
-python -m inferno.inferno "$@"
+$SCRIPTPATH/inferno/inferno.py "$@"
diff --git a/simpleperf/scripts/inferno/Android.bp b/simpleperf/scripts/inferno/Android.bp
index f49dadd3..14d18778 100644
--- a/simpleperf/scripts/inferno/Android.bp
+++ b/simpleperf/scripts/inferno/Android.bp
@@ -12,6 +12,15 @@
// 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_simpleperf_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_extras_simpleperf_license"],
+}
+
python_library_host {
name: "simpleperf-inferno",
srcs: [
@@ -29,4 +38,3 @@ python_library_host {
},
},
}
-
diff --git a/simpleperf/scripts/inferno/inferno.py b/simpleperf/scripts/inferno/inferno.py
index 12b9d904..31954943 100755
--- a/simpleperf/scripts/inferno/inferno.py
+++ b/simpleperf/scripts/inferno/inferno.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2016 The Android Open Source Project
#
@@ -36,14 +36,16 @@ import os
import subprocess
import sys
+# fmt: off
# pylint: disable=wrong-import-position
SCRIPTS_PATH = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.append(SCRIPTS_PATH)
from simpleperf_report_lib import ReportLib
-from utils import log_exit, log_fatal, log_info, AdbHelper, open_report_in_browser
+from simpleperf_utils import log_exit, log_fatal, log_info, AdbHelper, open_report_in_browser
from data_types import Process
from svg_renderer import get_proper_scaled_time_string, render_svg
+# fmt: on
def collect_data(args):
@@ -109,6 +111,8 @@ def parse_samples(process, args, sample_filter_fn):
lib.SetKallsymsFile(kallsyms_file)
if args.show_art_frames:
lib.ShowArtFrames(True)
+ for file_path in args.proguard_mapping_file or []:
+ lib.AddProguardMappingFile(file_path)
process.cmd = lib.GetRecordCmd()
product_props = lib.MetaInfo().get("product_props")
if product_props:
@@ -184,7 +188,7 @@ def output_report(process, args):
event_entry = 'Event count: %s<br/>' % ("{:,}".format(process.num_events))
# TODO: collect capture duration info from perf.data.
duration_entry = ("Duration: %s seconds<br/>" % args.capture_duration
- ) if args.capture_duration else ""
+ ) if args.capture_duration else ""
f.write("""<div style='display:inline-block;'>
<font size='8'>
Inferno Flamegraph Report%s</font><br/><br/>
@@ -305,6 +309,8 @@ def main():
report_group.add_argument('--title', help='Show a title in the report.')
report_group.add_argument('--show_art_frames', action='store_true',
help='Show frames of internal methods in the ART Java interpreter.')
+ report_group.add_argument('--proguard-mapping-file', nargs='+',
+ help='Add proguard mapping file to de-obfuscate symbols')
debug_group = parser.add_argument_group('Debug options')
debug_group.add_argument('--disable_adb_root', action='store_true', help="""Force adb to run
@@ -361,5 +367,6 @@ def main():
log_info("Flamegraph generated at '%s'." % report_path)
+
if __name__ == "__main__":
main()
diff --git a/simpleperf/scripts/pprof_proto_generator.py b/simpleperf/scripts/pprof_proto_generator.py
index 2b8b8e24..11806852 100755
--- a/simpleperf/scripts/pprof_proto_generator.py
+++ b/simpleperf/scripts/pprof_proto_generator.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2017 The Android Open Source Project
#
@@ -24,19 +24,19 @@
pprof -text pprof.profile
"""
-from __future__ import print_function
import argparse
import os
import os.path
from simpleperf_report_lib import ReportLib
-from utils import Addr2Nearestline, extant_dir, find_real_dso_path, find_tool_path, flatten_arg_list
-from utils import log_info, log_exit, ReadElf
+from simpleperf_utils import (Addr2Nearestline, BinaryFinder, extant_dir,
+ flatten_arg_list, log_info, log_exit, ReadElf, ToolFinder)
try:
import profile_pb2
except ImportError:
log_exit('google.protobuf module is missing. Please install it first.')
+
def load_pprof_profile(filename):
profile = profile_pb2.Profile()
with open(filename, "rb") as f:
@@ -244,20 +244,11 @@ class PprofProfileGenerator(object):
def __init__(self, config):
self.config = config
- self.lib = ReportLib()
+ self.lib = None
config['binary_cache_dir'] = 'binary_cache'
if not os.path.isdir(config['binary_cache_dir']):
config['binary_cache_dir'] = None
- else:
- self.lib.SetSymfs(config['binary_cache_dir'])
- if config.get('perf_data_path'):
- self.lib.SetRecordFile(config['perf_data_path'])
- kallsyms = 'binary_cache/kallsyms'
- if os.path.isfile(kallsyms):
- self.lib.SetKallsymsFile(kallsyms)
- if config.get('show_art_frames'):
- self.lib.ShowArtFrames()
self.comm_filter = set(config['comm_filters']) if config.get('comm_filters') else None
if config.get('pid_filters'):
self.pid_filter = {int(x) for x in config['pid_filters']}
@@ -285,13 +276,29 @@ class PprofProfileGenerator(object):
# Map from dso_name in perf.data to (binary path, build_id).
self.binary_map = {}
self.read_elf = ReadElf(self.config['ndk_path'])
+ self.binary_finder = BinaryFinder(config['binary_cache_dir'], self.read_elf)
- def gen(self):
- # 1. Process all samples in perf.data, aggregate samples.
+ def load_record_file(self, record_file):
+ self.lib = ReportLib()
+ self.lib.SetRecordFile(record_file)
+
+ if self.config['binary_cache_dir']:
+ self.lib.SetSymfs(self.config['binary_cache_dir'])
+ kallsyms = os.path.join(self.config['binary_cache_dir'], 'kallsyms')
+ if os.path.isfile(kallsyms):
+ self.lib.SetKallsymsFile(kallsyms)
+
+ if self.config.get('show_art_frames'):
+ self.lib.ShowArtFrames()
+ for file_path in self.config['proguard_mapping_file'] or []:
+ self.lib.AddProguardMappingFile(file_path)
+
+ # Process all samples in perf.data, aggregate samples.
while True:
report_sample = self.lib.GetNextSample()
if report_sample is None:
self.lib.Close()
+ self.lib = None
break
event = self.lib.GetEventOfCurrentSample()
symbol = self.lib.GetSymbolOfCurrentSample()
@@ -315,10 +322,11 @@ class PprofProfileGenerator(object):
if sample.location_ids:
self.add_sample(sample)
- # 2. Generate line info for locations and functions.
+ def gen(self):
+ # 1. Generate line info for locations and functions.
self.gen_source_lines()
- # 3. Produce samples/locations/functions in profile
+ # 2. Produce samples/locations/functions in profile.
for sample in self.sample_list:
self.gen_profile_sample(sample)
for mapping in self.mapping_list:
@@ -425,17 +433,10 @@ class PprofProfileGenerator(object):
# perf.data.
build_id_in_perf_data = self.lib.GetBuildIdForPath(dso_name)
# Try elf_path in binary cache.
- elf_path = find_real_dso_path(dso_name, self.config['binary_cache_dir'])
+ elf_path = self.binary_finder.find_binary(dso_name, build_id_in_perf_data)
if elf_path:
- elf_build_id = self.read_elf.get_build_id(elf_path, False)
- if build_id_in_perf_data:
- match = build_id_in_perf_data == self.read_elf.pad_build_id(elf_build_id)
- else:
- # odex files generated by ART on Android O don't contain build id.
- match = not elf_build_id
- if match:
- build_id = elf_build_id
- binary_path = elf_path
+ build_id = build_id_in_perf_data
+ binary_path = str(elf_path)
# When there is no matching elf_path, try converting build_id in perf.data.
if not build_id and build_id_in_perf_data.startswith('0x'):
@@ -480,12 +481,13 @@ class PprofProfileGenerator(object):
if not self.config.get('binary_cache_dir'):
log_info("Can't generate line information because binary_cache is missing.")
return
- if not find_tool_path('llvm-symbolizer', self.config['ndk_path']):
+ if not ToolFinder.find_tool_path('llvm-symbolizer', self.config['ndk_path']):
log_info("Can't generate line information because can't find llvm-symbolizer.")
return
# We have changed dso names to paths in binary_cache in self.get_binary(). So no need to
- # pass binary_cache_dir to addr2line.
- addr2line = Addr2Nearestline(self.config['ndk_path'], None, True)
+ # pass binary_cache_dir to BinaryFinder.
+ binary_finder = BinaryFinder(None, self.read_elf)
+ addr2line = Addr2Nearestline(self.config['ndk_path'], binary_finder, True)
# 2. Put all needed addresses to it.
for location in self.location_list:
@@ -493,10 +495,10 @@ class PprofProfileGenerator(object):
dso_name = self.get_string(mapping.filename_id)
if location.lines:
function = self.get_function(location.lines[0].function_id)
- addr2line.add_addr(dso_name, function.vaddr_in_dso, location.vaddr_in_dso)
+ addr2line.add_addr(dso_name, None, function.vaddr_in_dso, location.vaddr_in_dso)
for function in self.function_list:
dso_name = self.get_string(function.dso_name_id)
- addr2line.add_addr(dso_name, function.vaddr_in_dso, function.vaddr_in_dso)
+ addr2line.add_addr(dso_name, None, function.vaddr_in_dso, function.vaddr_in_dso)
# 3. Generate source lines.
addr2line.convert_addrs_to_lines()
@@ -591,8 +593,8 @@ class PprofProfileGenerator(object):
def main():
parser = argparse.ArgumentParser(description='Generate pprof profile data in pprof.profile.')
parser.add_argument('--show', nargs='?', action='append', help='print existing pprof.profile.')
- parser.add_argument('-i', '--perf_data_path', default='perf.data', help="""
- The path of profiling data.""")
+ parser.add_argument('-i', '--record_file', nargs='+', default=['perf.data'], help="""
+ Set profiling data file to report. Default is perf.data""")
parser.add_argument('-o', '--output_file', default='pprof.profile', help="""
The path of generated pprof profile data.""")
parser.add_argument('--comm', nargs='+', action='append', help="""
@@ -608,6 +610,9 @@ def main():
parser.add_argument('--ndk_path', type=extant_dir, help='Set the path of a ndk release.')
parser.add_argument('--show_art_frames', action='store_true',
help='Show frames of internal methods in the ART Java interpreter.')
+ parser.add_argument(
+ '--proguard-mapping-file', nargs='+',
+ help='Add proguard mapping file to de-obfuscate symbols')
args = parser.parse_args()
if args.show:
@@ -618,7 +623,6 @@ def main():
return
config = {}
- config['perf_data_path'] = args.perf_data_path
config['output_file'] = args.output_file
config['comm_filters'] = flatten_arg_list(args.comm)
config['pid_filters'] = flatten_arg_list(args.pid)
@@ -627,7 +631,10 @@ def main():
config['ndk_path'] = args.ndk_path
config['show_art_frames'] = args.show_art_frames
config['max_chain_length'] = args.max_chain_length
+ config['proguard_mapping_file'] = args.proguard_mapping_file
generator = PprofProfileGenerator(config)
+ for record_file in args.record_file:
+ generator.load_record_file(record_file)
profile = generator.gen()
store_pprof_profile(config['output_file'], profile)
diff --git a/simpleperf/scripts/purgatorio/README.md b/simpleperf/scripts/purgatorio/README.md
new file mode 100644
index 00000000..bea1b0e2
--- /dev/null
+++ b/simpleperf/scripts/purgatorio/README.md
@@ -0,0 +1,69 @@
+# Purgatorio
+
+[link on wikipedia](https://en.wikipedia.org/wiki/Purgatorio)
+
+![user interface](images/user_interface.png)
+
+Purgatorio is a visualization tool for simpleperf traces. It's based on [libsimpleperf](https://source.corp.google.com/android/system/extras/simpleperf/;l=1?q=simpleperf&sq=package:%5Eandroid$),
+[Bokeh](https://bokeh.org/) and [D3 flamegraphs](https://github.com/spiermar/d3-flame-graph).
+
+The main difference from [Inferno](https://source.corp.google.com/android/system/extras/simpleperf/scripts/inferno/;l=1) is that Purgatorio focuses on visualizing system-wide traces (the ones with the `-a` argument) on a time-organized sequence, and allow the user to interact with the graph by zooming, hovering on samples and visualize a flame graph for specific samples (be it restricted by time interval, set of threads or whatever subset).
+
+## Obtaining the sources
+
+ git clone sso://user/balejs/purgatorio
+
+## Getting ready
+
+**NOTE**: In theory it should work on most OSes, but Purgatorio has been tested on gLinux only. Any feedback, recommendations and patches to get it work elsewhere will be welcome (balejs@).
+
+Purgatorio tends to be self-contained, but Bokeh and some of its dependencies aren't shipped with the default python libraries, hence they require to be installed with pip3. Assuming they already have python3 installed, Purgatorio hopefuls should follow these steps:
+
+ $ sudo apt-get install python3-pip
+ $ pip3 install jinja2 bokeh pandas
+
+Run `python3 purgatorio.py -h` for a list of command-line arguments.
+
+## Example
+
+One can trace a Camera warm launch with:
+
+ $ adb shell simpleperf record --trace-offcpu --call-graph fp -o /data/local/camera_warm_launch.data -a
+ [launch camera here, then press ctrl + c]
+ $ adb pull /data/local/camera_warm_launch.data
+
+And then run:
+
+ python3 purgatorio.py camera_warm_launch.data
+
+If you get lots of "Failed to read symbols" messages, and backtraces in the diagram don't show the symbols you're interested into, you might want to try [building a symbols cache](https://chromium.googlesource.com/android_ndk/+/refs/heads/master/simpleperf/doc/README.md#how-to-solve-missing-symbols-in-report) for the trace, then run purgatorio again with:
+
+ python3 purgatorio.py camera_warm_launch.data -u [symbols cache]
+
+# Purgatorio interface
+The Purgatorio User Interface is divided in three areas:
+
+## Main Graph
+It's the area to the left, including process names and color-coded dots grouped by process. It's used to navigate throungh the trace and identify samples of ineterest. By hovering on a sample (or set of samples) their callstacks will be visualized over the graph. When selecting a et of samples, their aggregated data will be visualized in the other sections of the UI. Multiple sections of the graph can be aggregated by holding down the [ctrl] button during selection.
+
+The toolbox to the right can be used to configure interactions with the graph:
+
+![toolbox description](images/toolbox.png)
+
+## Flame graph
+The flame graph is located in the upper right portion. Once samples are selected in the main graph, the flame graph will show an interactive visualization for their aggregated callstacks. In this case the selection included mostly samples for com.google.android.GoogleCamera
+
+![flame graph](images/flame_graph.png)
+
+It's possible to select a given stack entry to zoom on it and look at entry deeper in the callstack
+
+![flame graph](images/flame_graph_zoomed.png)
+
+When studiyng system issues it's often useful to visualize an inverted callstack. This can be done by clicking the related check box. The graph below is the same as in the first flame graph above, but with call stack inverted. In this case, inverted visualization directly points at [possible issues with io](http://b/158783580#comment12)
+
+![inverted flame graph](images/inverted_flame_graph.png)
+
+## Sample table
+It's located in the lower right and counts samples by thread (for direct flame graphs) or symbol (for inverted flame graphs). Table columns can be sorted by clicking on their respective layers, and selecting specific lines filters the contents of the flame graph to the selected threads or symbols. Multiple lines can be selected at the same time.
+
+![table](images/table.png)
diff --git a/simpleperf/scripts/purgatorio/images/flame_graph.png b/simpleperf/scripts/purgatorio/images/flame_graph.png
new file mode 100644
index 00000000..b216d298
--- /dev/null
+++ b/simpleperf/scripts/purgatorio/images/flame_graph.png
Binary files differ
diff --git a/simpleperf/scripts/purgatorio/images/flame_graph_zoomed.png b/simpleperf/scripts/purgatorio/images/flame_graph_zoomed.png
new file mode 100644
index 00000000..53ad8f98
--- /dev/null
+++ b/simpleperf/scripts/purgatorio/images/flame_graph_zoomed.png
Binary files differ
diff --git a/simpleperf/scripts/purgatorio/images/inverted_flame_graph.png b/simpleperf/scripts/purgatorio/images/inverted_flame_graph.png
new file mode 100644
index 00000000..7fee7669
--- /dev/null
+++ b/simpleperf/scripts/purgatorio/images/inverted_flame_graph.png
Binary files differ
diff --git a/simpleperf/scripts/purgatorio/images/table.png b/simpleperf/scripts/purgatorio/images/table.png
new file mode 100644
index 00000000..baa56a90
--- /dev/null
+++ b/simpleperf/scripts/purgatorio/images/table.png
Binary files differ
diff --git a/simpleperf/scripts/purgatorio/images/toolbox.png b/simpleperf/scripts/purgatorio/images/toolbox.png
new file mode 100644
index 00000000..d63270a1
--- /dev/null
+++ b/simpleperf/scripts/purgatorio/images/toolbox.png
Binary files differ
diff --git a/simpleperf/scripts/purgatorio/images/user_interface.png b/simpleperf/scripts/purgatorio/images/user_interface.png
new file mode 100644
index 00000000..18ae652b
--- /dev/null
+++ b/simpleperf/scripts/purgatorio/images/user_interface.png
Binary files differ
diff --git a/simpleperf/scripts/purgatorio/purgatorio.py b/simpleperf/scripts/purgatorio/purgatorio.py
new file mode 100755
index 00000000..0f072547
--- /dev/null
+++ b/simpleperf/scripts/purgatorio/purgatorio.py
@@ -0,0 +1,311 @@
+#!/usr/bin/env python3
+#
+# 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.
+#
+
+import argparse
+import bisect
+import jinja2
+import io
+import math
+import os
+import pandas as pd
+from pathlib import Path
+import re
+import sys
+
+from bokeh.embed import components
+from bokeh.io import output_file, show
+from bokeh.layouts import layout, Spacer
+from bokeh.models import ColumnDataSource, CustomJS, WheelZoomTool, HoverTool, FuncTickFormatter
+from bokeh.models.widgets import DataTable, DateFormatter, TableColumn
+from bokeh.models.ranges import FactorRange
+from bokeh.palettes import Category20b
+from bokeh.plotting import figure
+from bokeh.resources import INLINE
+from bokeh.transform import jitter
+from bokeh.util.browser import view
+from functools import cmp_to_key
+
+# fmt: off
+simpleperf_path = Path(__file__).absolute().parents[1]
+sys.path.insert(0, str(simpleperf_path))
+import simpleperf_report_lib as sp
+# fmt: on
+
+
+def create_graph(args, source, data_range):
+ graph = figure(
+ sizing_mode='stretch_both', x_range=data_range,
+ tools=['pan', 'wheel_zoom', 'ywheel_zoom', 'xwheel_zoom', 'reset', 'tap', 'box_select'],
+ active_drag='box_select', active_scroll='wheel_zoom',
+ tooltips=[('thread', '@thread'),
+ ('callchain', '@callchain{safe}')],
+ title=args.title, name='graph')
+
+ # a crude way to avoid process name cluttering at some zoom levels.
+ # TODO: remove processes from the ticker base on the number of samples currently visualized.
+ # The process with most samples visualized should always be visible on the ticker
+ graph.xaxis.formatter = FuncTickFormatter(args={'range': data_range, 'graph': graph}, code="""
+ var pixels_per_entry = graph.inner_height / (range.end - range.start) //Do not rond end and start here
+ var entries_to_skip = Math.ceil(12 / pixels_per_entry) // kind of 12 px per entry
+ var desc = tick.split(/:| /)
+ // desc[0] == desc[1] for main threads
+ var keep = (desc[0] == desc[1]) &&
+ !(desc[2].includes('unknown') ||
+ desc[2].includes('Binder') ||
+ desc[2].includes('kworker'))
+
+ if (pixels_per_entry < 8 && !keep) {
+ //if (index + Math.round(range.start)) % entries_to_skip != 0) {
+ return ""
+ }
+
+ return tick """)
+
+ graph.xaxis.major_label_orientation = math.pi/6
+
+ graph.circle(y='time',
+ x='thread',
+ source=source,
+ color='color',
+ alpha=0.3,
+ selection_fill_color='White',
+ selection_line_color='Black',
+ selection_line_width=0.5,
+ selection_alpha=1.0)
+
+ graph.y_range.range_padding = 0
+ graph.xgrid.grid_line_color = None
+ return graph
+
+
+def create_table(graph):
+ # Empty dataframe, will be filled up in js land
+ empty_data = {'thread': [], 'count': []}
+ table_source = ColumnDataSource(pd.DataFrame(
+ empty_data, columns=['thread', 'count'], index=None))
+ graph_source = graph.renderers[0].data_source
+
+ columns = [
+ TableColumn(field='thread', title='Thread'),
+ TableColumn(field='count', title='Count')
+ ]
+
+ # start with a small table size (stretch doesn't reduce from the preferred size)
+ table = DataTable(
+ width=100,
+ height=100,
+ sizing_mode='stretch_both',
+ source=table_source,
+ columns=columns,
+ index_position=None,
+ name='table')
+
+ graph_selection_cb = CustomJS(code='update_selections()')
+
+ graph_source.selected.js_on_change('indices', graph_selection_cb)
+ table_source.selected.js_on_change('indices', CustomJS(args={}, code='update_flamegraph()'))
+
+ return table
+
+
+def generate_template(template_file='index.html.jinja2'):
+ loader = jinja2.FileSystemLoader(
+ searchpath=os.path.dirname(os.path.realpath(__file__)) + '/templates/')
+
+ env = jinja2.Environment(loader=loader)
+ return env.get_template(template_file)
+
+
+def generate_html(args, components_dict, title):
+ resources = INLINE.render()
+ script, div = components(components_dict)
+ return generate_template().render(
+ resources=resources, plot_script=script, plot_div=div, title=title)
+
+
+class ThreadDescriptor:
+ def __init__(self, pid, tid, name):
+ self.name = name
+ self.tid = tid
+ self.pid = pid
+
+ def __lt__(self, other):
+ return self.pid < other.pid or (self.pid == other.pid and self.tid < other.tid)
+
+ def __gt__(self, other):
+ return self.pid > other.pid or (self.pid == other.pid and self.tid > other.tid)
+
+ def __eq__(self, other):
+ return self.pid == other.pid and self.tid == other.tid and self.name == other.name
+
+ def __str__(self):
+ return str(self.pid) + ':' + str(self.tid) + ' ' + self.name
+
+
+def generate_datasource(args):
+ lib = sp.ReportLib()
+ lib.ShowIpForUnknownSymbol()
+
+ if args.usyms:
+ lib.SetSymfs(args.usyms)
+
+ if args.input_file:
+ lib.SetRecordFile(args.input_file)
+
+ if args.ksyms:
+ lib.SetKallsymsFile(args.ksyms)
+
+ if not args.not_art:
+ lib.ShowArtFrames(True)
+
+ for file_path in args.proguard_mapping_file or []:
+ lib.AddProguardMappingFile(file_path)
+
+ product = lib.MetaInfo().get('product_props')
+
+ if product:
+ manufacturer, model, name = product.split(':')
+
+ start_time = -1
+ end_time = -1
+
+ times = []
+ threads = []
+ thread_descs = []
+ callchains = []
+
+ while True:
+ sample = lib.GetNextSample()
+
+ if sample is None:
+ lib.Close()
+ break
+
+ symbol = lib.GetSymbolOfCurrentSample()
+ callchain = lib.GetCallChainOfCurrentSample()
+
+ if start_time == -1:
+ start_time = sample.time
+
+ sample_time = (sample.time - start_time) / 1e6 # convert to ms
+
+ times.append(sample_time)
+
+ if sample_time > end_time:
+ end_time = sample_time
+
+ thread_desc = ThreadDescriptor(sample.pid, sample.tid, sample.thread_comm)
+
+ threads.append(str(thread_desc))
+
+ if thread_desc not in thread_descs:
+ bisect.insort(thread_descs, thread_desc)
+
+ callchain_str = ''
+
+ for i in range(callchain.nr):
+ symbol = callchain.entries[i].symbol # SymbolStruct
+ entry_line = ''
+
+ if args.include_dso_names:
+ entry_line += symbol._dso_name.decode('utf-8') + ':'
+
+ entry_line += symbol._symbol_name.decode('utf-8')
+
+ if args.include_symbols_addr:
+ entry_line += ':' + hex(symbol.symbol_addr)
+
+ if i < callchain.nr - 1:
+ callchain_str += entry_line + '<br>'
+
+ callchains.append(callchain_str)
+
+ # define colors per-process
+ palette = Category20b[20]
+ color_map = {}
+
+ last_pid = -1
+ palette_index = 0
+
+ for thread_desc in thread_descs:
+ if thread_desc.pid != last_pid:
+ last_pid = thread_desc.pid
+ palette_index += 1
+ palette_index %= len(palette)
+
+ color_map[str(thread_desc.pid)] = palette[palette_index]
+
+ colors = []
+ for sample_thread in threads:
+ pid = str(sample_thread.split(':')[0])
+ colors.append(color_map[pid])
+
+ threads_range = [str(thread_desc) for thread_desc in thread_descs]
+ data_range = FactorRange(factors=threads_range, bounds='auto')
+
+ data = {'time': times,
+ 'thread': threads,
+ 'callchain': callchains,
+ 'color': colors}
+
+ source = ColumnDataSource(data)
+
+ return source, data_range
+
+
+def main():
+ parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+ parser.add_argument('-i', '--input_file', type=str, required=True, help='input file')
+ parser.add_argument('--title', '-t', type=str, help='document title')
+ parser.add_argument('--ksyms', '-k', type=str, help='path to kernel symbols (kallsyms)')
+ parser.add_argument('--usyms', '-u', type=str, help='path to tree with user space symbols')
+ parser.add_argument('--not_art', '-a', action='store_true', help='Don\'t show ART symbols')
+ parser.add_argument('--output', '-o', type=str, help='output file')
+ parser.add_argument('--dont_open', '-d', action='store_true', help='Don\'t open output file')
+ parser.add_argument('--include_dso_names', '-n', action='store_true',
+ help='Include dso names in backtraces')
+ parser.add_argument('--include_symbols_addr', '-s', action='store_true',
+ help='Include addresses of symbols in backtraces')
+ parser.add_argument(
+ '--proguard-mapping-file', nargs='+',
+ help='Add proguard mapping file to de-obfuscate symbols')
+ args = parser.parse_args()
+
+ # TODO test hierarchical ranges too
+ source, data_range = generate_datasource(args)
+
+ graph = create_graph(args, source, data_range)
+ table = create_table(graph)
+
+ output_filename = args.output
+
+ if not output_filename:
+ output_filename = os.path.splitext(os.path.basename(args.input_file))[0] + '.html'
+
+ title = os.path.splitext(os.path.basename(output_filename))[0]
+
+ html = generate_html(args, {'graph': graph, 'table': table}, title)
+
+ with io.open(output_filename, mode='w', encoding='utf-8') as fout:
+ fout.write(html)
+
+ if not args.dont_open:
+ view(output_filename)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/simpleperf/scripts/purgatorio/templates/index.html.jinja2 b/simpleperf/scripts/purgatorio/templates/index.html.jinja2
new file mode 100644
index 00000000..f3e6046c
--- /dev/null
+++ b/simpleperf/scripts/purgatorio/templates/index.html.jinja2
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <title>{{ title }}</title>
+ <script
+ src="https://code.jquery.com/jquery-3.5.1.min.js"
+ integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
+ crossorigin="anonymous"></script>
+
+ <meta charset="utf-8">
+
+ {{ resources }}
+
+ <style>
+ {% include 'styles.css' %}
+ </style>
+
+ <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/d3-flame-graph@4.0.6/dist/d3-flamegraph.css">
+
+<script
+ src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"
+ integrity="sha256-VazP97ZCwtekAsvgPBSUwPFKdrwD3unUfSGVYrahUqU="
+ crossorigin="anonymous"></script>
+ <script type="text/javascript" src="https://d3js.org/d3.v4.min.js"></script>
+ <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/d3-flame-graph@4.0.6/dist/d3-flamegraph.min.js"></script>
+
+ </head>
+ <body>
+ <div id="help_dialog" class="dialog">
+ <div class="dialog_area">
+ <span class="dialog_close">&times;</span>
+ <p> <b>Main plot (upper left):</b> pan with click+mouse movement, zoom in/out with the mouse
+ wheel, hover on sample clusters to see backtraces. Select samples with the rectangular
+ selection tool or by clicking on them. Select holding shift to add or ctrl+shift to
+ remove samples to or from the selection. Different tools can be enabled/disabled from
+ the toolbox.</p>
+ <p><b>Flame graph (upper right):</b> click on specific items to zoom in.</p>
+ <p><b>Sample table (lower right):</b> select processes to filter in the Flame graph.</p>
+ </div>
+ </div>
+
+ <div class="top_right">
+ <button id="help_button" class="help" text-align="right">HELP</button>
+ </div>
+ <div class="left"> {{ plot_div.graph }} </div>
+ <div class="middle_right">
+ <div id="flame"/>
+ </div>
+
+ <div class="bottom_right">
+ <div style="display: flex; justify-content: space-around">
+ <div>
+ <label for="regex">Filter by regex:</label>
+ <input type="text" id="regex" oninput="update_selections()"/>
+ </div>
+ <div>
+ Invert callstack <input type="checkbox" id="inverted_checkbox" onclick="update_selections()">
+ </div>
+ </div>
+ {{ plot_div.table }}
+ </div>
+
+ <script>{% include 'main.js' %}</script>
+ {{ plot_script }}
+ </body>
+</html>
diff --git a/simpleperf/scripts/purgatorio/templates/main.js b/simpleperf/scripts/purgatorio/templates/main.js
new file mode 100644
index 00000000..ec8e7cbe
--- /dev/null
+++ b/simpleperf/scripts/purgatorio/templates/main.js
@@ -0,0 +1,245 @@
+/*
+ * 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.
+ */
+
+function generateHash (name) {
+ // Return a vector (0.0->1.0) that is a hash of the input string.
+ // The hash is computed to favor early characters over later ones, so
+ // that strings with similar starts have similar vectors. Only the first
+ // 6 characters are considered.
+ const MAX_CHAR = 6
+
+ var hash = 0
+ var maxHash = 0
+ var weight = 1
+ var mod = 10
+
+ if (name) {
+ for (var i = 0; i < name.length; i++) {
+ if (i > MAX_CHAR) { break }
+ hash += weight * (name.charCodeAt(i) % mod)
+ maxHash += weight * (mod - 1)
+ weight *= 0.70
+ }
+ if (maxHash > 0) { hash = hash / maxHash }
+ }
+ return hash
+}
+
+function offCpuColorMapper (d) {
+ if (d.highlight) return '#E600E6'
+
+ let name = d.data.n || d.data.name
+ let vector = 0
+ const nameArr = name.split('`')
+
+ if (nameArr.length > 1) {
+ name = nameArr[nameArr.length - 1] // drop module name if present
+ }
+ name = name.split('(')[0] // drop extra info
+ vector = generateHash(name)
+
+ const r = 0 + Math.round(55 * (1 - vector))
+ const g = 0 + Math.round(230 * (1 - vector))
+ const b = 200 + Math.round(55 * vector)
+
+ return 'rgb(' + r + ',' + g + ',' + b + ')'
+}
+
+var flame = flamegraph()
+ .cellHeight(18)
+  .width(window.innerWidth * 3 / 10 - 20) // 30% width
+ .transitionDuration(750)
+ .minFrameSize(5)
+ .transitionEase(d3.easeCubic)
+ .inverted(false)
+ .sort(true)
+ .title("")
+ //.differential(false)
+ //.elided(false)
+ .selfValue(false)
+ .setColorMapper(offCpuColorMapper);
+
+
+function update_table() {
+ let inverted = document.getElementById("inverted_checkbox").checked
+ let regex
+ let graph_source = Bokeh.documents[0].get_model_by_name('graph').renderers[0].data_source
+ let table_source = Bokeh.documents[0].get_model_by_name('table').source
+
+ let graph_selection = graph_source.selected.indices
+ let threads = graph_source.data.thread
+ let callchains = graph_source.data.callchain
+
+ let selection_len = graph_selection.length;
+
+ if (document.getElementById("regex").value) {
+ regex = new RegExp(document.getElementById("regex").value)
+ }
+
+ table_source.data.thread = []
+ table_source.data.count = []
+ table_source.data.index = []
+
+ for (let i = 0; i < selection_len; i ++) {
+ let entry = "<no callchain>"
+
+ if (regex !== undefined && !regex.test(callchains[graph_selection[i]])) {
+ continue;
+ }
+
+ if (inverted) {
+ let callchain = callchains[graph_selection[i]].split("<br>")
+
+ for (let e = 0; e < callchain.length; e ++) {
+ if (callchain[e] != "") { // last entry is apparently always an empty string
+ entry = callchain[e]
+ break
+ }
+ }
+ } else {
+ entry = threads[graph_selection[i]]
+ }
+
+ let pos = table_source.data.thread.indexOf(entry)
+
+ if(pos == -1) {
+ table_source.data.thread.push(entry)
+ table_source.data.count.push(1)
+ table_source.data.index.push(table_source.data.thread.length)
+ } else {
+ table_source.data.count[pos] ++
+ }
+ }
+
+ table_source.selected.indices = []
+ table_source.change.emit()
+}
+
+
+function should_insert_callchain(callchain, items, filter_index, inverted) {
+ for (t = 0; t < filter_index.length; t ++) {
+ if (callchain[0] === items[filter_index[t]]) {
+ return true
+ }
+ }
+
+ if (filter_index.length > 0) {
+ return false
+ }
+
+ return true
+}
+
+
+function insert_callchain(root, callchain, inverted) {
+ let root_pos = -1
+ let node = root
+
+ node.value ++
+
+ for (let e = 0; e < callchain.length; e ++) {
+ let entry = callchain[e].replace(/^\s+|\s+$/g, '')
+ let entry_pos = -1
+
+ for (let j = 0; j < node.children.length; j ++) {
+ if (node.children[j].name == entry) {
+ entry_pos = j
+ break
+ }
+ }
+
+ if (entry_pos == -1) {
+ node.children.push({name: entry, value:0, children:[]})
+ entry_pos = node.children.length - 1
+ }
+
+ node = node.children[entry_pos]
+ node.value ++
+ }
+}
+
+
+function update_flamegraph() {
+ let inverted = document.getElementById("inverted_checkbox").checked
+ let root = {name: inverted ? "samples" : "processes", value: 0, children: []}
+
+ let graph_source = Bokeh.documents[0].get_model_by_name('graph').renderers[0].data_source
+ let graph_selection = graph_source.selected.indices
+ let callchains = graph_source.data.callchain
+ let graph_threads = graph_source.data.thread
+
+ let table_source = Bokeh.documents[0].get_model_by_name('table').source
+ let table_selection = table_source.selected.indices
+ let table_threads = table_source.data.thread
+ let regex
+
+ if (document.getElementById("regex").value) {
+ regex = new RegExp(document.getElementById("regex").value)
+ }
+
+ for (let i = 0; i < graph_selection.length; i ++) {
+ let thread = graph_threads[graph_selection[i]]
+ let callchain = callchains[graph_selection[i]].split("<br>")
+ callchain = callchain.filter(function(e){return e != ""})
+
+ if (regex !== undefined && !regex.test(callchains[graph_selection[i]])) {
+ continue;
+ }
+
+ if (callchain.length == 0) {
+ callchain.push("<no callchain>")
+ }
+
+ callchain.push(thread)
+
+ if (!inverted){
+ callchain = callchain.reverse()
+ }
+
+ if (should_insert_callchain(callchain, table_threads, table_selection)) {
+ insert_callchain(root, callchain)
+ }
+ }
+
+ if (root.children.length == 1) {
+ root = root.children[0]
+ }
+
+ d3.select("#flame")
+ .datum(root)
+ .call(flame)
+}
+
+var help_dialog = document.getElementById("help_dialog");
+
+document.getElementById("help_button").onclick = function() {
+ help_dialog.style.display = "block";
+}
+
+window.onclick = function(event) {
+ if (event.target == help_dialog) {
+ help_dialog.style.display = "none";
+ }
+}
+
+document.getElementsByClassName("dialog_close")[0].onclick = function() {
+ help_dialog.style.display = "none";
+}
+
+function update_selections() {
+ update_flamegraph()
+ update_table()
+}
diff --git a/simpleperf/scripts/purgatorio/templates/styles.css b/simpleperf/scripts/purgatorio/templates/styles.css
new file mode 100644
index 00000000..4378b0b4
--- /dev/null
+++ b/simpleperf/scripts/purgatorio/templates/styles.css
@@ -0,0 +1,133 @@
+/*
+ * 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.
+ */
+
+body {
+ font-family: sans-serif;
+}
+
+::-webkit-scrollbar {
+ width: 1em;
+}
+
+::-webkit-scrollbar-track {
+ box-shadow: inset 0 0 0.1em white;
+ border-radius: 0.5em;
+}
+
+::-webkit-scrollbar-thumb {
+ background: lightgrey;
+ border-radius: 0.5em;
+}
+
+div.left {
+ position:fixed;
+ top: 0;
+ left: 0;
+ width: 70%;
+ height: 100%;
+}
+
+div.top_right {
+ position: fixed;
+ top: 0;
+ right: 0;
+ text-align: right;
+ width: 30%;
+ height: 1em;
+}
+
+div.middle_right {
+ position:fixed;
+ top: 2%;
+ right: 0;
+ width: 30%;
+ height: 78%;
+ overflow-y: scroll;
+}
+
+div.bottom_right {
+ position: fixed;
+ width: 30%;
+ height: 20%;
+ bottom: 0;
+ right: 0;
+}
+
+button {
+ border: none;
+ outline: none;
+ padding: 0;
+ background: white;
+ font-size: 0.5em;
+}
+
+button.help:before
+{
+ content: '?';
+ display: inline-block;
+ font-weight: bold;
+ text-align: center;
+ font-size: 1.4em;
+ width: 1.5em;
+ height: 1.5em;
+ line-height: 1.6em;
+ border-radius: 1.2em;
+ margin-right: 0.3em;
+ color: GoldenRod;
+ background: white;
+ border: 0.1em solid GoldenRod;
+}
+
+button.help:hover:before
+{
+ color: white;
+ background: GoldenRod;
+}
+
+.dialog {
+ display: none;
+ position: fixed;
+ z-index: 1;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+ background-color: rgba(0,0,0,0.4);
+}
+
+.dialog_area {
+ background-color: white;
+ margin: 20% auto;
+ border: 0.05em solid gray;
+ border-radius: 0.5em;
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+ width: 50%;
+}
+
+.dialog_close {
+ color: darkgray;
+ float: right;
+ font-size: 2em;
+ font-weight: bold;
+}
+
+.dialog_close:focus,
+.dialog_close:hover {
+ cursor: pointer;
+ color: black;
+}
diff --git a/simpleperf/scripts/report.py b/simpleperf/scripts/report.py
index ae99e25f..e42160c9 100755
--- a/simpleperf/scripts/report.py
+++ b/simpleperf/scripts/report.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2015 The Android Open Source Project
#
@@ -39,7 +39,7 @@ except ImportError:
from tkFont import Font
from ttk import *
-from utils import *
+from simpleperf_utils import *
PAD_X = 3
PAD_Y = 3
@@ -47,224 +47,225 @@ PAD_Y = 3
class CallTreeNode(object):
- """Representing a node in call-graph."""
+ """Representing a node in call-graph."""
- def __init__(self, percentage, function_name):
- self.percentage = percentage
- self.call_stack = [function_name]
- self.children = []
+ def __init__(self, percentage, function_name):
+ self.percentage = percentage
+ self.call_stack = [function_name]
+ self.children = []
- def add_call(self, function_name):
- self.call_stack.append(function_name)
+ def add_call(self, function_name):
+ self.call_stack.append(function_name)
- def add_child(self, node):
- self.children.append(node)
+ def add_child(self, node):
+ self.children.append(node)
- def __str__(self):
- strs = self.dump()
- return '\n'.join(strs)
+ def __str__(self):
+ strs = self.dump()
+ return '\n'.join(strs)
- def dump(self):
- strs = []
- strs.append('CallTreeNode percentage = %.2f' % self.percentage)
- for function_name in self.call_stack:
- strs.append(' %s' % function_name)
- for child in self.children:
- child_strs = child.dump()
- strs.extend([' ' + x for x in child_strs])
- return strs
+ def dump(self):
+ strs = []
+ strs.append('CallTreeNode percentage = %.2f' % self.percentage)
+ for function_name in self.call_stack:
+ strs.append(' %s' % function_name)
+ for child in self.children:
+ child_strs = child.dump()
+ strs.extend([' ' + x for x in child_strs])
+ return strs
class ReportItem(object):
- """Representing one item in report, may contain a CallTree."""
+ """Representing one item in report, may contain a CallTree."""
- def __init__(self, raw_line):
- self.raw_line = raw_line
- self.call_tree = None
+ def __init__(self, raw_line):
+ self.raw_line = raw_line
+ self.call_tree = None
+
+ def __str__(self):
+ strs = []
+ strs.append('ReportItem (raw_line %s)' % self.raw_line)
+ if self.call_tree is not None:
+ strs.append('%s' % self.call_tree)
+ return '\n'.join(strs)
- def __str__(self):
- strs = []
- strs.append('ReportItem (raw_line %s)' % self.raw_line)
- if self.call_tree is not None:
- strs.append('%s' % self.call_tree)
- return '\n'.join(strs)
class EventReport(object):
- """Representing report for one event attr."""
+ """Representing report for one event attr."""
- def __init__(self, common_report_context):
- self.context = common_report_context[:]
- self.title_line = None
- self.report_items = []
+ def __init__(self, common_report_context):
+ self.context = common_report_context[:]
+ self.title_line = None
+ self.report_items = []
def parse_event_reports(lines):
- # Parse common report context
- common_report_context = []
- line_id = 0
- while line_id < len(lines):
- line = lines[line_id]
- if not line or line.find('Event:') == 0:
- break
- common_report_context.append(line)
- line_id += 1
-
- event_reports = []
- in_report_context = True
- cur_event_report = EventReport(common_report_context)
- cur_report_item = None
- call_tree_stack = {}
- vertical_columns = []
- last_node = None
-
- has_skipped_callgraph = False
-
- for line in lines[line_id:]:
- if not line:
- in_report_context = not in_report_context
- if in_report_context:
- cur_event_report = EventReport(common_report_context)
- continue
-
- if in_report_context:
- cur_event_report.context.append(line)
- if line.find('Event:') == 0:
- event_reports.append(cur_event_report)
- continue
-
- if cur_event_report.title_line is None:
- cur_event_report.title_line = line
- elif not line[0].isspace():
- cur_report_item = ReportItem(line)
- cur_event_report.report_items.append(cur_report_item)
- # Each report item can have different column depths.
- vertical_columns = []
- else:
- for i in range(len(line)):
- if line[i] == '|':
- if not vertical_columns or vertical_columns[-1] < i:
- vertical_columns.append(i)
-
- if not line.strip('| \t'):
- continue
- if 'skipped in brief callgraph mode' in line:
- has_skipped_callgraph = True
- continue
-
- if line.find('-') == -1:
- line = line.strip('| \t')
- function_name = line
- last_node.add_call(function_name)
- else:
- pos = line.find('-')
- depth = -1
- for i in range(len(vertical_columns)):
- if pos >= vertical_columns[i]:
- depth = i
- assert depth != -1
-
- line = line.strip('|- \t')
- m = re.search(r'^([\d\.]+)%[-\s]+(.+)$', line)
- if m:
- percentage = float(m.group(1))
- function_name = m.group(2)
- else:
- percentage = 100.0
- function_name = line
-
- node = CallTreeNode(percentage, function_name)
- if depth == 0:
- cur_report_item.call_tree = node
+ # Parse common report context
+ common_report_context = []
+ line_id = 0
+ while line_id < len(lines):
+ line = lines[line_id]
+ if not line or line.find('Event:') == 0:
+ break
+ common_report_context.append(line)
+ line_id += 1
+
+ event_reports = []
+ in_report_context = True
+ cur_event_report = EventReport(common_report_context)
+ cur_report_item = None
+ call_tree_stack = {}
+ vertical_columns = []
+ last_node = None
+
+ has_skipped_callgraph = False
+
+ for line in lines[line_id:]:
+ if not line:
+ in_report_context = not in_report_context
+ if in_report_context:
+ cur_event_report = EventReport(common_report_context)
+ continue
+
+ if in_report_context:
+ cur_event_report.context.append(line)
+ if line.find('Event:') == 0:
+ event_reports.append(cur_event_report)
+ continue
+
+ if cur_event_report.title_line is None:
+ cur_event_report.title_line = line
+ elif not line[0].isspace():
+ cur_report_item = ReportItem(line)
+ cur_event_report.report_items.append(cur_report_item)
+ # Each report item can have different column depths.
+ vertical_columns = []
else:
- call_tree_stack[depth - 1].add_child(node)
- call_tree_stack[depth] = node
- last_node = node
-
- if has_skipped_callgraph:
- log_warning('some callgraphs are skipped in brief callgraph mode')
-
- return event_reports
+ for i in range(len(line)):
+ if line[i] == '|':
+ if not vertical_columns or vertical_columns[-1] < i:
+ vertical_columns.append(i)
+
+ if not line.strip('| \t'):
+ continue
+ if 'skipped in brief callgraph mode' in line:
+ has_skipped_callgraph = True
+ continue
+
+ if line.find('-') == -1:
+ line = line.strip('| \t')
+ function_name = line
+ last_node.add_call(function_name)
+ else:
+ pos = line.find('-')
+ depth = -1
+ for i in range(len(vertical_columns)):
+ if pos >= vertical_columns[i]:
+ depth = i
+ assert depth != -1
+
+ line = line.strip('|- \t')
+ m = re.search(r'^([\d\.]+)%[-\s]+(.+)$', line)
+ if m:
+ percentage = float(m.group(1))
+ function_name = m.group(2)
+ else:
+ percentage = 100.0
+ function_name = line
+
+ node = CallTreeNode(percentage, function_name)
+ if depth == 0:
+ cur_report_item.call_tree = node
+ else:
+ call_tree_stack[depth - 1].add_child(node)
+ call_tree_stack[depth] = node
+ last_node = node
+
+ if has_skipped_callgraph:
+ log_warning('some callgraphs are skipped in brief callgraph mode')
+
+ return event_reports
class ReportWindow(object):
- """A window used to display report file."""
-
- def __init__(self, master, report_context, title_line, report_items):
- frame = Frame(master)
- frame.pack(fill=BOTH, expand=1)
-
- font = Font(family='courier', size=12)
-
- # Report Context
- for line in report_context:
- label = Label(frame, text=line, font=font)
- label.pack(anchor=W, padx=PAD_X, pady=PAD_Y)
-
- # Space
- label = Label(frame, text='', font=font)
- label.pack(anchor=W, padx=PAD_X, pady=PAD_Y)
-
- # Title
- label = Label(frame, text=' ' + title_line, font=font)
- label.pack(anchor=W, padx=PAD_X, pady=PAD_Y)
-
- # Report Items
- report_frame = Frame(frame)
- report_frame.pack(fill=BOTH, expand=1)
-
- yscrollbar = Scrollbar(report_frame)
- yscrollbar.pack(side=RIGHT, fill=Y)
- xscrollbar = Scrollbar(report_frame, orient=HORIZONTAL)
- xscrollbar.pack(side=BOTTOM, fill=X)
-
- tree = Treeview(report_frame, columns=[title_line], show='')
- tree.pack(side=LEFT, fill=BOTH, expand=1)
- tree.tag_configure('set_font', font=font)
-
- tree.config(yscrollcommand=yscrollbar.set)
- yscrollbar.config(command=tree.yview)
- tree.config(xscrollcommand=xscrollbar.set)
- xscrollbar.config(command=tree.xview)
-
- self.display_report_items(tree, report_items)
-
- def display_report_items(self, tree, report_items):
- for report_item in report_items:
- prefix_str = '+ ' if report_item.call_tree is not None else ' '
- id = tree.insert(
- '',
- 'end',
- None,
- values=[
- prefix_str +
- report_item.raw_line],
- tag='set_font')
- if report_item.call_tree is not None:
- self.display_call_tree(tree, id, report_item.call_tree, 1)
-
- def display_call_tree(self, tree, parent_id, node, indent):
- id = parent_id
- indent_str = ' ' * indent
-
- if node.percentage != 100.0:
- percentage_str = '%.2f%% ' % node.percentage
- else:
- percentage_str = ''
-
- for i in range(len(node.call_stack)):
- s = indent_str
- s += '+ ' if node.children and i == len(node.call_stack) - 1 else ' '
- s += percentage_str if i == 0 else ' ' * len(percentage_str)
- s += node.call_stack[i]
- child_open = False if i == len(node.call_stack) - 1 and indent > 1 else True
- id = tree.insert(id, 'end', None, values=[s], open=child_open,
- tag='set_font')
-
- for child in node.children:
- self.display_call_tree(tree, id, child, indent + 1)
+ """A window used to display report file."""
+
+ def __init__(self, main, report_context, title_line, report_items):
+ frame = Frame(main)
+ frame.pack(fill=BOTH, expand=1)
+
+ font = Font(family='courier', size=12)
+
+ # Report Context
+ for line in report_context:
+ label = Label(frame, text=line, font=font)
+ label.pack(anchor=W, padx=PAD_X, pady=PAD_Y)
+
+ # Space
+ label = Label(frame, text='', font=font)
+ label.pack(anchor=W, padx=PAD_X, pady=PAD_Y)
+
+ # Title
+ label = Label(frame, text=' ' + title_line, font=font)
+ label.pack(anchor=W, padx=PAD_X, pady=PAD_Y)
+
+ # Report Items
+ report_frame = Frame(frame)
+ report_frame.pack(fill=BOTH, expand=1)
+
+ yscrollbar = Scrollbar(report_frame)
+ yscrollbar.pack(side=RIGHT, fill=Y)
+ xscrollbar = Scrollbar(report_frame, orient=HORIZONTAL)
+ xscrollbar.pack(side=BOTTOM, fill=X)
+
+ tree = Treeview(report_frame, columns=[title_line], show='')
+ tree.pack(side=LEFT, fill=BOTH, expand=1)
+ tree.tag_configure('set_font', font=font)
+
+ tree.config(yscrollcommand=yscrollbar.set)
+ yscrollbar.config(command=tree.yview)
+ tree.config(xscrollcommand=xscrollbar.set)
+ xscrollbar.config(command=tree.xview)
+
+ self.display_report_items(tree, report_items)
+
+ def display_report_items(self, tree, report_items):
+ for report_item in report_items:
+ prefix_str = '+ ' if report_item.call_tree is not None else ' '
+ id = tree.insert(
+ '',
+ 'end',
+ None,
+ values=[
+ prefix_str +
+ report_item.raw_line],
+ tag='set_font')
+ if report_item.call_tree is not None:
+ self.display_call_tree(tree, id, report_item.call_tree, 1)
+
+ def display_call_tree(self, tree, parent_id, node, indent):
+ id = parent_id
+ indent_str = ' ' * indent
+
+ if node.percentage != 100.0:
+ percentage_str = '%.2f%% ' % node.percentage
+ else:
+ percentage_str = ''
+
+ for i in range(len(node.call_stack)):
+ s = indent_str
+ s += '+ ' if node.children and i == len(node.call_stack) - 1 else ' '
+ s += percentage_str if i == 0 else ' ' * len(percentage_str)
+ s += node.call_stack[i]
+ child_open = False if i == len(node.call_stack) - 1 and indent > 1 else True
+ id = tree.insert(id, 'end', None, values=[s], open=child_open,
+ tag='set_font')
+
+ for child in node.children:
+ self.display_call_tree(tree, id, child, indent + 1)
def display_report_file(report_file, self_kill_after_sec):
diff --git a/simpleperf/scripts/report_html.js b/simpleperf/scripts/report_html.js
index 40964099..f33711bc 100644
--- a/simpleperf/scripts/report_html.js
+++ b/simpleperf/scripts/report_html.js
@@ -1615,7 +1615,7 @@ function collectDisassemblyForFunction(func) {
let hasCount = false;
function addEventCount(addr) {
- while (hitAddrPos < hitAddrs.length && hitAddrs[hitAddrPos].a < addr) {
+ while (hitAddrPos < hitAddrs.length && BigInt(hitAddrs[hitAddrPos].a) < addr) {
if (codeForLastAddr) {
codeForLastAddr.eventCount += hitAddrs[hitAddrPos].e;
codeForLastAddr.subtreeEventCount += hitAddrs[hitAddrPos].s;
@@ -1627,7 +1627,7 @@ function collectDisassemblyForFunction(func) {
for (let line of rawCode) {
let code = line[0];
- let addr = line[1];
+ let addr = BigInt(line[1]);
addEventCount(addr);
let item = {code: code, eventCount: 0, subtreeEventCount: 0};
diff --git a/simpleperf/scripts/report_html.py b/simpleperf/scripts/report_html.py
index f2292720..6e6a90e1 100755
--- a/simpleperf/scripts/report_html.py
+++ b/simpleperf/scripts/report_html.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2017 The Android Open Source Project
#
@@ -15,30 +15,36 @@
# limitations under the License.
#
+from __future__ import annotations
import argparse
import collections
+from concurrent.futures import ThreadPoolExecutor
+from dataclasses import dataclass
import datetime
import json
import os
+from pathlib import Path
import sys
+from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Tuple, Union
-from simpleperf_report_lib import ReportLib
-from utils import log_info, log_exit
-from utils import Addr2Nearestline, get_script_dir, Objdump, open_report_in_browser
-from utils import SourceFileSearcher
+from simpleperf_report_lib import ReportLib, SymbolStruct
+from simpleperf_utils import (
+ Addr2Nearestline, ArgParseFormatter, BinaryFinder, get_script_dir, log_exit, log_info, Objdump,
+ open_report_in_browser, ReadElf, SourceFileSearcher)
MAX_CALLSTACK_LENGTH = 750
+
class HtmlWriter(object):
- def __init__(self, output_path):
+ def __init__(self, output_path: Union[Path, str]):
self.fh = open(output_path, 'w')
self.tag_stack = []
def close(self):
self.fh.close()
- def open_tag(self, tag, **attrs):
+ def open_tag(self, tag: str, **attrs: Dict[str, str]) -> HtmlWriter:
attr_str = ''
for key in attrs:
attr_str += ' %s="%s"' % (key, attrs[key])
@@ -46,39 +52,47 @@ class HtmlWriter(object):
self.tag_stack.append(tag)
return self
- def close_tag(self, tag=None):
+ def close_tag(self, tag: Optional[str] = None):
if tag:
assert tag == self.tag_stack[-1]
self.fh.write('</%s>\n' % self.tag_stack.pop())
- def add(self, text):
+ def add(self, text: str) -> HtmlWriter:
self.fh.write(text)
return self
- def add_file(self, file_path):
+ def add_file(self, file_path: Union[Path, str]) -> HtmlWriter:
file_path = os.path.join(get_script_dir(), file_path)
with open(file_path, 'r') as f:
self.add(f.read())
return self
-def modify_text_for_html(text):
+
+def modify_text_for_html(text: str) -> str:
return text.replace('>', '&gt;').replace('<', '&lt;')
+
+def hex_address_for_json(addr: int) -> str:
+ """ To handle big addrs (nears uint64_max) in Javascript, store addrs as hex strings in Json.
+ """
+ return '0x%x' % addr
+
+
class EventScope(object):
- def __init__(self, name):
+ def __init__(self, name: str):
self.name = name
- self.processes = {} # map from pid to ProcessScope
+ self.processes: Dict[int, ProcessScope] = {} # map from pid to ProcessScope
self.sample_count = 0
self.event_count = 0
- def get_process(self, pid):
+ def get_process(self, pid: int) -> ProcessScope:
process = self.processes.get(pid)
if not process:
process = self.processes[pid] = ProcessScope(pid)
return process
- def get_sample_info(self, gen_addr_hit_map):
+ def get_sample_info(self, gen_addr_hit_map: bool) -> Dict[str, Any]:
result = {}
result['eventName'] = self.name
result['eventCount'] = self.event_count
@@ -88,13 +102,13 @@ class EventScope(object):
return result
@property
- def threads(self):
+ def threads(self) -> Iterator[ThreadScope]:
for process in self.processes.values():
for thread in process.threads.values():
yield thread
@property
- def libraries(self):
+ def libraries(self) -> Iterator[LibScope]:
for process in self.processes.values():
for thread in process.threads.values():
for lib in thread.libs.values():
@@ -103,13 +117,13 @@ class EventScope(object):
class ProcessScope(object):
- def __init__(self, pid):
+ def __init__(self, pid: int):
self.pid = pid
self.name = ''
self.event_count = 0
- self.threads = {} # map from tid to ThreadScope
+ self.threads: Dict[int, ThreadScope] = {} # map from tid to ThreadScope
- def get_thread(self, tid, thread_name):
+ def get_thread(self, tid: int, thread_name: str) -> ThreadScope:
thread = self.threads.get(tid)
if not thread:
thread = self.threads[tid] = ThreadScope(tid)
@@ -118,7 +132,7 @@ class ProcessScope(object):
self.name = thread_name
return thread
- def get_sample_info(self, gen_addr_hit_map):
+ def get_sample_info(self, gen_addr_hit_map: bool) -> Dict[str, Any]:
result = {}
result['pid'] = self.pid
result['eventCount'] = self.event_count
@@ -127,10 +141,11 @@ class ProcessScope(object):
for thread in threads]
return result
- def merge_by_thread_name(self, process):
+ def merge_by_thread_name(self, process: ProcessScope):
self.event_count += process.event_count
- thread_list = list(self.threads.values()) + list(process.threads.values())
- new_threads = {} # map from thread name to ThreadScope
+ thread_list: List[ThreadScope] = list(
+ self.threads.values()) + list(process.threads.values())
+ new_threads: Dict[str, ThreadScope] = {} # map from thread name to ThreadScope
for thread in thread_list:
cur_thread = new_threads.get(thread.name)
if cur_thread is None:
@@ -144,19 +159,21 @@ class ProcessScope(object):
class ThreadScope(object):
- def __init__(self, tid):
+ def __init__(self, tid: int):
self.tid = tid
self.name = ''
self.event_count = 0
self.sample_count = 0
- self.libs = {} # map from lib_id to LibScope
+ self.libs: Dict[int, LibScope] = {} # map from lib_id to LibScope
self.call_graph = CallNode(-1)
self.reverse_call_graph = CallNode(-1)
- def add_callstack(self, event_count, callstack, build_addr_hit_map):
+ def add_callstack(
+ self, event_count: int, callstack: List[Tuple[int, int, int]],
+ build_addr_hit_map: bool):
""" callstack is a list of tuple (lib_id, func_id, addr).
For each i > 0, callstack[i] calls callstack[i-1]."""
- hit_func_ids = set()
+ hit_func_ids: Set[int] = set()
for i, (lib_id, func_id, addr) in enumerate(callstack):
# When a callstack contains recursive function, only add for each function once.
if func_id in hit_func_ids:
@@ -189,7 +206,8 @@ class ThreadScope(object):
self.call_graph.update_subtree_event_count()
self.reverse_call_graph.update_subtree_event_count()
- def limit_percents(self, min_func_limit, min_callchain_percent, hit_func_ids):
+ def limit_percents(self, min_func_limit: float, min_callchain_percent: float,
+ hit_func_ids: Set[int]):
for lib in self.libs.values():
to_del_funcs = []
for function in lib.functions.values():
@@ -203,7 +221,7 @@ class ThreadScope(object):
self.call_graph.cut_edge(min_limit, hit_func_ids)
self.reverse_call_graph.cut_edge(min_limit, hit_func_ids)
- def get_sample_info(self, gen_addr_hit_map):
+ def get_sample_info(self, gen_addr_hit_map: bool) -> Dict[str, Any]:
result = {}
result['tid'] = self.tid
result['eventCount'] = self.event_count
@@ -214,7 +232,7 @@ class ThreadScope(object):
result['rg'] = self.reverse_call_graph.gen_sample_info()
return result
- def merge(self, thread):
+ def merge(self, thread: ThreadScope):
self.event_count += thread.event_count
self.sample_count += thread.sample_count
for lib_id, lib in thread.libs.items():
@@ -229,18 +247,18 @@ class ThreadScope(object):
class LibScope(object):
- def __init__(self, lib_id):
+ def __init__(self, lib_id: int):
self.lib_id = lib_id
self.event_count = 0
- self.functions = {} # map from func_id to FunctionScope.
+ self.functions: Dict[int, FunctionScope] = {} # map from func_id to FunctionScope.
- def get_function(self, func_id):
+ def get_function(self, func_id: int) -> FunctionScope:
function = self.functions.get(func_id)
if not function:
function = self.functions[func_id] = FunctionScope(func_id)
return function
- def gen_sample_info(self, gen_addr_hit_map):
+ def gen_sample_info(self, gen_addr_hit_map: bool) -> Dict[str, Any]:
result = {}
result['libId'] = self.lib_id
result['eventCount'] = self.event_count
@@ -248,7 +266,7 @@ class LibScope(object):
for func in self.functions.values()]
return result
- def merge(self, lib):
+ def merge(self, lib: LibScope):
self.event_count += lib.event_count
for func_id, function in lib.functions.items():
cur_function = self.functions.get(func_id)
@@ -260,7 +278,7 @@ class LibScope(object):
class FunctionScope(object):
- def __init__(self, func_id):
+ def __init__(self, func_id: int):
self.func_id = func_id
self.sample_count = 0
self.event_count = 0
@@ -269,7 +287,7 @@ class FunctionScope(object):
# map from (source_file_id, line) to [event_count, subtree_event_count].
self.line_hit_map = None
- def build_addr_hit_map(self, addr, event_count, subtree_event_count):
+ def build_addr_hit_map(self, addr: int, event_count: int, subtree_event_count: int):
if self.addr_hit_map is None:
self.addr_hit_map = {}
count_info = self.addr_hit_map.get(addr)
@@ -279,7 +297,8 @@ class FunctionScope(object):
count_info[0] += event_count
count_info[1] += subtree_event_count
- def build_line_hit_map(self, source_file_id, line, event_count, subtree_event_count):
+ def build_line_hit_map(self, source_file_id: int, line: int, event_count: int,
+ subtree_event_count: int):
if self.line_hit_map is None:
self.line_hit_map = {}
key = (source_file_id, line)
@@ -290,7 +309,7 @@ class FunctionScope(object):
count_info[0] += event_count
count_info[1] += subtree_event_count
- def gen_sample_info(self, gen_addr_hit_map):
+ def gen_sample_info(self, gen_addr_hit_map: bool) -> Dict[str, Any]:
result = {}
result['f'] = self.func_id
result['c'] = [self.sample_count, self.event_count, self.subtree_event_count]
@@ -305,11 +324,14 @@ class FunctionScope(object):
items = []
for addr in sorted(self.addr_hit_map):
count_info = self.addr_hit_map[addr]
- items.append({'a': addr, 'e': count_info[0], 's': count_info[1]})
+ items.append(
+ {'a': hex_address_for_json(addr),
+ 'e': count_info[0],
+ 's': count_info[1]})
result['a'] = items
return result
- def merge(self, function):
+ def merge(self, function: FunctionScope):
self.sample_count += function.sample_count
self.event_count += function.event_count
self.subtree_event_count += function.subtree_event_count
@@ -317,7 +339,8 @@ class FunctionScope(object):
self.line_hit_map = self.__merge_hit_map(self.line_hit_map, function.line_hit_map)
@staticmethod
- def __merge_hit_map(map1, map2):
+ def __merge_hit_map(map1: Optional[Dict[int, List[int]]],
+ map2: Optional[Dict[int, List[int]]]) -> Optional[Dict[int, List[int]]]:
if not map1:
return map2
if not map2:
@@ -334,13 +357,14 @@ class FunctionScope(object):
class CallNode(object):
- def __init__(self, func_id):
+ def __init__(self, func_id: int):
self.event_count = 0
self.subtree_event_count = 0
self.func_id = func_id
- self.children = collections.OrderedDict() # map from func_id to CallNode
+ # map from func_id to CallNode
+ self.children: Dict[int, CallNode] = collections.OrderedDict()
- def get_child(self, func_id):
+ def get_child(self, func_id: int) -> CallNode:
child = self.children.get(func_id)
if not child:
child = self.children[func_id] = CallNode(func_id)
@@ -352,7 +376,7 @@ class CallNode(object):
self.subtree_event_count += child.update_subtree_event_count()
return self.subtree_event_count
- def cut_edge(self, min_limit, hit_func_ids):
+ def cut_edge(self, min_limit: float, hit_func_ids: Set[int]):
hit_func_ids.add(self.func_id)
to_del_children = []
for key in self.children:
@@ -364,7 +388,7 @@ class CallNode(object):
for key in to_del_children:
del self.children[key]
- def gen_sample_info(self):
+ def gen_sample_info(self) -> Dict[str, Any]:
result = {}
result['e'] = self.event_count
result['s'] = self.subtree_event_count
@@ -372,7 +396,7 @@ class CallNode(object):
result['c'] = [child.gen_sample_info() for child in self.children.values()]
return result
- def merge(self, node):
+ def merge(self, node: CallNode):
self.event_count += node.event_count
self.subtree_event_count += node.subtree_event_count
for key, child in node.children.items():
@@ -383,27 +407,37 @@ class CallNode(object):
cur_child.merge(child)
+@dataclass
+class LibInfo:
+ name: str
+ build_id: str
+
+
class LibSet(object):
""" Collection of shared libraries used in perf.data. """
+
def __init__(self):
- self.lib_name_to_id = {}
- self.lib_id_to_name = []
-
- def get_lib_id(self, lib_name):
- lib_id = self.lib_name_to_id.get(lib_name)
- if lib_id is None:
- lib_id = len(self.lib_id_to_name)
- self.lib_name_to_id[lib_name] = lib_id
- self.lib_id_to_name.append(lib_name)
+ self.lib_name_to_id: Dict[str, int] = {}
+ self.libs: List[LibInfo] = []
+
+ def get_lib_id(self, lib_name: str) -> Optional[int]:
+ return self.lib_name_to_id.get(lib_name)
+
+ def add_lib(self, lib_name: str, build_id: str) -> int:
+ """ Return lib_id of the newly added lib. """
+ lib_id = len(self.libs)
+ self.libs.append(LibInfo(lib_name, build_id))
+ self.lib_name_to_id[lib_name] = lib_id
return lib_id
- def get_lib_name(self, lib_id):
- return self.lib_id_to_name[lib_id]
+ def get_lib(self, lib_id: int) -> LibInfo:
+ return self.libs[lib_id]
class Function(object):
""" Represent a function in a shared library. """
- def __init__(self, lib_id, func_name, func_id, start_addr, addr_len):
+
+ def __init__(self, lib_id: int, func_name: str, func_id: int, start_addr: int, addr_len: int):
self.lib_id = lib_id
self.func_name = func_name
self.func_id = func_id
@@ -415,11 +449,12 @@ class Function(object):
class FunctionSet(object):
""" Collection of functions used in perf.data. """
+
def __init__(self):
- self.name_to_func = {}
- self.id_to_func = {}
+ self.name_to_func: Dict[Tuple[int, str], Function] = {}
+ self.id_to_func: Dict[int, Function] = {}
- def get_func_id(self, lib_id, symbol):
+ def get_func_id(self, lib_id: int, symbol: SymbolStruct) -> int:
key = (lib_id, symbol.symbol_name)
function = self.name_to_func.get(key)
if function is None:
@@ -430,7 +465,7 @@ class FunctionSet(object):
self.id_to_func[func_id] = function
return function.func_id
- def trim_functions(self, left_func_ids):
+ 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():
if function.func_id not in left_func_ids:
@@ -441,17 +476,18 @@ class FunctionSet(object):
class SourceFile(object):
""" A source file containing source code hit by samples. """
- def __init__(self, file_id, abstract_path):
+
+ def __init__(self, file_id: int, abstract_path: str):
self.file_id = file_id
self.abstract_path = abstract_path # path reported by addr2line
- self.real_path = None # file path in the file system
- self.requested_lines = set()
- self.line_to_code = {} # map from line to code in that line.
+ self.real_path: Optional[str] = None # file path in the file system
+ self.requested_lines: Optional[Set[int]] = set()
+ self.line_to_code: Dict[int, str] = {} # map from line to code in that line.
- def request_lines(self, start_line, end_line):
+ def request_lines(self, start_line: int, end_line: int):
self.requested_lines |= set(range(start_line, end_line + 1))
- def add_source_code(self, real_path):
+ def add_source_code(self, real_path: str):
self.real_path = real_path
with open(real_path, 'r') as f:
source_code = f.readlines()
@@ -465,17 +501,18 @@ class SourceFile(object):
class SourceFileSet(object):
""" Collection of source files. """
+
def __init__(self):
- self.path_to_source_files = {} # map from file path to SourceFile.
+ self.path_to_source_files: Dict[str, SourceFile] = {} # map from file path to SourceFile.
- def get_source_file(self, file_path):
+ def get_source_file(self, file_path: str) -> SourceFile:
source_file = self.path_to_source_files.get(file_path)
- if source_file is None:
+ if not source_file:
source_file = SourceFile(len(self.path_to_source_files), file_path)
self.path_to_source_files[file_path] = source_file
return source_file
- def load_source_code(self, source_dirs):
+ def load_source_code(self, source_dirs: List[str]):
file_searcher = SourceFileSearcher(source_dirs)
for source_file in self.path_to_source_files.values():
real_path = file_searcher.get_real_path(source_file.abstract_path)
@@ -483,10 +520,9 @@ class SourceFileSet(object):
source_file.add_source_code(real_path)
-
class RecordData(object):
- """RecordData reads perf.data, and generates data used by report.js in json format.
+ """RecordData reads perf.data, and generates data used by report_html.js in json format.
All generated items are listed as below:
1. recordTime: string
2. machineType: string
@@ -564,21 +600,26 @@ class RecordData(object):
}
"""
- def __init__(self, binary_cache_path, ndk_path, build_addr_hit_map):
+ def __init__(
+ self, binary_cache_path: Optional[str],
+ ndk_path: Optional[str],
+ build_addr_hit_map: bool, proguard_mapping_files: Optional[List[str]] = None):
self.binary_cache_path = binary_cache_path
self.ndk_path = ndk_path
self.build_addr_hit_map = build_addr_hit_map
- self.meta_info = None
- self.cmdline = None
- self.arch = None
- self.events = {}
+ self.proguard_mapping_files = proguard_mapping_files
+ self.meta_info: Optional[Dict[str, str]] = None
+ self.cmdline: Optional[str] = None
+ self.arch: Optional[str] = None
+ self.events: Dict[str, EventScope] = {}
self.libs = LibSet()
self.functions = FunctionSet()
self.total_samples = 0
self.source_files = SourceFileSet()
self.gen_addr_hit_map_in_record_info = False
+ self.binary_finder = BinaryFinder(binary_cache_path, ReadElf(ndk_path))
- def load_record_file(self, record_file, show_art_frames):
+ def load_record_file(self, record_file: str, show_art_frames: bool):
lib = ReportLib()
lib.SetRecordFile(record_file)
# If not showing ip for unknown symbols, the percent of the unknown symbol may be
@@ -588,6 +629,8 @@ class RecordData(object):
lib.ShowArtFrames()
if self.binary_cache_path:
lib.SetSymfs(self.binary_cache_path)
+ for file_path in self.proguard_mapping_files or []:
+ lib.AddProguardMappingFile(file_path)
self.meta_info = lib.MetaInfo()
self.cmdline = lib.GetRecordCmd()
self.arch = lib.GetArch()
@@ -610,11 +653,16 @@ class RecordData(object):
thread.sample_count += 1
lib_id = self.libs.get_lib_id(symbol.dso_name)
+ if lib_id is None:
+ lib_id = self.libs.add_lib(symbol.dso_name, lib.GetBuildIdForPath(symbol.dso_name))
func_id = self.functions.get_func_id(lib_id, symbol)
callstack = [(lib_id, func_id, symbol.vaddr_in_file)]
for i in range(callchain.nr):
symbol = callchain.entries[i].symbol
lib_id = self.libs.get_lib_id(symbol.dso_name)
+ if lib_id is None:
+ lib_id = self.libs.add_lib(
+ symbol.dso_name, lib.GetBuildIdForPath(symbol.dso_name))
func_id = self.functions.get_func_id(lib_id, symbol)
callstack.append((lib_id, func_id, symbol.vaddr_in_file))
if len(callstack) > MAX_CALLSTACK_LENGTH:
@@ -638,8 +686,8 @@ class RecordData(object):
for process in new_processes.values():
event.processes[process.pid] = process
- def limit_percents(self, min_func_percent, min_callchain_percent):
- hit_func_ids = set()
+ def limit_percents(self, min_func_percent: float, min_callchain_percent: float):
+ hit_func_ids: Set[int] = set()
for event in self.events.values():
min_limit = event.event_count * min_func_percent * 0.01
to_del_processes = []
@@ -658,43 +706,44 @@ class RecordData(object):
del event.processes[process]
self.functions.trim_functions(hit_func_ids)
- def _get_event(self, event_name):
+ def _get_event(self, event_name: str) -> EventScope:
if event_name not in self.events:
self.events[event_name] = EventScope(event_name)
return self.events[event_name]
- def add_source_code(self, source_dirs, filter_lib):
+ def add_source_code(self, source_dirs: List[str], filter_lib: Callable[[str], bool]):
""" Collect source code information:
1. Find line ranges for each function in FunctionSet.
2. Find line for each addr in FunctionScope.addr_hit_map.
3. Collect needed source code in SourceFileSet.
"""
- addr2line = Addr2Nearestline(self.ndk_path, self.binary_cache_path, False)
+ addr2line = Addr2Nearestline(self.ndk_path, self.binary_finder, False)
# Request line range for each function.
for function in self.functions.id_to_func.values():
if function.func_name == 'unknown':
continue
- lib_name = self.libs.get_lib_name(function.lib_id)
- if filter_lib(lib_name):
- addr2line.add_addr(lib_name, function.start_addr, function.start_addr)
- addr2line.add_addr(lib_name, function.start_addr,
+ lib_info = self.libs.get_lib(function.lib_id)
+ if filter_lib(lib_info.name):
+ addr2line.add_addr(lib_info.name, lib_info.build_id,
+ function.start_addr, function.start_addr)
+ addr2line.add_addr(lib_info.name, lib_info.build_id, function.start_addr,
function.start_addr + function.addr_len - 1)
# Request line for each addr in FunctionScope.addr_hit_map.
for event in self.events.values():
for lib in event.libraries:
- lib_name = self.libs.get_lib_name(lib.lib_id)
- if filter_lib(lib_name):
+ lib_info = self.libs.get_lib(lib.lib_id)
+ if filter_lib(lib_info.name):
for function in lib.functions.values():
func_addr = self.functions.id_to_func[function.func_id].start_addr
for addr in function.addr_hit_map:
- addr2line.add_addr(lib_name, func_addr, addr)
+ addr2line.add_addr(lib_info.name, lib_info.build_id, func_addr, addr)
addr2line.convert_addrs_to_lines()
# Set line range for each function.
for function in self.functions.id_to_func.values():
if function.func_name == 'unknown':
continue
- dso = addr2line.get_dso(self.libs.get_lib_name(function.lib_id))
+ dso = addr2line.get_dso(self.libs.get_lib(function.lib_id).name)
if not dso:
continue
start_source = addr2line.get_addr_source(dso, function.start_addr)
@@ -712,7 +761,7 @@ class RecordData(object):
# Build FunctionScope.line_hit_map.
for event in self.events.values():
for lib in event.libraries:
- dso = addr2line.get_dso(self.libs.get_lib_name(lib.lib_id))
+ dso = addr2line.get_dso(self.libs.get_lib(lib.lib_id).name)
if not dso:
continue
for function in lib.functions.values():
@@ -731,33 +780,38 @@ class RecordData(object):
# Collect needed source code in SourceFileSet.
self.source_files.load_source_code(source_dirs)
- def add_disassembly(self, filter_lib):
+ def add_disassembly(self, filter_lib: Callable[[str], bool], jobs: 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.
"""
- objdump = Objdump(self.ndk_path, self.binary_cache_path)
- cur_lib_name = None
- dso_info = None
- for function in sorted(self.functions.id_to_func.values(), key=lambda a: a.lib_id):
+ objdump = Objdump(self.ndk_path, self.binary_finder)
+ executor = ThreadPoolExecutor(jobs)
+ lib_functions: Dict[int, List[Function]] = collections.defaultdict(list)
+
+ for function in self.functions.id_to_func.values():
if function.func_name == 'unknown':
continue
- lib_name = self.libs.get_lib_name(function.lib_id)
- if lib_name != cur_lib_name:
- cur_lib_name = lib_name
- if filter_lib(lib_name):
- dso_info = objdump.get_dso_info(lib_name)
- else:
- dso_info = None
- if dso_info:
- log_info('Disassemble %s' % dso_info[0])
- if dso_info:
- code = objdump.disassemble_code(dso_info, function.start_addr, function.addr_len)
- function.disassembly = code
+ lib_functions[function.lib_id].append(function)
+ for lib_id, functions in lib_functions.items():
+ lib = self.libs.get_lib(lib_id)
+ if not filter_lib(lib.name):
+ continue
+ dso_info = objdump.get_dso_info(lib.name, lib.build_id)
+ if not dso_info:
+ continue
+ log_info('Disassemble %s' % dso_info[0])
+ for function in functions:
+ def task(function, dso_info):
+ function.disassembly = objdump.disassemble_code(
+ dso_info, function.start_addr, function.addr_len)
+ executor.submit(task, function, dso_info)
+ executor.shutdown(wait=True)
self.gen_addr_hit_map_in_record_info = True
- def gen_record_info(self):
+ def gen_record_info(self) -> Dict[str, Any]:
+ """ Return json data which will be used by report_html.js. """
record_info = {}
timestamp = self.meta_info.get('timestamp')
if timestamp:
@@ -783,26 +837,26 @@ class RecordData(object):
record_info['sourceFiles'] = self._gen_source_files()
return record_info
- def _gen_process_names(self):
- process_names = {}
+ def _gen_process_names(self) -> Dict[int, str]:
+ process_names: Dict[int, str] = {}
for event in self.events.values():
for process in event.processes.values():
process_names[process.pid] = process.name
return process_names
- def _gen_thread_names(self):
- thread_names = {}
+ def _gen_thread_names(self) -> Dict[int, str]:
+ thread_names: Dict[int, str] = {}
for event in self.events.values():
for process in event.processes.values():
for thread in process.threads.values():
thread_names[thread.tid] = thread.name
return thread_names
- def _gen_lib_list(self):
- return [modify_text_for_html(x) for x in self.libs.lib_id_to_name]
+ def _gen_lib_list(self) -> List[str]:
+ return [modify_text_for_html(lib.name) for lib in self.libs.libs]
- def _gen_function_map(self):
- func_map = {}
+ def _gen_function_map(self) -> Dict[int, Any]:
+ func_map: Dict[int, Any] = {}
for func_id in sorted(self.functions.id_to_func):
function = self.functions.id_to_func[func_id]
func_data = {}
@@ -813,16 +867,18 @@ class RecordData(object):
if function.disassembly:
disassembly_list = []
for code, addr in function.disassembly:
- disassembly_list.append([modify_text_for_html(code), addr])
+ disassembly_list.append(
+ [modify_text_for_html(code),
+ hex_address_for_json(addr)])
func_data['d'] = disassembly_list
func_map[func_id] = func_data
return func_map
- def _gen_sample_info(self):
+ def _gen_sample_info(self) -> List[Dict[str, Any]]:
return [event.get_sample_info(self.gen_addr_hit_map_in_record_info)
for event in self.events.values()]
- def _gen_source_files(self):
+ def _gen_source_files(self) -> List[Dict[str, Any]]:
source_files = sorted(self.source_files.path_to_source_files.values(),
key=lambda x: x.file_id)
file_list = []
@@ -840,6 +896,7 @@ class RecordData(object):
file_list.append(file_data)
return file_list
+
URLS = {
'jquery': 'https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js',
'bootstrap4-css': 'https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/css/bootstrap.min.css',
@@ -852,9 +909,10 @@ URLS = {
'gstatic-charts': 'https://www.gstatic.com/charts/loader.js',
}
+
class ReportGenerator(object):
- def __init__(self, html_path):
+ def __init__(self, html_path: Union[Path, str]):
self.hw = HtmlWriter(html_path)
self.hw.open_tag('html')
self.hw.open_tag('head')
@@ -874,12 +932,11 @@ class ReportGenerator(object):
""").close_tag()
self.hw.close_tag('head')
self.hw.open_tag('body')
- self.record_info = {}
def write_content_div(self):
self.hw.open_tag('div', id='report_content').close_tag()
- def write_record_data(self, record_data):
+ def write_record_data(self, record_data: Dict[str, Any]):
self.hw.open_tag('script', id='record_data', type='application/json')
self.hw.add(json.dumps(record_data))
self.hw.close_tag()
@@ -893,27 +950,29 @@ class ReportGenerator(object):
self.hw.close()
-def main():
- sys.setrecursionlimit(MAX_CALLSTACK_LENGTH * 2 + 50)
- parser = argparse.ArgumentParser(description='report profiling data')
+def get_args() -> argparse.Namespace:
+ parser = argparse.ArgumentParser(
+ description='report profiling data', formatter_class=ArgParseFormatter)
parser.add_argument('-i', '--record_file', nargs='+', default=['perf.data'], help="""
- Set profiling data file to report. Default is perf.data.""")
- parser.add_argument('-o', '--report_path', default='report.html', help="""
- Set output html file. Default is report.html.""")
+ Set profiling data file to report.""")
+ parser.add_argument('-o', '--report_path', default='report.html', help='Set output html file')
parser.add_argument('--min_func_percent', default=0.01, type=float, help="""
Set min percentage of functions shown in the report.
For example, when set to 0.01, only functions taking >= 0.01%% of total
- event count are collected in the report. Default is 0.01.""")
+ event count are collected in the report.""")
parser.add_argument('--min_callchain_percent', default=0.01, type=float, help="""
Set min percentage of callchains shown in the report.
It is used to limit nodes shown in the function flamegraph. For example,
when set to 0.01, only callchains taking >= 0.01%% of the event count of
- the starting function are collected in the report. Default is 0.01.""")
+ the starting function are collected in the report.""")
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('--binary_filter', nargs='+', help="""Annotate source code and disassembly
only for selected binaries.""")
+ parser.add_argument(
+ '-j', '--jobs', type=int, default=os.cpu_count(),
+ help='Use multithreading to speed up disassembly and source code annotation.')
parser.add_argument('--ndk_path', nargs=1, help='Find tools in the ndk path.')
parser.add_argument('--no_browser', action='store_true', help="Don't open report in browser.")
parser.add_argument('--show_art_frames', action='store_true',
@@ -921,7 +980,15 @@ def main():
parser.add_argument('--aggregate-by-thread-name', action='store_true', help="""aggregate
samples by thread name instead of thread id. This is useful for
showing multiple perf.data generated for the same app.""")
- args = parser.parse_args()
+ parser.add_argument(
+ '--proguard-mapping-file', nargs='+',
+ help='Add proguard mapping file to de-obfuscate symbols')
+ return parser.parse_args()
+
+
+def main():
+ sys.setrecursionlimit(MAX_CALLSTACK_LENGTH * 2 + 50)
+ args = get_args()
# 1. Process args.
binary_cache_path = 'binary_cache'
@@ -937,16 +1004,19 @@ def main():
log_exit('--source_dirs is needed to add source code.')
build_addr_hit_map = args.add_source_code or args.add_disassembly
ndk_path = None if not args.ndk_path else args.ndk_path[0]
+ if args.jobs < 1:
+ log_exit('Invalid --jobs option.')
# 2. Produce record data.
- record_data = RecordData(binary_cache_path, ndk_path, build_addr_hit_map)
+ record_data = RecordData(binary_cache_path, ndk_path,
+ build_addr_hit_map, args.proguard_mapping_file)
for record_file in args.record_file:
record_data.load_record_file(record_file, args.show_art_frames)
if args.aggregate_by_thread_name:
record_data.aggregate_by_thread_name()
record_data.limit_percents(args.min_func_percent, args.min_callchain_percent)
- def filter_lib(lib_name):
+ def filter_lib(lib_name: str) -> bool:
if not args.binary_filter:
return True
for binary in args.binary_filter:
@@ -956,7 +1026,7 @@ def main():
if args.add_source_code:
record_data.add_source_code(args.source_dirs, filter_lib)
if args.add_disassembly:
- record_data.add_disassembly(filter_lib)
+ record_data.add_disassembly(filter_lib, args.jobs)
# 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 31a280ff..d05f1f7c 100755
--- a/simpleperf/scripts/report_sample.py
+++ b/simpleperf/scripts/report_sample.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2016 The Android Open Source Project
#
diff --git a/simpleperf/scripts/run_simpleperf_on_device.py b/simpleperf/scripts/run_simpleperf_on_device.py
index 9732d6d7..4cd167c0 100755
--- a/simpleperf/scripts/run_simpleperf_on_device.py
+++ b/simpleperf/scripts/run_simpleperf_on_device.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2017 The Android Open Source Project
#
@@ -21,7 +21,8 @@
"""
import subprocess
import sys
-from utils import AdbHelper, disable_debug_log, get_target_binary_path
+from simpleperf_utils import AdbHelper, disable_debug_log, get_target_binary_path
+
def main():
disable_debug_log()
@@ -33,5 +34,6 @@ def main():
shell_cmd = 'cd /data/local/tmp && ./simpleperf ' + ' '.join(sys.argv[1:])
sys.exit(subprocess.call([adb.adb_path, 'shell', shell_cmd]))
+
if __name__ == '__main__':
main()
diff --git a/simpleperf/scripts/run_simpleperf_without_usb_connection.py b/simpleperf/scripts/run_simpleperf_without_usb_connection.py
index a3524f67..19700ee8 100755
--- a/simpleperf/scripts/run_simpleperf_without_usb_connection.py
+++ b/simpleperf/scripts/run_simpleperf_without_usb_connection.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2018 The Android Open Source Project
#
@@ -32,7 +32,8 @@ import subprocess
import sys
import time
-from utils import AdbHelper, get_target_binary_path, log_warning
+from simpleperf_utils import AdbHelper, get_target_binary_path, log_warning
+
def start_recording(args):
adb = AdbHelper()
@@ -59,6 +60,7 @@ def start_recording(args):
adb.run(['shell', 'cat', '/data/local/tmp/simpleperf_output'])
sys.exit(subproc.returncode)
+
def stop_recording(args):
adb = AdbHelper()
result = adb.run(['shell', 'pidof', 'simpleperf'])
@@ -73,6 +75,7 @@ def stop_recording(args):
adb.check_run(['pull', '/data/local/tmp/perf.data', args.perf_data_path])
print('The recording data has been collected in %s.' % args.perf_data_path)
+
def main():
parser = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
@@ -95,5 +98,6 @@ def main():
args = parser.parse_args()
args.func(args)
+
if __name__ == '__main__':
main()
diff --git a/simpleperf/scripts/script_testdata/java_api-debug_Q.apk b/simpleperf/scripts/script_testdata/java_api-debug_Q.apk
deleted file mode 100644
index 4b141cdf..00000000
--- a/simpleperf/scripts/script_testdata/java_api-debug_Q.apk
+++ /dev/null
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/java_api-debug_prev_Q.apk b/simpleperf/scripts/script_testdata/java_api-debug_prev_Q.apk
deleted file mode 100644
index 0323aa23..00000000
--- a/simpleperf/scripts/script_testdata/java_api-debug_prev_Q.apk
+++ /dev/null
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/java_api-profile_Q.apk b/simpleperf/scripts/script_testdata/java_api-profile_Q.apk
deleted file mode 100644
index bb5f651d..00000000
--- a/simpleperf/scripts/script_testdata/java_api-profile_Q.apk
+++ /dev/null
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/java_api-profile_prev_Q.apk b/simpleperf/scripts/script_testdata/java_api-profile_prev_Q.apk
deleted file mode 100644
index 7a7fcb85..00000000
--- a/simpleperf/scripts/script_testdata/java_api-profile_prev_Q.apk
+++ /dev/null
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/simpleperf_runtest_two_functions_arm64 b/simpleperf/scripts/script_testdata/simpleperf_runtest_two_functions_arm64
deleted file mode 100755
index 49d76130..00000000
--- a/simpleperf/scripts/script_testdata/simpleperf_runtest_two_functions_arm64
+++ /dev/null
Binary files differ
diff --git a/simpleperf/scripts/simpleperf_report_lib.py b/simpleperf/scripts/simpleperf_report_lib.py
index 3e677be5..a38f4876 100644
--- a/simpleperf/scripts/simpleperf_report_lib.py
+++ b/simpleperf/scripts/simpleperf_report_lib.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2016 The Android Open Source Project
#
@@ -22,28 +22,28 @@
import collections
import ctypes as ct
+from pathlib import Path
import struct
-from utils import bytes_to_str, get_host_binary_path, is_windows, str_to_bytes
+from typing import Any, Dict, List, Optional, Union
+from simpleperf_utils import bytes_to_str, get_host_binary_path, is_windows, str_to_bytes
-def _get_native_lib():
- return get_host_binary_path('libsimpleperf_report.so')
-
-def _is_null(p):
+def _is_null(p: Optional[ct._Pointer]) -> bool:
if p:
return False
return ct.cast(p, ct.c_void_p).value is None
-def _char_pt(s):
+def _char_pt(s: str) -> bytes:
return str_to_bytes(s)
-def _char_pt_to_str(char_pt):
+def _char_pt_to_str(char_pt: ct.c_char_p) -> str:
return bytes_to_str(char_pt)
-def _check(cond, failmsg):
+
+def _check(cond: bool, failmsg: str):
if not cond:
raise RuntimeError(failmsg)
@@ -72,7 +72,7 @@ class SampleStruct(ct.Structure):
('period', ct.c_uint64)]
@property
- def thread_comm(self):
+ def thread_comm(self) -> str:
return _char_pt_to_str(self._thread_comm)
@@ -83,31 +83,41 @@ class TracingFieldFormatStruct(ct.Structure):
elem_size: size of the element type.
elem_count: the number of elements in this field, more than one if the field is an array.
is_signed: whether the element type is signed or unsigned.
+ is_dynamic: whether the element is a dynamic string.
"""
_fields_ = [('_name', ct.c_char_p),
('offset', ct.c_uint32),
('elem_size', ct.c_uint32),
('elem_count', ct.c_uint32),
- ('is_signed', ct.c_uint32)]
+ ('is_signed', ct.c_uint32),
+ ('is_dynamic', ct.c_uint32)]
_unpack_key_dict = {1: 'b', 2: 'h', 4: 'i', 8: 'q'}
@property
- def name(self):
+ def name(self) -> str:
return _char_pt_to_str(self._name)
- def parse_value(self, data):
+ def parse_value(self, data: ct.c_char_p) -> Union[str, bytes, List[bytes]]:
""" Parse value of a field in a tracepoint event.
The return value depends on the type of the field, and can be an int value, a string,
an array of int values, etc. If the type can't be parsed, return a byte array or an
array of byte arrays.
"""
- if self.elem_count > 1 and self.elem_size == 1 and self.is_signed == 0:
- # The field is a string.
+ if self.is_dynamic:
+ offset, max_len = struct.unpack('<HH', data[self.offset:self.offset + 4])
+ length = 0
+ while length < max_len and bytes_to_str(data[offset + length]) != '\x00':
+ length += 1
+ return bytes_to_str(data[offset: offset + length])
+
+ if self.elem_count > 1 and self.elem_size == 1:
+ # Probably the field is a string.
+ # Don't use self.is_signed, which has different values on x86 and arm.
length = 0
while length < self.elem_count and bytes_to_str(data[self.offset + length]) != '\x00':
length += 1
- return bytes_to_str(data[self.offset : self.offset + length])
+ return bytes_to_str(data[self.offset: self.offset + length])
unpack_key = self._unpack_key_dict.get(self.elem_size)
if unpack_key:
if not self.is_signed:
@@ -119,7 +129,7 @@ class TracingFieldFormatStruct(ct.Structure):
value = []
offset = self.offset
for _ in range(self.elem_count):
- value.append(data[offset : offset + self.elem_size])
+ value.append(data[offset: offset + self.elem_size])
offset += self.elem_size
if self.elem_count == 1:
value = value[0]
@@ -147,7 +157,7 @@ class EventStruct(ct.Structure):
('tracing_data_format', TracingDataFormatStruct)]
@property
- def name(self):
+ def name(self) -> str:
return _char_pt_to_str(self._name)
@@ -179,11 +189,11 @@ class SymbolStruct(ct.Structure):
('mapping', ct.POINTER(MappingStruct))]
@property
- def dso_name(self):
+ def dso_name(self) -> str:
return _char_pt_to_str(self._dso_name)
@property
- def symbol_name(self):
+ def symbol_name(self) -> str:
return _char_pt_to_str(self._symbol_name)
@@ -225,9 +235,9 @@ class ReportLibStructure(ct.Structure):
# pylint: disable=invalid-name
class ReportLib(object):
- def __init__(self, native_lib_path=None):
+ def __init__(self, native_lib_path: Optional[str] = None):
if native_lib_path is None:
- native_lib_path = _get_native_lib()
+ native_lib_path = self._get_native_lib()
self._load_dependent_lib()
self._lib = ct.CDLL(native_lib_path)
@@ -241,6 +251,8 @@ class ReportLib(object):
self._ShowIpForUnknownSymbolFunc = self._lib.ShowIpForUnknownSymbol
self._ShowArtFramesFunc = self._lib.ShowArtFrames
self._MergeJavaMethodsFunc = self._lib.MergeJavaMethods
+ self._AddProguardMappingFileFunc = self._lib.AddProguardMappingFile
+ self._AddProguardMappingFileFunc.restype = ct.c_bool
self._GetNextSampleFunc = self._lib.GetNextSample
self._GetNextSampleFunc.restype = ct.POINTER(SampleStruct)
self._GetEventOfCurrentSampleFunc = self._lib.GetEventOfCurrentSample
@@ -258,9 +270,12 @@ class ReportLib(object):
self._instance = self._CreateReportLibFunc()
assert not _is_null(self._instance)
- self.meta_info = None
- self.current_sample = None
- self.record_cmd = None
+ self.meta_info: Optional[Dict[str, str]] = None
+ self.current_sample: Optional[SampleStruct] = None
+ self.record_cmd: Optional[str] = None
+
+ def _get_native_lib(self) -> str:
+ return get_host_binary_path('libsimpleperf_report.so')
def _load_dependent_lib(self):
# As the windows dll is built with mingw we need to load 'libwinpthread-1.dll'.
@@ -268,34 +283,33 @@ class ReportLib(object):
self._libwinpthread = ct.CDLL(get_host_binary_path('libwinpthread-1.dll'))
def Close(self):
- if self._instance is None:
- return
- self._DestroyReportLibFunc(self._instance)
- self._instance = None
+ if self._instance:
+ self._DestroyReportLibFunc(self._instance)
+ self._instance = None
- def SetLogSeverity(self, log_level='info'):
+ def SetLogSeverity(self, log_level: str = 'info'):
""" Set log severity of native lib, can be verbose,debug,info,error,fatal."""
- cond = self._SetLogSeverityFunc(self.getInstance(), _char_pt(log_level))
+ cond: bool = self._SetLogSeverityFunc(self.getInstance(), _char_pt(log_level))
_check(cond, 'Failed to set log level')
- def SetSymfs(self, symfs_dir):
+ def SetSymfs(self, symfs_dir: str):
""" Set directory used to find symbols."""
- cond = self._SetSymfsFunc(self.getInstance(), _char_pt(symfs_dir))
+ cond: bool = self._SetSymfsFunc(self.getInstance(), _char_pt(symfs_dir))
_check(cond, 'Failed to set symbols directory')
- def SetRecordFile(self, record_file):
+ def SetRecordFile(self, record_file: str):
""" Set the path of record file, like perf.data."""
- cond = self._SetRecordFileFunc(self.getInstance(), _char_pt(record_file))
+ cond: bool = self._SetRecordFileFunc(self.getInstance(), _char_pt(record_file))
_check(cond, 'Failed to set record file')
def ShowIpForUnknownSymbol(self):
self._ShowIpForUnknownSymbolFunc(self.getInstance())
- def ShowArtFrames(self, show=True):
+ def ShowArtFrames(self, show: bool = True):
""" Show frames of internal methods of the Java interpreter. """
self._ShowArtFramesFunc(self.getInstance(), show)
- def MergeJavaMethods(self, merge=True):
+ 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,
by mapping jitted methods to their corresponding dex files.
@@ -306,12 +320,18 @@ class ReportLib(object):
"""
self._MergeJavaMethodsFunc(self.getInstance(), merge)
- def SetKallsymsFile(self, kallsym_file):
+ def AddProguardMappingFile(self, mapping_file: Union[str, Path]):
+ """ Add proguard mapping.txt to de-obfuscate method names. """
+ if not self._AddProguardMappingFileFunc(self.getInstance(), _char_pt(str(mapping_file))):
+ raise ValueError(f'failed to add proguard mapping file: {mapping_file}')
+
+ def SetKallsymsFile(self, kallsym_file: str):
""" Set the file path to a copy of the /proc/kallsyms file (for off device decoding) """
- cond = self._SetKallsymsFileFunc(self.getInstance(), _char_pt(kallsym_file))
+ cond: bool = self._SetKallsymsFileFunc(self.getInstance(), _char_pt(kallsym_file))
_check(cond, 'Failed to set kallsyms file')
- def GetNextSample(self):
+ def GetNextSample(self) -> Optional[SampleStruct]:
+ """ Return the next sample. If no more samples, return None. """
psample = self._GetNextSampleFunc(self.getInstance())
if _is_null(psample):
self.current_sample = None
@@ -319,25 +339,25 @@ class ReportLib(object):
self.current_sample = psample[0]
return self.current_sample
- def GetCurrentSample(self):
+ def GetCurrentSample(self) -> Optional[SampleStruct]:
return self.current_sample
- def GetEventOfCurrentSample(self):
+ def GetEventOfCurrentSample(self) -> EventStruct:
event = self._GetEventOfCurrentSampleFunc(self.getInstance())
assert not _is_null(event)
return event[0]
- def GetSymbolOfCurrentSample(self):
+ def GetSymbolOfCurrentSample(self) -> SymbolStruct:
symbol = self._GetSymbolOfCurrentSampleFunc(self.getInstance())
assert not _is_null(symbol)
return symbol[0]
- def GetCallChainOfCurrentSample(self):
+ def GetCallChainOfCurrentSample(self) -> CallChainStructure:
callchain = self._GetCallChainOfCurrentSampleFunc(self.getInstance())
assert not _is_null(callchain)
return callchain[0]
- def GetTracingDataOfCurrentSample(self):
+ def GetTracingDataOfCurrentSample(self) -> Optional[Dict[str, Any]]:
data = self._GetTracingDataOfCurrentSampleFunc(self.getInstance())
if _is_null(data):
return None
@@ -348,12 +368,12 @@ class ReportLib(object):
result[field.name] = field.parse_value(data)
return result
- def GetBuildIdForPath(self, path):
+ def GetBuildIdForPath(self, path: str) -> str:
build_id = self._GetBuildIdForPathFunc(self.getInstance(), _char_pt(path))
assert not _is_null(build_id)
return _char_pt_to_str(build_id)
- def GetRecordCmd(self):
+ def GetRecordCmd(self) -> str:
if self.record_cmd is not None:
return self.record_cmd
self.record_cmd = ''
@@ -379,7 +399,7 @@ class ReportLib(object):
self.record_cmd = ' '.join(args)
return self.record_cmd
- def _GetFeatureString(self, feature_name):
+ def _GetFeatureString(self, feature_name: str) -> str:
feature_data = self._GetFeatureSection(self.getInstance(), _char_pt(feature_name))
result = ''
if not _is_null(feature_data):
@@ -394,10 +414,10 @@ class ReportLib(object):
result += c
return result
- def GetArch(self):
+ def GetArch(self) -> str:
return self._GetFeatureString('arch')
- def MetaInfo(self):
+ def MetaInfo(self) -> Dict[str, str]:
""" Return a string to string map stored in meta_info section in perf.data.
It is used to pass some short meta information.
"""
@@ -420,7 +440,7 @@ class ReportLib(object):
self.meta_info[str_list[i]] = str_list[i + 1]
return self.meta_info
- def getInstance(self):
+ def getInstance(self) -> ct._Pointer:
if self._instance is None:
raise Exception('Instance is Closed')
return self._instance
diff --git a/simpleperf/scripts/utils.py b/simpleperf/scripts/simpleperf_utils.py
index 85c00290..cf50fcb2 100644
--- a/simpleperf/scripts/utils.py
+++ b/simpleperf/scripts/simpleperf_utils.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2016 The Android Open Source Project
#
@@ -18,59 +18,69 @@
"""utils.py: export utility functions.
"""
-from __future__ import print_function
+from __future__ import annotations
import argparse
import logging
import os
import os.path
+from pathlib import Path
import re
import shutil
import subprocess
import sys
import time
+from typing import Dict, Iterator, List, Optional, Set, Union
-def get_script_dir():
+
+def get_script_dir() -> str:
return os.path.dirname(os.path.realpath(__file__))
-def is_windows():
+
+def is_windows() -> bool:
return sys.platform == 'win32' or sys.platform == 'cygwin'
-def is_darwin():
+
+def is_darwin() -> bool:
return sys.platform == 'darwin'
-def get_platform():
+
+def get_platform() -> str:
if is_windows():
return 'windows'
if is_darwin():
return 'darwin'
return 'linux'
-def is_python3():
+
+def is_python3() -> str:
return sys.version_info >= (3, 0)
-def log_debug(msg):
+def log_debug(msg: str):
logging.debug(msg)
-def log_info(msg):
+def log_info(msg: str):
logging.info(msg)
-def log_warning(msg):
+def log_warning(msg: str):
logging.warning(msg)
-def log_fatal(msg):
+def log_fatal(msg: str):
raise Exception(msg)
-def log_exit(msg):
+
+def log_exit(msg: str):
sys.exit(msg)
+
def disable_debug_log():
logging.getLogger().setLevel(logging.WARN)
-def set_log_level(level_name):
+
+def set_log_level(level_name: str):
if level_name == 'debug':
level = logging.DEBUG
elif level_name == 'info':
@@ -81,21 +91,24 @@ def set_log_level(level_name):
log_fatal('unknown log level: %s' % level_name)
logging.getLogger().setLevel(level)
-def str_to_bytes(str_value):
+
+def str_to_bytes(str_value: str) -> bytes:
if not is_python3():
return str_value
# In python 3, str are wide strings whereas the C api expects 8 bit strings,
# hence we have to convert. For now using utf-8 as the encoding.
return str_value.encode('utf-8')
-def bytes_to_str(bytes_value):
+
+def bytes_to_str(bytes_value: Optional[bytes]) -> str:
if not bytes_value:
return ''
if not is_python3():
return bytes_value
return bytes_value.decode('utf-8')
-def get_target_binary_path(arch, binary_name):
+
+def get_target_binary_path(arch: str, binary_name: str) -> str:
if arch == 'aarch64':
arch = 'arm64'
arch_dir = os.path.join(get_script_dir(), "bin", "android", arch)
@@ -107,7 +120,7 @@ def get_target_binary_path(arch, binary_name):
return binary_path
-def get_host_binary_path(binary_name):
+def get_host_binary_path(binary_name: str) -> str:
dirname = os.path.join(get_script_dir(), 'bin')
if is_windows():
if binary_name.endswith('.so'):
@@ -115,7 +128,7 @@ def get_host_binary_path(binary_name):
elif '.' not in binary_name:
binary_name += '.exe'
dirname = os.path.join(dirname, 'windows')
- elif sys.platform == 'darwin': # OSX
+ elif sys.platform == 'darwin': # OSX
if binary_name.endswith('.so'):
binary_name = binary_name[0:-3] + '.dylib'
dirname = os.path.join(dirname, 'darwin')
@@ -128,7 +141,7 @@ def get_host_binary_path(binary_name):
return binary_path
-def is_executable_available(executable, option='--help'):
+def is_executable_available(executable: str, option='--help') -> bool:
""" Run an executable to see if it exists. """
try:
subproc = subprocess.Popen([executable, option], stdout=subprocess.PIPE,
@@ -138,138 +151,209 @@ def is_executable_available(executable, option='--help'):
except OSError:
return False
-DEFAULT_NDK_PATH = {
- 'darwin': 'Library/Android/sdk/ndk-bundle',
- 'linux': 'Android/Sdk/ndk-bundle',
- 'windows': 'AppData/Local/Android/sdk/ndk-bundle',
-}
-
-EXPECTED_TOOLS = {
- 'adb': {
- 'is_binutils': False,
- 'test_option': 'version',
- 'path_in_ndk': lambda _: '../platform-tools/adb',
- },
- 'readelf': {
- 'is_binutils': True,
- 'accept_tool_without_arch': True,
- },
- 'llvm-symbolizer': {
- 'is_binutils': False,
- 'path_in_ndk':
- lambda platform: 'toolchains/llvm/prebuilt/%s-x86_64/bin/llvm-symbolizer' % platform,
- },
- 'objdump': {
- 'is_binutils': True,
- },
- 'strip': {
- 'is_binutils': True,
- },
-}
-
-def _get_binutils_path_in_ndk(toolname, arch, platform):
- if not arch:
- arch = 'arm64'
- if arch == 'arm64':
- name = 'aarch64-linux-android-' + toolname
- path = 'toolchains/llvm/prebuilt/%s-x86_64/bin/%s' % (platform, name)
- elif arch == 'arm':
- name = 'arm-linux-androideabi-' + toolname
- path = 'toolchains/llvm/prebuilt/%s-x86_64/bin/%s' % (platform, name)
- elif arch == 'x86_64':
- name = 'x86_64-linux-android-' + toolname
- path = 'toolchains/llvm/prebuilt/%s-x86_64/bin/%s' % (platform, name)
- elif arch == 'x86':
- name = 'i686-linux-android-' + toolname
- path = 'toolchains/llvm/prebuilt/%s-x86_64/bin/%s' % (platform, name)
- else:
- log_fatal('unexpected arch %s' % arch)
- return (name, path)
-def find_tool_path(toolname, ndk_path=None, arch=None):
- if toolname not in EXPECTED_TOOLS:
+class ToolFinder:
+ """ Find tools in ndk or sdk. """
+ DEFAULT_SDK_PATH = {
+ 'darwin': 'Library/Android/sdk',
+ 'linux': 'Android/Sdk',
+ 'windows': 'AppData/Local/Android/sdk',
+ }
+
+ EXPECTED_TOOLS = {
+ 'adb': {
+ 'is_binutils': False,
+ 'test_option': 'version',
+ 'path_in_sdk': 'platform-tools/adb',
+ },
+ 'llvm-objdump': {
+ 'is_binutils': False,
+ 'path_in_ndk':
+ lambda platform: 'toolchains/llvm/prebuilt/%s-x86_64/bin/llvm-objdump' % platform,
+ },
+ 'llvm-readelf': {
+ 'is_binutils': False,
+ 'path_in_ndk':
+ lambda platform: 'toolchains/llvm/prebuilt/%s-x86_64/bin/llvm-readelf' % platform,
+ },
+ 'llvm-symbolizer': {
+ 'is_binutils': False,
+ 'path_in_ndk':
+ lambda platform: 'toolchains/llvm/prebuilt/%s-x86_64/bin/llvm-symbolizer' % platform,
+ },
+ 'objdump': {
+ 'is_binutils': True,
+ },
+ 'strip': {
+ 'is_binutils': True,
+ },
+ }
+
+ @classmethod
+ def find_ndk_and_sdk_paths(cls, ndk_path: Optional[str] = None
+ ) -> Iterator[Tuple[Optional[str], Optional[str]]]:
+ # Use the given ndk path.
+ if ndk_path and os.path.isdir(ndk_path):
+ ndk_path = os.path.abspath(ndk_path)
+ yield ndk_path, cls.find_sdk_path(ndk_path)
+ # Find ndk in the parent directory containing simpleperf scripts.
+ ndk_path = os.path.dirname(os.path.abspath(get_script_dir()))
+ yield ndk_path, cls.find_sdk_path(ndk_path)
+ # Find ndk in the default sdk installation path.
+ if is_windows():
+ home = os.environ.get('HOMEDRIVE') + os.environ.get('HOMEPATH')
+ else:
+ home = os.environ.get('HOME')
+ if home:
+ platform = get_platform()
+ sdk_path = os.path.join(home, cls.DEFAULT_SDK_PATH[platform].replace('/', os.sep))
+ if os.path.isdir(sdk_path):
+ path = os.path.join(sdk_path, 'ndk')
+ if os.path.isdir(path):
+ # Android Studio can install multiple ndk versions in 'ndk'.
+ # Find the newest one.
+ ndk_version = None
+ for name in os.listdir(path):
+ if not ndk_version or ndk_version < name:
+ ndk_version = name
+ if ndk_version:
+ yield os.path.join(path, ndk_version), sdk_path
+ ndk_path = os.path.join(sdk_path, 'ndk-bundle')
+ if os.path.isdir(ndk_path):
+ yield ndk_path, sdk_path
+
+ @classmethod
+ def find_sdk_path(cls, ndk_path: str) -> Optional[str]:
+ path = ndk_path
+ for _ in range(2):
+ path = os.path.dirname(path)
+ if os.path.isdir(os.path.join(path, 'platform-tools')):
+ return path
return None
- tool_info = EXPECTED_TOOLS[toolname]
- is_binutils = tool_info['is_binutils']
- test_option = tool_info.get('test_option', '--help')
- platform = get_platform()
- if is_binutils:
- toolname_with_arch, path_in_ndk = _get_binutils_path_in_ndk(toolname, arch, platform)
- else:
- toolname_with_arch = toolname
- path_in_ndk = tool_info['path_in_ndk'](platform)
- path_in_ndk = path_in_ndk.replace('/', os.sep)
-
- # 1. Find tool in the given ndk path.
- if ndk_path:
- path = os.path.join(ndk_path, path_in_ndk)
- if is_executable_available(path, test_option):
- return path
- # 2. Find tool in the ndk directory containing simpleperf scripts.
- path = os.path.join('..', path_in_ndk)
- if is_executable_available(path, test_option):
- return path
-
- # 3. Find tool in the default ndk installation path.
- home = os.environ.get('HOMEPATH') if is_windows() else os.environ.get('HOME')
- if home:
- default_ndk_path = os.path.join(home, DEFAULT_NDK_PATH[platform].replace('/', os.sep))
- path = os.path.join(default_ndk_path, path_in_ndk)
- if is_executable_available(path, test_option):
- return path
+ @classmethod
+ def _get_binutils_path_in_ndk(cls, toolname: str, arch: Optional[str], platform: str
+ ) -> Tuple[str, str]:
+ if not arch:
+ arch = 'arm64'
+ if arch == 'arm64':
+ name = 'aarch64-linux-android-' + toolname
+ elif arch == 'arm':
+ name = 'arm-linux-androideabi-' + toolname
+ elif arch == 'x86_64':
+ name = 'x86_64-linux-android-' + toolname
+ elif arch == 'x86':
+ name = 'i686-linux-android-' + toolname
+ else:
+ log_fatal('unexpected arch %s' % arch)
+ path = 'toolchains/llvm/prebuilt/%s-x86_64/bin/%s' % (platform, name)
+ return (name, path)
- # 4. Find tool in $PATH.
- if is_executable_available(toolname_with_arch, test_option):
- return toolname_with_arch
+ @classmethod
+ def find_tool_path(cls, toolname: str, ndk_path: Optional[str] = None,
+ arch: Optional[str] = None) -> Optional[str]:
+ tool_info = cls.EXPECTED_TOOLS.get(toolname)
+ if not tool_info:
+ return None
- # 5. Find tool without arch in $PATH.
- if is_binutils and tool_info.get('accept_tool_without_arch'):
- if is_executable_available(toolname, test_option):
- return toolname
- return None
+ is_binutils = tool_info['is_binutils']
+ test_option = tool_info.get('test_option', '--help')
+ platform = get_platform()
+
+ # Find tool in clang prebuilts in Android platform.
+ if toolname.startswith('llvm-') and platform == 'linux' and get_script_dir().endswith(
+ 'system/extras/simpleperf/scripts'):
+ path = str(
+ Path(get_script_dir()).parents[3] / 'prebuilts' / 'clang' / 'host' / 'linux-x86' /
+ 'llvm-binutils-stable' / toolname)
+ if is_executable_available(path, test_option):
+ return path
+
+ # Find tool in NDK or SDK.
+ path_in_ndk = None
+ path_in_sdk = None
+ if is_binutils:
+ toolname_with_arch, path_in_ndk = cls._get_binutils_path_in_ndk(
+ toolname, arch, platform)
+ else:
+ toolname_with_arch = toolname
+ if 'path_in_ndk' in tool_info:
+ path_in_ndk = tool_info['path_in_ndk'](platform)
+ elif 'path_in_sdk' in tool_info:
+ path_in_sdk = tool_info['path_in_sdk']
+ if path_in_ndk:
+ path_in_ndk = path_in_ndk.replace('/', os.sep)
+ elif path_in_sdk:
+ path_in_sdk = path_in_sdk.replace('/', os.sep)
+
+ for ndk_dir, sdk_dir in cls.find_ndk_and_sdk_paths(ndk_path):
+ if path_in_ndk and ndk_dir:
+ path = os.path.join(ndk_dir, path_in_ndk)
+ if is_executable_available(path, test_option):
+ return path
+ elif path_in_sdk and sdk_dir:
+ path = os.path.join(sdk_dir, path_in_sdk)
+ if is_executable_available(path, test_option):
+ return path
+
+ # Find tool in $PATH.
+ if is_executable_available(toolname_with_arch, test_option):
+ return toolname_with_arch
+
+ # Find tool without arch in $PATH.
+ if is_binutils and tool_info.get('accept_tool_without_arch'):
+ if is_executable_available(toolname, test_option):
+ return toolname
+ return None
class AdbHelper(object):
- def __init__(self, enable_switch_to_root=True):
- adb_path = find_tool_path('adb')
+ def __init__(self, enable_switch_to_root: bool = True):
+ adb_path = ToolFinder.find_tool_path('adb')
if not adb_path:
log_exit("Can't find adb in PATH environment.")
- self.adb_path = adb_path
+ self.adb_path: str = adb_path
self.enable_switch_to_root = enable_switch_to_root
+ self.serial_number: Optional[str] = None
+ def is_device_available(self) -> bool:
+ return self.run_and_return_output(['shell', 'whoami'])[0]
- def run(self, adb_args):
- return self.run_and_return_output(adb_args)[0]
-
+ def run(self, adb_args: List[str], log_output: bool = False, log_stderr: bool = False) -> bool:
+ return self.run_and_return_output(adb_args, log_output, log_stderr)[0]
- def run_and_return_output(self, adb_args, log_output=True, log_stderr=True):
+ def run_and_return_output(self, adb_args: List[str], log_output: bool = False,
+ log_stderr: bool = False) -> Tuple[bool, str]:
adb_args = [self.adb_path] + adb_args
log_debug('run adb cmd: %s' % adb_args)
- subproc = subprocess.Popen(adb_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ env = None
+ if self.serial_number:
+ env = os.environ.copy()
+ env['ANDROID_SERIAL'] = self.serial_number
+ subproc = subprocess.Popen(
+ adb_args, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout_data, stderr_data = subproc.communicate()
stdout_data = bytes_to_str(stdout_data)
stderr_data = bytes_to_str(stderr_data)
returncode = subproc.returncode
result = (returncode == 0)
- if log_output and stdout_data and adb_args[1] != 'push' and adb_args[1] != 'pull':
+ if log_output and stdout_data:
log_debug(stdout_data)
if log_stderr and stderr_data:
log_warning(stderr_data)
log_debug('run adb cmd: %s [result %s]' % (adb_args, result))
return (result, stdout_data)
- def check_run(self, adb_args):
- self.check_run_and_return_output(adb_args)
-
+ def check_run(self, adb_args: List[str], log_output: bool = False):
+ self.check_run_and_return_output(adb_args, log_output)
- def check_run_and_return_output(self, adb_args, stdout_file=None, log_output=True):
- result, stdoutdata = self.run_and_return_output(adb_args, stdout_file, log_output)
+ def check_run_and_return_output(self, adb_args: List[str], log_output: bool = False,
+ log_stderr: bool = False) -> str:
+ result, stdoutdata = self.run_and_return_output(adb_args, log_output, True)
if not result:
- log_exit('run "adb %s" failed' % adb_args)
+ log_exit('run "adb %s" failed: %s' % (adb_args, stdoutdata))
return stdoutdata
-
def _unroot(self):
result, stdoutdata = self.run_and_return_output(['shell', 'whoami'])
if not result:
@@ -281,8 +365,7 @@ class AdbHelper(object):
self.run(['wait-for-device'])
time.sleep(1)
-
- def switch_to_root(self):
+ def switch_to_root(self) -> bool:
if not self.enable_switch_to_root:
self._unroot()
return False
@@ -300,15 +383,14 @@ class AdbHelper(object):
result, stdoutdata = self.run_and_return_output(['shell', 'whoami'])
return result and 'root' in stdoutdata
- def get_property(self, name):
+ def get_property(self, name: str) -> Optional[str]:
result, stdoutdata = self.run_and_return_output(['shell', 'getprop', name])
return stdoutdata if result else None
- def set_property(self, name, value):
+ def set_property(self, name: str, value: str) -> bool:
return self.run(['shell', 'setprop', name, value])
-
- def get_device_arch(self):
+ def get_device_arch(self) -> str:
output = self.check_run_and_return_output(['shell', 'uname', '-m'])
if 'aarch64' in output:
return 'arm64'
@@ -321,8 +403,7 @@ class AdbHelper(object):
log_fatal('unsupported architecture: %s' % output.strip())
return ''
-
- def get_android_version(self):
+ def get_android_version(self) -> int:
""" Get Android version on device, like 7 is for Android N, 8 is for Android O."""
build_version = self.get_property('ro.build.version.release')
android_version = 0
@@ -338,7 +419,7 @@ class AdbHelper(object):
return android_version
-def flatten_arg_list(arg_list):
+def flatten_arg_list(arg_list: List[List[str]]) -> List[str]:
res = []
if arg_list:
for items in arg_list:
@@ -346,14 +427,14 @@ def flatten_arg_list(arg_list):
return res
-def remove(dir_or_file):
+def remove(dir_or_file: Union[Path, str]):
if os.path.isfile(dir_or_file):
os.remove(dir_or_file)
elif os.path.isdir(dir_or_file):
shutil.rmtree(dir_or_file, ignore_errors=True)
-def open_report_in_browser(report_path):
+def open_report_in_browser(report_path: str):
if is_darwin():
# On darwin 10.12.6, webbrowser can't open browser, so try `open` cmd first.
try:
@@ -370,21 +451,54 @@ def open_report_in_browser(report_path):
# webbrowser.get() doesn't work well on darwin/windows.
webbrowser.open_new_tab(report_path)
-def is_elf_file(path):
- if os.path.isfile(path):
- with open(path, 'rb') as fh:
- return fh.read(4) == b'\x7fELF'
- return False
-def find_real_dso_path(dso_path_in_record_file, binary_cache_path):
- """ Given the path of a shared library in perf.data, find its real path in the file system. """
- if binary_cache_path:
- tmp_path = os.path.join(binary_cache_path, dso_path_in_record_file[1:])
- if is_elf_file(tmp_path):
- return tmp_path
- if is_elf_file(dso_path_in_record_file):
- return dso_path_in_record_file
- return None
+class BinaryFinder:
+ def __init__(self, binary_cache_dir: Optional[Union[Path, str]], readelf: ReadElf):
+ if isinstance(binary_cache_dir, str):
+ binary_cache_dir = Path(binary_cache_dir)
+ self.binary_cache_dir = binary_cache_dir
+ self.readelf = readelf
+ self.build_id_map = self._load_build_id_map()
+
+ def _load_build_id_map(self) -> Dict[str, Path]:
+ build_id_map: Dict[str, Path] = {}
+ if self.binary_cache_dir:
+ build_id_list_file = self.binary_cache_dir / 'build_id_list'
+ if build_id_list_file.is_file():
+ with open(self.binary_cache_dir / 'build_id_list', 'rb') as fh:
+ for line in fh.readlines():
+ # lines are in format "<build_id>=<path_in_binary_cache>".
+ items = bytes_to_str(line).strip().split('=')
+ if len(items) == 2:
+ build_id_map[items[0]] = self.binary_cache_dir / items[1]
+ return build_id_map
+
+ def find_binary(self, dso_path_in_record_file: str,
+ expected_build_id: Optional[str]) -> Optional[Path]:
+ """ If expected_build_id is None, don't check build id.
+ Otherwise, the build id of the found binary should match the expected one."""
+ # Find binary from build id map.
+ if expected_build_id:
+ path = self.build_id_map.get(expected_build_id)
+ if path and self._check_path(path, expected_build_id):
+ return path
+ # Find binary by path in binary cache.
+ if self.binary_cache_dir:
+ path = self.binary_cache_dir / dso_path_in_record_file[1:]
+ if self._check_path(path, expected_build_id):
+ return path
+ # Find binary by its absolute path.
+ path = Path(dso_path_in_record_file)
+ if self._check_path(path, expected_build_id):
+ return path
+ return None
+
+ def _check_path(self, path: Path, expected_build_id: Optional[str]) -> bool:
+ if not self.readelf.is_elf_file(path):
+ return False
+ if expected_build_id is not None:
+ return self.readelf.get_build_id(path) == expected_build_id
+ return True
class Addr2Nearestline(object):
@@ -418,8 +532,10 @@ class Addr2Nearestline(object):
""" Info of a dynamic shared library.
addrs: a map from address to Addr object in this dso.
"""
- def __init__(self):
- self.addrs = {}
+
+ def __init__(self, build_id: Optional[str]):
+ self.build_id = build_id
+ self.addrs: Dict[int, Addr2Nearestline.Addr] = {}
class Addr(object):
""" Info of an addr request.
@@ -427,38 +543,41 @@ class Addr2Nearestline(object):
source_lines: a list of [file_id, line_number] for addr.
source_lines[:-1] are all for inlined functions.
"""
- def __init__(self, func_addr):
+
+ def __init__(self, func_addr: int):
self.func_addr = func_addr
- self.source_lines = None
+ self.source_lines: Optional[List[int, int]] = None
- def __init__(self, ndk_path, binary_cache_path, with_function_name):
- self.symbolizer_path = find_tool_path('llvm-symbolizer', ndk_path)
+ def __init__(
+ self, ndk_path: Optional[str],
+ binary_finder: BinaryFinder, with_function_name: bool):
+ self.symbolizer_path = ToolFinder.find_tool_path('llvm-symbolizer', ndk_path)
if not self.symbolizer_path:
log_exit("Can't find llvm-symbolizer. Please set ndk path with --ndk_path option.")
self.readelf = ReadElf(ndk_path)
- self.dso_map = {} # map from dso_path to Dso.
- self.binary_cache_path = binary_cache_path
+ self.dso_map: Dict[str, Addr2Nearestline.Dso] = {} # map from dso_path to Dso.
+ self.binary_finder = binary_finder
self.with_function_name = with_function_name
# Saving file names for each addr takes a lot of memory. So we store file ids in Addr,
# and provide data structures connecting file id and file name here.
- self.file_name_to_id = {}
- self.file_id_to_name = []
- self.func_name_to_id = {}
- self.func_id_to_name = []
+ self.file_name_to_id: Dict[str, int] = {}
+ self.file_id_to_name: List[str] = []
+ self.func_name_to_id: Dict[str, int] = {}
+ self.func_id_to_name: List[str] = []
- def add_addr(self, dso_path, func_addr, addr):
+ def add_addr(self, dso_path: str, build_id: Optional[str], func_addr: int, addr: int):
dso = self.dso_map.get(dso_path)
if dso is None:
- dso = self.dso_map[dso_path] = self.Dso()
+ dso = self.dso_map[dso_path] = self.Dso(build_id)
if addr not in dso.addrs:
dso.addrs[addr] = self.Addr(func_addr)
def convert_addrs_to_lines(self):
- for dso_path in self.dso_map:
- self._convert_addrs_in_one_dso(dso_path, self.dso_map[dso_path])
+ for dso_path, dso in self.dso_map.items():
+ self._convert_addrs_in_one_dso(dso_path, dso)
- def _convert_addrs_in_one_dso(self, dso_path, dso):
- real_path = find_real_dso_path(dso_path, self.binary_cache_path)
+ def _convert_addrs_in_one_dso(self, dso_path: str, dso: Addr2Nearestline.Dso):
+ real_path = self.binary_finder.find_binary(dso_path, dso.build_id)
if not real_path:
if dso_path not in ['//anon', 'unknown', '[kernel.kallsyms]']:
log_debug("Can't find dso %s" % dso_path)
@@ -474,10 +593,10 @@ class Addr2Nearestline(object):
self._collect_line_info(dso, real_path,
range(-addr_step * 5, -addr_step * 128 - 1, -addr_step))
- def _check_debug_line_section(self, real_path):
+ def _check_debug_line_section(self, real_path: Path) -> bool:
return '.debug_line' in self.readelf.get_sections(real_path)
- def _get_addr_step(self, real_path):
+ def _get_addr_step(self, real_path: Path) -> int:
arch = self.readelf.get_arch(real_path)
if arch == 'arm64':
return 4
@@ -485,10 +604,11 @@ class Addr2Nearestline(object):
return 2
return 1
- def _collect_line_info(self, dso, real_path, addr_shifts):
+ def _collect_line_info(
+ self, dso: Addr2Nearestline.Dso, real_path: Path, addr_shifts: List[int]):
""" Use addr2line to get line info in a dso, with given addr shifts. """
# 1. Collect addrs to send to addr2line.
- addr_set = set()
+ addr_set: Set[int] = set()
for addr in dso.addrs:
addr_obj = dso.addrs[addr]
if addr_obj.source_lines: # already has source line, no need to search.
@@ -511,10 +631,10 @@ class Addr2Nearestline(object):
stdoutdata = bytes_to_str(stdoutdata)
except OSError:
return
- addr_map = {}
- cur_line_list = None
+ addr_map: Dict[int, List[Tuple[int]]] = {}
+ cur_line_list: Optional[List[Tuple[int]]] = None
need_function_name = self.with_function_name
- cur_function_name = None
+ cur_function_name: Optional[str] = None
for line in stdoutdata.strip().split('\n'):
line = line.strip()
if not line:
@@ -558,15 +678,15 @@ class Addr2Nearestline(object):
if shifted_addr == addr_obj.func_addr:
break
- def _build_symbolizer_args(self, binary_path):
- args = [self.symbolizer_path, '-print-address', '-inlining', '-obj=%s' % binary_path]
+ def _build_symbolizer_args(self, binary_path: Path) -> List[str]:
+ args = [self.symbolizer_path, '--print-address', '--inlining', '--obj=%s' % binary_path]
if self.with_function_name:
- args += ['-functions=linkage', '-demangle']
+ args += ['--functions=linkage', '--demangle']
else:
- args.append('-functions=none')
+ args.append('--functions=none')
return args
- def _parse_source_location(self, line):
+ def _parse_source_location(self, line: str) -> Tuple[Optional[str], Optional[int]]:
file_path, line_number = None, None
# Handle lines in format filename:line:column, like "runtest/two_functions.cpp:14:25".
# Filename may contain ':' like "C:\Users\...\file".
@@ -581,24 +701,24 @@ class Addr2Nearestline(object):
return None, None
return file_path, line_number
- def _get_file_id(self, file_path):
+ def _get_file_id(self, file_path: str) -> int:
file_id = self.file_name_to_id.get(file_path)
if file_id is None:
file_id = self.file_name_to_id[file_path] = len(self.file_id_to_name)
self.file_id_to_name.append(file_path)
return file_id
- def _get_func_id(self, func_name):
+ def _get_func_id(self, func_name: str) -> int:
func_id = self.func_name_to_id.get(func_name)
if func_id is None:
func_id = self.func_name_to_id[func_name] = len(self.func_id_to_name)
self.func_id_to_name.append(func_name)
return func_id
- def get_dso(self, dso_path):
+ def get_dso(self, dso_path: str) -> Addr2Nearestline.Dso:
return self.dso_map.get(dso_path)
- def get_addr_source(self, dso, addr):
+ def get_addr_source(self, dso: Addr2Nearestline.Dso, addr: int) -> Optional[List[Tuple[int]]]:
source = dso.addrs[addr].source_lines
if source is None:
return None
@@ -630,16 +750,16 @@ class SourceFileSearcher(object):
'.java', '.kt'}
@classmethod
- def is_source_filename(cls, filename):
+ def is_source_filename(cls, filename: str) -> bool:
ext = os.path.splitext(filename)[1]
return ext in cls.SOURCE_FILE_EXTS
- def __init__(self, source_dirs):
+ def __init__(self, source_dirs: List[str]):
# Map from filename to a list of reversed directory path containing filename.
- self.filename_to_rparents = {}
+ self.filename_to_rparents: Dict[str, List[str]] = {}
self._collect_paths(source_dirs)
- def _collect_paths(self, source_dirs):
+ def _collect_paths(self, source_dirs: List[str]):
for source_dir in source_dirs:
for parent, _, file_names in os.walk(source_dir):
rparent = None
@@ -652,7 +772,7 @@ class SourceFileSearcher(object):
rparent = parent[::-1]
rparents.append(rparent)
- def get_real_path(self, abstract_path):
+ def get_real_path(self, abstract_path: str) -> Optional[str]:
abstract_path = abstract_path.replace('/', os.sep)
abstract_parent, file_name = os.path.split(abstract_path)
abstract_rparent = abstract_parent[::-1]
@@ -673,31 +793,38 @@ class SourceFileSearcher(object):
class Objdump(object):
""" A wrapper of objdump to disassemble code. """
- def __init__(self, ndk_path, binary_cache_path):
+
+ def __init__(self, ndk_path: Optional[str], binary_finder: BinaryFinder):
self.ndk_path = ndk_path
- self.binary_cache_path = binary_cache_path
+ self.binary_finder = binary_finder
self.readelf = ReadElf(ndk_path)
- self.objdump_paths = {}
+ self.objdump_paths: Dict[str, str] = {}
- def get_dso_info(self, dso_path):
- real_path = find_real_dso_path(dso_path, self.binary_cache_path)
+ def get_dso_info(self, dso_path: str, expected_build_id: Optional[str]
+ ) -> Optional[Tuple[str, str]]:
+ real_path = self.binary_finder.find_binary(dso_path, expected_build_id)
if not real_path:
return None
arch = self.readelf.get_arch(real_path)
if arch == 'unknown':
return None
- return (real_path, arch)
+ return (str(real_path), arch)
- def disassemble_code(self, dso_info, start_addr, addr_len):
+ 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).
"""
real_path, arch = dso_info
objdump_path = self.objdump_paths.get(arch)
if not objdump_path:
- objdump_path = find_tool_path('objdump', self.ndk_path, arch)
+ if arch == 'arm':
+ # llvm-objdump for arm is not good at showing branch targets.
+ # So still prefer objdump.
+ objdump_path = ToolFinder.find_tool_path('objdump', self.ndk_path, arch)
if not objdump_path:
- log_exit("Can't find objdump. Please set ndk path with --ndk_path option.")
+ objdump_path = ToolFinder.find_tool_path('llvm-objdump', self.ndk_path, arch)
+ if not objdump_path:
+ log_exit("Can't find llvm-objdump. Please set ndk path with --ndk_path option.")
self.objdump_paths[arch] = objdump_path
# 3. Run objdump.
@@ -705,6 +832,8 @@ class Objdump(object):
'--start-address=0x%x' % start_addr,
'--stop-address=0x%x' % (start_addr + addr_len),
real_path]
+ if arch == 'arm' and 'llvm-objdump' in objdump_path:
+ args += ['--print-imm-hex']
try:
subproc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
(stdoutdata, _) = subproc.communicate()
@@ -728,16 +857,24 @@ class Objdump(object):
class ReadElf(object):
""" A wrapper of readelf. """
- def __init__(self, ndk_path):
- self.readelf_path = find_tool_path('readelf', ndk_path)
+
+ def __init__(self, ndk_path: Optional[str]):
+ self.readelf_path = ToolFinder.find_tool_path('llvm-readelf', ndk_path)
if not self.readelf_path:
- log_exit("Can't find readelf. Please set ndk path with --ndk_path option.")
+ log_exit("Can't find llvm-readelf. Please set ndk path with --ndk_path option.")
+
+ @staticmethod
+ def is_elf_file(path: Union[Path, str]) -> bool:
+ if os.path.isfile(path):
+ with open(path, 'rb') as fh:
+ return fh.read(4) == b'\x7fELF'
+ return False
- def get_arch(self, elf_file_path):
+ def get_arch(self, elf_file_path: Union[Path, str]) -> str:
""" Get arch of an elf file. """
- if is_elf_file(elf_file_path):
+ if self.is_elf_file(elf_file_path):
try:
- output = subprocess.check_output([self.readelf_path, '-h', elf_file_path])
+ output = subprocess.check_output([self.readelf_path, '-h', str(elf_file_path)])
output = bytes_to_str(output)
if output.find('AArch64') != -1:
return 'arm64'
@@ -751,11 +888,11 @@ class ReadElf(object):
pass
return 'unknown'
- def get_build_id(self, elf_file_path, with_padding=True):
+ def get_build_id(self, elf_file_path: Union[Path, str], with_padding=True) -> str:
""" Get build id of an elf file. """
- if is_elf_file(elf_file_path):
+ if self.is_elf_file(elf_file_path):
try:
- output = subprocess.check_output([self.readelf_path, '-n', elf_file_path])
+ output = subprocess.check_output([self.readelf_path, '-n', str(elf_file_path)])
output = bytes_to_str(output)
result = re.search(r'Build ID:\s*(\S+)', output)
if result:
@@ -768,7 +905,7 @@ class ReadElf(object):
return ""
@staticmethod
- def pad_build_id(build_id):
+ def pad_build_id(build_id: str) -> str:
""" Pad build id to 40 hex numbers (20 bytes). """
if len(build_id) < 40:
build_id += '0' * (40 - len(build_id))
@@ -776,12 +913,12 @@ class ReadElf(object):
build_id = build_id[:40]
return '0x' + build_id
- def get_sections(self, elf_file_path):
+ def get_sections(self, elf_file_path: Union[Path, str]) -> List[str]:
""" Get sections of an elf file. """
- section_names = []
- if is_elf_file(elf_file_path):
+ section_names: List[str] = []
+ if self.is_elf_file(elf_file_path):
try:
- output = subprocess.check_output([self.readelf_path, '-SW', elf_file_path])
+ output = subprocess.check_output([self.readelf_path, '-SW', str(elf_file_path)])
output = bytes_to_str(output)
for line in output.split('\n'):
# Parse line like:" [ 1] .note.android.ident NOTE 0000000000400190 ...".
@@ -794,7 +931,8 @@ class ReadElf(object):
pass
return section_names
-def extant_dir(arg):
+
+def extant_dir(arg: str) -> str:
"""ArgumentParser type that only accepts extant directories.
Args:
@@ -808,7 +946,8 @@ def extant_dir(arg):
raise argparse.ArgumentTypeError('{} is not a directory.'.format(path))
return path
-def extant_file(arg):
+
+def extant_file(arg: str) -> str:
"""ArgumentParser type that only accepts extant files.
Args:
@@ -822,4 +961,10 @@ def extant_file(arg):
raise argparse.ArgumentTypeError('{} is not a file.'.format(path))
return path
+
+class ArgParseFormatter(
+ argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter):
+ pass
+
+
logging.getLogger().setLevel(logging.DEBUG)
diff --git a/simpleperf/scripts/test.py b/simpleperf/scripts/test.py
deleted file mode 100755
index a42497a0..00000000
--- a/simpleperf/scripts/test.py
+++ /dev/null
@@ -1,1787 +0,0 @@
-#!/usr/bin/env python
-#
-# 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.
-# 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.
-#
-"""test.py: Tests for simpleperf python scripts.
-
-These are smoke tests Using examples to run python scripts.
-For each example, we go through the steps of running each python script.
-Examples are collected from simpleperf/demo, which includes:
- SimpleperfExamplePureJava
- SimpleperfExampleWithNative
- SimpleperfExampleOfKotlin
-
-Tested python scripts include:
- app_profiler.py
- report.py
- annotate.py
- report_sample.py
- pprof_proto_generator.py
- report_html.py
-
-Test using both `adb root` and `adb unroot`.
-
-"""
-from __future__ import print_function
-import argparse
-import collections
-import filecmp
-import fnmatch
-import inspect
-import json
-import logging
-import os
-import re
-import shutil
-import signal
-import subprocess
-import sys
-import time
-import types
-import unittest
-
-from app_profiler import NativeLibDownloader
-from binary_cache_builder import BinaryCacheBuilder
-from simpleperf_report_lib import ReportLib
-from utils import log_exit, log_info, log_fatal
-from utils import AdbHelper, Addr2Nearestline, bytes_to_str, find_tool_path, get_script_dir
-from utils import is_elf_file, is_python3, is_windows, Objdump, ReadElf, remove, SourceFileSearcher
-from utils import str_to_bytes
-
-try:
- # pylint: disable=unused-import
- import google.protobuf
- # pylint: disable=ungrouped-imports
- from pprof_proto_generator import load_pprof_profile
- HAS_GOOGLE_PROTOBUF = True
-except ImportError:
- HAS_GOOGLE_PROTOBUF = False
-
-INFERNO_SCRIPT = os.path.join(get_script_dir(), "inferno.bat" if is_windows() else "./inferno.sh")
-
-
-class TestLogger(object):
- """ Write test progress in sys.stderr and keep verbose log in log file. """
- def __init__(self):
- self.log_file = self.get_log_file(3 if is_python3() else 2)
- if os.path.isfile(self.log_file):
- remove(self.log_file)
- # Logs can come from multiple processes. So use append mode to avoid overwrite.
- self.log_fh = open(self.log_file, 'a')
- logging.basicConfig(filename=self.log_file)
-
- @staticmethod
- def get_log_file(python_version):
- return 'test_python_%d.log' % python_version
-
- def writeln(self, s):
- return self.write(s + '\n')
-
- def write(self, s):
- sys.stderr.write(s)
- self.log_fh.write(s)
- # Child processes can also write to log file, so flush it immediately to keep the order.
- self.flush()
-
- def flush(self):
- self.log_fh.flush()
-
-
-TEST_LOGGER = TestLogger()
-
-
-class TestHelper(object):
- """ Keep global test info. """
-
- def __init__(self):
- self.python_version = 3 if is_python3() else 2
- self.repeat_count = 0
- self.script_dir = os.path.abspath(get_script_dir())
- self.cur_dir = os.getcwd()
- self.testdata_dir = os.path.join(self.cur_dir, 'testdata')
- self.test_base_dir = self.get_test_base_dir(self.python_version)
- self.adb = AdbHelper(enable_switch_to_root=True)
- self.android_version = self.adb.get_android_version()
- self.device_features = None
- self.browser_option = []
-
- def get_test_base_dir(self, python_version):
- """ Return the dir of generated data for a python version. """
- return os.path.join(self.cur_dir, 'test_python_%d' % python_version)
-
- def testdata_path(self, testdata_name):
- """ Return the path of a test data. """
- return os.path.join(self.testdata_dir, testdata_name.replace('/', os.sep))
-
- def test_dir(self, test_name):
- """ Return the dir to run a test. """
- return os.path.join(
- self.test_base_dir, 'repeat_%d' % TEST_HELPER.repeat_count, test_name)
-
- def script_path(self, script_name):
- """ Return the dir of python scripts. """
- return os.path.join(self.script_dir, script_name)
-
- def get_device_features(self):
- if self.device_features is None:
- args = [sys.executable, self.script_path(
- 'run_simpleperf_on_device.py'), 'list', '--show-features']
- output = subprocess.check_output(args, stderr=TEST_LOGGER.log_fh)
- output = bytes_to_str(output)
- self.device_features = output.split()
- return self.device_features
-
- def is_trace_offcpu_supported(self):
- return 'trace-offcpu' in self.get_device_features()
-
- def build_testdata(self):
- """ Collect testdata in self.testdata_dir.
- In system/extras/simpleperf/scripts, testdata comes from:
- <script_dir>/../testdata, <script_dir>/script_testdata, <script_dir>/../demo
- In prebuilts/simpleperf, testdata comes from:
- <script_dir>/testdata
- """
- if os.path.isdir(self.testdata_dir):
- return # already built
- os.makedirs(self.testdata_dir)
-
- source_dirs = [os.path.join('..', 'testdata'), 'script_testdata',
- os.path.join('..', 'demo'), 'testdata']
- source_dirs = [os.path.join(self.script_dir, x) for x in source_dirs]
- source_dirs = [x for x in source_dirs if os.path.isdir(x)]
-
- for source_dir in source_dirs:
- for name in os.listdir(source_dir):
- source = os.path.join(source_dir, name)
- target = os.path.join(self.testdata_dir, name)
- if os.path.exists(target):
- continue
- if os.path.isfile(source):
- shutil.copyfile(source, target)
- elif os.path.isdir(source):
- shutil.copytree(source, target)
-
- def get_32bit_abi(self):
- return self.adb.get_property('ro.product.cpu.abilist32').strip().split(',')[0]
-
-
-TEST_HELPER = TestHelper()
-
-
-class TestBase(unittest.TestCase):
- def setUp(self):
- """ Run each test in a separate dir. """
- self.test_dir = TEST_HELPER.test_dir('%s.%s' % (
- self.__class__.__name__, self._testMethodName))
- os.makedirs(self.test_dir)
- os.chdir(self.test_dir)
-
- def run_cmd(self, args, return_output=False):
- if args[0] == 'report_html.py' or args[0] == INFERNO_SCRIPT:
- args += TEST_HELPER.browser_option
- if args[0].endswith('.py'):
- args = [sys.executable, TEST_HELPER.script_path(args[0])] + args[1:]
- use_shell = args[0].endswith('.bat')
- try:
- if not return_output:
- returncode = subprocess.call(args, shell=use_shell, stderr=TEST_LOGGER.log_fh)
- else:
- subproc = subprocess.Popen(args, stdout=subprocess.PIPE,
- stderr=TEST_LOGGER.log_fh, shell=use_shell)
- (output_data, _) = subproc.communicate()
- output_data = bytes_to_str(output_data)
- returncode = subproc.returncode
- except OSError:
- returncode = None
- self.assertEqual(returncode, 0, msg="failed to run cmd: %s" % args)
- if return_output:
- return output_data
- return ''
-
- def check_strings_in_file(self, filename, strings):
- self.check_exist(filename=filename)
- with open(filename, 'r') as fh:
- self.check_strings_in_content(fh.read(), strings)
-
- def check_exist(self, filename=None, dirname=None):
- if filename:
- self.assertTrue(os.path.isfile(filename), filename)
- if dirname:
- self.assertTrue(os.path.isdir(dirname), dirname)
-
- def check_strings_in_content(self, content, strings):
- for s in strings:
- self.assertNotEqual(content.find(s), -1, "s: %s, content: %s" % (s, content))
-
-
-class TestExampleBase(TestBase):
- @classmethod
- def prepare(cls, example_name, package_name, activity_name, abi=None, adb_root=False):
- cls.adb = AdbHelper(enable_switch_to_root=adb_root)
- cls.example_path = TEST_HELPER.testdata_path(example_name)
- if not os.path.isdir(cls.example_path):
- log_fatal("can't find " + cls.example_path)
- for root, _, files in os.walk(cls.example_path):
- if 'app-profiling.apk' in files:
- cls.apk_path = os.path.join(root, 'app-profiling.apk')
- break
- if not hasattr(cls, 'apk_path'):
- log_fatal("can't find app-profiling.apk under " + cls.example_path)
- cls.package_name = package_name
- cls.activity_name = activity_name
- args = ["install", "-r"]
- if abi:
- args += ["--abi", abi]
- args.append(cls.apk_path)
- cls.adb.check_run(args)
- cls.adb_root = adb_root
- cls.has_perf_data_for_report = False
- # On Android >= P (version 9), we can profile JITed and interpreted Java code.
- # So only compile Java code on Android <= O (version 8).
- cls.use_compiled_java_code = TEST_HELPER.android_version <= 8
- cls.testcase_dir = TEST_HELPER.test_dir(cls.__name__)
-
- @classmethod
- def tearDownClass(cls):
- remove(cls.testcase_dir)
- if hasattr(cls, 'package_name'):
- cls.adb.check_run(["uninstall", cls.package_name])
-
- def setUp(self):
- super(TestExampleBase, self).setUp()
- if 'TraceOffCpu' in self.id() and not TEST_HELPER.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
- # generate it for each test.
- if not os.path.isdir(self.testcase_dir):
- os.makedirs(self.testcase_dir)
- os.chdir(self.testcase_dir)
- self.run_app_profiler(compile_java_code=self.use_compiled_java_code)
- remove(self.test_dir)
- shutil.copytree(self.testcase_dir, self.test_dir)
- os.chdir(self.test_dir)
-
- def run(self, result=None):
- self.__class__.test_result = result
- super(TestExampleBase, self).run(result)
-
- def run_app_profiler(self, record_arg="-g --duration 10", build_binary_cache=True,
- start_activity=True, compile_java_code=False):
- args = ['app_profiler.py', '--app', self.package_name, '-r', record_arg, '-o', 'perf.data']
- if not build_binary_cache:
- args.append("-nb")
- if compile_java_code:
- args.append('--compile_java_code')
- if start_activity:
- args += ["-a", self.activity_name]
- args += ["-lib", self.example_path]
- if not self.adb_root:
- args.append("--disable_adb_root")
- self.run_cmd(args)
- self.check_exist(filename="perf.data")
- if build_binary_cache:
- self.check_exist(dirname="binary_cache")
-
- def check_file_under_dir(self, dirname, filename):
- self.check_exist(dirname=dirname)
- for _, _, files in os.walk(dirname):
- for f in files:
- if f == filename:
- return
- self.fail("Failed to call check_file_under_dir(dir=%s, file=%s)" % (dirname, filename))
-
- def check_annotation_summary(self, summary_file, check_entries):
- """ check_entries is a list of (name, accumulated_period, period).
- This function checks for each entry, if the line containing [name]
- has at least required accumulated_period and period.
- """
- self.check_exist(filename=summary_file)
- with open(summary_file, 'r') as fh:
- summary = fh.read()
- fulfilled = [False for x in check_entries]
- summary_check_re = re.compile(r'accumulated_period:\s*([\d.]+)%.*period:\s*([\d.]+)%')
- for line in summary.split('\n'):
- for i, (name, need_acc_period, need_period) in enumerate(check_entries):
- if not fulfilled[i] and name in line:
- m = summary_check_re.search(line)
- if m:
- acc_period = float(m.group(1))
- period = float(m.group(2))
- if acc_period >= need_acc_period and period >= need_period:
- fulfilled[i] = True
- self.assertEqual(len(fulfilled), sum([int(x) for x in fulfilled]), fulfilled)
-
- def check_inferno_report_html(self, check_entries, filename="report.html"):
- self.check_exist(filename=filename)
- with open(filename, 'r') as fh:
- data = fh.read()
- fulfilled = [False for _ in check_entries]
- for line in data.split('\n'):
- # each entry is a (function_name, min_percentage) pair.
- for i, entry in enumerate(check_entries):
- if fulfilled[i] or line.find(entry[0]) == -1:
- continue
- m = re.search(r'(\d+\.\d+)%', line)
- if m and float(m.group(1)) >= entry[1]:
- fulfilled[i] = True
- break
- self.assertEqual(fulfilled, [True for _ in check_entries])
-
- def common_test_app_profiler(self):
- self.run_cmd(["app_profiler.py", "-h"])
- remove("binary_cache")
- self.run_app_profiler(build_binary_cache=False)
- self.assertFalse(os.path.isdir("binary_cache"))
- args = ["binary_cache_builder.py"]
- if not self.adb_root:
- args.append("--disable_adb_root")
- self.run_cmd(args)
- self.check_exist(dirname="binary_cache")
- remove("binary_cache")
- self.run_app_profiler(build_binary_cache=True)
- self.run_app_profiler()
- self.run_app_profiler(start_activity=False)
-
- def common_test_report(self):
- self.run_cmd(["report.py", "-h"])
- self.run_cmd(["report.py"])
- self.run_cmd(["report.py", "-i", "perf.data"])
- self.run_cmd(["report.py", "-g"])
- self.run_cmd(["report.py", "--self-kill-for-testing", "-g", "--gui"])
-
- def common_test_annotate(self):
- self.run_cmd(["annotate.py", "-h"])
- remove("annotated_files")
- self.run_cmd(["annotate.py", "-s", self.example_path])
- self.check_exist(dirname="annotated_files")
-
- def common_test_report_sample(self, check_strings):
- self.run_cmd(["report_sample.py", "-h"])
- self.run_cmd(["report_sample.py"])
- output = self.run_cmd(["report_sample.py", "perf.data"], return_output=True)
- self.check_strings_in_content(output, check_strings)
-
- def common_test_pprof_proto_generator(self, check_strings_with_lines,
- check_strings_without_lines):
- if not HAS_GOOGLE_PROTOBUF:
- log_info('Skip test for pprof_proto_generator because google.protobuf is missing')
- return
- self.run_cmd(["pprof_proto_generator.py", "-h"])
- self.run_cmd(["pprof_proto_generator.py"])
- remove("pprof.profile")
- self.run_cmd(["pprof_proto_generator.py", "-i", "perf.data", "-o", "pprof.profile"])
- self.check_exist(filename="pprof.profile")
- self.run_cmd(["pprof_proto_generator.py", "--show"])
- output = self.run_cmd(["pprof_proto_generator.py", "--show", "pprof.profile"],
- return_output=True)
- self.check_strings_in_content(output, check_strings_with_lines + ["has_line_numbers: True"])
- remove("binary_cache")
- self.run_cmd(["pprof_proto_generator.py"])
- output = self.run_cmd(["pprof_proto_generator.py", "--show", "pprof.profile"],
- return_output=True)
- self.check_strings_in_content(output, check_strings_without_lines +
- ["has_line_numbers: False"])
-
- def common_test_inferno(self):
- self.run_cmd([INFERNO_SCRIPT, "-h"])
- remove("perf.data")
- append_args = [] if self.adb_root else ["--disable_adb_root"]
- self.run_cmd([INFERNO_SCRIPT, "-p", self.package_name, "-t", "3"] + append_args)
- self.check_exist(filename="perf.data")
- self.run_cmd([INFERNO_SCRIPT, "-p", self.package_name, "-f", "1000", "-du", "-t", "1"] +
- append_args)
- self.run_cmd([INFERNO_SCRIPT, "-p", self.package_name, "-e", "100000 cpu-cycles",
- "-t", "1"] + append_args)
- self.run_cmd([INFERNO_SCRIPT, "-sc"])
-
- def common_test_report_html(self):
- self.run_cmd(['report_html.py', '-h'])
- self.run_cmd(['report_html.py'])
- self.run_cmd(['report_html.py', '--add_source_code', '--source_dirs', 'testdata'])
- self.run_cmd(['report_html.py', '--add_disassembly'])
- # Test with multiple perf.data.
- shutil.move('perf.data', 'perf2.data')
- self.run_app_profiler(record_arg='-g -f 1000 --duration 3 -e task-clock:u')
- self.run_cmd(['report_html.py', '-i', 'perf.data', 'perf2.data'])
-
-
-class TestExamplePureJava(TestExampleBase):
- @classmethod
- def setUpClass(cls):
- cls.prepare("SimpleperfExamplePureJava",
- "com.example.simpleperf.simpleperfexamplepurejava",
- ".MainActivity")
-
- def test_app_profiler(self):
- self.common_test_app_profiler()
-
- def test_app_profiler_profile_from_launch(self):
- self.run_app_profiler(start_activity=True, build_binary_cache=False)
- self.run_cmd(["report.py", "-g", "-o", "report.txt"])
- self.check_strings_in_file("report.txt", [
- "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run",
- "__start_thread"])
-
- def test_app_profiler_multiprocesses(self):
- self.adb.check_run(['shell', 'am', 'force-stop', self.package_name])
- self.adb.check_run(['shell', 'am', 'start', '-n',
- self.package_name + '/.MultiProcessActivity'])
- # Wait until both MultiProcessActivity and MultiProcessService set up.
- time.sleep(3)
- self.run_app_profiler(start_activity=False)
- self.run_cmd(["report.py", "-o", "report.txt"])
- self.check_strings_in_file("report.txt", ["BusyService", "BusyThread"])
-
- def test_app_profiler_with_ctrl_c(self):
- if is_windows():
- return
- self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity'])
- time.sleep(1)
- args = [sys.executable, TEST_HELPER.script_path("app_profiler.py"),
- "--app", self.package_name, "-r", "--duration 10000", "--disable_adb_root"]
- subproc = subprocess.Popen(args)
- time.sleep(3)
-
- subproc.send_signal(signal.SIGINT)
- subproc.wait()
- self.assertEqual(subproc.returncode, 0)
- self.run_cmd(["report.py"])
-
- def test_app_profiler_stop_after_app_exit(self):
- self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity'])
- time.sleep(1)
- subproc = subprocess.Popen(
- [sys.executable, TEST_HELPER.script_path('app_profiler.py'),
- '--app', self.package_name, '-r', '--duration 10000', '--disable_adb_root'])
- time.sleep(3)
- self.adb.check_run(['shell', 'am', 'force-stop', self.package_name])
- subproc.wait()
- self.assertEqual(subproc.returncode, 0)
- self.run_cmd(["report.py"])
-
- def test_app_profiler_with_ndk_path(self):
- # Although we pass an invalid ndk path, it should be able to find tools in default ndk path.
- self.run_cmd(['app_profiler.py', '--app', self.package_name, '-a', self.activity_name,
- '--ndk_path', '.'])
-
- def test_report(self):
- self.common_test_report()
- self.run_cmd(["report.py", "-g", "-o", "report.txt"])
- self.check_strings_in_file("report.txt", [
- "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run",
- "__start_thread"])
-
- def test_profile_with_process_id(self):
- self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity'])
- time.sleep(1)
- pid = self.adb.check_run_and_return_output([
- 'shell', 'pidof', 'com.example.simpleperf.simpleperfexamplepurejava']).strip()
- self.run_app_profiler(start_activity=False, record_arg='-g --duration 10 -p ' + pid)
- self.run_cmd(["report.py", "-g", "-o", "report.txt"])
- self.check_strings_in_file("report.txt", [
- "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run",
- "__start_thread"])
-
- def test_annotate(self):
- self.common_test_annotate()
- if not self.use_compiled_java_code:
- # Currently annotating Java code is only supported when the Java code is compiled.
- return
- self.check_file_under_dir("annotated_files", "MainActivity.java")
- summary_file = os.path.join("annotated_files", "summary")
- self.check_annotation_summary(summary_file, [
- ("MainActivity.java", 80, 80),
- ("run", 80, 0),
- ("callFunction", 0, 0),
- ("line 23", 80, 0)])
-
- def test_report_sample(self):
- self.common_test_report_sample(
- ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run",
- "__start_thread"])
-
- def test_pprof_proto_generator(self):
- check_strings_with_lines = []
- if self.use_compiled_java_code:
- check_strings_with_lines = [
- "com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java",
- "run"]
- self.common_test_pprof_proto_generator(
- check_strings_with_lines=check_strings_with_lines,
- check_strings_without_lines=[
- "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run"])
-
- def test_inferno(self):
- self.common_test_inferno()
- self.run_app_profiler()
- self.run_cmd([INFERNO_SCRIPT, "-sc"])
- self.check_inferno_report_html(
- [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run', 80)])
- self.run_cmd([INFERNO_SCRIPT, "-sc", "-o", "report2.html"])
- self.check_inferno_report_html(
- [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run', 80)],
- "report2.html")
-
- def test_inferno_in_another_dir(self):
- test_dir = 'inferno_testdir'
- os.mkdir(test_dir)
- os.chdir(test_dir)
- self.run_cmd(['app_profiler.py', '--app', self.package_name,
- '-r', '-e task-clock:u -g --duration 3'])
- self.check_exist(filename="perf.data")
- self.run_cmd([INFERNO_SCRIPT, "-sc"])
-
- def test_report_html(self):
- self.common_test_report_html()
-
- def test_run_simpleperf_without_usb_connection(self):
- self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity'])
- self.run_cmd(['run_simpleperf_without_usb_connection.py', 'start', '-p',
- self.package_name, '--size_limit', '1M'])
- self.adb.check_run(['kill-server'])
- time.sleep(3)
- self.run_cmd(['run_simpleperf_without_usb_connection.py', 'stop'])
- self.check_exist(filename="perf.data")
- self.run_cmd(["report.py", "-g", "-o", "report.txt"])
-
-
-class TestExamplePureJavaRoot(TestExampleBase):
- @classmethod
- def setUpClass(cls):
- cls.prepare("SimpleperfExamplePureJava",
- "com.example.simpleperf.simpleperfexamplepurejava",
- ".MainActivity",
- adb_root=True)
-
- def test_app_profiler(self):
- self.common_test_app_profiler()
-
-
-class TestExamplePureJavaTraceOffCpu(TestExampleBase):
- @classmethod
- def setUpClass(cls):
- cls.prepare("SimpleperfExamplePureJava",
- "com.example.simpleperf.simpleperfexamplepurejava",
- ".SleepActivity")
-
- def test_smoke(self):
- self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-cycles:u --trace-offcpu")
- self.run_cmd(["report.py", "-g", "-o", "report.txt"])
- self.check_strings_in_file("report.txt", [
- "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run",
- "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction",
- "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction"
- ])
- remove("annotated_files")
- self.run_cmd(["annotate.py", "-s", self.example_path])
- self.check_exist(dirname="annotated_files")
- if self.use_compiled_java_code:
- self.check_file_under_dir("annotated_files", "SleepActivity.java")
- summary_file = os.path.join("annotated_files", "summary")
- self.check_annotation_summary(summary_file, [
- ("SleepActivity.java", 80, 20),
- ("run", 80, 0),
- ("RunFunction", 20, 20),
- ("SleepFunction", 20, 0),
- ("line 24", 1, 0),
- ("line 32", 20, 0)])
- self.run_cmd([INFERNO_SCRIPT, "-sc"])
- self.check_inferno_report_html(
- [('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run', 80),
- ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction',
- 20),
- ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction',
- 20)])
-
-
-class TestExampleWithNative(TestExampleBase):
- @classmethod
- def setUpClass(cls):
- cls.prepare("SimpleperfExampleWithNative",
- "com.example.simpleperf.simpleperfexamplewithnative",
- ".MainActivity")
-
- def test_app_profiler(self):
- self.common_test_app_profiler()
-
- def test_app_profiler_profile_from_launch(self):
- self.run_app_profiler(start_activity=True, build_binary_cache=False)
- self.run_cmd(["report.py", "-g", "-o", "report.txt"])
- self.check_strings_in_file("report.txt", ["BusyLoopThread", "__start_thread"])
-
- def test_report(self):
- self.common_test_report()
- self.run_cmd(["report.py", "-g", "-o", "report.txt"])
- self.check_strings_in_file("report.txt", ["BusyLoopThread", "__start_thread"])
-
- def test_annotate(self):
- self.common_test_annotate()
- self.check_file_under_dir("annotated_files", "native-lib.cpp")
- summary_file = os.path.join("annotated_files", "summary")
- self.check_annotation_summary(summary_file, [
- ("native-lib.cpp", 20, 0),
- ("BusyLoopThread", 20, 0),
- ("line 46", 20, 0)])
-
- def test_report_sample(self):
- self.common_test_report_sample(
- ["BusyLoopThread",
- "__start_thread"])
-
- def test_pprof_proto_generator(self):
- check_strings_with_lines = [
- "native-lib.cpp",
- "BusyLoopThread",
- # Check if dso name in perf.data is replaced by binary path in binary_cache.
- 'filename: binary_cache/data/app/com.example.simpleperf.simpleperfexamplewithnative-']
- self.common_test_pprof_proto_generator(
- check_strings_with_lines,
- check_strings_without_lines=["BusyLoopThread"])
-
- def test_inferno(self):
- self.common_test_inferno()
- self.run_app_profiler()
- self.run_cmd([INFERNO_SCRIPT, "-sc"])
- self.check_inferno_report_html([('BusyLoopThread', 20)])
-
- def test_report_html(self):
- self.common_test_report_html()
- self.run_cmd(['report_html.py', '--add_source_code', '--source_dirs', 'testdata',
- '--add_disassembly', '--binary_filter', "libnative-lib.so"])
-
-
-class TestExampleWithNativeRoot(TestExampleBase):
- @classmethod
- def setUpClass(cls):
- cls.prepare("SimpleperfExampleWithNative",
- "com.example.simpleperf.simpleperfexamplewithnative",
- ".MainActivity",
- adb_root=True)
-
- def test_app_profiler(self):
- self.common_test_app_profiler()
-
-
-class TestExampleWithNativeTraceOffCpu(TestExampleBase):
- @classmethod
- def setUpClass(cls):
- cls.prepare("SimpleperfExampleWithNative",
- "com.example.simpleperf.simpleperfexamplewithnative",
- ".SleepActivity")
-
- def test_smoke(self):
- self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-cycles:u --trace-offcpu")
- self.run_cmd(["report.py", "-g", "--comms", "SleepThread", "-o", "report.txt"])
- self.check_strings_in_file("report.txt", [
- "SleepThread(void*)",
- "RunFunction()",
- "SleepFunction(unsigned long long)"])
- remove("annotated_files")
- self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "SleepThread"])
- self.check_exist(dirname="annotated_files")
- self.check_file_under_dir("annotated_files", "native-lib.cpp")
- summary_file = os.path.join("annotated_files", "summary")
- self.check_annotation_summary(summary_file, [
- ("native-lib.cpp", 80, 20),
- ("SleepThread", 80, 0),
- ("RunFunction", 20, 20),
- ("SleepFunction", 20, 0),
- ("line 73", 20, 0),
- ("line 83", 20, 0)])
- self.run_cmd([INFERNO_SCRIPT, "-sc"])
- self.check_inferno_report_html([('SleepThread', 80),
- ('RunFunction', 20),
- ('SleepFunction', 20)])
-
-
-class TestExampleWithNativeJniCall(TestExampleBase):
- @classmethod
- def setUpClass(cls):
- cls.prepare("SimpleperfExampleWithNative",
- "com.example.simpleperf.simpleperfexamplewithnative",
- ".MixActivity")
-
- def test_smoke(self):
- self.run_app_profiler()
- self.run_cmd(["report.py", "-g", "--comms", "BusyThread", "-o", "report.txt"])
- self.check_strings_in_file("report.txt", [
- "com.example.simpleperf.simpleperfexamplewithnative.MixActivity$1.run",
- "Java_com_example_simpleperf_simpleperfexamplewithnative_MixActivity_callFunction"])
- remove("annotated_files")
- self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "BusyThread"])
- self.check_exist(dirname="annotated_files")
- self.check_file_under_dir("annotated_files", "native-lib.cpp")
- summary_file = os.path.join("annotated_files", "summary")
- self.check_annotation_summary(summary_file, [("native-lib.cpp", 5, 0), ("line 40", 5, 0)])
- if self.use_compiled_java_code:
- self.check_file_under_dir("annotated_files", "MixActivity.java")
- self.check_annotation_summary(summary_file, [
- ("MixActivity.java", 80, 0),
- ("run", 80, 0),
- ("line 26", 20, 0),
- ("native-lib.cpp", 5, 0),
- ("line 40", 5, 0)])
-
- self.run_cmd([INFERNO_SCRIPT, "-sc"])
-
-
-class TestExampleWithNativeForce32Bit(TestExampleWithNative):
- @classmethod
- def setUpClass(cls):
- cls.prepare("SimpleperfExampleWithNative",
- "com.example.simpleperf.simpleperfexamplewithnative",
- ".MainActivity",
- abi=TEST_HELPER.get_32bit_abi())
-
-
-class TestExampleWithNativeRootForce32Bit(TestExampleWithNativeRoot):
- @classmethod
- def setUpClass(cls):
- cls.prepare("SimpleperfExampleWithNative",
- "com.example.simpleperf.simpleperfexamplewithnative",
- ".MainActivity",
- abi=TEST_HELPER.get_32bit_abi(),
- adb_root=False)
-
-
-class TestExampleWithNativeTraceOffCpuForce32Bit(TestExampleWithNativeTraceOffCpu):
- @classmethod
- def setUpClass(cls):
- cls.prepare("SimpleperfExampleWithNative",
- "com.example.simpleperf.simpleperfexamplewithnative",
- ".SleepActivity",
- abi=TEST_HELPER.get_32bit_abi())
-
-
-class TestExampleOfKotlin(TestExampleBase):
- @classmethod
- def setUpClass(cls):
- cls.prepare("SimpleperfExampleOfKotlin",
- "com.example.simpleperf.simpleperfexampleofkotlin",
- ".MainActivity")
-
- def test_app_profiler(self):
- self.common_test_app_profiler()
-
- def test_app_profiler_profile_from_launch(self):
- self.run_app_profiler(start_activity=True, build_binary_cache=False)
- self.run_cmd(["report.py", "-g", "-o", "report.txt"])
- self.check_strings_in_file("report.txt", [
- "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." +
- "run", "__start_thread"])
-
- def test_report(self):
- self.common_test_report()
- self.run_cmd(["report.py", "-g", "-o", "report.txt"])
- self.check_strings_in_file("report.txt", [
- "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." +
- "run", "__start_thread"])
-
- def test_annotate(self):
- if not self.use_compiled_java_code:
- return
- self.common_test_annotate()
- self.check_file_under_dir("annotated_files", "MainActivity.kt")
- summary_file = os.path.join("annotated_files", "summary")
- self.check_annotation_summary(summary_file, [
- ("MainActivity.kt", 80, 80),
- ("run", 80, 0),
- ("callFunction", 0, 0),
- ("line 19", 80, 0),
- ("line 25", 0, 0)])
-
- def test_report_sample(self):
- self.common_test_report_sample([
- "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." +
- "run", "__start_thread"])
-
- def test_pprof_proto_generator(self):
- check_strings_with_lines = []
- if self.use_compiled_java_code:
- check_strings_with_lines = [
- "com/example/simpleperf/simpleperfexampleofkotlin/MainActivity.kt",
- "run"]
- self.common_test_pprof_proto_generator(
- check_strings_with_lines=check_strings_with_lines,
- check_strings_without_lines=["com.example.simpleperf.simpleperfexampleofkotlin." +
- "MainActivity$createBusyThread$1.run"])
-
- def test_inferno(self):
- self.common_test_inferno()
- self.run_app_profiler()
- self.run_cmd([INFERNO_SCRIPT, "-sc"])
- self.check_inferno_report_html([('com.example.simpleperf.simpleperfexampleofkotlin.' +
- 'MainActivity$createBusyThread$1.run', 80)])
-
- def test_report_html(self):
- self.common_test_report_html()
-
-
-class TestExampleOfKotlinRoot(TestExampleBase):
- @classmethod
- def setUpClass(cls):
- cls.prepare("SimpleperfExampleOfKotlin",
- "com.example.simpleperf.simpleperfexampleofkotlin",
- ".MainActivity",
- adb_root=True)
-
- def test_app_profiler(self):
- self.common_test_app_profiler()
-
-
-class TestExampleOfKotlinTraceOffCpu(TestExampleBase):
- @classmethod
- def setUpClass(cls):
- cls.prepare("SimpleperfExampleOfKotlin",
- "com.example.simpleperf.simpleperfexampleofkotlin",
- ".SleepActivity")
-
- def test_smoke(self):
- self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-cycles:u --trace-offcpu")
- self.run_cmd(["report.py", "-g", "-o", "report.txt"])
- function_prefix = "com.example.simpleperf.simpleperfexampleofkotlin." + \
- "SleepActivity$createRunSleepThread$1."
- self.check_strings_in_file("report.txt", [
- function_prefix + "run",
- function_prefix + "RunFunction",
- function_prefix + "SleepFunction"
- ])
- if self.use_compiled_java_code:
- remove("annotated_files")
- self.run_cmd(["annotate.py", "-s", self.example_path])
- self.check_exist(dirname="annotated_files")
- self.check_file_under_dir("annotated_files", "SleepActivity.kt")
- summary_file = os.path.join("annotated_files", "summary")
- self.check_annotation_summary(summary_file, [
- ("SleepActivity.kt", 80, 20),
- ("run", 80, 0),
- ("RunFunction", 20, 20),
- ("SleepFunction", 20, 0),
- ("line 24", 20, 0),
- ("line 32", 20, 0)])
-
- self.run_cmd([INFERNO_SCRIPT, "-sc"])
- self.check_inferno_report_html([
- (function_prefix + 'run', 80),
- (function_prefix + 'RunFunction', 20),
- (function_prefix + 'SleepFunction', 20)])
-
-
-class TestNativeProfiling(TestBase):
- def setUp(self):
- super(TestNativeProfiling, self).setUp()
- self.is_rooted_device = TEST_HELPER.adb.switch_to_root()
-
- def test_profile_cmd(self):
- self.run_cmd(["app_profiler.py", "-cmd", "pm -l", "--disable_adb_root"])
- self.run_cmd(["report.py", "-g", "-o", "report.txt"])
-
- def test_profile_native_program(self):
- if not self.is_rooted_device:
- return
- self.run_cmd(["app_profiler.py", "-np", "surfaceflinger"])
- self.run_cmd(["report.py", "-g", "-o", "report.txt"])
- self.run_cmd([INFERNO_SCRIPT, "-sc"])
- self.run_cmd([INFERNO_SCRIPT, "-np", "surfaceflinger"])
-
- def test_profile_pids(self):
- if not self.is_rooted_device:
- return
- pid = int(TEST_HELPER.adb.check_run_and_return_output(['shell', 'pidof', 'system_server']))
- self.run_cmd(['app_profiler.py', '--pid', str(pid), '-r', '--duration 1'])
- self.run_cmd(['app_profiler.py', '--pid', str(pid), str(pid), '-r', '--duration 1'])
- self.run_cmd(['app_profiler.py', '--tid', str(pid), '-r', '--duration 1'])
- self.run_cmd(['app_profiler.py', '--tid', str(pid), str(pid), '-r', '--duration 1'])
- self.run_cmd([INFERNO_SCRIPT, '--pid', str(pid), '-t', '1'])
-
- def test_profile_system_wide(self):
- if not self.is_rooted_device:
- return
- self.run_cmd(['app_profiler.py', '--system_wide', '-r', '--duration 1'])
-
-
-class TestReportLib(TestBase):
- def setUp(self):
- super(TestReportLib, self).setUp()
- self.report_lib = ReportLib()
- self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_symbols.data'))
-
- def tearDown(self):
- self.report_lib.Close()
- super(TestReportLib, self).tearDown()
-
- def test_build_id(self):
- build_id = self.report_lib.GetBuildIdForPath('/data/t2')
- self.assertEqual(build_id, '0x70f1fe24500fc8b0d9eb477199ca1ca21acca4de')
-
- def test_symbol(self):
- found_func2 = False
- while self.report_lib.GetNextSample():
- symbol = self.report_lib.GetSymbolOfCurrentSample()
- if symbol.symbol_name == 'func2(int, int)':
- found_func2 = True
- self.assertEqual(symbol.symbol_addr, 0x4004ed)
- self.assertEqual(symbol.symbol_len, 0x14)
- self.assertTrue(found_func2)
-
- def test_sample(self):
- found_sample = False
- while self.report_lib.GetNextSample():
- sample = self.report_lib.GetCurrentSample()
- if sample.ip == 0x4004ff and sample.time == 7637889424953:
- found_sample = True
- self.assertEqual(sample.pid, 15926)
- self.assertEqual(sample.tid, 15926)
- self.assertEqual(sample.thread_comm, 't2')
- self.assertEqual(sample.cpu, 5)
- self.assertEqual(sample.period, 694614)
- event = self.report_lib.GetEventOfCurrentSample()
- self.assertEqual(event.name, 'cpu-cycles')
- callchain = self.report_lib.GetCallChainOfCurrentSample()
- self.assertEqual(callchain.nr, 0)
- self.assertTrue(found_sample)
-
- def test_meta_info(self):
- self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_trace_offcpu.data'))
- meta_info = self.report_lib.MetaInfo()
- self.assertTrue("simpleperf_version" in meta_info)
- self.assertEqual(meta_info["system_wide_collection"], "false")
- self.assertEqual(meta_info["trace_offcpu"], "true")
- self.assertEqual(meta_info["event_type_info"], "cpu-cycles,0,0\nsched:sched_switch,2,47")
- self.assertTrue("product_props" in meta_info)
-
- def test_event_name_from_meta_info(self):
- self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_tracepoint_event.data'))
- event_names = set()
- while self.report_lib.GetNextSample():
- event_names.add(self.report_lib.GetEventOfCurrentSample().name)
- self.assertTrue('sched:sched_switch' in event_names)
- self.assertTrue('cpu-cycles' in event_names)
-
- def test_record_cmd(self):
- self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_trace_offcpu.data'))
- self.assertEqual(self.report_lib.GetRecordCmd(),
- "/data/local/tmp/simpleperf record --trace-offcpu --duration 2 -g " +
- "./simpleperf_runtest_run_and_sleep64")
-
- def test_offcpu(self):
- self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_trace_offcpu.data'))
- total_period = 0
- sleep_function_period = 0
- sleep_function_name = "SleepFunction(unsigned long long)"
- while self.report_lib.GetNextSample():
- sample = self.report_lib.GetCurrentSample()
- total_period += sample.period
- if self.report_lib.GetSymbolOfCurrentSample().symbol_name == sleep_function_name:
- sleep_function_period += sample.period
- continue
- callchain = self.report_lib.GetCallChainOfCurrentSample()
- for i in range(callchain.nr):
- if callchain.entries[i].symbol.symbol_name == sleep_function_name:
- sleep_function_period += sample.period
- break
- self.assertEqual(self.report_lib.GetEventOfCurrentSample().name, 'cpu-cycles')
- sleep_percentage = float(sleep_function_period) / total_period
- self.assertGreater(sleep_percentage, 0.30)
-
- def test_show_art_frames(self):
- def has_art_frame(report_lib):
- report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_interpreter_frames.data'))
- result = False
- while report_lib.GetNextSample():
- callchain = report_lib.GetCallChainOfCurrentSample()
- for i in range(callchain.nr):
- if callchain.entries[i].symbol.symbol_name == 'artMterpAsmInstructionStart':
- result = True
- break
- report_lib.Close()
- return result
-
- report_lib = ReportLib()
- self.assertFalse(has_art_frame(report_lib))
- report_lib = ReportLib()
- report_lib.ShowArtFrames(False)
- self.assertFalse(has_art_frame(report_lib))
- report_lib = ReportLib()
- report_lib.ShowArtFrames(True)
- self.assertTrue(has_art_frame(report_lib))
-
- def test_merge_java_methods(self):
- def parse_dso_names(report_lib):
- dso_names = set()
- report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_interpreter_frames.data'))
- while report_lib.GetNextSample():
- dso_names.add(report_lib.GetSymbolOfCurrentSample().dso_name)
- callchain = report_lib.GetCallChainOfCurrentSample()
- for i in range(callchain.nr):
- dso_names.add(callchain.entries[i].symbol.dso_name)
- report_lib.Close()
- has_jit_symfiles = any('TemporaryFile-' in name for name in dso_names)
- has_jit_cache = '[JIT cache]' in dso_names
- return has_jit_symfiles, has_jit_cache
-
- report_lib = ReportLib()
- self.assertEqual(parse_dso_names(report_lib), (False, True))
-
- report_lib = ReportLib()
- report_lib.MergeJavaMethods(True)
- self.assertEqual(parse_dso_names(report_lib), (False, True))
-
- report_lib = ReportLib()
- report_lib.MergeJavaMethods(False)
- self.assertEqual(parse_dso_names(report_lib), (True, False))
-
- def test_tracing_data(self):
- self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_tracepoint_event.data'))
- has_tracing_data = False
- while self.report_lib.GetNextSample():
- event = self.report_lib.GetEventOfCurrentSample()
- tracing_data = self.report_lib.GetTracingDataOfCurrentSample()
- if event.name == 'sched:sched_switch':
- self.assertIsNotNone(tracing_data)
- self.assertIn('prev_pid', tracing_data)
- self.assertIn('next_comm', tracing_data)
- if tracing_data['prev_pid'] == 9896 and tracing_data['next_comm'] == 'swapper/4':
- has_tracing_data = True
- else:
- self.assertIsNone(tracing_data)
- self.assertTrue(has_tracing_data)
-
-
-class TestRunSimpleperfOnDevice(TestBase):
- def test_smoke(self):
- self.run_cmd(['run_simpleperf_on_device.py', 'list', '--show-features'])
-
-
-class TestTools(TestBase):
- def test_addr2nearestline(self):
- self.run_addr2nearestline_test(True)
- self.run_addr2nearestline_test(False)
-
- def run_addr2nearestline_test(self, with_function_name):
- binary_cache_path = TEST_HELPER.testdata_dir
- test_map = {
- '/simpleperf_runtest_two_functions_arm64': [
- {
- 'func_addr': 0x668,
- 'addr': 0x668,
- 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:20',
- 'function': 'main',
- },
- {
- 'func_addr': 0x668,
- 'addr': 0x6a4,
- 'source': """system/extras/simpleperf/runtest/two_functions.cpp:7
- system/extras/simpleperf/runtest/two_functions.cpp:22""",
- 'function': """Function1()
- main""",
- },
- ],
- '/simpleperf_runtest_two_functions_arm': [
- {
- 'func_addr': 0x784,
- 'addr': 0x7b0,
- 'source': """system/extras/simpleperf/runtest/two_functions.cpp:14
- system/extras/simpleperf/runtest/two_functions.cpp:23""",
- 'function': """Function2()
- main""",
- },
- {
- 'func_addr': 0x784,
- 'addr': 0x7d0,
- 'source': """system/extras/simpleperf/runtest/two_functions.cpp:15
- system/extras/simpleperf/runtest/two_functions.cpp:23""",
- 'function': """Function2()
- main""",
- }
- ],
- '/simpleperf_runtest_two_functions_x86_64': [
- {
- 'func_addr': 0x840,
- 'addr': 0x840,
- 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:7',
- 'function': 'Function1()',
- },
- {
- 'func_addr': 0x920,
- 'addr': 0x94a,
- 'source': """system/extras/simpleperf/runtest/two_functions.cpp:7
- system/extras/simpleperf/runtest/two_functions.cpp:22""",
- 'function': """Function1()
- main""",
- }
- ],
- '/simpleperf_runtest_two_functions_x86': [
- {
- 'func_addr': 0x6d0,
- 'addr': 0x6da,
- 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:14',
- 'function': 'Function2()',
- },
- {
- 'func_addr': 0x710,
- 'addr': 0x749,
- 'source': """system/extras/simpleperf/runtest/two_functions.cpp:8
- system/extras/simpleperf/runtest/two_functions.cpp:22""",
- 'function': """Function1()
- main""",
- }
- ],
- }
- addr2line = Addr2Nearestline(None, binary_cache_path, with_function_name)
- for dso_path in test_map:
- test_addrs = test_map[dso_path]
- for test_addr in test_addrs:
- addr2line.add_addr(dso_path, test_addr['func_addr'], test_addr['addr'])
- addr2line.convert_addrs_to_lines()
- for dso_path in test_map:
- dso = addr2line.get_dso(dso_path)
- self.assertTrue(dso is not None)
- test_addrs = test_map[dso_path]
- for test_addr in test_addrs:
- expected_files = []
- expected_lines = []
- expected_functions = []
- for line in test_addr['source'].split('\n'):
- items = line.split(':')
- expected_files.append(items[0].strip())
- expected_lines.append(int(items[1]))
- for line in test_addr['function'].split('\n'):
- expected_functions.append(line.strip())
- self.assertEqual(len(expected_files), len(expected_functions))
-
- actual_source = addr2line.get_addr_source(dso, test_addr['addr'])
- self.assertTrue(actual_source is not None)
- self.assertEqual(len(actual_source), len(expected_files))
- for i, source in enumerate(actual_source):
- self.assertEqual(len(source), 3 if with_function_name else 2)
- self.assertEqual(source[0], expected_files[i])
- self.assertEqual(source[1], expected_lines[i])
- if with_function_name:
- self.assertEqual(source[2], expected_functions[i])
-
- def test_objdump(self):
- binary_cache_path = TEST_HELPER.testdata_dir
- test_map = {
- '/simpleperf_runtest_two_functions_arm64': {
- 'start_addr': 0x668,
- 'len': 116,
- 'expected_items': [
- ('main():', 0),
- ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0),
- (' 694: add x20, x20, #0x6de', 0x694),
- ],
- },
- '/simpleperf_runtest_two_functions_arm': {
- 'start_addr': 0x784,
- 'len': 80,
- 'expected_items': [
- ('main():', 0),
- ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0),
- (' 7ae: bne.n 7a6 <main+0x22>', 0x7ae),
- ],
- },
- '/simpleperf_runtest_two_functions_x86_64': {
- 'start_addr': 0x920,
- 'len': 201,
- 'expected_items': [
- ('main():', 0),
- ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0),
- (' 96e: mov %edx,(%rbx,%rax,4)', 0x96e),
- ],
- },
- '/simpleperf_runtest_two_functions_x86': {
- 'start_addr': 0x710,
- 'len': 98,
- 'expected_items': [
- ('main():', 0),
- ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0),
- (' 748: cmp $0x5f5e100,%ebp', 0x748),
- ],
- },
- }
- objdump = Objdump(None, binary_cache_path)
- for dso_path in test_map:
- dso = test_map[dso_path]
- dso_info = objdump.get_dso_info(dso_path)
- self.assertIsNotNone(dso_info)
- disassemble_code = objdump.disassemble_code(dso_info, dso['start_addr'], dso['len'])
- self.assertTrue(disassemble_code)
- for item in dso['expected_items']:
- self.assertTrue(item in disassemble_code)
-
- def test_readelf(self):
- test_map = {
- 'simpleperf_runtest_two_functions_arm64': {
- 'arch': 'arm64',
- 'build_id': '0xe8ecb3916d989dbdc068345c30f0c24300000000',
- 'sections': ['.interp', '.note.android.ident', '.note.gnu.build-id', '.dynsym',
- '.dynstr', '.gnu.hash', '.gnu.version', '.gnu.version_r', '.rela.dyn',
- '.rela.plt', '.plt', '.text', '.rodata', '.eh_frame', '.eh_frame_hdr',
- '.preinit_array', '.init_array', '.fini_array', '.dynamic', '.got',
- '.got.plt', '.data', '.bss', '.comment', '.debug_str', '.debug_loc',
- '.debug_abbrev', '.debug_info', '.debug_ranges', '.debug_macinfo',
- '.debug_pubnames', '.debug_pubtypes', '.debug_line',
- '.note.gnu.gold-version', '.symtab', '.strtab', '.shstrtab'],
- },
- 'simpleperf_runtest_two_functions_arm': {
- 'arch': 'arm',
- 'build_id': '0x718f5b36c4148ee1bd3f51af89ed2be600000000',
- },
- 'simpleperf_runtest_two_functions_x86_64': {
- 'arch': 'x86_64',
- },
- 'simpleperf_runtest_two_functions_x86': {
- 'arch': 'x86',
- }
- }
- readelf = ReadElf(None)
- for dso_path in test_map:
- dso_info = test_map[dso_path]
- path = os.path.join(TEST_HELPER.testdata_dir, dso_path)
- self.assertEqual(dso_info['arch'], readelf.get_arch(path))
- if 'build_id' in dso_info:
- self.assertEqual(dso_info['build_id'], readelf.get_build_id(path))
- if 'sections' in dso_info:
- self.assertEqual(dso_info['sections'], readelf.get_sections(path))
- self.assertEqual(readelf.get_arch('not_exist_file'), 'unknown')
- self.assertEqual(readelf.get_build_id('not_exist_file'), '')
- self.assertEqual(readelf.get_sections('not_exist_file'), [])
-
- def test_source_file_searcher(self):
- searcher = SourceFileSearcher(
- [TEST_HELPER.testdata_path('SimpleperfExampleWithNative'),
- TEST_HELPER.testdata_path('SimpleperfExampleOfKotlin')])
- def format_path(path):
- return os.path.join(TEST_HELPER.testdata_dir, path.replace('/', os.sep))
- # Find a C++ file with pure file name.
- self.assertEqual(
- format_path('SimpleperfExampleWithNative/app/src/main/cpp/native-lib.cpp'),
- searcher.get_real_path('native-lib.cpp'))
- # Find a C++ file with an absolute file path.
- self.assertEqual(
- format_path('SimpleperfExampleWithNative/app/src/main/cpp/native-lib.cpp'),
- searcher.get_real_path('/data/native-lib.cpp'))
- # Find a Java file.
- self.assertEqual(
- format_path('SimpleperfExampleWithNative/app/src/main/java/com/example/' +
- 'simpleperf/simpleperfexamplewithnative/MainActivity.java'),
- searcher.get_real_path('simpleperfexamplewithnative/MainActivity.java'))
- # Find a Kotlin file.
- self.assertEqual(
- format_path('SimpleperfExampleOfKotlin/app/src/main/java/com/example/' +
- 'simpleperf/simpleperfexampleofkotlin/MainActivity.kt'),
- searcher.get_real_path('MainActivity.kt'))
-
- def test_is_elf_file(self):
- self.assertTrue(is_elf_file(TEST_HELPER.testdata_path(
- 'simpleperf_runtest_two_functions_arm')))
- with open('not_elf', 'wb') as fh:
- fh.write(b'\x90123')
- try:
- self.assertFalse(is_elf_file('not_elf'))
- finally:
- remove('not_elf')
-
-
-class TestNativeLibDownloader(TestBase):
- def setUp(self):
- super(TestNativeLibDownloader, self).setUp()
- self.adb = TEST_HELPER.adb
- self.adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/native_libs'])
-
- def tearDown(self):
- self.adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/native_libs'])
- super(TestNativeLibDownloader, self).tearDown()
-
- def list_lib_on_device(self, path):
- result, output = self.adb.run_and_return_output(
- ['shell', 'ls', '-llc', path], log_output=False)
- return output if result else ''
-
- def test_smoke(self):
- # Sync all native libs on device.
- downloader = NativeLibDownloader(None, 'arm64', self.adb)
- downloader.collect_native_libs_on_host(TEST_HELPER.testdata_path(
- 'SimpleperfExampleWithNative/app/build/intermediates/cmake/profiling'))
- self.assertEqual(len(downloader.host_build_id_map), 2)
- for entry in downloader.host_build_id_map.values():
- self.assertEqual(entry.score, 3)
- downloader.collect_native_libs_on_device()
- self.assertEqual(len(downloader.device_build_id_map), 0)
-
- lib_list = list(downloader.host_build_id_map.items())
- for sync_count in [0, 1, 2]:
- build_id_map = {}
- for i in range(sync_count):
- build_id_map[lib_list[i][0]] = lib_list[i][1]
- downloader.host_build_id_map = build_id_map
- downloader.sync_native_libs_on_device()
- downloader.collect_native_libs_on_device()
- self.assertEqual(len(downloader.device_build_id_map), sync_count)
- for i, item in enumerate(lib_list):
- build_id = item[0]
- name = item[1].name
- if i < sync_count:
- self.assertTrue(build_id in downloader.device_build_id_map)
- self.assertEqual(name, downloader.device_build_id_map[build_id])
- self.assertTrue(self.list_lib_on_device(downloader.dir_on_device + name))
- else:
- self.assertTrue(build_id not in downloader.device_build_id_map)
- self.assertFalse(self.list_lib_on_device(downloader.dir_on_device + name))
- if sync_count == 1:
- self.adb.run(['pull', '/data/local/tmp/native_libs/build_id_list',
- 'build_id_list'])
- with open('build_id_list', 'rb') as fh:
- self.assertEqual(bytes_to_str(fh.read()),
- '{}={}\n'.format(lib_list[0][0], lib_list[0][1].name))
- remove('build_id_list')
-
- def test_handle_wrong_build_id_list(self):
- with open('build_id_list', 'wb') as fh:
- fh.write(str_to_bytes('fake_build_id=binary_not_exist\n'))
- self.adb.check_run(['shell', 'mkdir', '-p', '/data/local/tmp/native_libs'])
- self.adb.check_run(['push', 'build_id_list', '/data/local/tmp/native_libs'])
- remove('build_id_list')
- downloader = NativeLibDownloader(None, 'arm64', self.adb)
- downloader.collect_native_libs_on_device()
- self.assertEqual(len(downloader.device_build_id_map), 0)
-
- def test_download_file_without_build_id(self):
- downloader = NativeLibDownloader(None, 'x86_64', self.adb)
- name = 'elf.so'
- shutil.copyfile(TEST_HELPER.testdata_path('data/symfs_without_build_id/elf'), name)
- downloader.collect_native_libs_on_host('.')
- downloader.collect_native_libs_on_device()
- self.assertIn(name, downloader.no_build_id_file_map)
- # Check if file wihtout build id can be downloaded.
- downloader.sync_native_libs_on_device()
- target_file = downloader.dir_on_device + name
- target_file_stat = self.list_lib_on_device(target_file)
- self.assertTrue(target_file_stat)
-
- # No need to re-download if file size doesn't change.
- downloader.sync_native_libs_on_device()
- self.assertEqual(target_file_stat, self.list_lib_on_device(target_file))
-
- # Need to re-download if file size changes.
- self.adb.check_run(['shell', 'truncate', '-s', '0', target_file])
- target_file_stat = self.list_lib_on_device(target_file)
- downloader.sync_native_libs_on_device()
- self.assertNotEqual(target_file_stat, self.list_lib_on_device(target_file))
-
-
-class TestReportHtml(TestBase):
- def test_long_callchain(self):
- self.run_cmd(['report_html.py', '-i',
- TEST_HELPER.testdata_path('perf_with_long_callchain.data')])
-
- def test_aggregated_by_thread_name(self):
- # Calculate event_count for each thread name before aggregation.
- event_count_for_thread_name = collections.defaultdict(lambda: 0)
- # use "--min_func_percent 0" to avoid cutting any thread.
- self.run_cmd(['report_html.py', '--min_func_percent', '0', '-i',
- TEST_HELPER.testdata_path('aggregatable_perf1.data'),
- TEST_HELPER.testdata_path('aggregatable_perf2.data')])
- record_data = self._load_record_data_in_html('report.html')
- event = record_data['sampleInfo'][0]
- for process in event['processes']:
- for thread in process['threads']:
- thread_name = record_data['threadNames'][str(thread['tid'])]
- event_count_for_thread_name[thread_name] += thread['eventCount']
-
- # Check event count for each thread after aggregation.
- self.run_cmd(['report_html.py', '--aggregate-by-thread-name',
- '--min_func_percent', '0', '-i',
- TEST_HELPER.testdata_path('aggregatable_perf1.data'),
- TEST_HELPER.testdata_path('aggregatable_perf2.data')])
- record_data = self._load_record_data_in_html('report.html')
- event = record_data['sampleInfo'][0]
- hit_count = 0
- for process in event['processes']:
- for thread in process['threads']:
- thread_name = record_data['threadNames'][str(thread['tid'])]
- self.assertEqual(thread['eventCount'],
- event_count_for_thread_name[thread_name])
- hit_count += 1
- self.assertEqual(hit_count, len(event_count_for_thread_name))
-
- def test_no_empty_process(self):
- """ Test not showing a process having no threads. """
- perf_data = TEST_HELPER.testdata_path('two_process_perf.data')
- self.run_cmd(['report_html.py', '-i', perf_data])
- record_data = self._load_record_data_in_html('report.html')
- processes = record_data['sampleInfo'][0]['processes']
- self.assertEqual(len(processes), 2)
-
- # One process is removed because all its threads are removed for not
- # reaching the min_func_percent limit.
- self.run_cmd(['report_html.py', '-i', perf_data, '--min_func_percent', '20'])
- record_data = self._load_record_data_in_html('report.html')
- processes = record_data['sampleInfo'][0]['processes']
- self.assertEqual(len(processes), 1)
-
- def _load_record_data_in_html(self, html_file):
- with open(html_file, 'r') as fh:
- data = fh.read()
- start_str = 'type="application/json"'
- end_str = '</script>'
- start_pos = data.find(start_str)
- self.assertNotEqual(start_pos, -1)
- start_pos = data.find('>', start_pos)
- self.assertNotEqual(start_pos, -1)
- start_pos += 1
- end_pos = data.find(end_str, start_pos)
- self.assertNotEqual(end_pos, -1)
- json_data = data[start_pos:end_pos]
- return json.loads(json_data)
-
-
-class TestBinaryCacheBuilder(TestBase):
- def test_copy_binaries_from_symfs_dirs(self):
- readelf = ReadElf(None)
- strip = find_tool_path('strip', arch='arm')
- self.assertIsNotNone(strip)
- symfs_dir = os.path.join(self.test_dir, 'symfs_dir')
- remove(symfs_dir)
- os.mkdir(symfs_dir)
- filename = 'simpleperf_runtest_two_functions_arm'
- origin_file = TEST_HELPER.testdata_path(filename)
- source_file = os.path.join(symfs_dir, filename)
- target_file = os.path.join('binary_cache', filename)
- expected_build_id = readelf.get_build_id(origin_file)
- binary_cache_builder = BinaryCacheBuilder(None, False)
- binary_cache_builder.binaries['simpleperf_runtest_two_functions_arm'] = expected_build_id
-
- # Copy binary if target file doesn't exist.
- remove(target_file)
- self.run_cmd([strip, '--strip-all', '-o', source_file, origin_file])
- binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir])
- self.assertTrue(filecmp.cmp(target_file, source_file))
-
- # Copy binary if target file doesn't have .symtab and source file has .symtab.
- self.run_cmd([strip, '--strip-debug', '-o', source_file, origin_file])
- binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir])
- self.assertTrue(filecmp.cmp(target_file, source_file))
-
- # Copy binary if target file doesn't have .debug_line and source_files has .debug_line.
- shutil.copy(origin_file, source_file)
- binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir])
- self.assertTrue(filecmp.cmp(target_file, source_file))
-
- def test_copy_elf_without_build_id_from_symfs_dir(self):
- binary_cache_builder = BinaryCacheBuilder(None, False)
- binary_cache_builder.binaries['elf'] = ''
- symfs_dir = TEST_HELPER.testdata_path('data/symfs_without_build_id')
- source_file = os.path.join(symfs_dir, 'elf')
- target_file = os.path.join('binary_cache', 'elf')
- binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir])
- self.assertTrue(filecmp.cmp(target_file, source_file))
- binary_cache_builder.pull_binaries_from_device()
- self.assertTrue(filecmp.cmp(target_file, source_file))
-
-
-class TestApiProfiler(TestBase):
- def run_api_test(self, package_name, apk_name, expected_reports, min_android_version):
- adb = TEST_HELPER.adb
- if TEST_HELPER.android_version < ord(min_android_version) - ord('L') + 5:
- log_info('skip this test on Android < %s.' % min_android_version)
- return
- # step 1: Prepare profiling.
- self.run_cmd(['api_profiler.py', 'prepare'])
- # step 2: Install and run the app.
- apk_path = TEST_HELPER.testdata_path(apk_name)
- adb.run(['uninstall', package_name])
- adb.check_run(['install', '-t', apk_path])
- # Without sleep, the activity may be killed by post install intent ACTION_PACKAGE_CHANGED.
- time.sleep(3)
- adb.check_run(['shell', 'am', 'start', '-n', package_name + '/.MainActivity'])
- # step 3: Wait until the app exits.
- time.sleep(4)
- while True:
- result = adb.run(['shell', 'pidof', package_name])
- if not result:
- break
- time.sleep(1)
- # step 4: Collect recording data.
- remove('simpleperf_data')
- self.run_cmd(['api_profiler.py', 'collect', '-p', package_name, '-o', 'simpleperf_data'])
- # step 5: Check recording data.
- names = os.listdir('simpleperf_data')
- self.assertGreater(len(names), 0)
- for name in names:
- path = os.path.join('simpleperf_data', name)
- remove('report.txt')
- self.run_cmd(['report.py', '-g', '-o', 'report.txt', '-i', path])
- self.check_strings_in_file('report.txt', expected_reports)
- # step 6: Clean up.
- adb.check_run(['uninstall', package_name])
-
- def run_cpp_api_test(self, apk_name, min_android_version):
- self.run_api_test('simpleperf.demo.cpp_api', apk_name, ['BusyThreadFunc'],
- min_android_version)
-
- def test_cpp_api_on_a_debuggable_app_targeting_prev_q(self):
- # The source code of the apk is in simpleperf/demo/CppApi (with a small change to exit
- # after recording).
- self.run_cpp_api_test('cpp_api-debug_prev_Q.apk', 'N')
-
- def test_cpp_api_on_a_debuggable_app_targeting_q(self):
- self.run_cpp_api_test('cpp_api-debug_Q.apk', 'N')
-
- def test_cpp_api_on_a_profileable_app_targeting_prev_q(self):
- # a release apk with <profileable android:shell="true" />
- self.run_cpp_api_test('cpp_api-profile_prev_Q.apk', 'Q')
-
- def test_cpp_api_on_a_profileable_app_targeting_q(self):
- self.run_cpp_api_test('cpp_api-profile_Q.apk', 'Q')
-
- def run_java_api_test(self, apk_name, min_android_version):
- self.run_api_test('simpleperf.demo.java_api', apk_name,
- ['simpleperf.demo.java_api.MainActivity', 'java.lang.Thread.run'],
- min_android_version)
-
- def test_java_api_on_a_debuggable_app_targeting_prev_q(self):
- # The source code of the apk is in simpleperf/demo/JavaApi (with a small change to exit
- # after recording).
- self.run_java_api_test('java_api-debug_prev_Q.apk', 'P')
-
- def test_java_api_on_a_debuggable_app_targeting_q(self):
- self.run_java_api_test('java_api-debug_Q.apk', 'P')
-
- def test_java_api_on_a_profileable_app_targeting_prev_q(self):
- # a release apk with <profileable android:shell="true" />
- self.run_java_api_test('java_api-profile_prev_Q.apk', 'Q')
-
- def test_java_api_on_a_profileable_app_targeting_q(self):
- self.run_java_api_test('java_api-profile_Q.apk', 'Q')
-
-
-class TestPprofProtoGenerator(TestBase):
- def setUp(self):
- super(TestPprofProtoGenerator, self).setUp()
- if not HAS_GOOGLE_PROTOBUF:
- raise unittest.SkipTest(
- 'Skip test for pprof_proto_generator because google.protobuf is missing')
-
- def run_generator(self, options=None, testdata_file='perf_with_interpreter_frames.data'):
- testdata_path = TEST_HELPER.testdata_path(testdata_file)
- options = options or []
- self.run_cmd(['pprof_proto_generator.py', '-i', testdata_path] + options)
- return self.run_cmd(['pprof_proto_generator.py', '--show'], return_output=True)
-
- def test_show_art_frames(self):
- art_frame_str = 'art::interpreter::DoCall'
- # By default, don't show art frames.
- self.assertNotIn(art_frame_str, self.run_generator())
- # Use --show_art_frames to show art frames.
- self.assertIn(art_frame_str, self.run_generator(['--show_art_frames']))
-
- def test_pid_filter(self):
- key = 'PlayScene::DoFrame()' # function in process 10419
- self.assertIn(key, self.run_generator())
- self.assertIn(key, self.run_generator(['--pid', '10419']))
- self.assertIn(key, self.run_generator(['--pid', '10419', '10416']))
- self.assertNotIn(key, self.run_generator(['--pid', '10416']))
-
- def test_tid_filter(self):
- key1 = 'art::ProfileSaver::Run()' # function in thread 10459
- key2 = 'PlayScene::DoFrame()' # function in thread 10463
- for options in ([], ['--tid', '10459', '10463']):
- output = self.run_generator(options)
- self.assertIn(key1, output)
- self.assertIn(key2, output)
- output = self.run_generator(['--tid', '10459'])
- self.assertIn(key1, output)
- self.assertNotIn(key2, output)
- output = self.run_generator(['--tid', '10463'])
- self.assertNotIn(key1, output)
- self.assertIn(key2, output)
-
- def test_comm_filter(self):
- key1 = 'art::ProfileSaver::Run()' # function in thread 'Profile Saver'
- key2 = 'PlayScene::DoFrame()' # function in thread 'e.sample.tunnel'
- for options in ([], ['--comm', 'Profile Saver', 'e.sample.tunnel']):
- output = self.run_generator(options)
- self.assertIn(key1, output)
- self.assertIn(key2, output)
- output = self.run_generator(['--comm', 'Profile Saver'])
- self.assertIn(key1, output)
- self.assertNotIn(key2, output)
- output = self.run_generator(['--comm', 'e.sample.tunnel'])
- self.assertNotIn(key1, output)
- self.assertIn(key2, output)
-
- def test_build_id(self):
- """ Test the build ids generated are not padded with zeros. """
- self.assertIn('build_id: e3e938cc9e40de2cfe1a5ac7595897de(', self.run_generator())
-
- def test_location_address(self):
- """ Test if the address of a location is within the memory range of the corresponding
- mapping.
- """
- self.run_cmd(['pprof_proto_generator.py', '-i',
- TEST_HELPER.testdata_path('perf_with_interpreter_frames.data')])
-
- profile = load_pprof_profile('pprof.profile')
- # pylint: disable=no-member
- for location in profile.location:
- mapping = profile.mapping[location.mapping_id - 1]
- self.assertLessEqual(mapping.memory_start, location.address)
- self.assertGreaterEqual(mapping.memory_limit, location.address)
-
-
-class TestRecordingRealApps(TestBase):
- def setUp(self):
- super(TestRecordingRealApps, self).setUp()
- self.adb = TEST_HELPER.adb
- self.installed_packages = []
-
- def tearDown(self):
- for package in self.installed_packages:
- self.adb.run(['shell', 'pm', 'uninstall', package])
- super(TestRecordingRealApps, self).tearDown()
-
- def install_apk(self, apk_path, package_name):
- self.adb.run(['install', '-t', apk_path])
- self.installed_packages.append(package_name)
-
- def start_app(self, start_cmd):
- subprocess.Popen(self.adb.adb_path + ' ' + start_cmd, shell=True,
- stdout=TEST_LOGGER.log_fh, stderr=TEST_LOGGER.log_fh)
-
- def record_data(self, package_name, record_arg):
- self.run_cmd(['app_profiler.py', '--app', package_name, '-r', record_arg])
-
- def check_symbol_in_record_file(self, symbol_name):
- self.run_cmd(['report.py', '--children', '-o', 'report.txt'])
- self.check_strings_in_file('report.txt', [symbol_name])
-
- def test_recording_displaybitmaps(self):
- self.install_apk(TEST_HELPER.testdata_path('DisplayBitmaps.apk'),
- 'com.example.android.displayingbitmaps')
- self.install_apk(TEST_HELPER.testdata_path('DisplayBitmapsTest.apk'),
- 'com.example.android.displayingbitmaps.test')
- self.start_app('shell am instrument -w -r -e debug false -e class ' +
- 'com.example.android.displayingbitmaps.tests.GridViewTest ' +
- 'com.example.android.displayingbitmaps.test/' +
- 'androidx.test.runner.AndroidJUnitRunner')
- self.record_data('com.example.android.displayingbitmaps', '-e cpu-clock -g --duration 10')
- if TEST_HELPER.android_version >= 9:
- self.check_symbol_in_record_file('androidx.test.espresso')
-
- def test_recording_endless_tunnel(self):
- self.install_apk(TEST_HELPER.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')
- self.check_symbol_in_record_file('PlayScene::DoFrame')
-
-
-def get_all_tests():
- tests = []
- for name, value in globals().items():
- if isinstance(value, type) and issubclass(value, unittest.TestCase):
- for member_name, member in inspect.getmembers(value):
- if isinstance(member, (types.MethodType, types.FunctionType)):
- if member_name.startswith('test'):
- tests.append(name + '.' + member_name)
- return sorted(tests)
-
-
-def run_tests(tests, repeats):
- TEST_HELPER.build_testdata()
- argv = [sys.argv[0]] + tests
- test_runner = unittest.TextTestRunner(stream=TEST_LOGGER, verbosity=2)
- success = True
- for repeat in range(1, repeats + 1):
- print('Run tests with python %d for %dth time\n%s' % (
- TEST_HELPER.python_version, repeat, '\n'.join(tests)), file=TEST_LOGGER)
- TEST_HELPER.repeat_count = repeat
- test_program = unittest.main(argv=argv, testRunner=test_runner, exit=False)
- if not test_program.result.wasSuccessful():
- success = False
- return success
-
-
-def main():
- parser = argparse.ArgumentParser(description='Test simpleperf scripts')
- parser.add_argument('--list-tests', action='store_true', help='List all tests.')
- parser.add_argument('--test-from', nargs=1, help='Run left tests from the selected test.')
- parser.add_argument('--python-version', choices=['2', '3', 'both'], default='both', help="""
- Run tests on which python versions.""")
- parser.add_argument('--repeat', type=int, nargs=1, default=[1], help='run test multiple times')
- parser.add_argument('--no-test-result', dest='report_test_result',
- action='store_false', help="Don't report test result.")
- parser.add_argument('--browser', action='store_true', help='pop report html file in browser.')
- parser.add_argument('pattern', nargs='*', help='Run tests matching the selected pattern.')
- args = parser.parse_args()
- tests = get_all_tests()
- if args.list_tests:
- print('\n'.join(tests))
- return True
- if args.test_from:
- start_pos = 0
- while start_pos < len(tests) and tests[start_pos] != args.test_from[0]:
- start_pos += 1
- if start_pos == len(tests):
- log_exit("Can't find test %s" % args.test_from[0])
- tests = tests[start_pos:]
- if args.pattern:
- patterns = [re.compile(fnmatch.translate(x)) for x in args.pattern]
- tests = [t for t in tests if any(pattern.match(t) for pattern in patterns)]
- if not tests:
- log_exit('No tests are matched.')
-
- if TEST_HELPER.android_version < 7:
- print("Skip tests on Android version < N.", file=TEST_LOGGER)
- return False
-
- if args.python_version == 'both':
- python_versions = [2, 3]
- else:
- python_versions = [int(args.python_version)]
-
- for python_version in python_versions:
- remove(TEST_HELPER.get_test_base_dir(python_version))
-
- if not args.browser:
- TEST_HELPER.browser_option = ['--no_browser']
-
- test_results = []
- for version in python_versions:
- os.chdir(TEST_HELPER.cur_dir)
- if version == TEST_HELPER.python_version:
- test_result = run_tests(tests, args.repeat[0])
- else:
- argv = ['python3' if version == 3 else 'python']
- argv.append(TEST_HELPER.script_path('test.py'))
- argv += sys.argv[1:]
- argv += ['--python-version', str(version), '--no-test-result']
- test_result = subprocess.call(argv) == 0
- test_results.append(test_result)
-
- if args.report_test_result:
- for version, test_result in zip(python_versions, test_results):
- if not test_result:
- print('Tests with python %d failed, see %s for details.' %
- (version, TEST_LOGGER.get_log_file(version)), file=TEST_LOGGER)
-
- return test_results.count(False) == 0
-
-
-if __name__ == '__main__':
- sys.exit(0 if main() else 1)
diff --git a/simpleperf/scripts/test/__init__.py b/simpleperf/scripts/test/__init__.py
new file mode 100644
index 00000000..9e414374
--- /dev/null
+++ b/simpleperf/scripts/test/__init__.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+from . do_test import main
diff --git a/simpleperf/scripts/test/api_profiler_test.py b/simpleperf/scripts/test/api_profiler_test.py
new file mode 100644
index 00000000..037b8fca
--- /dev/null
+++ b/simpleperf/scripts/test/api_profiler_test.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+import os
+import time
+
+from simpleperf_utils import log_info, remove
+from . test_utils import TestBase, TestHelper
+
+
+class TestApiProfiler(TestBase):
+ def run_api_test(self, package_name, apk_name, expected_reports, min_android_version):
+ adb = TestHelper.adb
+ if TestHelper.android_version < ord(min_android_version) - ord('L') + 5:
+ log_info('skip this test on Android < %s.' % min_android_version)
+ return
+ # step 1: Prepare profiling.
+ self.run_cmd(['api_profiler.py', 'prepare'])
+ # step 2: Install and run the app.
+ apk_path = TestHelper.testdata_path(apk_name)
+ adb.run(['uninstall', package_name])
+ adb.check_run(['install', '-t', apk_path])
+ # Without sleep, the activity may be killed by post install intent ACTION_PACKAGE_CHANGED.
+ time.sleep(3)
+ adb.check_run(['shell', 'am', 'start', '-n', package_name + '/.MainActivity'])
+ # step 3: Wait until the app exits.
+ time.sleep(4)
+ while True:
+ result = adb.run(['shell', 'pidof', package_name])
+ if not result:
+ break
+ time.sleep(1)
+ # step 4: Collect recording data.
+ remove('simpleperf_data')
+ self.run_cmd(['api_profiler.py', 'collect', '-p', package_name, '-o', 'simpleperf_data'])
+ # step 5: Check recording data.
+ names = os.listdir('simpleperf_data')
+ self.assertGreater(len(names), 0)
+ for name in names:
+ path = os.path.join('simpleperf_data', name)
+ remove('report.txt')
+ self.run_cmd(['report.py', '-g', '-o', 'report.txt', '-i', path])
+ self.check_strings_in_file('report.txt', expected_reports)
+ # step 6: Clean up.
+ adb.check_run(['uninstall', package_name])
+
+ def run_cpp_api_test(self, apk_name, min_android_version):
+ self.run_api_test('simpleperf.demo.cpp_api', apk_name, ['BusyThreadFunc'],
+ min_android_version)
+
+ def test_cpp_api_on_a_debuggable_app_targeting_prev_q(self):
+ # The source code of the apk is in simpleperf/demo/CppApi (with a small change to exit
+ # after recording).
+ self.run_cpp_api_test('cpp_api-debug_prev_Q.apk', 'N')
+
+ def test_cpp_api_on_a_debuggable_app_targeting_q(self):
+ self.run_cpp_api_test('cpp_api-debug_Q.apk', 'N')
+
+ def test_cpp_api_on_a_profileable_app_targeting_prev_q(self):
+ # a release apk with <profileable android:shell="true" />
+ self.run_cpp_api_test('cpp_api-profile_prev_Q.apk', 'Q')
+
+ def test_cpp_api_on_a_profileable_app_targeting_q(self):
+ self.run_cpp_api_test('cpp_api-profile_Q.apk', 'Q')
+
+ def run_java_api_test(self, apk_name, min_android_version):
+ self.run_api_test('simpleperf.demo.java_api', apk_name,
+ ['simpleperf.demo.java_api.MainActivity', 'java.lang.Thread.run'],
+ min_android_version)
+
+ def test_java_api_on_a_debuggable_app_targeting_prev_q(self):
+ # The source code of the apk is in simpleperf/demo/JavaApi (with a small change to exit
+ # after recording).
+ self.run_java_api_test('java_api-debug_prev_Q.apk', 'P')
+
+ def test_java_api_on_a_debuggable_app_targeting_q(self):
+ self.run_java_api_test('java_api-debug_Q.apk', 'P')
+
+ def test_java_api_on_a_profileable_app_targeting_prev_q(self):
+ # a release apk with <profileable android:shell="true" />
+ self.run_java_api_test('java_api-profile_prev_Q.apk', 'Q')
+
+ def test_java_api_on_a_profileable_app_targeting_q(self):
+ self.run_java_api_test('java_api-profile_Q.apk', 'Q')
diff --git a/simpleperf/scripts/test/app_profiler_test.py b/simpleperf/scripts/test/app_profiler_test.py
new file mode 100644
index 00000000..72c68d24
--- /dev/null
+++ b/simpleperf/scripts/test/app_profiler_test.py
@@ -0,0 +1,141 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+from app_profiler import NativeLibDownloader
+import shutil
+
+from simpleperf_utils import str_to_bytes, bytes_to_str, remove
+from . test_utils import TestBase, TestHelper, INFERNO_SCRIPT
+
+
+class TestNativeProfiling(TestBase):
+ def setUp(self):
+ super(TestNativeProfiling, self).setUp()
+ self.is_rooted_device = TestHelper.adb.switch_to_root()
+
+ def test_profile_cmd(self):
+ self.run_cmd(["app_profiler.py", "-cmd", "pm -l", "--disable_adb_root"])
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+
+ def test_profile_native_program(self):
+ if not self.is_rooted_device:
+ return
+ self.run_cmd(["app_profiler.py", "-np", "surfaceflinger"])
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+ self.run_cmd([INFERNO_SCRIPT, "-sc"])
+ self.run_cmd([INFERNO_SCRIPT, "-np", "surfaceflinger"])
+
+ def test_profile_pids(self):
+ if not self.is_rooted_device:
+ return
+ pid = int(TestHelper.adb.check_run_and_return_output(['shell', 'pidof', 'system_server']))
+ self.run_cmd(['app_profiler.py', '--pid', str(pid), '-r', '--duration 1'])
+ self.run_cmd(['app_profiler.py', '--pid', str(pid), str(pid), '-r', '--duration 1'])
+ self.run_cmd(['app_profiler.py', '--tid', str(pid), '-r', '--duration 1'])
+ self.run_cmd(['app_profiler.py', '--tid', str(pid), str(pid), '-r', '--duration 1'])
+ self.run_cmd([INFERNO_SCRIPT, '--pid', str(pid), '-t', '1'])
+
+ def test_profile_system_wide(self):
+ if not self.is_rooted_device:
+ return
+ self.run_cmd(['app_profiler.py', '--system_wide', '-r', '--duration 1'])
+
+
+class TestNativeLibDownloader(TestBase):
+ def setUp(self):
+ super(TestNativeLibDownloader, self).setUp()
+ self.adb = TestHelper.adb
+ self.adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/native_libs'])
+ self.ndk_path = TestHelper.ndk_path
+
+ def tearDown(self):
+ self.adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/native_libs'])
+ super(TestNativeLibDownloader, self).tearDown()
+
+ def list_lib_on_device(self, path):
+ result, output = self.adb.run_and_return_output(['shell', 'ls', '-llc', path])
+ return output if result else ''
+
+ def test_smoke(self):
+ # Sync all native libs on device.
+ downloader = NativeLibDownloader(self.ndk_path, 'arm64', self.adb)
+ downloader.collect_native_libs_on_host(TestHelper.testdata_path(
+ 'SimpleperfExampleWithNative/app/build/intermediates/cmake/profiling'))
+ self.assertEqual(len(downloader.host_build_id_map), 2)
+ for entry in downloader.host_build_id_map.values():
+ self.assertEqual(entry.score, 3)
+ downloader.collect_native_libs_on_device()
+ self.assertEqual(len(downloader.device_build_id_map), 0)
+
+ lib_list = list(downloader.host_build_id_map.items())
+ for sync_count in [0, 1, 2]:
+ build_id_map = {}
+ for i in range(sync_count):
+ build_id_map[lib_list[i][0]] = lib_list[i][1]
+ downloader.host_build_id_map = build_id_map
+ downloader.sync_native_libs_on_device()
+ downloader.collect_native_libs_on_device()
+ self.assertEqual(len(downloader.device_build_id_map), sync_count)
+ for i, item in enumerate(lib_list):
+ build_id = item[0]
+ name = item[1].name
+ if i < sync_count:
+ self.assertTrue(build_id in downloader.device_build_id_map)
+ self.assertEqual(name, downloader.device_build_id_map[build_id])
+ self.assertTrue(self.list_lib_on_device(downloader.dir_on_device + name))
+ else:
+ self.assertTrue(build_id not in downloader.device_build_id_map)
+ self.assertFalse(self.list_lib_on_device(downloader.dir_on_device + name))
+ if sync_count == 1:
+ self.adb.run(['pull', '/data/local/tmp/native_libs/build_id_list',
+ 'build_id_list'])
+ with open('build_id_list', 'rb') as fh:
+ self.assertEqual(bytes_to_str(fh.read()),
+ '{}={}\n'.format(lib_list[0][0], lib_list[0][1].name))
+ remove('build_id_list')
+
+ def test_handle_wrong_build_id_list(self):
+ with open('build_id_list', 'wb') as fh:
+ fh.write(str_to_bytes('fake_build_id=binary_not_exist\n'))
+ self.adb.check_run(['shell', 'mkdir', '-p', '/data/local/tmp/native_libs'])
+ self.adb.check_run(['push', 'build_id_list', '/data/local/tmp/native_libs'])
+ remove('build_id_list')
+ downloader = NativeLibDownloader(self.ndk_path, 'arm64', self.adb)
+ downloader.collect_native_libs_on_device()
+ self.assertEqual(len(downloader.device_build_id_map), 0)
+
+ def test_download_file_without_build_id(self):
+ downloader = NativeLibDownloader(self.ndk_path, 'x86_64', self.adb)
+ name = 'elf.so'
+ shutil.copyfile(TestHelper.testdata_path('data/symfs_without_build_id/elf'), name)
+ downloader.collect_native_libs_on_host('.')
+ downloader.collect_native_libs_on_device()
+ self.assertIn(name, downloader.no_build_id_file_map)
+ # Check if file without build id can be downloaded.
+ downloader.sync_native_libs_on_device()
+ target_file = downloader.dir_on_device + name
+ target_file_stat = self.list_lib_on_device(target_file)
+ self.assertTrue(target_file_stat)
+
+ # No need to re-download if file size doesn't change.
+ downloader.sync_native_libs_on_device()
+ self.assertEqual(target_file_stat, self.list_lib_on_device(target_file))
+
+ # Need to re-download if file size changes.
+ self.adb.check_run(['shell', 'truncate', '-s', '0', target_file])
+ target_file_stat = self.list_lib_on_device(target_file)
+ downloader.sync_native_libs_on_device()
+ self.assertNotEqual(target_file_stat, self.list_lib_on_device(target_file))
diff --git a/simpleperf/scripts/test/app_test.py b/simpleperf/scripts/test/app_test.py
new file mode 100644
index 00000000..c3a152bf
--- /dev/null
+++ b/simpleperf/scripts/test/app_test.py
@@ -0,0 +1,266 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+import os
+import re
+import shutil
+import subprocess
+
+from simpleperf_utils import remove
+from . test_utils import TestBase, TestHelper, AdbHelper, INFERNO_SCRIPT
+
+
+class TestExampleBase(TestBase):
+ @classmethod
+ def prepare(cls, example_name, package_name, activity_name, abi=None, adb_root=False):
+ cls.adb = AdbHelper(enable_switch_to_root=adb_root)
+ cls.example_path = TestHelper.testdata_path(example_name)
+ if not os.path.isdir(cls.example_path):
+ log_fatal("can't find " + cls.example_path)
+ for root, _, files in os.walk(cls.example_path):
+ if 'app-profiling.apk' in files:
+ cls.apk_path = os.path.join(root, 'app-profiling.apk')
+ break
+ if not hasattr(cls, 'apk_path'):
+ log_fatal("can't find app-profiling.apk under " + cls.example_path)
+ cls.package_name = package_name
+ cls.activity_name = activity_name
+ args = ["install", "-r"]
+ if abi:
+ args += ["--abi", abi]
+ args.append(cls.apk_path)
+ cls.adb.check_run(args)
+ cls.adb_root = adb_root
+ cls.has_perf_data_for_report = False
+ # On Android >= P (version 9), we can profile JITed and interpreted Java code.
+ # So only compile Java code on Android <= O (version 8).
+ cls.use_compiled_java_code = TestHelper.android_version <= 8
+ cls.testcase_dir = TestHelper.get_test_dir(cls.__name__)
+
+ @classmethod
+ def tearDownClass(cls):
+ remove(cls.testcase_dir)
+ if hasattr(cls, 'package_name'):
+ cls.adb.check_run(["uninstall", cls.package_name])
+
+ def setUp(self):
+ super(TestExampleBase, self).setUp()
+ 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
+ # generate it for each test.
+ if not os.path.isdir(self.testcase_dir):
+ os.makedirs(self.testcase_dir)
+ os.chdir(self.testcase_dir)
+ self.run_app_profiler(compile_java_code=self.use_compiled_java_code)
+ os.chdir(self.test_dir)
+
+ for name in os.listdir(self.testcase_dir):
+ path = os.path.join(self.testcase_dir, name)
+ if os.path.isfile(path):
+ shutil.copy(path, self.test_dir)
+ elif os.path.isdir(path):
+ shutil.copytree(path, os.path.join(self.test_dir, name))
+
+ def run(self, result=None):
+ self.__class__.test_result = result
+ super(TestExampleBase, self).run(result)
+
+ def run_app_profiler(self, record_arg="-g --duration 10", build_binary_cache=True,
+ start_activity=True, compile_java_code=False):
+ args = ['app_profiler.py', '--app', self.package_name, '-r', record_arg, '-o', 'perf.data']
+ if not build_binary_cache:
+ args.append("-nb")
+ if compile_java_code:
+ args.append('--compile_java_code')
+ if start_activity:
+ args += ["-a", self.activity_name]
+ args += ["-lib", self.example_path]
+ if not self.adb_root:
+ args.append("--disable_adb_root")
+ self.run_cmd(args)
+ self.check_exist(filename="perf.data")
+ if build_binary_cache:
+ self.check_exist(dirname="binary_cache")
+
+ def check_file_under_dir(self, dirname, filename):
+ self.check_exist(dirname=dirname)
+ for _, _, files in os.walk(dirname):
+ for f in files:
+ if f == filename:
+ return
+ self.fail("Failed to call check_file_under_dir(dir=%s, file=%s)" % (dirname, filename))
+
+ def check_annotation_summary(self, summary_file, check_entries):
+ """ check_entries is a list of (name, accumulated_period, period).
+ This function checks for each entry, if the line containing [name]
+ has at least required accumulated_period and period.
+ """
+ self.check_exist(filename=summary_file)
+ with open(summary_file, 'r') as fh:
+ summary = fh.read()
+ fulfilled = [False for x in check_entries]
+ summary_check_re = re.compile(r'accumulated_period:\s*([\d.]+)%.*period:\s*([\d.]+)%')
+ for line in summary.split('\n'):
+ for i, (name, need_acc_period, need_period) in enumerate(check_entries):
+ if not fulfilled[i] and name in line:
+ m = summary_check_re.search(line)
+ if m:
+ acc_period = float(m.group(1))
+ period = float(m.group(2))
+ if acc_period >= need_acc_period and period >= need_period:
+ fulfilled[i] = True
+
+ self.check_fulfilled_entries(fulfilled, check_entries)
+
+ def check_inferno_report_html(self, check_entries, filename="report.html"):
+ self.check_exist(filename=filename)
+ with open(filename, 'r') as fh:
+ data = fh.read()
+ fulfilled = [False for _ in check_entries]
+ for line in data.split('\n'):
+ # each entry is a (function_name, min_percentage) pair.
+ for i, entry in enumerate(check_entries):
+ if fulfilled[i] or line.find(entry[0]) == -1:
+ continue
+ m = re.search(r'(\d+\.\d+)%', line)
+ if m and float(m.group(1)) >= entry[1]:
+ fulfilled[i] = True
+ break
+ self.check_fulfilled_entries(fulfilled, check_entries)
+
+ def common_test_app_profiler(self):
+ self.run_cmd(["app_profiler.py", "-h"])
+ remove("binary_cache")
+ self.run_app_profiler(build_binary_cache=False)
+ self.assertFalse(os.path.isdir("binary_cache"))
+ args = ["binary_cache_builder.py"]
+ if not self.adb_root:
+ args.append("--disable_adb_root")
+ self.run_cmd(args)
+ self.check_exist(dirname="binary_cache")
+ remove("binary_cache")
+ self.run_app_profiler(build_binary_cache=True)
+ self.run_app_profiler()
+ self.run_app_profiler(start_activity=False)
+
+ def common_test_report(self):
+ self.run_cmd(["report.py", "-h"])
+ self.run_cmd(["report.py"])
+ self.run_cmd(["report.py", "-i", "perf.data"])
+ self.run_cmd(["report.py", "-g"])
+ self.run_cmd(["report.py", "--self-kill-for-testing", "-g", "--gui"])
+
+ def common_test_annotate(self):
+ self.run_cmd(["annotate.py", "-h"])
+ remove("annotated_files")
+ self.run_cmd(["annotate.py", "-s", self.example_path])
+ self.check_exist(dirname="annotated_files")
+
+ def common_test_report_sample(self, check_strings):
+ self.run_cmd(["report_sample.py", "-h"])
+ self.run_cmd(["report_sample.py"])
+ output = self.run_cmd(["report_sample.py", "perf.data"], return_output=True)
+ self.check_strings_in_content(output, check_strings)
+
+ def common_test_pprof_proto_generator(self, check_strings_with_lines,
+ check_strings_without_lines):
+ self.run_cmd(["pprof_proto_generator.py", "-h"])
+ self.run_cmd(["pprof_proto_generator.py"])
+ remove("pprof.profile")
+ self.run_cmd(["pprof_proto_generator.py", "-i", "perf.data", "-o", "pprof.profile"])
+ self.check_exist(filename="pprof.profile")
+ self.run_cmd(["pprof_proto_generator.py", "--show"])
+ output = self.run_cmd(["pprof_proto_generator.py", "--show", "pprof.profile"],
+ return_output=True)
+ self.check_strings_in_content(output, check_strings_with_lines + ["has_line_numbers: True"])
+ remove("binary_cache")
+ self.run_cmd(["pprof_proto_generator.py"])
+ output = self.run_cmd(["pprof_proto_generator.py", "--show", "pprof.profile"],
+ return_output=True)
+ self.check_strings_in_content(output, check_strings_without_lines +
+ ["has_line_numbers: False"])
+
+ def common_test_inferno(self):
+ self.run_cmd([INFERNO_SCRIPT, "-h"])
+ remove("perf.data")
+ append_args = [] if self.adb_root else ["--disable_adb_root"]
+ self.run_cmd([INFERNO_SCRIPT, "-p", self.package_name, "-t", "3"] + append_args)
+ self.check_exist(filename="perf.data")
+ self.run_cmd([INFERNO_SCRIPT, "-p", self.package_name, "-f", "1000", "-du", "-t", "1"] +
+ append_args)
+ self.run_cmd([INFERNO_SCRIPT, "-p", self.package_name, "-e", "100000 cpu-cycles",
+ "-t", "1"] + append_args)
+ self.run_cmd([INFERNO_SCRIPT, "-sc"])
+
+ def common_test_report_html(self):
+ self.run_cmd(['report_html.py', '-h'])
+ self.run_cmd(['report_html.py'])
+ self.run_cmd(['report_html.py', '--add_source_code', '--source_dirs', 'testdata'])
+ self.run_cmd(['report_html.py', '--add_disassembly'])
+ # Test with multiple perf.data.
+ shutil.move('perf.data', 'perf2.data')
+ self.run_app_profiler(record_arg='-g -f 1000 --duration 3 -e task-clock:u')
+ self.run_cmd(['report_html.py', '-i', 'perf.data', 'perf2.data'])
+
+
+class TestRecordingRealApps(TestBase):
+ def setUp(self):
+ super(TestRecordingRealApps, self).setUp()
+ self.adb = TestHelper.adb
+ self.installed_packages = []
+
+ def tearDown(self):
+ for package in self.installed_packages:
+ self.adb.run(['shell', 'pm', 'uninstall', package])
+ super(TestRecordingRealApps, self).tearDown()
+
+ def install_apk(self, apk_path, package_name):
+ self.adb.run(['uninstall', package_name])
+ self.adb.run(['install', '-t', apk_path])
+ self.installed_packages.append(package_name)
+
+ def start_app(self, start_cmd):
+ subprocess.Popen(self.adb.adb_path + ' ' + start_cmd, shell=True,
+ stdout=TestHelper.log_fh, stderr=TestHelper.log_fh)
+
+ def record_data(self, package_name, record_arg):
+ self.run_cmd(['app_profiler.py', '--app', package_name, '-r', record_arg])
+
+ def check_symbol_in_record_file(self, symbol_name):
+ self.run_cmd(['report.py', '--children', '-o', 'report.txt'])
+ self.check_strings_in_file('report.txt', [symbol_name])
+
+ def test_recording_displaybitmaps(self):
+ self.install_apk(TestHelper.testdata_path('DisplayBitmaps.apk'),
+ 'com.example.android.displayingbitmaps')
+ self.install_apk(TestHelper.testdata_path('DisplayBitmapsTest.apk'),
+ 'com.example.android.displayingbitmaps.test')
+ self.start_app('shell am instrument -w -r -e debug false -e class ' +
+ 'com.example.android.displayingbitmaps.tests.GridViewTest ' +
+ 'com.example.android.displayingbitmaps.test/' +
+ 'androidx.test.runner.AndroidJUnitRunner')
+ self.record_data('com.example.android.displayingbitmaps', '-e cpu-clock -g --duration 10')
+ if TestHelper.android_version >= 9:
+ self.check_symbol_in_record_file('androidx.test.espresso')
+
+ 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')
+ self.check_symbol_in_record_file('PlayScene::DoFrame')
diff --git a/simpleperf/scripts/test/binary_cache_builder_test.py b/simpleperf/scripts/test/binary_cache_builder_test.py
new file mode 100644
index 00000000..cf71783c
--- /dev/null
+++ b/simpleperf/scripts/test/binary_cache_builder_test.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+import filecmp
+import os
+from pathlib import Path
+import shutil
+import tempfile
+
+from binary_cache_builder import BinaryCacheBuilder
+from simpleperf_utils import ReadElf, remove, ToolFinder
+from . test_utils import TestBase, TestHelper
+
+
+class TestBinaryCacheBuilder(TestBase):
+ def test_copy_binaries_from_symfs_dirs(self):
+ readelf = ReadElf(TestHelper.ndk_path)
+ strip = ToolFinder.find_tool_path('strip', arch='arm')
+ self.assertIsNotNone(strip)
+ symfs_dir = os.path.join(self.test_dir, 'symfs_dir')
+ remove(symfs_dir)
+ os.mkdir(symfs_dir)
+ filename = 'simpleperf_runtest_two_functions_arm'
+ origin_file = TestHelper.testdata_path(filename)
+ source_file = os.path.join(symfs_dir, filename)
+ target_file = os.path.join('binary_cache', filename)
+ expected_build_id = readelf.get_build_id(origin_file)
+ binary_cache_builder = BinaryCacheBuilder(TestHelper.ndk_path, False)
+ binary_cache_builder.binaries['simpleperf_runtest_two_functions_arm'] = expected_build_id
+
+ # Copy binary if target file doesn't exist.
+ remove(target_file)
+ self.run_cmd([strip, '--strip-all', '-o', source_file, origin_file])
+ binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir])
+ self.assertTrue(filecmp.cmp(target_file, source_file))
+
+ # Copy binary if target file doesn't have .symtab and source file has .symtab.
+ self.run_cmd([strip, '--strip-debug', '-o', source_file, origin_file])
+ binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir])
+ self.assertTrue(filecmp.cmp(target_file, source_file))
+
+ # Copy binary if target file doesn't have .debug_line and source_files has .debug_line.
+ shutil.copy(origin_file, source_file)
+ binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir])
+ self.assertTrue(filecmp.cmp(target_file, source_file))
+
+ def test_copy_elf_without_build_id_from_symfs_dir(self):
+ binary_cache_builder = BinaryCacheBuilder(TestHelper.ndk_path, False)
+ binary_cache_builder.binaries['elf'] = ''
+ symfs_dir = TestHelper.testdata_path('data/symfs_without_build_id')
+ source_file = os.path.join(symfs_dir, 'elf')
+ target_file = os.path.join('binary_cache', 'elf')
+ binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir])
+ self.assertTrue(filecmp.cmp(target_file, source_file))
+ binary_cache_builder.pull_binaries_from_device()
+ self.assertTrue(filecmp.cmp(target_file, source_file))
+
+ def test_prefer_binary_with_debug_info(self):
+ binary_cache_builder = BinaryCacheBuilder(TestHelper.ndk_path, False)
+ binary_cache_builder.collect_used_binaries(
+ TestHelper.testdata_path('runtest_two_functions_arm64_perf.data'))
+
+ # Create a symfs_dir, which contains elf file with and without debug info.
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ shutil.copy(
+ TestHelper.testdata_path(
+ 'simpleperf_runtest_two_functions_arm64_without_debug_info'),
+ Path(tmp_dir) / 'simpleperf_runtest_two_functions_arm64')
+
+ debug_dir = Path(tmp_dir) / 'debug'
+ debug_dir.mkdir()
+ shutil.copy(TestHelper.testdata_path(
+ 'simpleperf_runtest_two_functions_arm64'), debug_dir)
+ # Check if the elf file with debug info is chosen.
+ binary_cache_builder.copy_binaries_from_symfs_dirs([tmp_dir])
+ elf_path = (Path(binary_cache_builder.binary_cache_dir) / 'data' /
+ 'local' / 'tmp' / 'simpleperf_runtest_two_functions_arm64')
+ self.assertTrue(elf_path.is_file())
+ self.assertIn('.debug_info', binary_cache_builder.readelf.get_sections(elf_path))
+
+ def test_create_build_id_list(self):
+ symfs_dir = TestHelper.testdata_dir
+ binary_cache_builder = BinaryCacheBuilder(TestHelper.ndk_path, False)
+ binary_cache_builder.collect_used_binaries(
+ TestHelper.testdata_path('runtest_two_functions_arm64_perf.data'))
+ binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir])
+ elf_path = (Path(binary_cache_builder.binary_cache_dir) / 'data' /
+ 'local' / 'tmp' / 'simpleperf_runtest_two_functions_arm64')
+ self.assertTrue(elf_path.is_file())
+
+ binary_cache_builder.create_build_id_list()
+ build_id_list_path = Path(binary_cache_builder.binary_cache_dir) / 'build_id_list'
+ self.assertTrue(build_id_list_path.is_file())
+ with open(build_id_list_path, 'r') as fh:
+ self.assertIn('simpleperf_runtest_two_functions_arm64', fh.read())
diff --git a/simpleperf/scripts/test/cpp_app_test.py b/simpleperf/scripts/test/cpp_app_test.py
new file mode 100644
index 00000000..8565cb3a
--- /dev/null
+++ b/simpleperf/scripts/test/cpp_app_test.py
@@ -0,0 +1,180 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+import os
+
+from simpleperf_utils import remove
+from . app_test import TestExampleBase
+from . test_utils import INFERNO_SCRIPT, TestHelper
+
+
+class TestExampleWithNative(TestExampleBase):
+ @classmethod
+ def setUpClass(cls):
+ cls.prepare("SimpleperfExampleWithNative",
+ "com.example.simpleperf.simpleperfexamplewithnative",
+ ".MainActivity")
+
+ def test_app_profiler(self):
+ self.common_test_app_profiler()
+
+ def test_app_profiler_profile_from_launch(self):
+ self.run_app_profiler(start_activity=True, build_binary_cache=False)
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+ self.check_strings_in_file("report.txt", ["BusyLoopThread", "__start_thread"])
+
+ def test_report(self):
+ self.common_test_report()
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+ self.check_strings_in_file("report.txt", ["BusyLoopThread", "__start_thread"])
+
+ def test_annotate(self):
+ self.common_test_annotate()
+ self.check_file_under_dir("annotated_files", "native-lib.cpp")
+ summary_file = os.path.join("annotated_files", "summary")
+ self.check_annotation_summary(summary_file, [
+ ("native-lib.cpp", 20, 0),
+ ("BusyLoopThread", 20, 0),
+ ("line 46", 20, 0)])
+
+ def test_report_sample(self):
+ self.common_test_report_sample(
+ ["BusyLoopThread",
+ "__start_thread"])
+
+ def test_pprof_proto_generator(self):
+ check_strings_with_lines = [
+ "native-lib.cpp",
+ "BusyLoopThread",
+ # Check if dso name in perf.data is replaced by binary path in binary_cache.
+ 'filename: binary_cache']
+ self.common_test_pprof_proto_generator(
+ check_strings_with_lines,
+ check_strings_without_lines=["BusyLoopThread"])
+
+ def test_inferno(self):
+ self.common_test_inferno()
+ self.run_app_profiler()
+ self.run_cmd([INFERNO_SCRIPT, "-sc"])
+ self.check_inferno_report_html([('BusyLoopThread', 20)])
+
+ def test_report_html(self):
+ self.common_test_report_html()
+ self.run_cmd(['report_html.py', '--add_source_code', '--source_dirs', 'testdata',
+ '--add_disassembly', '--binary_filter', "libnative-lib.so"])
+
+
+class TestExampleWithNativeRoot(TestExampleBase):
+ @classmethod
+ def setUpClass(cls):
+ cls.prepare("SimpleperfExampleWithNative",
+ "com.example.simpleperf.simpleperfexamplewithnative",
+ ".MainActivity",
+ adb_root=True)
+
+ def test_app_profiler(self):
+ self.common_test_app_profiler()
+
+
+class TestExampleWithNativeTraceOffCpu(TestExampleBase):
+ @classmethod
+ def setUpClass(cls):
+ cls.prepare("SimpleperfExampleWithNative",
+ "com.example.simpleperf.simpleperfexamplewithnative",
+ ".SleepActivity")
+
+ def test_smoke(self):
+ self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-cycles:u --trace-offcpu")
+ self.run_cmd(["report.py", "-g", "--comms", "SleepThread", "-o", "report.txt"])
+ self.check_strings_in_file("report.txt", [
+ "SleepThread(void*)",
+ "RunFunction()",
+ "SleepFunction(unsigned long long)"])
+ remove("annotated_files")
+ self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "SleepThread"])
+ self.check_exist(dirname="annotated_files")
+ self.check_file_under_dir("annotated_files", "native-lib.cpp")
+ summary_file = os.path.join("annotated_files", "summary")
+ self.check_annotation_summary(summary_file, [
+ ("native-lib.cpp", 80, 20),
+ ("SleepThread", 80, 0),
+ ("RunFunction", 20, 20),
+ ("SleepFunction", 20, 0),
+ ("line 73", 20, 0),
+ ("line 83", 20, 0)])
+ self.run_cmd([INFERNO_SCRIPT, "-sc"])
+ self.check_inferno_report_html([('SleepThread', 80),
+ ('RunFunction', 20),
+ ('SleepFunction', 20)])
+
+
+class TestExampleWithNativeJniCall(TestExampleBase):
+ @classmethod
+ def setUpClass(cls):
+ cls.prepare("SimpleperfExampleWithNative",
+ "com.example.simpleperf.simpleperfexamplewithnative",
+ ".MixActivity")
+
+ def test_smoke(self):
+ self.run_app_profiler()
+ self.run_cmd(["report.py", "-g", "--comms", "BusyThread", "-o", "report.txt"])
+ self.check_strings_in_file("report.txt", [
+ "com.example.simpleperf.simpleperfexamplewithnative.MixActivity$1.run",
+ "Java_com_example_simpleperf_simpleperfexamplewithnative_MixActivity_callFunction"])
+ remove("annotated_files")
+ self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "BusyThread"])
+ self.check_exist(dirname="annotated_files")
+ self.check_file_under_dir("annotated_files", "native-lib.cpp")
+ summary_file = os.path.join("annotated_files", "summary")
+ self.check_annotation_summary(summary_file, [("native-lib.cpp", 5, 0), ("line 40", 5, 0)])
+ if self.use_compiled_java_code:
+ self.check_file_under_dir("annotated_files", "MixActivity.java")
+ self.check_annotation_summary(summary_file, [
+ ("MixActivity.java", 80, 0),
+ ("run", 80, 0),
+ ("line 26", 20, 0),
+ ("native-lib.cpp", 5, 0),
+ ("line 40", 5, 0)])
+
+ self.run_cmd([INFERNO_SCRIPT, "-sc"])
+
+
+class TestExampleWithNativeForce32Bit(TestExampleWithNative):
+ @classmethod
+ def setUpClass(cls):
+ cls.prepare("SimpleperfExampleWithNative",
+ "com.example.simpleperf.simpleperfexamplewithnative",
+ ".MainActivity",
+ abi=TestHelper.get_32bit_abi())
+
+
+class TestExampleWithNativeRootForce32Bit(TestExampleWithNativeRoot):
+ @classmethod
+ def setUpClass(cls):
+ cls.prepare("SimpleperfExampleWithNative",
+ "com.example.simpleperf.simpleperfexamplewithnative",
+ ".MainActivity",
+ abi=TestHelper.get_32bit_abi(),
+ adb_root=False)
+
+
+class TestExampleWithNativeTraceOffCpuForce32Bit(TestExampleWithNativeTraceOffCpu):
+ @classmethod
+ def setUpClass(cls):
+ cls.prepare("SimpleperfExampleWithNative",
+ "com.example.simpleperf.simpleperfexamplewithnative",
+ ".SleepActivity",
+ abi=TestHelper.get_32bit_abi())
diff --git a/simpleperf/scripts/test/debug_unwind_reporter_test.py b/simpleperf/scripts/test/debug_unwind_reporter_test.py
new file mode 100644
index 00000000..0e381ac5
--- /dev/null
+++ b/simpleperf/scripts/test/debug_unwind_reporter_test.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+from typing import List
+
+from . test_utils import TestBase, TestHelper
+
+
+class TestDebugUnwindReporter(TestBase):
+ def run_reporter(self, options: List[str]) -> str:
+ report_file = TestHelper.testdata_dir / 'debug_unwind_report.txt'
+ return self.run_cmd(['debug_unwind_reporter.py', '-i',
+ str(report_file)] + options, return_output=True)
+
+ def test_show_callchain_fixed_by_joiner_option(self):
+ output = self.run_reporter([])
+ self.assertFalse('__start_thread' in output)
+ output = self.run_reporter(['--show-callchain-fixed-by-joiner'])
+ self.assertTrue('__start_thread' in output)
+
+ def test_summary_option(self):
+ output = self.run_reporter(['--summary'])
+ self.assertTrue('Error Code' in output)
+
+ def test_error_code_filter_options(self):
+ output = self.run_reporter(['--exclude-error-code', '1'])
+ self.assertFalse('sample_time: 626968109563718' in output)
+ output = self.run_reporter(
+ ['--include-error-code', '4', '--show-callchain-fixed-by-joiner'])
+ self.assertFalse('sample_time: 626968109563718' in output)
+ self.assertTrue('sample_time: 626970513562864' in output)
+
+ def test_end_dso_filter_options(self):
+ output = self.run_reporter(
+ ['--exclude-end-dso', '/apex/com.android.runtime/lib64/bionic/libc.so'])
+ self.assertFalse('sample_time: 626968109563718' in output)
+ output = self.run_reporter(
+ ['--include-end-dso', '/apex/com.android.runtime/lib64/bionic/libc.so'])
+ self.assertTrue('sample_time: 626968109563718' in output)
+
+ def test_end_symbol_filter_options(self):
+ output = self.run_reporter(['--exclude-end-symbol', 'clone'])
+ self.assertFalse('sample_time: 626968109563718' in output)
+ output = self.run_reporter(
+ ['--include-end-symbol', '__start_thread', '--show-callchain-fixed-by-joiner'])
+ self.assertFalse('sample_time: 626968109563718' in output)
+ self.assertTrue('sample_time: 626970513562864' in output)
+
+ def test_sample_time_filter_options(self):
+ output = self.run_reporter(['--exclude-sample-time', '626968109563718'])
+ self.assertFalse('sample_time: 626968109563718' in output)
+ output = self.run_reporter(
+ ['--include-sample-time', '626970513562864', '--show-callchain-fixed-by-joiner'])
+ self.assertFalse('sample_time: 626968109563718' in output)
+ self.assertTrue('sample_time: 626970513562864' in output)
diff --git a/simpleperf/scripts/test/do_test.py b/simpleperf/scripts/test/do_test.py
new file mode 100755
index 00000000..946390df
--- /dev/null
+++ b/simpleperf/scripts/test/do_test.py
@@ -0,0 +1,491 @@
+#!/usr/bin/env python3
+#
+# 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.
+# 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.
+#
+"""Release test for simpleperf prebuilts.
+
+It includes below tests:
+1. Test profiling Android apps on different Android versions (starting from Android N).
+2. Test simpleperf python scripts on different Hosts (linux, darwin and windows) on x86_64.
+3. Test using both devices and emulators.
+4. Test using both `adb root` and `adb unroot`.
+
+"""
+
+import argparse
+from dataclasses import dataclass
+import fnmatch
+import inspect
+import multiprocessing as mp
+import os
+from pathlib import Path
+import re
+import sys
+import time
+from tqdm import tqdm
+import types
+from typing import List, Optional
+import unittest
+
+from simpleperf_utils import extant_dir, log_exit, remove, ArgParseFormatter
+
+from . api_profiler_test import *
+from . app_profiler_test import *
+from . app_test import *
+from . binary_cache_builder_test import *
+from . cpp_app_test import *
+from . debug_unwind_reporter_test import *
+from . inferno_test import *
+from . java_app_test import *
+from . kotlin_app_test import *
+from . pprof_proto_generator_test import *
+from . purgatorio_test import *
+from . report_html_test import *
+from . report_lib_test import *
+from . run_simpleperf_on_device_test import *
+from . tools_test import *
+from . test_utils import TestHelper
+
+
+def get_args() -> argparse.Namespace:
+ parser = argparse.ArgumentParser(description=__doc__, formatter_class=ArgParseFormatter)
+ parser.add_argument('--browser', action='store_true', help='open report html file in browser.')
+ parser.add_argument(
+ '-d', '--device', nargs='+',
+ help='set devices used to run tests. Each device in format name:serial-number')
+ parser.add_argument('--only-host-test', action='store_true', help='Only run host tests')
+ parser.add_argument('--list-tests', action='store_true', help='List tests')
+ parser.add_argument('--ndk-path', type=extant_dir, help='Set the path of a ndk release')
+ parser.add_argument('-p', '--pattern', nargs='+',
+ help='Run tests matching the selected pattern.')
+ parser.add_argument('-r', '--repeat', type=int, default=1, help='times to repeat tests')
+ parser.add_argument('--test-from', help='Run tests following the selected test.')
+ parser.add_argument('--test-dir', default='test_dir', help='Directory to store test results')
+ return parser.parse_args()
+
+
+def get_all_tests() -> List[str]:
+ tests = []
+ for name, value in globals().items():
+ if isinstance(value, type) and issubclass(value, unittest.TestCase):
+ for member_name, member in inspect.getmembers(value):
+ if isinstance(member, (types.MethodType, types.FunctionType)):
+ if member_name.startswith('test'):
+ tests.append(name + '.' + member_name)
+ return sorted(tests)
+
+
+def get_host_tests() -> List[str]:
+ def filter_fn(test: str) -> bool:
+ return get_test_type(test) == 'host_test'
+ return list(filter(filter_fn, get_all_tests()))
+
+
+def get_filtered_tests(
+ tests: List[str],
+ test_from: Optional[str],
+ test_pattern: Optional[List[str]]) -> List[str]:
+ if test_from:
+ try:
+ tests = tests[tests.index(test_from):]
+ except ValueError:
+ log_exit("Can't find test %s" % test_from)
+ if test_pattern:
+ patterns = [re.compile(fnmatch.translate(x)) for x in test_pattern]
+ tests = [t for t in tests if any(pattern.match(t) for pattern in patterns)]
+ if not tests:
+ log_exit('No tests are matched.')
+ return tests
+
+
+def get_test_type(test: str) -> Optional[str]:
+ testcase_name, test_name = test.split('.')
+ if test_name == 'test_run_simpleperf_without_usb_connection':
+ return 'device_serialized_test'
+ if testcase_name in (
+ 'TestApiProfiler', 'TestNativeProfiling', 'TestNativeLibDownloader',
+ 'TestRecordingRealApps', 'TestRunSimpleperfOnDevice'):
+ return 'device_test'
+ if testcase_name.startswith('TestExample'):
+ return 'device_test'
+ if testcase_name in ('TestBinaryCacheBuilder', 'TestDebugUnwindReporter', 'TestInferno',
+ 'TestPprofProtoGenerator', 'TestPurgatorio', 'TestReportHtml',
+ 'TestReportLib', 'TestTools'):
+ return 'host_test'
+ return None
+
+
+def build_testdata(testdata_dir: Path):
+ """ Collect testdata in testdata_dir.
+ In system/extras/simpleperf/scripts, testdata comes from:
+ <script_dir>/../testdata, <script_dir>/test/script_testdata, <script_dir>/../demo
+ In prebuilts/simpleperf, testdata comes from:
+ <script_dir>/test/testdata
+ """
+ testdata_dir.mkdir()
+
+ script_test_dir = Path(__file__).resolve().parent
+ script_dir = script_test_dir.parent
+
+ source_dirs = [
+ script_test_dir / 'script_testdata',
+ script_test_dir / 'testdata',
+ script_dir.parent / 'testdata',
+ script_dir.parent / 'demo',
+ script_dir.parent / 'runtest',
+ ]
+
+ for source_dir in source_dirs:
+ if not source_dir.is_dir():
+ continue
+ for src_path in source_dir.iterdir():
+ dest_path = testdata_dir / src_path.name
+ if dest_path.exists():
+ continue
+ if src_path.is_file():
+ shutil.copyfile(src_path, dest_path)
+ elif src_path.is_dir():
+ shutil.copytree(src_path, dest_path)
+
+
+def run_tests(tests: List[str]) -> bool:
+ argv = [sys.argv[0]] + tests
+ test_runner = unittest.TextTestRunner(stream=TestHelper.log_fh, verbosity=0)
+ test_program = unittest.main(argv=argv, testRunner=test_runner,
+ exit=False, verbosity=0, module='test.do_test')
+ return test_program.result.wasSuccessful()
+
+
+def test_process_entry(tests: List[str], test_options: List[str], conn: mp.connection.Connection):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--browser', action='store_true')
+ parser.add_argument('--device', help='android device serial number')
+ parser.add_argument('--ndk-path', type=extant_dir)
+ parser.add_argument('--testdata-dir', type=extant_dir)
+ parser.add_argument('--test-dir', help='directory to store test results')
+ args = parser.parse_args(test_options)
+
+ TestHelper.init(args.test_dir, args.testdata_dir,
+ args.browser, args.ndk_path, args.device, conn)
+ run_tests(tests)
+
+
+@dataclass
+class Device:
+ name: str
+ serial_number: str
+
+
+@dataclass
+class TestResult:
+ try_time: int
+ ok: bool
+
+
+class TestProcess:
+ """ Create a test process to run selected tests on a device. """
+
+ TEST_MAX_TRY_TIME = 10
+ TEST_TIMEOUT_IN_SEC = 10 * 60
+
+ def __init__(
+ self, test_type: str, tests: List[str],
+ device: Optional[Device],
+ repeat_index: int,
+ test_options: List[str]):
+ self.test_type = test_type
+ self.tests = tests
+ self.device = device
+ self.repeat_index = repeat_index
+ self.test_options = test_options
+ self.try_time = 1
+ self.test_results: Dict[str, TestResult] = {}
+ self.parent_conn: Optional[mp.connection.Connection] = None
+ self.proc: Optional[mp.Process] = None
+ self.last_update_time = 0.0
+ self._start_test_process()
+
+ def _start_test_process(self):
+ unfinished_tests = [test for test in self.tests if test not in self.test_results]
+ self.parent_conn, child_conn = mp.Pipe(duplex=False)
+ test_options = self.test_options[:]
+ test_options += ['--test-dir', str(self.test_dir)]
+ if self.device:
+ test_options += ['--device', self.device.serial_number]
+ self.proc = mp.Process(target=test_process_entry, args=(
+ unfinished_tests, test_options, child_conn))
+ self.proc.start()
+ self.last_update_time = time.time()
+
+ @property
+ def name(self) -> str:
+ name = self.test_type
+ if self.device:
+ name += '_' + self.device.name
+ name += '_repeat_%d' % self.repeat_index
+ return name
+
+ @property
+ def test_dir(self) -> Path:
+ """ Directory to run the tests. """
+ return Path.cwd() / (self.name + '_try_%d' % self.try_time)
+
+ @property
+ def alive(self) -> bool:
+ """ Return if the test process is alive. """
+ return self.proc.is_alive()
+
+ @property
+ def finished(self) -> bool:
+ """ Return if all tests are finished. """
+ return len(self.test_results) == len(self.tests)
+
+ def check_update(self):
+ """ Check if there is any test update. """
+ try:
+ while self.parent_conn.poll():
+ msg = self.parent_conn.recv()
+ self._process_msg(msg)
+ self.last_update_time = time.time()
+ except (EOFError, BrokenPipeError) as e:
+ pass
+ if time.time() - self.last_update_time > TestProcess.TEST_TIMEOUT_IN_SEC:
+ self.proc.terminate()
+
+ def _process_msg(self, msg: str):
+ test_name, test_success = msg.split()
+ test_success = test_success == 'OK'
+ self.test_results[test_name] = TestResult(self.try_time, test_success)
+
+ def join(self):
+ self.proc.join()
+
+ def restart(self) -> bool:
+ """ Create a new test process to run unfinished tests. """
+ if self.finished:
+ return False
+ if self.try_time == self.TEST_MAX_TRY_TIME:
+ """ Exceed max try time. So mark left tests as failed. """
+ for test in self.tests:
+ if test not in self.test_results:
+ self.test_results[test] = TestResult(self.try_time, False)
+ return False
+
+ self.try_time += 1
+ self._start_test_process()
+ return True
+
+
+class ProgressBar:
+ def __init__(self, total_count: int):
+ self.total_bar = tqdm(
+ total=total_count, desc='test progress', ascii=' ##',
+ bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}, {rate_fmt}", position=0)
+ self.test_process_bars: Dict[str, tqdm] = {}
+
+ def update(self, test_proc: TestProcess):
+ if test_proc.name not in self.test_process_bars:
+ if not test_proc.alive:
+ return
+ bar = tqdm(total=len(test_proc.tests),
+ desc=test_proc.name, ascii=' ##',
+ bar_format="{l_bar}{bar} | {n_fmt}/{total_fmt} [{elapsed}]")
+ self.test_process_bars[test_proc.name] = bar
+ else:
+ bar = self.test_process_bars[test_proc.name]
+
+ add = len(test_proc.test_results) - bar.n
+ if add:
+ bar.update(add)
+ self.total_bar.update(add)
+ if not test_proc.alive:
+ bar.close()
+ del self.test_process_bars[test_proc.name]
+
+ def end_tests(self):
+ for bar in self.test_process_bars.values():
+ bar.close()
+ self.total_bar.close()
+
+
+class TestSummary:
+ def __init__(self, test_count: int):
+ self.summary_fh = open('test_summary.txt', 'w')
+ self.failed_summary_fh = open('failed_test_summary.txt', 'w')
+ self.results: Dict[Tuple[str, str], bool] = {}
+ self.test_count = test_count
+
+ @property
+ def failed_test_count(self) -> int:
+ return self.test_count - sum(1 for result in self.results.values() if result)
+
+ def update(self, test_proc: TestProcess):
+ for test, result in test_proc.test_results.items():
+ key = (test, '%s_try_%s' % (test_proc.name, result.try_time))
+ if key not in self.results:
+ self.results[key] = result.ok
+ self._write_result(key[0], key[1], result.ok)
+
+ def _write_result(self, test_name: str, test_env: str, test_result: bool):
+ print(
+ '%s %s %s' % (test_name, test_env, 'OK' if test_result else 'FAILED'),
+ file=self.summary_fh, flush=True)
+ if not test_result:
+ print('%s %s FAILED' % (test_name, test_env),
+ file=self.failed_summary_fh, flush=True)
+
+ def end_tests(self):
+ # Show sorted results after testing.
+ self.summary_fh.seek(0, 0)
+ self.failed_summary_fh.seek(0, 0)
+ for key in sorted(self.results.keys()):
+ self._write_result(key[0], key[1], self.results[key])
+ self.summary_fh.close()
+ self.failed_summary_fh.close()
+
+
+class TestManager:
+ """ Create test processes, monitor their status and log test progresses. """
+
+ def __init__(self, args: argparse.Namespace):
+ self.repeat_count = args.repeat
+ self.test_options = self._build_test_options(args)
+ self.devices = self._build_test_devices(args)
+ self.progress_bar: Optional[ProgressBar] = None
+ self.test_summary: Optional[TestSummary] = None
+
+ def _build_test_devices(self, args: argparse.Namespace) -> List[Device]:
+ devices = []
+ if args.device:
+ for s in args.device:
+ name, serial_number = s.split(':')
+ devices.append(Device(name, serial_number))
+ else:
+ devices.append(Device('default', ''))
+ return devices
+
+ def _build_test_options(self, args: argparse.Namespace) -> List[str]:
+ test_options: List[str] = []
+ if args.browser:
+ test_options.append('--browser')
+ if args.ndk_path:
+ test_options += ['--ndk-path', args.ndk_path]
+ testdata_dir = Path('testdata').resolve()
+ test_options += ['--testdata-dir', str(testdata_dir)]
+ return test_options
+
+ def run_all_tests(self, tests: List[str]):
+ device_tests = []
+ device_serialized_tests = []
+ host_tests = []
+ for test in tests:
+ test_type = get_test_type(test)
+ assert test_type, f'No test type for test {test}'
+ if test_type == 'device_test':
+ device_tests.append(test)
+ if test_type == 'device_serialized_test':
+ device_serialized_tests.append(test)
+ if test_type == 'host_test':
+ host_tests.append(test)
+ total_test_count = (len(device_tests) + len(device_serialized_tests)
+ ) * len(self.devices) * self.repeat_count + len(host_tests)
+ self.progress_bar = ProgressBar(total_test_count)
+ self.test_summary = TestSummary(total_test_count)
+ if device_tests:
+ self.run_device_tests(device_tests)
+ if device_serialized_tests:
+ self.run_device_serialized_tests(device_serialized_tests)
+ if host_tests:
+ self.run_host_tests(host_tests)
+ self.progress_bar.end_tests()
+ self.progress_bar = None
+ self.test_summary.end_tests()
+
+ def run_device_tests(self, tests: List[str]):
+ """ Tests can run in parallel on different devices. """
+ test_procs: List[TestProcess] = []
+ for device in self.devices:
+ test_procs.append(TestProcess('device_test', tests, device, 1, self.test_options))
+ self.wait_for_test_results(test_procs, self.repeat_count)
+
+ def run_device_serialized_tests(self, tests: List[str]):
+ """ Tests run on each device in order. """
+ for device in self.devices:
+ test_proc = TestProcess('device_serialized_test', tests, device, 1, self.test_options)
+ self.wait_for_test_results([test_proc], self.repeat_count)
+
+ def run_host_tests(self, tests: List[str]):
+ """ Tests run only once on host. """
+ test_proc = TestProcess('host_tests', tests, None, 1, self.test_options)
+ self.wait_for_test_results([test_proc], 1)
+
+ def wait_for_test_results(self, test_procs: List[TestProcess], repeat_count: int):
+ test_count = sum(len(test_proc.tests) for test_proc in test_procs)
+ while test_procs:
+ dead_procs: List[TestProcess] = []
+ # Check update.
+ for test_proc in test_procs:
+ if not test_proc.alive:
+ dead_procs.append(test_proc)
+ test_proc.check_update()
+ self.progress_bar.update(test_proc)
+ self.test_summary.update(test_proc)
+
+ # Process dead procs.
+ for test_proc in dead_procs:
+ test_proc.join()
+ if not test_proc.finished and test_proc.restart():
+ continue
+ test_procs.remove(test_proc)
+ if test_proc.repeat_index < repeat_count:
+ test_procs.append(
+ TestProcess(test_proc.test_type, test_proc.tests, test_proc.device,
+ test_proc.repeat_index + 1, test_proc.test_options))
+ time.sleep(0.1)
+ return True
+
+
+def run_tests_in_child_process(tests: List[str], args: argparse.Namespace) -> bool:
+ """ run tests in child processes, read test results through a pipe. """
+ mp.set_start_method('spawn') # to be consistent on darwin, linux, windows
+ test_manager = TestManager(args)
+ test_manager.run_all_tests(tests)
+
+ total_test_count = test_manager.test_summary.test_count
+ failed_test_count = test_manager.test_summary.failed_test_count
+ if failed_test_count == 0:
+ print('All tests passed!')
+ return True
+ print('%d of %d tests failed. See %s/failed_test_summary.txt for details.' %
+ (failed_test_count, total_test_count, args.test_dir))
+ return False
+
+
+def main() -> bool:
+ args = get_args()
+ tests = get_host_tests() if args.only_host_test else get_all_tests()
+ tests = get_filtered_tests(tests, args.test_from, args.pattern)
+
+ if args.list_tests:
+ print('\n'.join(tests))
+ return True
+
+ test_dir = Path(args.test_dir).resolve()
+ remove(test_dir)
+ test_dir.mkdir(parents=True)
+ # Switch to the test dir.
+ os.chdir(test_dir)
+ build_testdata(Path('testdata'))
+ return run_tests_in_child_process(tests, args)
diff --git a/simpleperf/scripts/test/inferno_test.py b/simpleperf/scripts/test/inferno_test.py
new file mode 100644
index 00000000..2ca4f5d9
--- /dev/null
+++ b/simpleperf/scripts/test/inferno_test.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+import collections
+import json
+from typing import Any, Dict, List
+
+from . test_utils import INFERNO_SCRIPT, TestBase, TestHelper
+
+
+class TestInferno(TestBase):
+ def get_report(self, options: List[str]) -> str:
+ self.run_cmd([INFERNO_SCRIPT] + options)
+ with open('report.html', 'r') as fh:
+ return fh.read()
+
+ def test_proguard_mapping_file(self):
+ """ Test --proguard-mapping-file option. """
+ testdata_file = TestHelper.testdata_path('perf_need_proguard_mapping.data')
+ proguard_mapping_file = TestHelper.testdata_path('proguard_mapping.txt')
+ original_methodname = 'androidx.fragment.app.FragmentActivity.startActivityForResult'
+ # Can't show original method name without proguard mapping file.
+ self.assertNotIn(original_methodname, self.get_report(
+ ['--record_file', testdata_file, '-sc']))
+ # Show original method name with proguard mapping file.
+ self.assertIn(original_methodname, self.get_report(
+ ['--record_file', testdata_file, '-sc', '--proguard-mapping-file', proguard_mapping_file]))
diff --git a/simpleperf/scripts/test/java_app_test.py b/simpleperf/scripts/test/java_app_test.py
new file mode 100644
index 00000000..f3c89136
--- /dev/null
+++ b/simpleperf/scripts/test/java_app_test.py
@@ -0,0 +1,218 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+import os
+import signal
+import subprocess
+import sys
+import time
+
+from simpleperf_utils import is_windows, remove
+from . app_test import TestExampleBase
+from . test_utils import TestHelper, INFERNO_SCRIPT
+
+
+class TestExamplePureJava(TestExampleBase):
+ @classmethod
+ def setUpClass(cls):
+ cls.prepare("SimpleperfExamplePureJava",
+ "com.example.simpleperf.simpleperfexamplepurejava",
+ ".MainActivity")
+
+ def test_app_profiler(self):
+ self.common_test_app_profiler()
+
+ def test_app_profiler_profile_from_launch(self):
+ self.run_app_profiler(start_activity=True, build_binary_cache=False)
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+ self.check_strings_in_file("report.txt", [
+ "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run",
+ "__start_thread"])
+
+ def test_app_profiler_multiprocesses(self):
+ self.adb.check_run(['shell', 'am', 'force-stop', self.package_name])
+ self.adb.check_run(['shell', 'am', 'start', '-n',
+ self.package_name + '/.MultiProcessActivity'])
+ # Wait until both MultiProcessActivity and MultiProcessService set up.
+ time.sleep(3)
+ self.run_app_profiler(start_activity=False)
+ self.run_cmd(["report.py", "-o", "report.txt"])
+ self.check_strings_in_file("report.txt", ["BusyService", "BusyThread"])
+
+ def test_app_profiler_with_ctrl_c(self):
+ if is_windows():
+ return
+ self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity'])
+ time.sleep(1)
+ args = [sys.executable, TestHelper.script_path("app_profiler.py"),
+ "--app", self.package_name, "-r", "--duration 10000", "--disable_adb_root"]
+ subproc = subprocess.Popen(args)
+ time.sleep(3)
+
+ subproc.send_signal(signal.SIGINT)
+ subproc.wait()
+ self.assertEqual(subproc.returncode, 0)
+ self.run_cmd(["report.py"])
+
+ def test_app_profiler_stop_after_app_exit(self):
+ self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity'])
+ time.sleep(1)
+ subproc = subprocess.Popen(
+ [sys.executable, TestHelper.script_path('app_profiler.py'),
+ '--app', self.package_name, '-r', '--duration 10000', '--disable_adb_root'])
+ time.sleep(3)
+ self.adb.check_run(['shell', 'am', 'force-stop', self.package_name])
+ subproc.wait()
+ self.assertEqual(subproc.returncode, 0)
+ self.run_cmd(["report.py"])
+
+ def test_app_profiler_with_ndk_path(self):
+ # Although we pass an invalid ndk path, it should be able to find tools in default ndk path.
+ self.run_cmd(['app_profiler.py', '--app', self.package_name, '-a', self.activity_name,
+ '--ndk_path', '.'])
+
+ def test_report(self):
+ self.common_test_report()
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+ self.check_strings_in_file("report.txt", [
+ "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run",
+ "__start_thread"])
+
+ def test_profile_with_process_id(self):
+ self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity'])
+ time.sleep(1)
+ pid = self.adb.check_run_and_return_output([
+ 'shell', 'pidof', 'com.example.simpleperf.simpleperfexamplepurejava']).strip()
+ self.run_app_profiler(start_activity=False, record_arg='-g --duration 10 -p ' + pid)
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+ self.check_strings_in_file("report.txt", [
+ "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run",
+ "__start_thread"])
+
+ def test_annotate(self):
+ self.common_test_annotate()
+ if not self.use_compiled_java_code:
+ # Currently annotating Java code is only supported when the Java code is compiled.
+ return
+ self.check_file_under_dir("annotated_files", "MainActivity.java")
+ summary_file = os.path.join("annotated_files", "summary")
+ self.check_annotation_summary(summary_file, [
+ ("MainActivity.java", 80, 80),
+ ("run", 80, 0),
+ ("callFunction", 0, 0),
+ ("line 23", 80, 0)])
+
+ def test_report_sample(self):
+ self.common_test_report_sample(
+ ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run",
+ "__start_thread"])
+
+ def test_pprof_proto_generator(self):
+ check_strings_with_lines = []
+ if self.use_compiled_java_code:
+ check_strings_with_lines = [
+ "com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java",
+ "run"]
+ self.common_test_pprof_proto_generator(
+ check_strings_with_lines=check_strings_with_lines,
+ check_strings_without_lines=[
+ "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run"])
+
+ def test_inferno(self):
+ self.common_test_inferno()
+ self.run_app_profiler()
+ self.run_cmd([INFERNO_SCRIPT, "-sc"])
+ self.check_inferno_report_html(
+ [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run', 80)])
+ self.run_cmd([INFERNO_SCRIPT, "-sc", "-o", "report2.html"])
+ self.check_inferno_report_html(
+ [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run', 80)],
+ "report2.html")
+
+ def test_inferno_in_another_dir(self):
+ test_dir = 'inferno_testdir'
+ os.mkdir(test_dir)
+ os.chdir(test_dir)
+ self.run_cmd(['app_profiler.py', '--app', self.package_name,
+ '-r', '-e task-clock:u -g --duration 3'])
+ self.check_exist(filename="perf.data")
+ self.run_cmd([INFERNO_SCRIPT, "-sc"])
+
+ def test_report_html(self):
+ self.common_test_report_html()
+
+ def test_run_simpleperf_without_usb_connection(self):
+ self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity'])
+ self.run_cmd(['run_simpleperf_without_usb_connection.py', 'start', '-p',
+ self.package_name, '--size_limit', '1M'])
+ self.adb.check_run(['kill-server'])
+ time.sleep(3)
+ # Start adb process outside self.test_dir. Because it will be removed after testing.
+ os.chdir(self.test_dir.parent)
+ self.adb.check_run(['devices'])
+ os.chdir(self.test_dir)
+ self.run_cmd(['run_simpleperf_without_usb_connection.py', 'stop'])
+ self.check_exist(filename="perf.data")
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+
+
+class TestExamplePureJavaRoot(TestExampleBase):
+ @classmethod
+ def setUpClass(cls):
+ cls.prepare("SimpleperfExamplePureJava",
+ "com.example.simpleperf.simpleperfexamplepurejava",
+ ".MainActivity",
+ adb_root=True)
+
+ def test_app_profiler(self):
+ self.common_test_app_profiler()
+
+
+class TestExamplePureJavaTraceOffCpu(TestExampleBase):
+ @classmethod
+ def setUpClass(cls):
+ cls.prepare("SimpleperfExamplePureJava",
+ "com.example.simpleperf.simpleperfexamplepurejava",
+ ".SleepActivity")
+
+ def test_smoke(self):
+ self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-cycles:u --trace-offcpu")
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+ self.check_strings_in_file("report.txt", [
+ "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run",
+ "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction",
+ "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction"
+ ])
+ remove("annotated_files")
+ self.run_cmd(["annotate.py", "-s", self.example_path])
+ self.check_exist(dirname="annotated_files")
+ if self.use_compiled_java_code:
+ self.check_file_under_dir("annotated_files", "SleepActivity.java")
+ summary_file = os.path.join("annotated_files", "summary")
+ self.check_annotation_summary(summary_file, [
+ ("SleepActivity.java", 80, 20),
+ ("run", 80, 0),
+ ("RunFunction", 20, 20),
+ ("SleepFunction", 20, 0),
+ ("line 24", 1, 0),
+ ("line 32", 20, 0)])
+ self.run_cmd([INFERNO_SCRIPT, "-sc"])
+ self.check_inferno_report_html(
+ [('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run', 80),
+ ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction',
+ 20),
+ ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction',
+ 20)])
diff --git a/simpleperf/scripts/test/kotlin_app_test.py b/simpleperf/scripts/test/kotlin_app_test.py
new file mode 100644
index 00000000..9055453e
--- /dev/null
+++ b/simpleperf/scripts/test/kotlin_app_test.py
@@ -0,0 +1,135 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+import os
+
+from simpleperf_utils import remove
+from . app_test import TestExampleBase
+from . test_utils import INFERNO_SCRIPT
+
+
+class TestExampleOfKotlin(TestExampleBase):
+ @classmethod
+ def setUpClass(cls):
+ cls.prepare("SimpleperfExampleOfKotlin",
+ "com.example.simpleperf.simpleperfexampleofkotlin",
+ ".MainActivity")
+
+ def test_app_profiler(self):
+ self.common_test_app_profiler()
+
+ def test_app_profiler_profile_from_launch(self):
+ self.run_app_profiler(start_activity=True, build_binary_cache=False)
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+ self.check_strings_in_file("report.txt", [
+ "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." +
+ "run", "__start_thread"])
+
+ def test_report(self):
+ self.common_test_report()
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+ self.check_strings_in_file("report.txt", [
+ "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." +
+ "run", "__start_thread"])
+
+ def test_annotate(self):
+ if not self.use_compiled_java_code:
+ return
+ self.common_test_annotate()
+ self.check_file_under_dir("annotated_files", "MainActivity.kt")
+ summary_file = os.path.join("annotated_files", "summary")
+ self.check_annotation_summary(summary_file, [
+ ("MainActivity.kt", 80, 80),
+ ("run", 80, 0),
+ ("callFunction", 0, 0),
+ ("line 19", 80, 0),
+ ("line 25", 0, 0)])
+
+ def test_report_sample(self):
+ self.common_test_report_sample([
+ "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." +
+ "run", "__start_thread"])
+
+ def test_pprof_proto_generator(self):
+ check_strings_with_lines = []
+ if self.use_compiled_java_code:
+ check_strings_with_lines = [
+ "com/example/simpleperf/simpleperfexampleofkotlin/MainActivity.kt",
+ "run"]
+ self.common_test_pprof_proto_generator(
+ check_strings_with_lines=check_strings_with_lines,
+ check_strings_without_lines=["com.example.simpleperf.simpleperfexampleofkotlin." +
+ "MainActivity$createBusyThread$1.run"])
+
+ def test_inferno(self):
+ self.common_test_inferno()
+ self.run_app_profiler()
+ self.run_cmd([INFERNO_SCRIPT, "-sc"])
+ self.check_inferno_report_html([('com.example.simpleperf.simpleperfexampleofkotlin.' +
+ 'MainActivity$createBusyThread$1.run', 80)])
+
+ def test_report_html(self):
+ self.common_test_report_html()
+
+
+class TestExampleOfKotlinRoot(TestExampleBase):
+ @classmethod
+ def setUpClass(cls):
+ cls.prepare("SimpleperfExampleOfKotlin",
+ "com.example.simpleperf.simpleperfexampleofkotlin",
+ ".MainActivity",
+ adb_root=True)
+
+ def test_app_profiler(self):
+ self.common_test_app_profiler()
+
+
+class TestExampleOfKotlinTraceOffCpu(TestExampleBase):
+ @classmethod
+ def setUpClass(cls):
+ cls.prepare("SimpleperfExampleOfKotlin",
+ "com.example.simpleperf.simpleperfexampleofkotlin",
+ ".SleepActivity")
+
+ def test_smoke(self):
+ self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-cycles:u --trace-offcpu")
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+ function_prefix = "com.example.simpleperf.simpleperfexampleofkotlin." + \
+ "SleepActivity$createRunSleepThread$1."
+ self.check_strings_in_file("report.txt", [
+ function_prefix + "run",
+ function_prefix + "RunFunction",
+ function_prefix + "SleepFunction"
+ ])
+ if self.use_compiled_java_code:
+ remove("annotated_files")
+ self.run_cmd(["annotate.py", "-s", self.example_path])
+ self.check_exist(dirname="annotated_files")
+ self.check_file_under_dir("annotated_files", "SleepActivity.kt")
+ summary_file = os.path.join("annotated_files", "summary")
+ self.check_annotation_summary(summary_file, [
+ ("SleepActivity.kt", 80, 20),
+ ("run", 80, 0),
+ ("RunFunction", 20, 20),
+ ("SleepFunction", 20, 0),
+ ("line 24", 20, 0),
+ ("line 32", 20, 0)])
+
+ self.run_cmd([INFERNO_SCRIPT, "-sc"])
+ self.check_inferno_report_html([
+ (function_prefix + 'run', 80),
+ (function_prefix + 'RunFunction', 20),
+ (function_prefix + 'SleepFunction', 20)])
diff --git a/simpleperf/scripts/test/pprof_proto_generator_test.py b/simpleperf/scripts/test/pprof_proto_generator_test.py
new file mode 100644
index 00000000..dcdf2ca9
--- /dev/null
+++ b/simpleperf/scripts/test/pprof_proto_generator_test.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+import google.protobuf
+from typing import List, Optional
+
+from binary_cache_builder import BinaryCacheBuilder
+from pprof_proto_generator import load_pprof_profile
+from . test_utils import TestBase, TestHelper
+
+
+class TestPprofProtoGenerator(TestBase):
+ def run_generator(self, options=None, testdata_file='perf_with_interpreter_frames.data'):
+ testdata_path = TestHelper.testdata_path(testdata_file)
+ options = options or []
+ self.run_cmd(['pprof_proto_generator.py', '-i', testdata_path] + options)
+ return self.run_cmd(['pprof_proto_generator.py', '--show'], return_output=True)
+
+ def generate_profile(self, options: Optional[List[str]], testdata_files: List[str]):
+ testdata_paths = [TestHelper.testdata_path(f) for f in testdata_files]
+ options = options or []
+ self.run_cmd(['pprof_proto_generator.py', '-i'] + testdata_paths + options)
+ return load_pprof_profile('pprof.profile')
+
+ def test_show_art_frames(self):
+ art_frame_str = 'art::interpreter::DoCall'
+ # By default, don't show art frames.
+ self.assertNotIn(art_frame_str, self.run_generator())
+ # Use --show_art_frames to show art frames.
+ self.assertIn(art_frame_str, self.run_generator(['--show_art_frames']))
+
+ def test_pid_filter(self):
+ key = 'PlayScene::DoFrame()' # function in process 10419
+ self.assertIn(key, self.run_generator())
+ self.assertIn(key, self.run_generator(['--pid', '10419']))
+ self.assertIn(key, self.run_generator(['--pid', '10419', '10416']))
+ self.assertNotIn(key, self.run_generator(['--pid', '10416']))
+
+ def test_tid_filter(self):
+ key1 = 'art::ProfileSaver::Run()' # function in thread 10459
+ key2 = 'PlayScene::DoFrame()' # function in thread 10463
+ for options in ([], ['--tid', '10459', '10463']):
+ output = self.run_generator(options)
+ self.assertIn(key1, output)
+ self.assertIn(key2, output)
+ output = self.run_generator(['--tid', '10459'])
+ self.assertIn(key1, output)
+ self.assertNotIn(key2, output)
+ output = self.run_generator(['--tid', '10463'])
+ self.assertNotIn(key1, output)
+ self.assertIn(key2, output)
+
+ def test_comm_filter(self):
+ key1 = 'art::ProfileSaver::Run()' # function in thread 'Profile Saver'
+ key2 = 'PlayScene::DoFrame()' # function in thread 'e.sample.tunnel'
+ for options in ([], ['--comm', 'Profile Saver', 'e.sample.tunnel']):
+ output = self.run_generator(options)
+ self.assertIn(key1, output)
+ self.assertIn(key2, output)
+ output = self.run_generator(['--comm', 'Profile Saver'])
+ self.assertIn(key1, output)
+ self.assertNotIn(key2, output)
+ output = self.run_generator(['--comm', 'e.sample.tunnel'])
+ self.assertNotIn(key1, output)
+ self.assertIn(key2, output)
+
+ def test_build_id(self):
+ """ Test the build ids generated are not padded with zeros. """
+ self.assertIn('build_id: e3e938cc9e40de2cfe1a5ac7595897de(', self.run_generator())
+
+ def test_location_address(self):
+ """ Test if the address of a location is within the memory range of the corresponding
+ mapping.
+ """
+ profile = self.generate_profile(None, ['perf_with_interpreter_frames.data'])
+ # pylint: disable=no-member
+ for location in profile.location:
+ mapping = profile.mapping[location.mapping_id - 1]
+ self.assertLessEqual(mapping.memory_start, location.address)
+ self.assertGreaterEqual(mapping.memory_limit, location.address)
+
+ def test_multiple_perf_data(self):
+ """ Test reporting multiple recording file. """
+ profile1 = self.generate_profile(None, ['aggregatable_perf1.data'])
+ profile2 = self.generate_profile(None, ['aggregatable_perf2.data'])
+ profile_both = self.generate_profile(
+ None, ['aggregatable_perf1.data', 'aggregatable_perf2.data'])
+ # pylint: disable=no-member
+ self.assertGreater(len(profile_both.sample), len(profile1.sample))
+ self.assertGreater(len(profile_both.sample), len(profile2.sample))
+
+ def test_proguard_mapping_file(self):
+ """ Test --proguard-mapping-file option. """
+ testdata_file = 'perf_need_proguard_mapping.data'
+ proguard_mapping_file = TestHelper.testdata_path('proguard_mapping.txt')
+ original_methodname = 'androidx.fragment.app.FragmentActivity.startActivityForResult'
+ # Can't show original method name without proguard mapping file.
+ self.assertNotIn(original_methodname, self.run_generator(testdata_file=testdata_file))
+ # Show original method name with proguard mapping file.
+ self.assertIn(original_methodname, self.run_generator(
+ ['--proguard-mapping-file', proguard_mapping_file], testdata_file))
+
+ def test_use_binary_cache(self):
+ testdata_file = TestHelper.testdata_path('runtest_two_functions_arm64_perf.data')
+
+ # Build binary_cache.
+ binary_cache_builder = BinaryCacheBuilder(TestHelper.ndk_path, False)
+ binary_cache_builder.build_binary_cache(testdata_file, [TestHelper.testdata_dir])
+
+ # Generate profile.
+ output = self.run_generator(testdata_file=testdata_file)
+ self.assertIn('simpleperf_runtest_two_functions_arm64', output)
+ self.assertIn('two_functions.cpp', output)
diff --git a/simpleperf/scripts/test/purgatorio_test.py b/simpleperf/scripts/test/purgatorio_test.py
new file mode 100644
index 00000000..e814c42d
--- /dev/null
+++ b/simpleperf/scripts/test/purgatorio_test.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+import collections
+import json
+import os
+from typing import Any, Dict, List
+
+from . test_utils import TestBase, TestHelper
+
+
+class TestPurgatorio(TestBase):
+ def setUp(self):
+ super().setUp()
+ self.script_path = os.path.join('purgatorio', 'purgatorio.py')
+
+ def get_report(self, options: List[str]) -> str:
+ self.run_cmd([self.script_path, '-d', '-o', 'report.html'] + options)
+ with open('report.html', 'r') as fh:
+ return fh.read()
+
+ def test_proguard_mapping_file(self):
+ """ Test --proguard-mapping-file option. """
+ testdata_file = TestHelper.testdata_path('perf_need_proguard_mapping.data')
+ proguard_mapping_file = TestHelper.testdata_path('proguard_mapping.txt')
+ original_methodname = 'androidx.fragment.app.FragmentActivity.startActivityForResult'
+ # Can't show original method name without proguard mapping file.
+ self.assertNotIn(original_methodname, self.get_report(['-i', testdata_file]))
+ # Show original method name with proguard mapping file.
+ self.assertIn(original_methodname, self.get_report(
+ ['--proguard-mapping-file', proguard_mapping_file, '-i', testdata_file]))
diff --git a/simpleperf/scripts/test/report_html_test.py b/simpleperf/scripts/test/report_html_test.py
new file mode 100644
index 00000000..91388064
--- /dev/null
+++ b/simpleperf/scripts/test/report_html_test.py
@@ -0,0 +1,182 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+import collections
+import json
+from typing import Any, Dict, List
+
+from binary_cache_builder import BinaryCacheBuilder
+from . test_utils import TestBase, TestHelper
+
+
+class TestReportHtml(TestBase):
+ def test_long_callchain(self):
+ self.run_cmd(['report_html.py', '-i',
+ TestHelper.testdata_path('perf_with_long_callchain.data')])
+
+ def test_aggregated_by_thread_name(self):
+ # Calculate event_count for each thread name before aggregation.
+ event_count_for_thread_name = collections.defaultdict(lambda: 0)
+ # use "--min_func_percent 0" to avoid cutting any thread.
+ record_data = self.get_record_data(['--min_func_percent', '0', '-i',
+ TestHelper.testdata_path('aggregatable_perf1.data'),
+ TestHelper.testdata_path('aggregatable_perf2.data')])
+ event = record_data['sampleInfo'][0]
+ for process in event['processes']:
+ for thread in process['threads']:
+ thread_name = record_data['threadNames'][str(thread['tid'])]
+ event_count_for_thread_name[thread_name] += thread['eventCount']
+
+ # Check event count for each thread after aggregation.
+ record_data = self.get_record_data(['--aggregate-by-thread-name',
+ '--min_func_percent', '0', '-i',
+ TestHelper.testdata_path('aggregatable_perf1.data'),
+ TestHelper.testdata_path('aggregatable_perf2.data')])
+ event = record_data['sampleInfo'][0]
+ hit_count = 0
+ for process in event['processes']:
+ for thread in process['threads']:
+ thread_name = record_data['threadNames'][str(thread['tid'])]
+ self.assertEqual(thread['eventCount'],
+ event_count_for_thread_name[thread_name])
+ hit_count += 1
+ self.assertEqual(hit_count, len(event_count_for_thread_name))
+
+ def test_no_empty_process(self):
+ """ Test not showing a process having no threads. """
+ perf_data = TestHelper.testdata_path('two_process_perf.data')
+ record_data = self.get_record_data(['-i', perf_data])
+ processes = record_data['sampleInfo'][0]['processes']
+ self.assertEqual(len(processes), 2)
+
+ # One process is removed because all its threads are removed for not
+ # reaching the min_func_percent limit.
+ record_data = self.get_record_data(['-i', perf_data, '--min_func_percent', '20'])
+ processes = record_data['sampleInfo'][0]['processes']
+ self.assertEqual(len(processes), 1)
+
+ def test_proguard_mapping_file(self):
+ """ Test --proguard-mapping-file option. """
+ testdata_file = TestHelper.testdata_path('perf_need_proguard_mapping.data')
+ proguard_mapping_file = TestHelper.testdata_path('proguard_mapping.txt')
+ original_methodname = 'androidx.fragment.app.FragmentActivity.startActivityForResult'
+ # Can't show original method name without proguard mapping file.
+ record_data = self.get_record_data(['-i', testdata_file])
+ self.assertNotIn(original_methodname, json.dumps(record_data))
+ # Show original method name with proguard mapping file.
+ record_data = self.get_record_data(
+ ['-i', testdata_file, '--proguard-mapping-file', proguard_mapping_file])
+ self.assertIn(original_methodname, json.dumps(record_data))
+
+ def get_record_data(self, options: List[str]) -> Dict[str, Any]:
+ args = ['report_html.py'] + options
+ if TestHelper.ndk_path:
+ args += ['--ndk_path', TestHelper.ndk_path]
+ self.run_cmd(args)
+ with open('report.html', 'r') as fh:
+ data = fh.read()
+ start_str = 'type="application/json"'
+ end_str = '</script>'
+ start_pos = data.find(start_str)
+ self.assertNotEqual(start_pos, -1)
+ start_pos = data.find('>', start_pos)
+ self.assertNotEqual(start_pos, -1)
+ start_pos += 1
+ end_pos = data.find(end_str, start_pos)
+ self.assertNotEqual(end_pos, -1)
+ json_data = data[start_pos:end_pos]
+ return json.loads(json_data)
+
+ def test_add_source_code(self):
+ """ Test --add_source_code option. """
+ testdata_file = TestHelper.testdata_path('runtest_two_functions_arm64_perf.data')
+
+ # Build binary_cache.
+ binary_cache_builder = BinaryCacheBuilder(TestHelper.ndk_path, False)
+ binary_cache_builder.build_binary_cache(testdata_file, [TestHelper.testdata_dir])
+
+ # Generate report.html.
+ source_dir = TestHelper.testdata_dir
+ record_data = self.get_record_data(
+ ['-i', testdata_file, '--add_source_code', '--source_dirs', str(source_dir)])
+
+ # Check source code info in samples.
+ source_code_list = []
+ thread = record_data['sampleInfo'][0]['processes'][0]['threads'][0]
+ for lib in thread['libs']:
+ for function in lib['functions']:
+ for source_code_info in function.get('s') or []:
+ source_file = record_data['sourceFiles'][source_code_info['f']]
+ file_path = source_file['path']
+ line_number = source_code_info['l']
+ line_content = source_file['code'][str(line_number)]
+ event_count = source_code_info['e']
+ subtree_event_count = source_code_info['s']
+ s = (f'{file_path}:{line_number}:{line_content}:' +
+ f'{event_count}:{subtree_event_count}')
+ source_code_list.append(s)
+ check_items = ['two_functions.cpp:9: *p = i;\n:590184:590184',
+ 'two_functions.cpp:16: *p = i;\n:591577:591577',
+ 'two_functions.cpp:22: Function1();\n:0:590184',
+ 'two_functions.cpp:23: Function2();\n:0:591577']
+ for item in check_items:
+ found = False
+ for source_code in source_code_list:
+ if item in source_code:
+ found = True
+ break
+ self.assertTrue(found, item)
+
+ def test_add_disassembly(self):
+ """ Test --add_disassembly option. """
+ testdata_file = TestHelper.testdata_path('runtest_two_functions_arm64_perf.data')
+
+ # Build binary_cache.
+ binary_cache_builder = BinaryCacheBuilder(TestHelper.ndk_path, False)
+ binary_cache_builder.build_binary_cache(testdata_file, [TestHelper.testdata_dir])
+
+ # Generate report.html.
+ record_data = self.get_record_data(['-i', testdata_file, '--add_disassembly'])
+
+ # Check disassembly in samples.
+ disassembly_list = []
+ thread = record_data['sampleInfo'][0]['processes'][0]['threads'][0]
+ for lib in thread['libs']:
+ lib_name = record_data['libList'][lib['libId']]
+ for function in lib['functions']:
+ for addr_info in function.get('a') or []:
+ addr = addr_info['a']
+ event_count = addr_info['e']
+ subtree_event_count = addr_info['s']
+ function_data = record_data['functionMap'][str(function['f'])]
+ function_name = function_data['f']
+ for dis_line, dis_addr in function_data.get('d') or []:
+ if addr == dis_addr:
+ s = (f'{lib_name}:{function_name}:{addr}:' +
+ f'{event_count}:{subtree_event_count}')
+ disassembly_list.append(s)
+
+ check_items = ['simpleperf_runtest_two_functions_arm64:Function1():0x1094:590184:590184',
+ 'simpleperf_runtest_two_functions_arm64:Function2():0x1104:591577:591577',
+ 'simpleperf_runtest_two_functions_arm64:main:0x113c:0:590184',
+ 'simpleperf_runtest_two_functions_arm64:main:0x1140:0:591577']
+ for item in check_items:
+ found = False
+ for disassembly in disassembly_list:
+ if item in disassembly:
+ found = True
+ break
+ self.assertTrue(found, item)
diff --git a/simpleperf/scripts/test/report_lib_test.py b/simpleperf/scripts/test/report_lib_test.py
new file mode 100644
index 00000000..ad09f0e4
--- /dev/null
+++ b/simpleperf/scripts/test/report_lib_test.py
@@ -0,0 +1,202 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+from simpleperf_report_lib import ReportLib
+from . test_utils import TestBase, TestHelper
+
+
+class TestReportLib(TestBase):
+ def setUp(self):
+ super(TestReportLib, self).setUp()
+ self.report_lib = ReportLib()
+ self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_symbols.data'))
+
+ def tearDown(self):
+ self.report_lib.Close()
+ super(TestReportLib, self).tearDown()
+
+ def test_build_id(self):
+ build_id = self.report_lib.GetBuildIdForPath('/data/t2')
+ self.assertEqual(build_id, '0x70f1fe24500fc8b0d9eb477199ca1ca21acca4de')
+
+ def test_symbol(self):
+ found_func2 = False
+ while self.report_lib.GetNextSample():
+ symbol = self.report_lib.GetSymbolOfCurrentSample()
+ if symbol.symbol_name == 'func2(int, int)':
+ found_func2 = True
+ self.assertEqual(symbol.symbol_addr, 0x4004ed)
+ self.assertEqual(symbol.symbol_len, 0x14)
+ self.assertTrue(found_func2)
+
+ def test_sample(self):
+ found_sample = False
+ while self.report_lib.GetNextSample():
+ sample = self.report_lib.GetCurrentSample()
+ if sample.ip == 0x4004ff and sample.time == 7637889424953:
+ found_sample = True
+ self.assertEqual(sample.pid, 15926)
+ self.assertEqual(sample.tid, 15926)
+ self.assertEqual(sample.thread_comm, 't2')
+ self.assertEqual(sample.cpu, 5)
+ self.assertEqual(sample.period, 694614)
+ event = self.report_lib.GetEventOfCurrentSample()
+ self.assertEqual(event.name, 'cpu-cycles')
+ callchain = self.report_lib.GetCallChainOfCurrentSample()
+ self.assertEqual(callchain.nr, 0)
+ self.assertTrue(found_sample)
+
+ def test_meta_info(self):
+ self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_trace_offcpu.data'))
+ meta_info = self.report_lib.MetaInfo()
+ self.assertTrue("simpleperf_version" in meta_info)
+ self.assertEqual(meta_info["system_wide_collection"], "false")
+ self.assertEqual(meta_info["trace_offcpu"], "true")
+ self.assertEqual(meta_info["event_type_info"], "cpu-cycles,0,0\nsched:sched_switch,2,47")
+ self.assertTrue("product_props" in meta_info)
+
+ def test_event_name_from_meta_info(self):
+ self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_tracepoint_event.data'))
+ event_names = set()
+ while self.report_lib.GetNextSample():
+ event_names.add(self.report_lib.GetEventOfCurrentSample().name)
+ self.assertTrue('sched:sched_switch' in event_names)
+ self.assertTrue('cpu-cycles' in event_names)
+
+ def test_record_cmd(self):
+ self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_trace_offcpu.data'))
+ self.assertEqual(self.report_lib.GetRecordCmd(),
+ "/data/local/tmp/simpleperf record --trace-offcpu --duration 2 -g " +
+ "./simpleperf_runtest_run_and_sleep64")
+
+ def test_offcpu(self):
+ self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_trace_offcpu.data'))
+ total_period = 0
+ sleep_function_period = 0
+ sleep_function_name = "SleepFunction(unsigned long long)"
+ while self.report_lib.GetNextSample():
+ sample = self.report_lib.GetCurrentSample()
+ total_period += sample.period
+ if self.report_lib.GetSymbolOfCurrentSample().symbol_name == sleep_function_name:
+ sleep_function_period += sample.period
+ continue
+ callchain = self.report_lib.GetCallChainOfCurrentSample()
+ for i in range(callchain.nr):
+ if callchain.entries[i].symbol.symbol_name == sleep_function_name:
+ sleep_function_period += sample.period
+ break
+ self.assertEqual(self.report_lib.GetEventOfCurrentSample().name, 'cpu-cycles')
+ sleep_percentage = float(sleep_function_period) / total_period
+ self.assertGreater(sleep_percentage, 0.30)
+
+ def test_show_art_frames(self):
+ def has_art_frame(report_lib):
+ report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_interpreter_frames.data'))
+ result = False
+ while report_lib.GetNextSample():
+ callchain = report_lib.GetCallChainOfCurrentSample()
+ for i in range(callchain.nr):
+ if callchain.entries[i].symbol.symbol_name == 'artMterpAsmInstructionStart':
+ result = True
+ break
+ report_lib.Close()
+ return result
+
+ report_lib = ReportLib()
+ self.assertFalse(has_art_frame(report_lib))
+ report_lib = ReportLib()
+ report_lib.ShowArtFrames(False)
+ self.assertFalse(has_art_frame(report_lib))
+ report_lib = ReportLib()
+ report_lib.ShowArtFrames(True)
+ self.assertTrue(has_art_frame(report_lib))
+
+ def test_merge_java_methods(self):
+ def parse_dso_names(report_lib):
+ dso_names = set()
+ report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_interpreter_frames.data'))
+ while report_lib.GetNextSample():
+ dso_names.add(report_lib.GetSymbolOfCurrentSample().dso_name)
+ callchain = report_lib.GetCallChainOfCurrentSample()
+ for i in range(callchain.nr):
+ dso_names.add(callchain.entries[i].symbol.dso_name)
+ report_lib.Close()
+ has_jit_symfiles = any('TemporaryFile-' in name for name in dso_names)
+ has_jit_cache = '[JIT cache]' in dso_names
+ return has_jit_symfiles, has_jit_cache
+
+ report_lib = ReportLib()
+ self.assertEqual(parse_dso_names(report_lib), (False, True))
+
+ report_lib = ReportLib()
+ report_lib.MergeJavaMethods(True)
+ self.assertEqual(parse_dso_names(report_lib), (False, True))
+
+ report_lib = ReportLib()
+ report_lib.MergeJavaMethods(False)
+ self.assertEqual(parse_dso_names(report_lib), (True, False))
+
+ def test_jited_java_methods(self):
+ report_lib = ReportLib()
+ report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_jit_symbol.data'))
+ has_jit_cache = False
+ while report_lib.GetNextSample():
+ if report_lib.GetSymbolOfCurrentSample().dso_name == '[JIT app cache]':
+ has_jit_cache = True
+ callchain = report_lib.GetCallChainOfCurrentSample()
+ for i in range(callchain.nr):
+ if callchain.entries[i].symbol.dso_name == '[JIT app cache]':
+ has_jit_cache = True
+ report_lib.Close()
+ self.assertTrue(has_jit_cache)
+
+ def test_tracing_data(self):
+ self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_with_tracepoint_event.data'))
+ has_tracing_data = False
+ while self.report_lib.GetNextSample():
+ event = self.report_lib.GetEventOfCurrentSample()
+ tracing_data = self.report_lib.GetTracingDataOfCurrentSample()
+ if event.name == 'sched:sched_switch':
+ self.assertIsNotNone(tracing_data)
+ self.assertIn('prev_pid', tracing_data)
+ self.assertIn('next_comm', tracing_data)
+ if tracing_data['prev_pid'] == 9896 and tracing_data['next_comm'] == 'swapper/4':
+ has_tracing_data = True
+ else:
+ self.assertIsNone(tracing_data)
+ self.assertTrue(has_tracing_data)
+
+ def test_dynamic_field_in_tracing_data(self):
+ self.report_lib.SetRecordFile(TestHelper.testdata_path(
+ 'perf_with_tracepoint_event_dynamic_field.data'))
+ has_dynamic_field = False
+ while self.report_lib.GetNextSample():
+ event = self.report_lib.GetEventOfCurrentSample()
+ tracing_data = self.report_lib.GetTracingDataOfCurrentSample()
+ if event.name == 'kprobes:myopen':
+ self.assertIsNotNone(tracing_data)
+ self.assertIn('name', tracing_data)
+ if tracing_data['name'] == '/sys/kernel/debug/tracing/events/kprobes/myopen/format':
+ has_dynamic_field = True
+ else:
+ self.assertIsNone(tracing_data)
+ self.assertTrue(has_dynamic_field)
+
+ def test_add_proguard_mapping_file(self):
+ with self.assertRaises(ValueError):
+ self.report_lib.AddProguardMappingFile('non_exist_file')
+ proguard_mapping_file = TestHelper.testdata_path('proguard_mapping.txt')
+ self.report_lib.AddProguardMappingFile(proguard_mapping_file)
diff --git a/tests/memeater/Android.mk b/simpleperf/scripts/test/run_simpleperf_on_device_test.py
index a07564ab..2de74228 100644
--- a/tests/memeater/Android.mk
+++ b/simpleperf/scripts/test/run_simpleperf_on_device_test.py
@@ -1,4 +1,6 @@
-# Copyright (C) 2017 The Android Open Source Project
+#!/usr/bin/env python3
+#
+# 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.
@@ -11,13 +13,10 @@
# 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
-LOCAL_PATH := $(call my-dir)
+from . test_utils import TestBase
+
-include $(CLEAR_VARS)
-LOCAL_MODULE := memeater
-LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
-LOCAL_SRC_FILES := memeater.c
-LOCAL_CFLAGS := -Wall -Werror -Wno-unused-parameter
-include $(BUILD_EXECUTABLE)
+class TestRunSimpleperfOnDevice(TestBase):
+ def test_smoke(self):
+ self.run_cmd(['run_simpleperf_on_device.py', 'list', '--show-features'])
diff --git a/simpleperf/scripts/script_testdata/aggregatable_perf1.data b/simpleperf/scripts/test/script_testdata/aggregatable_perf1.data
index 61f5258a..61f5258a 100644
--- a/simpleperf/scripts/script_testdata/aggregatable_perf1.data
+++ b/simpleperf/scripts/test/script_testdata/aggregatable_perf1.data
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/aggregatable_perf2.data b/simpleperf/scripts/test/script_testdata/aggregatable_perf2.data
index 7b8224e9..7b8224e9 100644
--- a/simpleperf/scripts/script_testdata/aggregatable_perf2.data
+++ b/simpleperf/scripts/test/script_testdata/aggregatable_perf2.data
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/cpp_api-debug_prev_Q.apk b/simpleperf/scripts/test/script_testdata/cpp_api-debug_Q.apk
index 2ebf32b0..37e9f547 100644
--- a/simpleperf/scripts/script_testdata/cpp_api-debug_prev_Q.apk
+++ b/simpleperf/scripts/test/script_testdata/cpp_api-debug_Q.apk
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/cpp_api-debug_Q.apk b/simpleperf/scripts/test/script_testdata/cpp_api-debug_prev_Q.apk
index 62591ad5..de750f4e 100644
--- a/simpleperf/scripts/script_testdata/cpp_api-debug_Q.apk
+++ b/simpleperf/scripts/test/script_testdata/cpp_api-debug_prev_Q.apk
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/cpp_api-profile_Q.apk b/simpleperf/scripts/test/script_testdata/cpp_api-profile_Q.apk
index 746b86cc..47929c35 100644
--- a/simpleperf/scripts/script_testdata/cpp_api-profile_Q.apk
+++ b/simpleperf/scripts/test/script_testdata/cpp_api-profile_Q.apk
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/cpp_api-profile_prev_Q.apk b/simpleperf/scripts/test/script_testdata/cpp_api-profile_prev_Q.apk
index 9718824e..ef17c140 100644
--- a/simpleperf/scripts/script_testdata/cpp_api-profile_prev_Q.apk
+++ b/simpleperf/scripts/test/script_testdata/cpp_api-profile_prev_Q.apk
Binary files differ
diff --git a/simpleperf/scripts/test/script_testdata/debug_unwind_report.txt b/simpleperf/scripts/test/script_testdata/debug_unwind_report.txt
new file mode 100644
index 00000000..b5f36ce8
--- /dev/null
+++ b/simpleperf/scripts/test/script_testdata/debug_unwind_report.txt
@@ -0,0 +1,760 @@
+sample_time: 626968109563718
+unwinding_used_time: 2.292 us
+unwinding_error_code: 1
+unwinding_error_addr: 0x71bf509e50
+stack_start: 0x71bf401ca0
+stack_end: 0x71bf402fd0
+ip_1: 0x746ac93ecc
+map_1: [0x746ac86000-0x746ad04000], pgoff 0x3c000
+dso_1: /apex/com.android.runtime/lib64/bionic/libc.so
+vaddr_in_file_1: 0x49ecc
+symbol_1: __bionic_clone
+ip_2: 0x746ac99894
+map_2: [0x746ac86000-0x746ad04000], pgoff 0x3c000
+dso_2: /apex/com.android.runtime/lib64/bionic/libc.so
+vaddr_in_file_2: 0x4f894
+symbol_2: clone
+reg_r0: 0x0
+reg_r1: 0x71bf401ca0
+reg_r2: 0x71bf401cc0
+reg_r3: 0x71bf402000
+reg_r4: 0x71bf401cc0
+reg_r5: 0x746acf955c
+reg_r6: 0x71bf401cb0
+reg_r7: 0xacd979ce
+reg_r8: 0xdc
+reg_r9: 0x71bf401cb0
+reg_r10: 0x71bf401cb0
+reg_r11: 0x746acf955c
+reg_r12: 0x4100
+reg_r13: 0x0
+reg_r14: 0x71d3878780
+reg_r15: 0x0
+reg_r16: 0x746ad07e30
+reg_r17: 0x746ac996ec
+reg_r18: 0x7158d8e000
+reg_r19: 0x71bf50bfb0
+reg_r20: 0x71bf50c000
+reg_r21: 0x71bf50bcb0
+reg_r22: 0x380d
+reg_r23: 0x3427
+reg_r24: 0x71bf401cb0
+reg_r25: 0x71bf401cb0
+reg_r26: 0x71bf401ff8
+reg_r27: 0x108000
+reg_r28: 0x71bf2fd000
+reg_r29: 0x71bf509e50
+reg_lr: 0x746ac99898
+reg_sp: 0x71bf401ca0
+reg_pc: 0x746ac93ecc
+stack_71bf401ca0: 000000746acf955c 00000071bf401cb0 00000071bf50bcb0 0000000000000000
+stack_71bf401cc0: 0000380d00003428 0000000000000001 00000071bf2fc000 0000000000105cb0
+stack_71bf401ce0: 0000000000001000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401d00: 0000000000000003 0000000000000000 00000071d38af76c 0000007346370dc0
+stack_71bf401d20: 0000000000000000 0000000080001204 0000000000000000 0000000000000000
+stack_71bf401d40: 00000071bf401cb0 0000000000000000 0000000000000000 00000071bf2fc000
+stack_71bf401d60: 000000000010a000 00000071bf2fd000 0000000000108000 0000000000000000
+stack_71bf401d80: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401da0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401dc0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401de0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401e00: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401e20: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401e40: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401e60: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401e80: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401ea0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401ec0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401ee0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401f00: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401f20: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401f40: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401f60: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401f80: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401fa0: 0000000000000000 00000071bf402040 0000000000000000 0000000000000000
+stack_71bf401fc0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401fe0: 0000000000000000 0000000000000000 0000000000000000 00000071bf402040
+stack_71bf402000: 000000746ac6d188 00000071bf401cb0 0000000000000000 0000000000000000
+stack_71bf402020: 0000000000000000 d31f8b8761d5c565 0000000000000000 0000000000000000
+stack_71bf402040: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402060: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402080: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4020a0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4020c0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4020e0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402100: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402120: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402140: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402160: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402180: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4021a0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4021c0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4021e0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402200: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402220: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402240: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402260: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402280: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4022a0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4022c0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4022e0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402300: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402320: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402340: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402360: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402380: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4023a0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4023c0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4023e0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402400: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402420: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402440: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402460: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402480: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4024a0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4024c0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4024e0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402500: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402520: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402540: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402560: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402580: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4025a0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4025c0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4025e0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402600: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402620: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402640: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402660: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402680: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4026a0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4026c0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4026e0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402700: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402720: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402740: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402760: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402780: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4027a0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4027c0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4027e0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402800: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402820: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402840: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402860: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402880: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4028a0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4028c0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4028e0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402900: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402920: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402940: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402960: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402980: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4029a0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4029c0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4029e0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402a00: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402a20: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402a40: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402a60: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402a80: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402aa0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402ac0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402ae0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402b00: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402b20: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402b40: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402b60: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402b80: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402ba0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402bc0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402be0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402c00: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402c20: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402c40: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402c60: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402c80: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402ca0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402cc0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402ce0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402d00: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402d20: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402d40: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402d60: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402d80: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402da0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402dc0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402de0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402e00: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402e20: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402e40: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402e60: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402e80: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402ea0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402ec0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402ee0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402f00: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402f20: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402f40: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402f60: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402f80: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402fa0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402fc0: 0000000000000000 0000000000000000
+
+sample_time: 626970513562864
+unwinding_used_time: 12.448 us
+unwinding_error_code: 4
+unwinding_error_addr: 0x0
+stack_start: 0x71bf400680
+stack_end: 0x71bf402fd0
+ip_1: 0x71d38c4250
+map_1: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_1: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_1: 0x2b1250
+symbol_1: artQuickToInterpreterBridge
+ip_2: 0x71d3881f68
+map_2: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_2: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_2: 0x26ef68
+symbol_2: art_quick_to_interpreter_bridge
+ip_3: 0x71d376e348
+map_3: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_3: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_3: 0x15b348
+symbol_3: NterpCommonInvokeInstance
+ip_4: 0x715b9550fc
+map_4: [0x715b4fa000-0x715cd03000], pgoff 0x0
+dso_4: /data/app/~~d9GU08QC_9TZAfKCecJBXg==/com.google.samples.apps.iosched-dJv39H5oeU5_UxPPstEYAw==/oat/arm64/base.vdex
+vaddr_in_file_4: 0x45b0fc
+symbol_4: com.google.android.gms.measurement.internal.zzer.getWritableDatabase
+ip_5: 0x71d376dd64
+map_5: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_5: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_5: 0x15ad64
+symbol_5: NterpCommonInvokeInstance
+ip_6: 0x715b952da8
+map_6: [0x715b4fa000-0x715cd03000], pgoff 0x0
+dso_6: /data/app/~~d9GU08QC_9TZAfKCecJBXg==/com.google.samples.apps.iosched-dJv39H5oeU5_UxPPstEYAw==/oat/arm64/base.vdex
+vaddr_in_file_6: 0x458da8
+symbol_6: com.google.android.gms.measurement.internal.zzeo.zzae
+ip_7: 0x71d376e390
+map_7: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_7: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_7: 0x15b390
+symbol_7: NterpCommonInvokeInstanceRange
+ip_8: 0x715b952eee
+map_8: [0x715b4fa000-0x715cd03000], pgoff 0x0
+dso_8: /data/app/~~d9GU08QC_9TZAfKCecJBXg==/com.google.samples.apps.iosched-dJv39H5oeU5_UxPPstEYAw==/oat/arm64/base.vdex
+vaddr_in_file_8: 0x458eee
+symbol_8: com.google.android.gms.measurement.internal.zzeo.zza
+ip_9: 0x71d376dd64
+map_9: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_9: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_9: 0x15ad64
+symbol_9: NterpCommonInvokeInstance
+ip_10: 0x715b952dda
+map_10: [0x715b4fa000-0x715cd03000], pgoff 0x0
+dso_10: /data/app/~~d9GU08QC_9TZAfKCecJBXg==/com.google.samples.apps.iosched-dJv39H5oeU5_UxPPstEYAw==/oat/arm64/base.vdex
+vaddr_in_file_10: 0x458dda
+symbol_10: com.google.android.gms.measurement.internal.zzeo.zza
+ip_11: 0x71d376dd64
+map_11: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_11: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_11: 0x15ad64
+symbol_11: NterpCommonInvokeInstance
+ip_12: 0x715b964204
+map_12: [0x715b4fa000-0x715cd03000], pgoff 0x0
+dso_12: /data/app/~~d9GU08QC_9TZAfKCecJBXg==/com.google.samples.apps.iosched-dJv39H5oeU5_UxPPstEYAw==/oat/arm64/base.vdex
+vaddr_in_file_12: 0x46a204
+symbol_12: com.google.android.gms.measurement.internal.zzil.zza
+ip_13: 0x9beff0f0
+map_13: [0x9befb5c0-0x9beffacc], pgoff 0x35a48
+dso_13: perf.data_jit_app_cache:219720-223168
+vaddr_in_file_13: 0x9beff0f0
+symbol_13: com.google.android.gms.measurement.internal.zzhb.zza
+ip_14: 0x71d3878564
+map_14: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_14: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_14: 0x265564
+symbol_14: art_quick_invoke_stub
+ip_15: 0x71d38abca0
+map_15: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_15: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_15: 0x298ca0
+symbol_15: art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::ShadowFrame*, unsigned short, art::JValue*)
+ip_16: 0x71d3a18634
+map_16: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_16: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_16: 0x405634
+symbol_16: bool art::interpreter::DoCall<true, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)
+ip_17: 0x71d3c6a13c
+map_17: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_17: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_17: 0x65713c
+symbol_17: MterpInvokeVirtualRange
+ip_18: 0x71d3903194
+map_18: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_18: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_18: 0x2f0194
+symbol_18: mterp_op_invoke_virtual_range
+ip_19: 0x715b960a08
+map_19: [0x715b4fa000-0x715cd03000], pgoff 0x0
+dso_19: /data/app/~~d9GU08QC_9TZAfKCecJBXg==/com.google.samples.apps.iosched-dJv39H5oeU5_UxPPstEYAw==/oat/arm64/base.vdex
+vaddr_in_file_19: 0x466a08
+symbol_19: com.google.android.gms.measurement.internal.zzhe.run
+ip_20: 0x71d38c5038
+map_20: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_20: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_20: 0x2b2038
+symbol_20: art::interpreter::Execute(art::Thread*, art::CodeItemDataAccessor const&, art::ShadowFrame&, art::JValue, bool, bool) (.llvm.4927020788110626733)
+ip_21: 0x71d38ac1f8
+map_21: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_21: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_21: 0x2991f8
+symbol_21: art::interpreter::ArtInterpreterToInterpreterBridge(art::Thread*, art::CodeItemDataAccessor const&, art::ShadowFrame*, art::JValue*)
+ip_22: 0x71d38ab9d0
+map_22: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_22: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_22: 0x2989d0
+symbol_22: bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)
+ip_23: 0x71d3c6da90
+map_23: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_23: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_23: 0x65aa90
+symbol_23: MterpInvokeInterface
+ip_24: 0x71d3903094
+map_24: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_24: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_24: 0x2f0094
+symbol_24: mterp_op_invoke_interface
+ip_25: 0x71d3335db8
+map_25: [0x71d313a000-0x71d3613000], pgoff 0x0
+dso_25: /apex/com.android.art/javalib/core-oj.jar
+vaddr_in_file_25: 0x1fbdb8
+symbol_25: java.util.concurrent.Executors$RunnableAdapter.call
+ip_26: 0x71d38c5038
+map_26: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_26: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_26: 0x2b2038
+symbol_26: art::interpreter::Execute(art::Thread*, art::CodeItemDataAccessor const&, art::ShadowFrame&, art::JValue, bool, bool) (.llvm.4927020788110626733)
+ip_27: 0x71d38c4654
+map_27: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_27: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_27: 0x2b1654
+symbol_27: artQuickToInterpreterBridge
+ip_28: 0x71d3881f68
+map_28: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_28: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_28: 0x26ef68
+symbol_28: art_quick_to_interpreter_bridge
+ip_29: 0x9bfe981c
+map_29: [0x9bfe9700-0x9bfe9a74], pgoff 0x10b38
+dso_29: perf.data_jit_app_cache:68408-84336
+vaddr_in_file_29: 0x9bfe981c
+symbol_29: java.util.concurrent.FutureTask.run
+ip_30: 0x71d376e348
+map_30: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_30: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_30: 0x15b348
+symbol_30: NterpCommonInvokeInstance
+ip_31: 0x715b95bdd0
+map_31: [0x715b4fa000-0x715cd03000], pgoff 0x0
+dso_31: /data/app/~~d9GU08QC_9TZAfKCecJBXg==/com.google.samples.apps.iosched-dJv39H5oeU5_UxPPstEYAw==/oat/arm64/base.vdex
+vaddr_in_file_31: 0x461dd0
+symbol_31: com.google.android.gms.measurement.internal.zzfx.run
+ip_32: 0x71d3878564
+map_32: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_32: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_32: 0x265564
+symbol_32: art_quick_invoke_stub
+ip_33: 0x71d386776c
+map_33: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_33: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_33: 0x25476c
+symbol_33: art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)
+ip_34: 0x71d38b0e18
+map_34: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_34: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_34: 0x29de18
+symbol_34: art::JValue art::InvokeVirtualOrInterfaceWithJValues<art::ArtMethod*>(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, art::ArtMethod*, jvalue const*)
+ip_35: 0x71d38afb4c
+map_35: [0x71d3764000-0x71d3cc0000], pgoff 0x151000
+dso_35: /apex/com.android.art/lib64/libart.so
+vaddr_in_file_35: 0x29cb4c
+symbol_35: art::Thread::CreateCallback(void*)
+ip_36: 0x746acf959c
+map_36: [0x746ac86000-0x746ad04000], pgoff 0x3c000
+dso_36: /apex/com.android.runtime/lib64/bionic/libc.so
+vaddr_in_file_36: 0xaf59c
+symbol_36: __pthread_start(void*)
+ip_37: 0x746ac996e4
+map_37: [0x746ac86000-0x746ad04000], pgoff 0x3c000
+dso_37: /apex/com.android.runtime/lib64/bionic/libc.so
+vaddr_in_file_37: 0x4f6e4
+symbol_37: __start_thread
+reg_r0: 0x71ab69f960
+reg_r1: 0x7346370dc0
+reg_r2: 0x71bf4006e0
+reg_r3: 0x9be86f90
+reg_r4: 0x705afabc
+reg_r5: 0x11c7
+reg_r6: 0x1445fbd0
+reg_r7: 0x10
+reg_r8: 0x715bb131c2
+reg_r9: 0x715bb131c5
+reg_r10: 0x0
+reg_r11: 0x0
+reg_r12: 0x71d3878454
+reg_r13: 0x71d387849c
+reg_r14: 0x71bf4007e0
+reg_r15: 0x1
+reg_r16: 0x71bf4006e0
+reg_r17: 0x46e
+reg_r18: 0x7157bd6000
+reg_r19: 0x7346370dc0
+reg_r20: 0x0
+reg_r21: 0x4
+reg_r22: 0x715b951f68
+reg_r23: 0x715bb131c3
+reg_r24: 0x71d3764780
+reg_r25: 0x71bf4007f8
+reg_r26: 0x10080011
+reg_r27: 0x11209
+reg_r28: 0x71bf400810
+reg_r29: 0x71bf400800
+reg_lr: 0x71d3881f6c
+reg_sp: 0x71bf400680
+reg_pc: 0x71d38c4250
+stack_71bf400680: 00000071bf400800 00000071d3881f6c 00000071bf400810 0000000000011209
+stack_71bf4006a0: 0000000010080011 00000071bf4007f8 00000071d3764780 000000715bb131c3
+stack_71bf4006c0: 000000715b951fbc 0000000000000004 0000000000000000 0000007346370dc0
+stack_71bf4006e0: 0000000070124080 13f1512013f15158 0000000000000000 507465670f00656d
+stack_71bf400700: 7465670d0073656d 000000000000001c fffffffffffffff4 4010040140100401
+stack_71bf400720: 1004010101004000 0000000000000000 0000000013f22900 00000071d3764090
+stack_71bf400740: 000000009be86f90 00000000705afabc 00000000000011c7 000000001445fbd0
+stack_71bf400760: 0000000000000010 0000000000000000 0000000000000004 000000715b951f68
+stack_71bf400780: 000000715bb131c3 00000071d3764780 00000071bf4007f8 0000000010080011
+stack_71bf4007a0: 0000000000011209 00000071bf400810 00000071bf400800 000000009be86fdc
+stack_71bf4007c0: 00000071b186f410 00000071bf400810 0000000000000004 00000071d376d6ac
+stack_71bf4007e0: 00000071b1602740 000000715b951f68 00000071bf400810 13f2290000000000
+stack_71bf400800: 13f2290070015758 000000009be955a8 0000000000000000 0000000000000000
+stack_71bf400820: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf400840: 0000000000000000 0000000000000000 0000007346370dc0 0000000000000000
+stack_71bf400860: 0000000013f1f680 000000001301c310 000000001445fa10 0000000013f1fb80
+stack_71bf400880: 0000000013f22900 0000000013080ea0 0000000000011209 0000000012f6d8e0
+stack_71bf4008a0: 0000000000006591 000000009bef4238 00000071b1870350 00000071d376dd68
+stack_71bf4008c0: 1445fa101301c310 6ff0a02013f1fb80 13f1ae8813f1ae88 00000071bf4008f0
+stack_71bf4008e0: 000000001308a6a0 0000000000000000 0000000013f1f680 0000000012f56508
+stack_71bf400900: 0000000012fd2bc8 000000009be98c44 00000071b185da40 0000000013f1f680
+stack_71bf400920: 0000000000000000 0000000000000000 000000001308a6a0 0000000013f1f680
+stack_71bf400940: 0000000000000000 0000000012f56508 0000000012fd2bc8 000000009bef93b4
+stack_71bf400960: 00000071b185d920 0000000013080ea0 0000000000011209 0000017788d41bdc
+stack_71bf400980: 000000001308a8f8 000000009bfd6c34 00000071b186c0f8 12f7507013edc0c0
+stack_71bf4009a0: 0000007100000000 00000071bf400a10 00000071bf4009f4 00000071d376dd68
+stack_71bf4009c0: 00000071b185f380 0000000000000000 000000715b955884 00000071bf400a10
+stack_71bf4009e0: 0000000000000000 0000000000000000 000000001308a738 00000071d3772c54
+stack_71bf400a00: 1308a738bf400af0 00000001bf400ad0 13febba800000000 1445c49000000000
+stack_71bf400a20: 0000000100000001 000000001308a6a0 13f1f68000000000 0000000000000000
+stack_71bf400a40: 0000000d00000000 0000000000000000 0000007346370dc0 0000000000000000
+stack_71bf400a60: 0000000000000020 000000715b963f52 000000715bb14219 00000071d3764780
+stack_71bf400a80: 00000071bf400ad0 00000071bf400adc 00000071bf400ad0 00000071bf400af0
+stack_71bf400aa0: 00000071bf400adc 00000071d376e34c 00000071b186d220 000000005b952e14
+stack_71bf400ac0: 000000715b963f52 00000071bf400af0 1308a8981308a8f8 1308a8f800000000
+stack_71bf400ae0: 000000001308a898 0000000012f5ace0 0000000000000000 0000000000000000
+stack_71bf400b00: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf400b20: 0000000000000000 0000000000000000 0000007346370dc0 0000000000000000
+stack_71bf400b40: 0000000000000020 000000715b964204 0000000000002070 00000071d3764780
+stack_71bf400b60: 00000071bf400bc8 0000000010080014 0000000012f5ace0 00000071bf400c24
+stack_71bf400b80: 00000071bf400bf4 00000071d376dd68 00000071b186d460 0000000000000000
+stack_71bf400ba0: 0000000000000004 000000715b95e0a8 000000000000106f 000000715b964204
+stack_71bf400bc0: 00000071bf400c20 0000000000000000 0000000000000000 0000000000000000
+stack_71bf400be0: 0000000000000000 14459e201308a898 0000000100000000 0000000100000001
+stack_71bf400c00: 0000007100000001 00000071bf400c20 1308a8981308a898 0000000014459e20
+stack_71bf400c20: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf400c40: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf400c60: 0000007346370dc0 0000000000000000 0000000012f62bb0 000000001308a820
+stack_71bf400c80: 0000000000000000 0000000000000000 00000000144598f0 000000001308a898
+stack_71bf400ca0: 0000000012f5ace0 0000000014457d00 0000000000000001 000000009beff0f4
+stack_71bf400cc0: 00000071b1861f68 12f5caa814457d00 0000000000000000 d31f8b8761d5c565
+stack_71bf400ce0: 00000001bf400d80 12f626e814457b20 14459d3012f62bb0 14459e2014459d30
+stack_71bf400d00: 1445985814459e40 00000071144598f0 0f963fa4d38ff780 00000000ce8c50f3
+stack_71bf400d20: 0000007346370dc0 00000071bf400f10 000000715b964414 00000071bf400ed0
+stack_71bf400d40: 0000000000000036 00000071d3cd7000 00000071b1861f68 0000000000000000
+stack_71bf400d60: 0000007346370e70 00000071b1861f68 000000000000000a 00000071bf402000
+stack_71bf400d80: 00000071d3cd7000 0000000000000000 00000071bf400dd0 00000071d3878568
+stack_71bf400da0: 0000000000000000 70951ba81308a820 fa9ecaf712f62bb0 1334ae4800000177
+stack_71bf400dc0: 0000000100000001 0000000000000001 00000071bf401130 000000715bbd2673
+stack_71bf400de0: 00000071bf401130 0000007346370dc0 00000071bf400e60 00000071d38abca4
+stack_71bf400e00: 0000000000000001 00000071bf402000 0000000000000018 00000071d3cd7000
+stack_71bf400e20: 0000000000000000 00000071bf402000 0000000000000000 00000071bf401690
+stack_71bf400e40: 00000071bf401270 0000007346370dc0 00000071bf401102 d31f8b8761d5c565
+stack_71bf400e60: 00000071bf400fa0 00000071d3a18638 000000000000000a d31f8b8761d5c565
+stack_71bf400e80: 00000071bf401270 0000000000000000 00000071bf400ec0 0000007346370dc0
+stack_71bf400ea0: 0000000000000000 00000071bf401130 00000071bf402000 0000000000000001
+stack_71bf400ec0: 00000071bf401270 00000071b1861f68 0000000000000000 0000000000000000
+stack_71bf400ee0: 0000000000000000 0000000000000000 000000000000000a 0000000000000000
+stack_71bf400f00: 70951ba81308a820 fa9ecaf712f62bb0 1334ae4800000177 0000000100000001
+stack_71bf400f20: 0000000000000001 70951ba81308a820 0000000012f62bb0 1334ae4800000000
+stack_71bf400f40: 0000000000000000 0000000000000000 00000071bf400f30 0000000000000000
+stack_71bf400f60: 0000000146370dc0 0000007346370dc0 00000071bf401130 00000071bf402000
+stack_71bf400f80: 00000000000005cd 000000715b95ef18 00040007000a0026 d31f8b8761d5c565
+stack_71bf400fa0: 00000071bf401080 00000071d3c6a140 0000000000000000 00000071bf402000
+stack_71bf400fc0: 00000071d3cd7000 b4000072b62a34e0 000000715b960a08 00000071b1861f68
+stack_71bf400fe0: 00000071b1861f6c 00000071bf401270 0000007346370dc0 00000071bf402000
+stack_71bf401000: 0000007346370dc0 000000000000206e 0000000000000000 00000a74762a9bd0
+stack_71bf401020: 00000071bf401130 00000071d3c71e8c 00000071bf401150 00000071bf402000
+stack_71bf401040: 00000071d3cc0b60 00000071bf401010 00000071bf402000 d31f8b8761d5c565
+stack_71bf401060: 00000071bf4010b0 00000071d3c7e258 0000000000001115 d31f8b8761d5c565
+stack_71bf401080: 00000071bf401120 00000071d3903198 000000000000000b 00000071d3cd7000
+stack_71bf4010a0: 0000000000001115 00000071bf4012dc 00000071d38ff780 0000000000000a74
+stack_71bf4010c0: 0000007346370dc0 00000071bf4012b0 000000715b960a08 00000071bf401270
+stack_71bf4010e0: 00000000000002eb 00000071d3cd7000 000000714243f5c8 00000071bf402000
+stack_71bf401100: 000000714243f5c8 000000714243f5c8 0000007346370dc0 00000071bf401358
+stack_71bf401120: 00000071bf401190 00000071d38c503c 0000000000000000 d31f8b8761d5c565
+stack_71bf401140: 00000071bf4011c0 00000071d3822108 0000000000000bd0 b4000072762a9bd0
+stack_71bf401160: 0000000000000bd0 00000071d31905e8 b4000072762a9bd0 00000071bf402000
+stack_71bf401180: 0000000000000003 d31f8b8761d5c565 00000071bf401220 00000071d38ac1fc
+stack_71bf4011a0: 0000000000000001 b4000072762a9bd0 0000000000000058 00000071d3cd7000
+stack_71bf4011c0: 0000000000000000 00000071bf402000 00000071bf401358 00000071bf401270
+stack_71bf4011e0: 00000071bf401530 0000007346370dc0 0000000000000000 000000006feeda38
+stack_71bf401200: 000000006feea410 0000000000000bd0 0000000000000000 d31f8b8761d5c565
+stack_71bf401220: 00000071bf401390 00000071d38ab9d4 000000000000000a 0000000000010000
+stack_71bf401240: 00000071bf401270 000000714243f5c8 0000000000000000 00000071bf4015f0
+stack_71bf401260: 00000071bf401530 0000007346370dc0 00000071bf4015f0 000000714243f5c8
+stack_71bf401280: 00000071bf401130 000000715b960a08 000000715b9609e4 0000000000000000
+stack_71bf4012a0: 000000000000000b 0000000011151115 70951ba81308a820 fa9ecaf712f62bb0
+stack_71bf4012c0: 1334ae4800000177 0000000100000001 0000000000000001 1308a8201334af08
+stack_71bf4012e0: 12f62bb070951ba8 0000000000000000 000000001334ae48 0000000000000000
+stack_71bf401300: 1334af0800000000 00000071d38ab15c 000000000000028a 00000071d3cd8000
+stack_71bf401320: 00000071bf401374 00000000bf402000 00000071bf401310 00000071bf401530
+stack_71bf401340: 0000007346370dc0 00000071bf402000 0000000170052d90 0000000000000016
+stack_71bf401360: 000000715b9609e4 0000000a0001000b 0000000000000000 0000000000000000
+stack_71bf401380: 0000007300000000 d31f8b8761d5c565 00000071bf401480 00000071d3c6da94
+stack_71bf4013a0: 0000007346370dc0 000000001334af08 b4000072b62a34e0 000000714243f5c8
+stack_71bf4013c0: 0000000070052d90 00000071d3335db8 0000000013020618 00000071bf4015f0
+stack_71bf4013e0: 0000000000000008 000000714243f5cc 00000071d3cd8000 00000071bf401444
+stack_71bf401400: 00001072bf402000 0000007346370dc0 00000071bf401530 00000071bf402000
+stack_71bf401420: 0000000000000000 0000000000001c8c 000000020004000b 0000000000000000
+stack_71bf401440: 6feeda3800000002 0000000200000000 0000007346370dc0 d31f8b8761d5c565
+stack_71bf401460: 00000071bf4014b0 00000071d3c7e258 0000000000001175 d31f8b8761d5c565
+stack_71bf401480: 00000071bf401520 00000071d3903098 000000000000028a 00000071d3cd7000
+stack_71bf4014a0: 0000000000001175 00000071bf401638 00000071d38ff780 0000000000001072
+stack_71bf4014c0: 0000007346370dc0 00000071bf401630 00000071d3335db8 00000071bf4015f0
+stack_71bf4014e0: 000000000000028b 00000071d3cd7000 0000000070052d90 00000071bf402000
+stack_71bf401500: 0000000070052d90 0000000070052d90 0000007346370dc0 00000071bf401678
+stack_71bf401520: 00000071bf401590 00000071d38c503c 0000000000000000 000000001334af30
+stack_71bf401540: 00000071bf401750 00000071d38c4690 0000000000001ea0 00000071d3cd7000
+stack_71bf401560: 00000071bf401620 00000071d3825c58 0000000070075fe8 d31f8b8761d5c565
+stack_71bf401580: 000000006ff1f5c0 d31f8b8761d5c565 00000071bf401750 00000071d38c4658
+stack_71bf4015a0: 00000071d3cd7000 00000071d3cd7000 00000071d3cd7000 b4000072b62a34e0
+stack_71bf4015c0: 0000000070052d90 00000071bf401618 00000071bf4015f0 00000071bf4017b0
+stack_71bf4015e0: 0000000070052d90 0000007346370dc0 0000000000000000 0000000070052d90
+stack_71bf401600: 00000071bf401530 00000071d3335db8 00000071d3335db4 0000000000000000
+stack_71bf401620: 0000000000000002 0000000011751175 1334af601334af08 1334af601334af08
+stack_71bf401640: 1444a8501334af30 0000000013edbb10 13edbd9000000000 00000071bf402000
+stack_71bf401660: 00000071d34e18ef 000000715bc492b5 0000000000000000 0000000000000008
+stack_71bf401680: 00000071d3335db4 0000000100010002 00000071bf4017b0 00000071bf401a60
+stack_71bf4016a0: 0000000000000000 000000000000001f 00000071d3340e84 0000000500010007
+stack_71bf4016c0: 00000071bf4017e0 00000071bf401a60 00000071d3ccd120 0000000000000000
+stack_71bf4016e0: 00000071d34e18ef 0000000000000001 00000071bf401800 00000071bf4017c0
+stack_71bf401700: 00000071bf401898 0000000000000001 0000000100000000 0000000000000000
+stack_71bf401720: 00000071bf4015f0 0000000000000002 00000071bf4018c8 d31f8b8761d5c565
+stack_71bf401740: 0000000100000000 0000000000000000 00000071bf401930 00000071d3881f6c
+stack_71bf401760: 00000071bf401950 0000000000000000 0000000070075fe8 000000001334af30
+stack_71bf401780: 0000000000000000 000000001334af60 000000001334a270 0000000000000000
+stack_71bf4017a0: 0000000000000000 0000007346370dc0 0000000070124080 d31f8b8761d5c565
+stack_71bf4017c0: 000000001334af30 646e61680b007470 54656c646e61680a 0000000000000000
+stack_71bf4017e0: 0000000000000000 4010040140100401 0000000000000000 0000000000000000
+stack_71bf401800: 000000001334af60 0000000000000000 000000006ff6eb78 000000006fef61e8
+stack_71bf401820: 0000000000000000 0080000000000000 607560694b1aff3a 0000000000000000
+stack_71bf401840: 0000000000000000 000000001334a270 000000001334af60 0000000000000000
+stack_71bf401860: 000000001334af30 0000000070075fe8 0000000000000000 00000071bf401950
+stack_71bf401880: 00000071bf401930 000000009bfe9820 0000000070079040 0000000000000000
+stack_71bf4018a0: 0000000000000014 000000715b95bdd0 1334af601334a270 00000071d3764780
+stack_71bf4018c0: 0000000000000014 000000715b95bdd0 00000071d3531f47 00000071d3764780
+stack_71bf4018e0: 00000071bf401918 00000071d376e34c 00000071b1865730 000000001334af30
+stack_71bf401900: 00000071bf401a70 000000715b95bdd0 00000071bf401950 1334af3000000000
+stack_71bf401920: 0000000000000000 1334a27000000000 1334af3000000000 000075300000000a
+stack_71bf401940: 1334a27000000000 d31f8b8761d5c565 0000000000000000 0000000000000000
+stack_71bf401960: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401980: 0000000000000000 0000000000000000 0000007346370dc0 0000000000000000
+stack_71bf4019a0: 0000007346370dc0 00000071bf402000 0000007346370e70 000000001334a270
+stack_71bf4019c0: 00000071bf401ae0 00000071bf402000 0000000000000000 0000000000000001
+stack_71bf4019e0: 00000071bf401a00 00000071d3878568 0000000000000000 d31f8b871334a270
+stack_71bf401a00: 00000071bf401ac0 000000715bbd16e6 00000071bf401ac0 00000071b1865730
+stack_71bf401a20: 00000071bf401a80 00000071d3867770 00000071d3cd7000 00000071bf402000
+stack_71bf401a40: 0000000000000000 0000000000000000 00000071bf401ab0 d31f8b8761d5c565
+stack_71bf401a60: 0000000000000000 0000000000000000 0000000000000000 d31f8b8761d5c565
+stack_71bf401a80: 00000071bf401b30 00000071d38b0e1c 000000001334a270 000000715bbd16e6
+stack_71bf401aa0: 00000071b1865730 0000000000000000 0000000000000005 00000071bf401bc0
+stack_71bf401ac0: 0000000000000000 000000715bbd16e6 0000000400000001 00000071bf401ae0
+stack_71bf401ae0: 000000001334a270 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401b00: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401b20: 0000000000000000 d31f8b8761d5c565 00000071bf401c00 00000071d38afb50
+stack_71bf401b40: 00000071bf2fd000 0000000000108000 00000071bf401ba8 00000071d3cd5000
+stack_71bf401b60: 00000071d3cd7000 00000071bf402000 0000000000000005 00000072962afc50
+stack_71bf401b80: 00000000700bb3a8 0000007346370dc0 00000071bf401cb0 d31f8b87005c0000
+stack_71bf401ba0: 00000071bf401bd0 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401bc0: 0000007346370dc0 00000072962afc50 b4000072a62a5150 0000007346370dc0
+stack_71bf401be0: 0000005c00000043 0000000000000000 00000071bf401c30 d31f8b8761d5c565
+stack_71bf401c00: 00000071bf401c60 000000746acf95a0 0000000000108000 0000000000000000
+stack_71bf401c20: 00000071bf401ff8 00000071bf401cb0 00000071bf401cb0 0000000000003427
+stack_71bf401c40: 000000000000380d 00000071bf401cb0 000000746acf955c 00000071bf401cb0
+stack_71bf401c60: 00000071bf401c80 000000746ac996e8 00000071bf401cb0 0000000000000000
+stack_71bf401c80: 0000000000000000 0000000000000000 00000071bf50bcb0 0000000000000000
+stack_71bf401ca0: 00000071bf50c000 00000071bf50bfb0 00000071bf50bcb0 00000071bf1edcb0
+stack_71bf401cc0: 0000380d00003428 0000000000000001 00000071bf2fc000 0000000000105cb0
+stack_71bf401ce0: 0000000000001000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401d00: 0000000000000003 0000000000000000 00000071d38af76c 0000007346370dc0
+stack_71bf401d20: 0000000000000000 0000000080001204 0000007473c00000 00000071570e6000
+stack_71bf401d40: 00000071bf401cb0 0000000100000000 0000000000000000 00000071bf2fc000
+stack_71bf401d60: 000000000010a000 00000071bf2fd000 0000000000108000 6e615f6b63617473
+stack_71bf401d80: 33313a736c745f64 0000000000323533 0000000000000000 0000000000000000
+stack_71bf401da0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401dc0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401de0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401e00: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401e20: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401e40: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401e60: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401e80: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401ea0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401ec0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401ee0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401f00: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401f20: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401f40: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401f60: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401f80: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401fa0: 0000000000000000 00000071bf402040 0000000000000000 0000000000000000
+stack_71bf401fc0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf401fe0: 0000000000000000 0000000000000000 0000000000000000 00000071bf402040
+stack_71bf402000: 000000746ac6d188 00000071bf401cb0 0000000000000000 0000000000000000
+stack_71bf402020: 0000000000000000 d31f8b8761d5c565 000000746ad1c140 0000007346370dc0
+stack_71bf402040: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402060: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402080: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4020a0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4020c0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4020e0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402100: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402120: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402140: 0000000000000000 0000000000000000 0000000000000001 00000072b62e0710
+stack_71bf402160: 0000000000000000 0000000000000000 0000000000000001 00000071f6354be8
+stack_71bf402180: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4021a0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4021c0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4021e0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402200: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402220: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402240: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402260: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402280: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4022a0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4022c0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4022e0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402300: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402320: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402340: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402360: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402380: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4023a0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4023c0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4023e0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402400: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402420: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402440: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402460: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402480: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4024a0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4024c0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4024e0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402500: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402520: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402540: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402560: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402580: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4025a0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4025c0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4025e0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402600: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402620: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402640: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402660: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402680: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4026a0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4026c0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4026e0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402700: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402720: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402740: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402760: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402780: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4027a0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4027c0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4027e0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402800: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402820: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402840: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402860: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402880: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4028a0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4028c0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4028e0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402900: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402920: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402940: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402960: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402980: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4029a0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4029c0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf4029e0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402a00: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402a20: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402a40: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402a60: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402a80: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402aa0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402ac0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402ae0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402b00: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402b20: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402b40: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402b60: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402b80: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402ba0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402bc0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402be0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402c00: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402c20: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402c40: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402c60: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402c80: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402ca0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402cc0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402ce0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402d00: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402d20: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402d40: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402d60: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402d80: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402da0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402dc0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402de0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402e00: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402e20: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402e40: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402e60: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402e80: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402ea0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402ec0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402ee0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402f00: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402f20: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402f40: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402f60: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402f80: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402fa0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
+stack_71bf402fc0: 0000000000000000 0000000000000000
+
diff --git a/simpleperf/scripts/test/script_testdata/java_api-debug_Q.apk b/simpleperf/scripts/test/script_testdata/java_api-debug_Q.apk
new file mode 100644
index 00000000..a9aeadf1
--- /dev/null
+++ b/simpleperf/scripts/test/script_testdata/java_api-debug_Q.apk
Binary files differ
diff --git a/simpleperf/scripts/test/script_testdata/java_api-debug_prev_Q.apk b/simpleperf/scripts/test/script_testdata/java_api-debug_prev_Q.apk
new file mode 100644
index 00000000..9dbf8448
--- /dev/null
+++ b/simpleperf/scripts/test/script_testdata/java_api-debug_prev_Q.apk
Binary files differ
diff --git a/simpleperf/scripts/test/script_testdata/java_api-profile_Q.apk b/simpleperf/scripts/test/script_testdata/java_api-profile_Q.apk
new file mode 100644
index 00000000..45ed9dd5
--- /dev/null
+++ b/simpleperf/scripts/test/script_testdata/java_api-profile_Q.apk
Binary files differ
diff --git a/simpleperf/scripts/test/script_testdata/java_api-profile_prev_Q.apk b/simpleperf/scripts/test/script_testdata/java_api-profile_prev_Q.apk
new file mode 100644
index 00000000..a1a4ec0c
--- /dev/null
+++ b/simpleperf/scripts/test/script_testdata/java_api-profile_prev_Q.apk
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/perf_with_long_callchain.data b/simpleperf/scripts/test/script_testdata/perf_with_long_callchain.data
index 87e59b7c..87e59b7c 100644
--- a/simpleperf/scripts/script_testdata/perf_with_long_callchain.data
+++ b/simpleperf/scripts/test/script_testdata/perf_with_long_callchain.data
Binary files differ
diff --git a/simpleperf/scripts/test/script_testdata/runtest_two_functions_arm64_perf.data b/simpleperf/scripts/test/script_testdata/runtest_two_functions_arm64_perf.data
new file mode 100644
index 00000000..e8c2c4a5
--- /dev/null
+++ b/simpleperf/scripts/test/script_testdata/runtest_two_functions_arm64_perf.data
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/simpleperf_runtest_two_functions_arm b/simpleperf/scripts/test/script_testdata/simpleperf_runtest_two_functions_arm
index 66b34bb2..66b34bb2 100755
--- a/simpleperf/scripts/script_testdata/simpleperf_runtest_two_functions_arm
+++ b/simpleperf/scripts/test/script_testdata/simpleperf_runtest_two_functions_arm
Binary files differ
diff --git a/simpleperf/scripts/test/script_testdata/simpleperf_runtest_two_functions_arm64 b/simpleperf/scripts/test/script_testdata/simpleperf_runtest_two_functions_arm64
new file mode 100755
index 00000000..b8c47208
--- /dev/null
+++ b/simpleperf/scripts/test/script_testdata/simpleperf_runtest_two_functions_arm64
Binary files differ
diff --git a/simpleperf/scripts/test/script_testdata/simpleperf_runtest_two_functions_arm64_without_debug_info b/simpleperf/scripts/test/script_testdata/simpleperf_runtest_two_functions_arm64_without_debug_info
new file mode 100755
index 00000000..7b0e1b02
--- /dev/null
+++ b/simpleperf/scripts/test/script_testdata/simpleperf_runtest_two_functions_arm64_without_debug_info
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/simpleperf_runtest_two_functions_x86 b/simpleperf/scripts/test/script_testdata/simpleperf_runtest_two_functions_x86
index bb0123d3..bb0123d3 100755
--- a/simpleperf/scripts/script_testdata/simpleperf_runtest_two_functions_x86
+++ b/simpleperf/scripts/test/script_testdata/simpleperf_runtest_two_functions_x86
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/simpleperf_runtest_two_functions_x86_64 b/simpleperf/scripts/test/script_testdata/simpleperf_runtest_two_functions_x86_64
index 71cfe3b6..71cfe3b6 100755
--- a/simpleperf/scripts/script_testdata/simpleperf_runtest_two_functions_x86_64
+++ b/simpleperf/scripts/test/script_testdata/simpleperf_runtest_two_functions_x86_64
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/two_process_perf.data b/simpleperf/scripts/test/script_testdata/two_process_perf.data
index c61d5916..c61d5916 100644
--- a/simpleperf/scripts/script_testdata/two_process_perf.data
+++ b/simpleperf/scripts/test/script_testdata/two_process_perf.data
Binary files differ
diff --git a/simpleperf/scripts/test/test.py b/simpleperf/scripts/test/test.py
new file mode 100755
index 00000000..eac3d29d
--- /dev/null
+++ b/simpleperf/scripts/test/test.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+from pathlib import Path
+import sys
+
+# fmt: off
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+import test
+# fmt: on
+
+if __name__ == '__main__':
+ sys.exit(0 if test.main() else 1)
diff --git a/simpleperf/scripts/test/test_utils.py b/simpleperf/scripts/test/test_utils.py
new file mode 100644
index 00000000..7041a61c
--- /dev/null
+++ b/simpleperf/scripts/test/test_utils.py
@@ -0,0 +1,205 @@
+#!/usr/bin/env python3
+#
+# 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.
+#
+"""test_utils.py: utils for testing.
+"""
+
+import logging
+from multiprocessing.connection import Connection
+import os
+from pathlib import Path
+import shutil
+import sys
+import subprocess
+import time
+from typing import List, Optional
+import unittest
+
+from simpleperf_utils import remove, get_script_dir, AdbHelper, is_windows, bytes_to_str
+
+INFERNO_SCRIPT = str(Path(__file__).parents[1] / ('inferno.bat' if is_windows() else 'inferno.sh'))
+
+
+class TestHelper:
+ """ Keep global test options. """
+
+ @classmethod
+ def init(
+ cls, test_dir: str, testdata_dir: str, use_browser: bool, ndk_path: Optional[str],
+ device_serial_number: Optional[str],
+ progress_conn: Optional[Connection]):
+ """
+ When device_serial_number is None, no Android device is used.
+ When device_serial_number is '', use the default Android device.
+ When device_serial_number is not empty, select Android device by serial number.
+ """
+ cls.script_dir = Path(__file__).resolve().parents[1]
+ cls.test_base_dir = Path(test_dir).resolve()
+ cls.test_base_dir.mkdir(parents=True, exist_ok=True)
+ cls.testdata_dir = Path(testdata_dir).resolve()
+ cls.browser_option = [] if use_browser else ['--no_browser']
+ cls.ndk_path = ndk_path
+ cls.progress_conn = progress_conn
+
+ # Logs can come from multiple processes. So use append mode to avoid overwrite.
+ cls.log_fh = open(cls.test_base_dir / 'test.log', 'a')
+ logging.getLogger().handlers.clear()
+ logging.getLogger().addHandler(logging.StreamHandler(cls.log_fh))
+ os.close(sys.stderr.fileno())
+ os.dup2(cls.log_fh.fileno(), sys.stderr.fileno())
+
+ if device_serial_number is not None:
+ if device_serial_number:
+ os.environ['ANDROID_SERIAL'] = device_serial_number
+ cls.adb = AdbHelper(enable_switch_to_root=True)
+ cls.android_version = cls.adb.get_android_version()
+ cls.device_features = None
+
+ @classmethod
+ def log(cls, s: str):
+ cls.log_fh.write(s + '\n')
+ # Child processes can also write to log file, so flush it immediately to keep the order.
+ cls.log_fh.flush()
+
+ @classmethod
+ def testdata_path(cls, testdata_name: str) -> str:
+ """ Return the path of a test data. """
+ return str(cls.testdata_dir / testdata_name)
+
+ @classmethod
+ def get_test_dir(cls, test_name: str) -> Path:
+ """ Return the dir to run a test. """
+ return cls.test_base_dir / test_name
+
+ @classmethod
+ def script_path(cls, script_name: str) -> str:
+ """ Return the dir of python scripts. """
+ return str(cls.script_dir / script_name)
+
+ @classmethod
+ def get_device_features(cls):
+ if cls.device_features is None:
+ args = [sys.executable, cls.script_path(
+ 'run_simpleperf_on_device.py'), 'list', '--show-features']
+ output = subprocess.check_output(args, stderr=TestHelper.log_fh)
+ output = bytes_to_str(output)
+ cls.device_features = output.split()
+ return cls.device_features
+
+ @classmethod
+ def is_trace_offcpu_supported(cls):
+ return 'trace-offcpu' in cls.get_device_features()
+
+ @classmethod
+ def get_32bit_abi(cls):
+ return cls.adb.get_property('ro.product.cpu.abilist32').strip().split(',')[0]
+
+ @classmethod
+ def write_progress(cls, progress: str):
+ if cls.progress_conn:
+ cls.progress_conn.send(progress)
+
+
+class TestBase(unittest.TestCase):
+ def setUp(self):
+ """ Run each test in a separate dir. """
+ self.test_dir = TestHelper.get_test_dir(
+ '%s.%s' % (self.__class__.__name__, self._testMethodName))
+ self.test_dir.mkdir()
+ os.chdir(self.test_dir)
+ TestHelper.log('begin test %s.%s' % (self.__class__.__name__, self._testMethodName))
+
+ def run(self, result=None):
+ start_time = time.time()
+ ret = super(TestBase, self).run(result)
+ if result.errors and result.errors[-1][0] == self:
+ status = 'FAILED'
+ err_info = result.errors[-1][1]
+ elif result.failures and result.failures[-1][0] == self:
+ status = 'FAILED'
+ err_info = result.failures[-1][1]
+ else:
+ status = 'OK'
+
+ time_taken = time.time() - start_time
+ TestHelper.log(
+ 'end test %s.%s %s (%.3fs)' %
+ (self.__class__.__name__, self._testMethodName, status, time_taken))
+ if status != 'OK':
+ TestHelper.log(err_info)
+
+ # Remove test data for passed tests to save space.
+ if status == 'OK':
+ remove(self.test_dir)
+ TestHelper.write_progress(
+ '%s.%s %s' % (self.__class__.__name__, self._testMethodName, status))
+ return ret
+
+ def run_cmd(self, args: List[str], return_output=False, drop_output=True) -> str:
+ if args[0] == 'report_html.py' or args[0] == INFERNO_SCRIPT:
+ args += TestHelper.browser_option
+ if TestHelper.ndk_path:
+ if args[0] in ['app_profiler.py', 'binary_cache_builder.py', 'pprof_proto_generator.py',
+ 'report_html.py']:
+ args += ['--ndk_path', TestHelper.ndk_path]
+ if args[0].endswith('.py'):
+ args = [sys.executable, TestHelper.script_path(args[0])] + args[1:]
+ use_shell = args[0].endswith('.bat')
+ try:
+ if return_output:
+ stdout_fd = subprocess.PIPE
+ drop_output = False
+ elif drop_output:
+ stdout_fd = subprocess.DEVNULL
+ else:
+ stdout_fd = None
+
+ subproc = subprocess.Popen(args, stdout=stdout_fd,
+ stderr=TestHelper.log_fh, shell=use_shell)
+ stdout_data, _ = subproc.communicate()
+ output_data = bytes_to_str(stdout_data)
+ returncode = subproc.returncode
+
+ except OSError:
+ returncode = None
+ self.assertEqual(returncode, 0, msg="failed to run cmd: %s" % args)
+ if return_output:
+ return output_data
+ return ''
+
+ def check_strings_in_file(self, filename, strings):
+ self.check_exist(filename=filename)
+ with open(filename, 'r') as fh:
+ self.check_strings_in_content(fh.read(), strings)
+
+ def check_exist(self, filename=None, dirname=None):
+ if filename:
+ self.assertTrue(os.path.isfile(filename), filename)
+ if dirname:
+ self.assertTrue(os.path.isdir(dirname), dirname)
+
+ def check_strings_in_content(self, content, strings):
+ fulfilled = [content.find(s) != -1 for s in strings]
+ self.check_fulfilled_entries(fulfilled, strings)
+
+ def check_fulfilled_entries(self, fulfilled, entries):
+ failed_entries = []
+ for ok, entry in zip(fulfilled, entries):
+ if not ok:
+ failed_entries.append(entry)
+
+ if failed_entries:
+ self.fail('failed in below entries: %s' % (failed_entries,))
diff --git a/simpleperf/scripts/test/tools_test.py b/simpleperf/scripts/test/tools_test.py
new file mode 100644
index 00000000..28b80b93
--- /dev/null
+++ b/simpleperf/scripts/test/tools_test.py
@@ -0,0 +1,298 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+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 . test_utils import TestBase, TestHelper
+
+
+class TestTools(TestBase):
+ def test_addr2nearestline(self):
+ self.run_addr2nearestline_test(True)
+ self.run_addr2nearestline_test(False)
+
+ def run_addr2nearestline_test(self, with_function_name):
+ test_map = {
+ '/simpleperf_runtest_two_functions_arm64': [
+ {
+ 'func_addr': 0x112c,
+ 'addr': 0x112c,
+ 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:20',
+ 'function': 'main',
+ },
+ {
+ 'func_addr': 0x104c,
+ 'addr': 0x105c,
+ 'source': "system/extras/simpleperf/runtest/two_functions.cpp:7",
+ 'function': "Function1()",
+ },
+ ],
+ '/simpleperf_runtest_two_functions_arm': [
+ {
+ 'func_addr': 0x784,
+ 'addr': 0x7b0,
+ 'source': """system/extras/simpleperf/runtest/two_functions.cpp:14
+ system/extras/simpleperf/runtest/two_functions.cpp:23""",
+ 'function': """Function2()
+ main""",
+ },
+ {
+ 'func_addr': 0x784,
+ 'addr': 0x7d0,
+ 'source': """system/extras/simpleperf/runtest/two_functions.cpp:15
+ system/extras/simpleperf/runtest/two_functions.cpp:23""",
+ 'function': """Function2()
+ main""",
+ }
+ ],
+ '/simpleperf_runtest_two_functions_x86_64': [
+ {
+ 'func_addr': 0x840,
+ 'addr': 0x840,
+ 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:7',
+ 'function': 'Function1()',
+ },
+ {
+ 'func_addr': 0x920,
+ 'addr': 0x94a,
+ 'source': """system/extras/simpleperf/runtest/two_functions.cpp:7
+ system/extras/simpleperf/runtest/two_functions.cpp:22""",
+ 'function': """Function1()
+ main""",
+ }
+ ],
+ '/simpleperf_runtest_two_functions_x86': [
+ {
+ 'func_addr': 0x6d0,
+ 'addr': 0x6da,
+ 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:14',
+ 'function': 'Function2()',
+ },
+ {
+ 'func_addr': 0x710,
+ 'addr': 0x749,
+ 'source': """system/extras/simpleperf/runtest/two_functions.cpp:8
+ system/extras/simpleperf/runtest/two_functions.cpp:22""",
+ 'function': """Function1()
+ main""",
+ }
+ ],
+ }
+ binary_finder = BinaryFinder(TestHelper.testdata_dir, ReadElf(TestHelper.ndk_path))
+ addr2line = Addr2Nearestline(TestHelper.ndk_path, binary_finder, with_function_name)
+ for dso_path in test_map:
+ test_addrs = test_map[dso_path]
+ for test_addr in test_addrs:
+ addr2line.add_addr(dso_path, None, test_addr['func_addr'], test_addr['addr'])
+ addr2line.convert_addrs_to_lines()
+ for dso_path in test_map:
+ dso = addr2line.get_dso(dso_path)
+ self.assertIsNotNone(dso, dso_path)
+ test_addrs = test_map[dso_path]
+ for test_addr in test_addrs:
+ expected_files = []
+ expected_lines = []
+ expected_functions = []
+ for line in test_addr['source'].split('\n'):
+ items = line.split(':')
+ expected_files.append(items[0].strip())
+ expected_lines.append(int(items[1]))
+ for line in test_addr['function'].split('\n'):
+ expected_functions.append(line.strip())
+ self.assertEqual(len(expected_files), len(expected_functions))
+
+ if with_function_name:
+ expected_source = list(zip(expected_files, expected_lines, expected_functions))
+ else:
+ expected_source = list(zip(expected_files, expected_lines))
+
+ actual_source = addr2line.get_addr_source(dso, test_addr['addr'])
+ if is_windows():
+ self.assertIsNotNone(actual_source, 'for %s:0x%x' %
+ (dso_path, test_addr['addr']))
+ for i, source in enumerate(actual_source):
+ new_source = list(source)
+ new_source[0] = new_source[0].replace('\\', '/')
+ actual_source[i] = tuple(new_source)
+
+ self.assertEqual(actual_source, expected_source,
+ 'for %s:0x%x, expected source %s, actual source %s' %
+ (dso_path, test_addr['addr'], expected_source, actual_source))
+
+ def test_objdump(self):
+ test_map = {
+ '/simpleperf_runtest_two_functions_arm64': {
+ 'start_addr': 0x112c,
+ 'len': 28,
+ 'expected_items': [
+ ('main():', 0),
+ ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0),
+ ('1134: add x29, sp, #16', 0x1134),
+ ],
+ },
+ '/simpleperf_runtest_two_functions_arm': {
+ 'start_addr': 0x784,
+ 'len': 80,
+ 'expected_items': [
+ ('main():', 0),
+ ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0),
+ ('7ae: bne.n 7a6 <main+0x22>', 0x7ae),
+ ],
+ },
+ '/simpleperf_runtest_two_functions_x86_64': {
+ 'start_addr': 0x920,
+ 'len': 201,
+ 'expected_items': [
+ ('main():', 0),
+ ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0),
+ ('96e: movl %edx, (%rbx,%rax,4)', 0x96e),
+ ],
+ },
+ '/simpleperf_runtest_two_functions_x86': {
+ 'start_addr': 0x710,
+ 'len': 98,
+ 'expected_items': [
+ ('main():', 0),
+ ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0),
+ ('748: cmpl $100000000, %ebp', 0x748),
+ ],
+ },
+ }
+ binary_finder = BinaryFinder(TestHelper.testdata_dir, ReadElf(TestHelper.ndk_path))
+ objdump = Objdump(TestHelper.ndk_path, binary_finder)
+ for dso_path in test_map:
+ 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
+ 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_readelf(self):
+ test_map = {
+ 'simpleperf_runtest_two_functions_arm64': {
+ 'arch': 'arm64',
+ 'build_id': '0xb4f1b49b0fe9e34e78fb14e5374c930c00000000',
+ 'sections': ['.note.gnu.build-id', '.dynsym', '.text', '.rodata', '.eh_frame',
+ '.eh_frame_hdr', '.debug_info', '.debug_line', '.symtab'],
+ },
+ 'simpleperf_runtest_two_functions_arm': {
+ 'arch': 'arm',
+ 'build_id': '0x718f5b36c4148ee1bd3f51af89ed2be600000000',
+ },
+ 'simpleperf_runtest_two_functions_x86_64': {
+ 'arch': 'x86_64',
+ },
+ 'simpleperf_runtest_two_functions_x86': {
+ 'arch': 'x86',
+ }
+ }
+ readelf = ReadElf(TestHelper.ndk_path)
+ for dso_path in test_map:
+ dso_info = test_map[dso_path]
+ path = os.path.join(TestHelper.testdata_dir, dso_path)
+ self.assertEqual(dso_info['arch'], readelf.get_arch(path))
+ if 'build_id' in dso_info:
+ self.assertEqual(dso_info['build_id'], readelf.get_build_id(path), dso_path)
+ if 'sections' in dso_info:
+ sections = readelf.get_sections(path)
+ for section in dso_info['sections']:
+ self.assertIn(section, sections)
+ self.assertEqual(readelf.get_arch('not_exist_file'), 'unknown')
+ self.assertEqual(readelf.get_build_id('not_exist_file'), '')
+ self.assertEqual(readelf.get_sections('not_exist_file'), [])
+
+ def test_source_file_searcher(self):
+ searcher = SourceFileSearcher(
+ [TestHelper.testdata_path('SimpleperfExampleWithNative'),
+ TestHelper.testdata_path('SimpleperfExampleOfKotlin')])
+
+ def format_path(path):
+ return os.path.join(TestHelper.testdata_dir, path.replace('/', os.sep))
+ # Find a C++ file with pure file name.
+ self.assertEqual(
+ format_path('SimpleperfExampleWithNative/app/src/main/cpp/native-lib.cpp'),
+ searcher.get_real_path('native-lib.cpp'))
+ # Find a C++ file with an absolute file path.
+ self.assertEqual(
+ format_path('SimpleperfExampleWithNative/app/src/main/cpp/native-lib.cpp'),
+ searcher.get_real_path('/data/native-lib.cpp'))
+ # Find a Java file.
+ self.assertEqual(
+ format_path('SimpleperfExampleWithNative/app/src/main/java/com/example/' +
+ 'simpleperf/simpleperfexamplewithnative/MainActivity.java'),
+ searcher.get_real_path('simpleperfexamplewithnative/MainActivity.java'))
+ # Find a Kotlin file.
+ self.assertEqual(
+ format_path('SimpleperfExampleOfKotlin/app/src/main/java/com/example/' +
+ 'simpleperf/simpleperfexampleofkotlin/MainActivity.kt'),
+ searcher.get_real_path('MainActivity.kt'))
+
+ def test_is_elf_file(self):
+ self.assertTrue(ReadElf.is_elf_file(TestHelper.testdata_path(
+ 'simpleperf_runtest_two_functions_arm')))
+ with open('not_elf', 'wb') as fh:
+ fh.write(b'\x90123')
+ try:
+ self.assertFalse(ReadElf.is_elf_file('not_elf'))
+ finally:
+ remove('not_elf')
+
+ def test_binary_finder(self):
+ # Create binary_cache.
+ binary_cache_builder = BinaryCacheBuilder(TestHelper.ndk_path, False)
+ elf_name = 'simpleperf_runtest_two_functions_arm'
+ elf_path = TestHelper.testdata_path(elf_name)
+ readelf = ReadElf(TestHelper.ndk_path)
+ build_id = readelf.get_build_id(elf_path)
+ self.assertGreater(len(build_id), 0)
+ binary_cache_builder.binaries[elf_name] = build_id
+ binary_cache_builder.copy_binaries_from_symfs_dirs([TestHelper.testdata_dir])
+ binary_cache_builder.create_build_id_list()
+
+ # Test BinaryFinder.
+ path_in_binary_cache = Path(binary_cache_builder.binary_cache_dir, elf_name)
+ binary_finder = BinaryFinder(binary_cache_builder.binary_cache_dir, readelf)
+ # Find binary using build id.
+ path = binary_finder.find_binary('[not_exist_file]', build_id)
+ self.assertEqual(path, path_in_binary_cache)
+ # Find binary using path.
+ path = binary_finder.find_binary('/' + elf_name, None)
+ self.assertEqual(path, path_in_binary_cache)
+ # Find binary using absolute path.
+ path = binary_finder.find_binary(str(path_in_binary_cache), None)
+ self.assertEqual(path, path_in_binary_cache)
+
+ # The binary should has a matched build id.
+ path = binary_finder.find_binary('/' + elf_name, 'wrong_build_id')
+ self.assertIsNone(path)
diff --git a/simpleperf/scripts/update.py b/simpleperf/scripts/update.py
index 3a2ab8b9..d27fff33 100755
--- a/simpleperf/scripts/update.py
+++ b/simpleperf/scripts/update.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2016 The Android Open Source Project
#
@@ -49,7 +49,7 @@ INSTALL_LIST = [
'simpleperf/android/x86/simpleperf_ndk',
'android/x86/simpleperf'),
- # simpleperf on host. Linux and macOS are 64-bit only these days.
+ # simpleperf on host.
InstallEntry('MODULES-IN-system-extras-simpleperf',
'simpleperf/linux/x86_64/simpleperf_ndk64',
'linux/x86_64/simpleperf', True),
@@ -59,9 +59,6 @@ INSTALL_LIST = [
InstallEntry('MODULES-IN-system-extras-simpleperf',
'simpleperf/windows/x86_64/simpleperf_ndk64.exe',
'windows/x86_64/simpleperf.exe', True),
- InstallEntry('MODULES-IN-system-extras-simpleperf',
- 'simpleperf/windows/x86/simpleperf_ndk.exe',
- 'windows/x86/simpleperf.exe', True),
# libsimpleperf_report.so on host
InstallEntry('MODULES-IN-system-extras-simpleperf',
@@ -73,17 +70,10 @@ INSTALL_LIST = [
InstallEntry('MODULES-IN-system-extras-simpleperf',
'simpleperf/windows/x86_64/libsimpleperf_report.dll',
'windows/x86_64/libsimpleperf_report.dll', True),
- InstallEntry('MODULES-IN-system-extras-simpleperf',
- 'simpleperf/windows/x86/libsimpleperf_report.dll',
- 'windows/x86/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),
- InstallEntry(MINGW + '/lib32/libwinpthread-1.dll',
- 'libwinpthread-1_32.dll',
- 'windows/x86/libwinpthread-1.dll',
- False),
]
diff --git a/simpleperf/simpleperf_app_runner/Android.bp b/simpleperf/simpleperf_app_runner/Android.bp
index 389421b6..3179f397 100644
--- a/simpleperf/simpleperf_app_runner/Android.bp
+++ b/simpleperf/simpleperf_app_runner/Android.bp
@@ -14,6 +14,15 @@
// 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_simpleperf_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_extras_simpleperf_license"],
+}
+
cc_binary {
name: "simpleperf_app_runner",
srcs: [
@@ -25,4 +34,5 @@ cc_binary {
"libpackagelistparser",
"libminijail",
],
+ header_libs: ["libcutils_headers"],
}
diff --git a/simpleperf/simpleperf_app_runner/simpleperf_app_runner.cpp b/simpleperf/simpleperf_app_runner/simpleperf_app_runner.cpp
index d7cf3562..f3e6b4b5 100644
--- a/simpleperf/simpleperf_app_runner/simpleperf_app_runner.cpp
+++ b/simpleperf/simpleperf_app_runner/simpleperf_app_runner.cpp
@@ -23,16 +23,29 @@
#include <set>
#include <string>
+#include <string_view>
#include <vector>
#include <android-base/file.h>
#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <packagelistparser/packagelistparser.h>
#include <private/android_filesystem_config.h>
#include <scoped_minijail.h>
#include <selinux/android.h>
+#include "../cmd_api_impl.h"
+#include "../cmd_record_impl.h"
+#include "../cmd_stat_impl.h"
+
+using android::base::ParseInt;
+using android::base::ParseUint;
+using android::base::Realpath;
+using android::base::StartsWith;
+using android::base::StringPrintf;
+using namespace simpleperf;
+
// simpleperf_app_runner is used to run simpleperf to profile apps with <profileable shell="true">
// on user devices. It works as below:
// simpleperf cmds in shell -> simpleperf_app_runner -> /system/bin/simpleperf in app's context
@@ -97,110 +110,107 @@ std::vector<gid_t> GetSupplementaryGids(uid_t userAppId) {
return gids;
}
-static void CheckSimpleperfArguments(const char* cmdname, char** args) {
- if (strcmp(cmdname, "stat") != 0 && strcmp(cmdname, "record") != 0 &&
- strcmp(cmdname, "api-collect") != 0) {
- error(1, 0, "cmd isn't allowed: %s", cmdname);
- }
- std::set<std::string> zero_arg_options = {
- "-b",
- "--csv",
- "--exclude-perf",
- "--exit-with-parent",
- "-g",
- "--in-app",
- "--interval-only-values",
- "--log-to-android-buffer",
- "--no-callchain-joiner",
- "--no-cut-samples",
- "--no-dump-kernel-symbols",
- "--no-dump-symbols",
- "--no-inherit",
- "--no-unwind",
- "--per-core",
- "--per-thread",
- "--post-unwind=no",
- "--post-unwind=yes",
- "--trace-offcpu",
- "--verbose",
- };
- std::set<std::string> one_arg_options = {
- "--aux-buffer-size",
- "-c",
- "--call-graph",
- "--callchain-joiner-min-matching-nodes",
- "--clockid",
- "--cpu",
- "--cpu-percent",
- "--duration",
- "-e",
- "-f",
- "--group",
- "--include-filter",
- "--interval",
- "-j",
- "--log",
- "-m",
- "-p",
- "--size-limit",
- "-t",
- };
- // options with a file descriptor
- std::set<std::string> fd_options = {
- "--start_profiling_fd",
- "--stop-signal-fd",
- "--out-fd",
- };
- // options with path from /data/local/tmp/
- std::set<std::string> path_options = {
- "--symfs",
- "--tracepoint-events",
- };
- one_arg_options.insert(fd_options.begin(), fd_options.end());
- one_arg_options.insert(path_options.begin(), path_options.end());
- for (int i = 0; args[i] != nullptr; ++i) {
- if (zero_arg_options.count(args[i])) {
- continue;
- } else if (one_arg_options.count(args[i])) {
- if (args[i + 1] == nullptr) {
- error(1, 0, "invalid arg: %s", args[i]);
+static void CheckSimpleperfArguments(std::string_view cmd_name, char** args) {
+ const OptionFormatMap& common_formats = GetCommonOptionFormatMap();
+ const OptionFormatMap* formats = nullptr;
+ if (cmd_name == "api-collect") {
+ formats = &GetApiCollectCmdOptionFormats();
+ } else if (cmd_name == "record") {
+ formats = &GetRecordCmdOptionFormats();
+ } else if (cmd_name == "stat") {
+ formats = &GetStatCmdOptionFormats();
+ } else {
+ error(1, 0, "cmd isn't allowed: %s", cmd_name.data());
+ }
+
+ for (size_t i = 0; args[i] != nullptr; ++i) {
+ auto it = formats->find(args[i]);
+ if (it == formats->end()) {
+ it = common_formats.find(args[i]);
+ if (it == common_formats.end()) {
+ error(1, 0, "arg isn't allowed: %s", args[i]);
}
- if (fd_options.count(args[i])) {
- // Check if the file descriptor is valid.
+ }
+ const OptionFormat& format = it->second;
+ if (format.value_type != OptionValueType::NONE && args[i + 1] == nullptr) {
+ error(1, 0, "invalid arg: %s", args[i]);
+ }
+ switch (format.app_runner_type) {
+ case AppRunnerType::ALLOWED:
+ break;
+ case AppRunnerType::NOT_ALLOWED:
+ error(1, 0, "arg isn't allowed: %s", args[i]);
+ break;
+ case AppRunnerType::CHECK_FD: {
int fd;
- if (!android::base::ParseInt(args[i + 1], &fd) || fd < 3 || fcntl(fd, F_GETFD) == -1) {
+ if (!ParseInt(args[i + 1], &fd) || fd < 3 || fcntl(fd, F_GETFD) == -1) {
error(1, 0, "invalid fd for arg: %s", args[i]);
}
- } else if (path_options.count(args[i])) {
+ break;
+ }
+ case AppRunnerType::CHECK_PATH: {
std::string path;
- if (!android::base::Realpath(args[i + 1], &path) ||
- !android::base::StartsWith(path, "/data/local/tmp/")) {
+ if (!Realpath(args[i + 1], &path) || !StartsWith(path, "/data/local/tmp/")) {
error(1, 0, "invalid path for arg: %s", args[i]);
}
+ break;
}
+ }
+ if (format.value_type != OptionValueType::NONE) {
++i;
- } else {
- error(1, 0, "arg isn't allowed: %s", args[i]);
}
}
}
int main(int argc, char* argv[]) {
- if (argc < 2) {
- error(1, 0, "usage: simpleperf_app_runner package_name simpleperf_cmd simpleperf_cmd_args...");
- }
if (argc < 3) {
+ fprintf(
+ stderr,
+ // clang-format off
+"Usage: simpleperf_app_runner package_name [options] [simpleperf cmd simpleperf_cmd_args]\n"
+"Options:\n"
+"--user uid profile app process run by uid\n"
+"--show-app-type show if the app is debuggable or profileable\n"
+ // clang-format on
+ );
+ return 1;
+ }
+ int i = 1;
+ char* pkgname = argv[i++];
+ uint32_t user_id = 0;
+ if (i + 1 < argc && strcmp(argv[i], "--user") == 0) {
+ if (!ParseUint(argv[i + 1], &user_id)) {
+ error(1, 0, "invalid uid");
+ }
+ i += 2;
+ }
+ if (i < argc && strcmp(argv[i], "--show-app-type") == 0) {
+ pkg_info* info = ReadPackageInfo(pkgname);
+ if (info == nullptr) {
+ error(1, 0, "failed to find package %s", pkgname);
+ }
+ if (info->debuggable) {
+ printf("debuggable\n");
+ } else if (info->profileable_from_shell) {
+ printf("profileable\n");
+ } else {
+ printf("non_profileable\n");
+ }
+ return 0;
+ }
+
+ if (i == argc) {
error(1, 0, "no simpleperf command name");
}
- char* pkgname = argv[1];
- char* simpleperf_cmdname = argv[2];
- int simpleperf_arg_start = 3;
+ char* simpleperf_cmdname = argv[i];
+ int simpleperf_arg_start = i + 1;
CheckSimpleperfArguments(simpleperf_cmdname, argv + simpleperf_arg_start);
if (getuid() != AID_SHELL && getuid() != AID_ROOT) {
error(1, 0, "program can only run from shell or root");
}
+ // Get package info.
pkg_info* info = ReadPackageInfo(pkgname);
if (info == nullptr) {
error(1, 0, "failed to find package %s", pkgname);
@@ -208,14 +218,25 @@ int main(int argc, char* argv[]) {
if (info->uid < AID_APP_START || info->uid > AID_APP_END) {
error(1, 0, "package isn't an application: %s", pkgname);
}
- if (!info->profileable_from_shell) {
- error(1, 0, "package isn't profileable from shell: %s", pkgname);
+ if (!(info->debuggable || info->profileable_from_shell)) {
+ error(1, 0, "package is neither debuggable nor profileable from shell: %s", pkgname);
+ }
+
+ uid_t user_app_id = info->uid;
+ std::string data_dir = info->data_dir;
+ if (user_id > 0) {
+ // Make sure user_app_id doesn't overflow.
+ if ((UID_MAX - info->uid) / AID_USER_OFFSET < user_id) {
+ error(1, 0, "user id is too big: %d", user_id);
+ }
+ user_app_id = (AID_USER_OFFSET * user_id) + info->uid;
+ data_dir = StringPrintf("/data/user/%d/%s", user_id, pkgname);
}
// Switch to the app's user id and group id.
- uid_t uid = info->uid;
- gid_t gid = info->uid;
- std::vector<gid_t> supplementary_gids = GetSupplementaryGids(info->uid);
+ uid_t uid = user_app_id;
+ gid_t gid = user_app_id;
+ std::vector<gid_t> supplementary_gids = GetSupplementaryGids(user_app_id);
ScopedMinijail j(minijail_new());
minijail_change_uid(j.get(), uid);
minijail_change_gid(j.get(), gid);
@@ -228,7 +249,7 @@ int main(int argc, char* argv[]) {
}
// Switch to the app's data directory.
- if (TEMP_FAILURE_RETRY(chdir(info->data_dir)) == -1) {
+ if (TEMP_FAILURE_RETRY(chdir(data_dir.c_str())) == -1) {
error(1, errno, "couldn't chdir to package's data directory");
}
diff --git a/simpleperf/test_util.cpp b/simpleperf/test_util.cpp
new file mode 100644
index 00000000..c2a21960
--- /dev/null
+++ b/simpleperf/test_util.cpp
@@ -0,0 +1,135 @@
+//
+// 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.
+//
+//
+
+#include <stdio.h>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+
+#include "event_attr.h"
+#include "event_fd.h"
+#include "event_type.h"
+#include "test_util.h"
+
+bool IsInNativeAbi() {
+ static int in_native_abi = -1;
+ if (in_native_abi == -1) {
+ FILE* fp = popen("uname -m", "re");
+ char buf[40];
+ memset(buf, '\0', sizeof(buf));
+ CHECK_EQ(fgets(buf, sizeof(buf), fp), buf);
+ pclose(fp);
+ std::string s = buf;
+ in_native_abi = 1;
+ if (GetBuildArch() == ARCH_X86_32 || GetBuildArch() == ARCH_X86_64) {
+ if (s.find("86") == std::string::npos) {
+ in_native_abi = 0;
+ }
+ } else if (GetBuildArch() == ARCH_ARM || GetBuildArch() == ARCH_ARM64) {
+ if (s.find("arm") == std::string::npos && s.find("aarch64") == std::string::npos) {
+ in_native_abi = 0;
+ }
+ }
+ }
+ return in_native_abi == 1;
+}
+
+static bool InCloudAndroid() {
+#if defined(__i386__) || defined(__x86_64__)
+#if defined(__ANDROID__)
+ std::string prop_value = android::base::GetProperty("ro.build.flavor", "");
+ if (android::base::StartsWith(prop_value, "cf_x86_phone") ||
+ android::base::StartsWith(prop_value, "aosp_cf_x86_phone") ||
+ android::base::StartsWith(prop_value, "cf_x86_64_phone") ||
+ android::base::StartsWith(prop_value, "aosp_cf_x86_64_phone")) {
+ return true;
+ }
+ // aosp_x86* builds may also run on cloud Android. Detect it by checking
+ /// if cpu-cycles isn't supported.
+ if (android::base::StartsWith(prop_value, "aosp_x86")) {
+ const simpleperf::EventType* type = simpleperf::FindEventTypeByName("cpu-cycles", false);
+ CHECK(type != nullptr);
+ perf_event_attr attr = CreateDefaultPerfEventAttr(*type);
+ return !IsEventAttrSupported(attr, "cpu-cycles");
+ }
+#endif
+#endif
+ return false;
+}
+
+#if defined(__arm__)
+// 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);
+ if (type == nullptr) {
+ return false;
+ }
+ perf_event_attr attr = CreateDefaultPerfEventAttr(*type);
+ std::unique_ptr<EventFd> event_fd =
+ EventFd::OpenEventFile(attr, gettid(), -1, nullptr, type->name, false);
+ if (!event_fd) {
+ return false;
+ }
+ // do some cpu work.
+ for (volatile int i = 0; i < 100000; ++i) {
+ }
+ PerfCounter counter;
+ if (event_fd->ReadCounter(&counter)) {
+ return counter.value != 0;
+ }
+ return false;
+}
+#endif // defined(__arm__)
+
+bool HasHardwareCounter() {
+ static int has_hw_counter = -1;
+ if (has_hw_counter == -1) {
+ // Cloud Android doesn't have hardware counters.
+ has_hw_counter = InCloudAndroid() ? 0 : 1;
+#if defined(__arm__)
+ // For arm32 devices, external non-invasive debug signal controls PMU counters. Once it is
+ // disabled for security reason, we always get zero values for PMU counters. And we want to
+ // skip hardware counter tests once we detect it.
+ has_hw_counter &= HasNonZeroInstructionEventCount() ? 1 : 0;
+#endif
+ }
+ return has_hw_counter == 1;
+}
+
+bool HasPmuCounter() {
+ static int has_pmu_counter = -1;
+ if (has_pmu_counter == -1) {
+ has_pmu_counter = 0;
+ auto callback = [&](const simpleperf::EventType& event_type) {
+ if (event_type.IsPmuEvent()) {
+ has_pmu_counter = 1;
+ return false;
+ }
+ return true;
+ };
+ simpleperf::EventTypeManager::Instance().ForEachType(callback);
+ }
+ return has_pmu_counter == 1;
+}
+
+bool HasTracepointEvents() {
+ static int has_tracepoint_events = -1;
+ if (has_tracepoint_events == -1) {
+ has_tracepoint_events = (GetTraceFsDir() != nullptr) ? 1 : 0;
+ }
+ return has_tracepoint_events == 1;
+}
diff --git a/simpleperf/test_util.h b/simpleperf/test_util.h
index 5780b6c0..662c3b2f 100644
--- a/simpleperf/test_util.h
+++ b/simpleperf/test_util.h
@@ -24,8 +24,11 @@
#include "environment.h"
#include "read_elf.h"
+#include "utils.h"
#include "workload.h"
+using namespace simpleperf;
+
static const std::string SLEEP_SEC = "0.001";
void RunWorkloadFunction();
@@ -34,67 +37,81 @@ void CreateProcesses(size_t count, std::vector<std::unique_ptr<Workload>>* workl
void ParseSymbol(const ElfFileSymbol& symbol, std::map<std::string, ElfFileSymbol>* symbols);
void CheckElfFileSymbols(const std::map<std::string, ElfFileSymbol>& symbols);
-bool IsRoot();
+#define TEST_IN_ROOT(TestStatement) \
+ do { \
+ if (IsRoot()) { \
+ TestStatement; \
+ } else { \
+ GTEST_LOG_(INFO) << "Didn't test \"" << #TestStatement << "\" requires root privileges"; \
+ } \
+ } while (0)
+
+#define TEST_REQUIRE_ROOT() \
+ do { \
+ if (!IsRoot()) { \
+ GTEST_LOG_(INFO) << "Skip this test as it needs root privileges."; \
+ return; \
+ } \
+ } while (0)
-#define TEST_IN_ROOT(TestStatement) \
- do { \
- if (IsRoot()) { \
- TestStatement; \
- } else { \
- GTEST_LOG_(INFO) << "Didn't test \"" << #TestStatement << "\" requires root privileges"; \
- } \
+#define TEST_REQUIRE_NON_ROOT() \
+ do { \
+ if (IsRoot()) { \
+ GTEST_LOG_(INFO) << "Skip this test as it tests non-root behavior."; \
+ return; \
+ } \
} while (0)
#if defined(__ANDROID__)
#define TEST_REQUIRE_HOST_ROOT()
#else
-#define TEST_REQUIRE_HOST_ROOT() if (!IsRoot()) return
+#define TEST_REQUIRE_HOST_ROOT() TEST_REQUIRE_ROOT()
#endif
bool IsInNativeAbi();
// Used to skip tests not supposed to run on non-native ABIs.
-#define OMIT_TEST_ON_NON_NATIVE_ABIS() \
- do { \
- if (!IsInNativeAbi()) { \
+#define OMIT_TEST_ON_NON_NATIVE_ABIS() \
+ do { \
+ if (!IsInNativeAbi()) { \
GTEST_LOG_(INFO) << "Skip this test as it only runs on native ABIs."; \
- return; \
- } \
+ return; \
+ } \
} while (0)
bool HasHardwareCounter();
-#define TEST_REQUIRE_HW_COUNTER() \
- do { \
- if (!HasHardwareCounter()) { \
+#define TEST_REQUIRE_HW_COUNTER() \
+ do { \
+ if (!HasHardwareCounter()) { \
GTEST_LOG_(INFO) << "Skip this test as the machine doesn't have hardware PMU counters."; \
- return; \
- } \
+ return; \
+ } \
} while (0)
bool HasPmuCounter();
-#define TEST_REQUIRE_PMU_COUNTER() \
- do { \
- if (!HasPmuCounter()) { \
+#define TEST_REQUIRE_PMU_COUNTER() \
+ do { \
+ if (!HasPmuCounter()) { \
GTEST_LOG_(INFO) << "Skip this test as the machine doesn't have low-level PMU counters."; \
- return; \
- } \
+ return; \
+ } \
} while (0)
bool HasTracepointEvents();
-#define TEST_REQUIRE_TRACEPOINT_EVENTS() \
- do { \
- if (!HasTracepointEvents()) { \
+#define TEST_REQUIRE_TRACEPOINT_EVENTS() \
+ do { \
+ if (!HasTracepointEvents()) { \
GTEST_LOG_(INFO) << "Skip this test as the machine doesn't support tracepoint events."; \
- return; \
- } \
+ return; \
+ } \
} while (0)
#if defined(IN_CTS_TEST)
#define TEST_REQUIRE_APPS()
#else
-#define TEST_REQUIRE_APPS() \
- do { \
+#define TEST_REQUIRE_APPS() \
+ do { \
GTEST_LOG_(INFO) << "Skip this test as test apps aren't available."; \
- return; \
+ return; \
} while (0)
#endif
@@ -172,7 +189,7 @@ class AppHelper {
#elif defined(__arm__)
return "armeabi-v7a";
#else
- #error "unrecognized ABI"
+#error "unrecognized ABI"
#endif
}
diff --git a/simpleperf/testdata/DisplayBitmaps.apk b/simpleperf/testdata/DisplayBitmaps.apk
index 681d0663..5b6f6967 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 c5243ed0..91a0abf2 100644
--- a/simpleperf/testdata/DisplayBitmapsTest.apk
+++ b/simpleperf/testdata/DisplayBitmapsTest.apk
Binary files differ
diff --git a/simpleperf/testdata/cpp_api.apk b/simpleperf/testdata/cpp_api.apk
index 5add50db..fddbc269 100644
--- a/simpleperf/testdata/cpp_api.apk
+++ b/simpleperf/testdata/cpp_api.apk
Binary files differ
diff --git a/simpleperf/testdata/etm/perf_kernel.data b/simpleperf/testdata/etm/perf_kernel.data
new file mode 100644
index 00000000..11f399f9
--- /dev/null
+++ b/simpleperf/testdata/etm/perf_kernel.data
Binary files differ
diff --git a/simpleperf/testdata/etm/perf_with_recording_process.data b/simpleperf/testdata/etm/perf_with_recording_process.data
new file mode 100644
index 00000000..92293bd0
--- /dev/null
+++ b/simpleperf/testdata/etm/perf_with_recording_process.data
Binary files differ
diff --git a/simpleperf/testdata/etm/rq_stats.ko b/simpleperf/testdata/etm/rq_stats.ko
new file mode 100644
index 00000000..c35dcd3f
--- /dev/null
+++ b/simpleperf/testdata/etm/rq_stats.ko
Binary files differ
diff --git a/simpleperf/testdata/java_api.apk b/simpleperf/testdata/java_api.apk
index a2dbb5df..73972ee3 100644
--- a/simpleperf/testdata/java_api.apk
+++ b/simpleperf/testdata/java_api.apk
Binary files differ
diff --git a/simpleperf/testdata/perf_display_bitmaps.data b/simpleperf/testdata/perf_display_bitmaps.data
new file mode 100644
index 00000000..f1c2780b
--- /dev/null
+++ b/simpleperf/testdata/perf_display_bitmaps.data
Binary files differ
diff --git a/simpleperf/testdata/perf_merge1.data b/simpleperf/testdata/perf_merge1.data
new file mode 100644
index 00000000..9b0b8df6
--- /dev/null
+++ b/simpleperf/testdata/perf_merge1.data
Binary files differ
diff --git a/simpleperf/testdata/perf_merge2.data b/simpleperf/testdata/perf_merge2.data
new file mode 100644
index 00000000..a761b32a
--- /dev/null
+++ b/simpleperf/testdata/perf_merge2.data
Binary files differ
diff --git a/simpleperf/testdata/perf_need_proguard_mapping.data b/simpleperf/testdata/perf_need_proguard_mapping.data
new file mode 100644
index 00000000..dba2d5b4
--- /dev/null
+++ b/simpleperf/testdata/perf_need_proguard_mapping.data
Binary files differ
diff --git a/simpleperf/testdata/perf_with_failed_unwinding_debug_info.data b/simpleperf/testdata/perf_with_failed_unwinding_debug_info.data
new file mode 100644
index 00000000..aedcb0b3
--- /dev/null
+++ b/simpleperf/testdata/perf_with_failed_unwinding_debug_info.data
Binary files differ
diff --git a/simpleperf/testdata/perf_with_generic_git_symbols.data b/simpleperf/testdata/perf_with_generic_git_symbols.data
new file mode 100644
index 00000000..dc5e45a7
--- /dev/null
+++ b/simpleperf/testdata/perf_with_generic_git_symbols.data
Binary files differ
diff --git a/simpleperf/testdata/perf_with_jit_symbol.data b/simpleperf/testdata/perf_with_jit_symbol.data
new file mode 100644
index 00000000..5f139204
--- /dev/null
+++ b/simpleperf/testdata/perf_with_jit_symbol.data
Binary files differ
diff --git a/simpleperf/testdata/perf_with_tracepoint_event_dynamic_field.data b/simpleperf/testdata/perf_with_tracepoint_event_dynamic_field.data
new file mode 100644
index 00000000..24e3d902
--- /dev/null
+++ b/simpleperf/testdata/perf_with_tracepoint_event_dynamic_field.data
Binary files differ
diff --git a/simpleperf/testdata/proguard_mapping.txt b/simpleperf/testdata/proguard_mapping.txt
new file mode 100644
index 00000000..c37d45da
--- /dev/null
+++ b/simpleperf/testdata/proguard_mapping.txt
@@ -0,0 +1,36 @@
+androidx.fragment.app.FragmentActivity -> a.h.a.c:
+ int mNextCandidateRequestIndex -> l
+ androidx.lifecycle.LifecycleRegistry mFragmentLifecycleRegistry -> g
+ boolean mResumed -> i
+ boolean mStopped -> j
+ boolean mStartedActivityFromFragment -> k
+ androidx.fragment.app.FragmentController mFragments -> f
+ androidx.collection.SparseArrayCompat mPendingFragmentActivityResults -> m
+ boolean mCreated -> h
+ 82:128:void <init>() -> <init>
+ 536:552:void onStart() -> onStart
+ 446:447:void onStateNotSaved() -> onStateNotSaved
+ 559:566:void onStop() -> onStop
+ 583:584:void supportInvalidateOptionsMenu() -> p
+ 658:664:void startActivityForResult(android.content.Intent,int) -> startActivityForResult
+ 671:677:void startActivityForResult(android.content.Intent,int,android.os.Bundle) -> startActivityForResult
+ 685:692:void startIntentSenderForResult(android.content.IntentSender,int,android.content.Intent,int,int,int) -> startIntentSenderForResult
+ 700:707:void startIntentSenderForResult(android.content.IntentSender,int,android.content.Intent,int,int,int,android.os.Bundle) -> startIntentSenderForResult
+com.example.android.displayingbitmaps.ui.ImageGridFragment -> b.a.a.b.c.b:
+ com.example.android.displayingbitmaps.ui.ImageGridFragment$ImageAdapter mAdapter -> Y
+ int mImageThumbSpacing -> X
+ int mImageThumbSize -> W
+ com.example.android.displayingbitmaps.util.ImageFetcher mImageFetcher -> Z
+ 70:70:void <init>() -> <init>
+ 74:91:void onCreate(android.os.Bundle) -> a0
+ 196:197:void onCreateOptionsMenu(android.view.Menu,android.view.MenuInflater) -> d0
+ 97:153:android.view.View onCreateView(android.view.LayoutInflater,android.view.ViewGroup,android.os.Bundle) -> e0
+ 173:175:void onDestroy() -> f0
+ 201:208:boolean onOptionsItemSelected(android.view.MenuItem) -> o0
+ 180:192:void onItemClick(android.widget.AdapterView,android.view.View,int,long) -> onItemClick
+ 165:169:void onPause() -> q0
+ 58:58:com.example.android.displayingbitmaps.util.ImageFetcher access$000(com.example.android.displayingbitmaps.ui.ImageGridFragment) -> r1
+ 58:58:com.example.android.displayingbitmaps.ui.ImageGridFragment$ImageAdapter access$100(com.example.android.displayingbitmaps.ui.ImageGridFragment) -> s1
+ 58:58:int access$200(com.example.android.displayingbitmaps.ui.ImageGridFragment) -> t1
+ 58:58:int access$300(com.example.android.displayingbitmaps.ui.ImageGridFragment) -> u1
+ 158:161:void onResume() -> v0
diff --git a/simpleperf/testdata/sysfs/module/fake_kernel_module/notes/note.gnu.build-id b/simpleperf/testdata/sysfs/module/fake_kernel_module/notes/note.gnu.build-id
new file mode 100644
index 00000000..5a4109cc
--- /dev/null
+++ b/simpleperf/testdata/sysfs/module/fake_kernel_module/notes/note.gnu.build-id
Binary files differ
diff --git a/simpleperf/thread_tree.cpp b/simpleperf/thread_tree.cpp
index 6babf8cc..28987222 100644
--- a/simpleperf/thread_tree.cpp
+++ b/simpleperf/thread_tree.cpp
@@ -22,17 +22,29 @@
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
#include "perf_event.h"
#include "record.h"
+#include "record_file.h"
+#include "utils.h"
namespace simpleperf {
+namespace {
+
+// Real map file path depends on where the process can create files.
+// For example, app can create files only in its data directory.
+// Use normalized name inherited from pid instead.
+std::string GetSymbolMapDsoName(int pid) {
+ return android::base::StringPrintf("perf-%d.map", pid);
+}
+
+} // namespace
void ThreadTree::SetThreadName(int pid, int tid, const std::string& comm) {
ThreadEntry* thread = FindThreadOrNew(pid, tid);
if (comm != thread->comm) {
- thread_comm_storage_.push_back(
- std::unique_ptr<std::string>(new std::string(comm)));
+ thread_comm_storage_.push_back(std::unique_ptr<std::string>(new std::string(comm)));
thread->comm = thread_comm_storage_.back()->c_str();
}
}
@@ -54,7 +66,7 @@ void ThreadTree::ForkThread(int pid, int tid, int ppid, int ptid) {
}
}
-ThreadEntry* ThreadTree::FindThread(int tid) {
+ThreadEntry* ThreadTree::FindThread(int tid) const {
if (auto it = thread_tree_.find(tid); it != thread_tree_.end()) {
return it->second.get();
}
@@ -85,12 +97,21 @@ ThreadEntry* ThreadTree::CreateThread(int pid, int tid) {
maps = process->maps;
}
ThreadEntry* thread = new ThreadEntry{
- pid, tid,
- comm,
- maps,
+ pid,
+ tid,
+ comm,
+ maps,
};
auto pair = thread_tree_.insert(std::make_pair(tid, std::unique_ptr<ThreadEntry>(thread)));
CHECK(pair.second);
+ if (pid == tid) {
+ // If there is a symbol map dso for the process, add maps for the symbols.
+ auto name = GetSymbolMapDsoName(pid);
+ auto it = user_dso_tree_.find(name);
+ if (it != user_dso_tree_.end()) {
+ AddThreadMapsForDsoSymbols(thread, it->second.get());
+ }
+ }
return thread;
}
@@ -107,30 +128,69 @@ void ThreadTree::AddKernelMap(uint64_t start_addr, uint64_t len, uint64_t pgoff,
if (len == 0) {
return;
}
- Dso* dso = FindKernelDsoOrNew(filename);
+ Dso* dso;
+ if (android::base::StartsWith(filename, DEFAULT_KERNEL_MMAP_NAME)) {
+ dso = FindKernelDsoOrNew();
+ } else {
+ dso = FindKernelModuleDsoOrNew(filename, start_addr, start_addr + len);
+ }
InsertMap(kernel_maps_, MapEntry(start_addr, len, pgoff, dso, true));
}
-Dso* ThreadTree::FindKernelDsoOrNew(const std::string& filename) {
- if (filename == DEFAULT_KERNEL_MMAP_NAME ||
- filename == DEFAULT_KERNEL_MMAP_NAME_PERF) {
- return kernel_dso_.get();
+Dso* ThreadTree::FindKernelDsoOrNew() {
+ if (!kernel_dso_) {
+ kernel_dso_ = Dso::CreateDso(DSO_KERNEL, DEFAULT_KERNEL_MMAP_NAME);
}
+ return kernel_dso_.get();
+}
+
+Dso* ThreadTree::FindKernelModuleDsoOrNew(const std::string& filename, uint64_t memory_start,
+ uint64_t memory_end) {
auto it = module_dso_tree_.find(filename);
if (it == module_dso_tree_.end()) {
- module_dso_tree_[filename] = Dso::CreateDso(DSO_KERNEL_MODULE, filename);
+ module_dso_tree_[filename] =
+ Dso::CreateKernelModuleDso(filename, memory_start, memory_end, FindKernelDsoOrNew());
it = module_dso_tree_.find(filename);
}
return it->second.get();
}
-void ThreadTree::AddThreadMap(int pid, int tid, uint64_t start_addr, uint64_t len,
- uint64_t pgoff, const std::string& filename, uint32_t flags) {
+void ThreadTree::AddThreadMap(int pid, int tid, uint64_t start_addr, uint64_t len, uint64_t pgoff,
+ const std::string& filename, uint32_t flags) {
ThreadEntry* thread = FindThreadOrNew(pid, tid);
Dso* dso = FindUserDsoOrNew(filename, start_addr);
InsertMap(*thread->maps, MapEntry(start_addr, len, pgoff, dso, false, flags));
}
+void ThreadTree::AddThreadMapsForDsoSymbols(ThreadEntry* thread, Dso* dso) {
+ const uint64_t page_size = GetPageSize();
+
+ auto maps = thread->maps;
+
+ uint64_t map_start = 0;
+ uint64_t map_end = 0;
+
+ // Dso symbols are sorted by address. Walk and calculate containing pages.
+ for (const auto& sym : dso->GetSymbols()) {
+ uint64_t sym_map_start = AlignDown(sym.addr, page_size);
+ uint64_t sym_map_end = Align(sym.addr + sym.len, page_size);
+
+ if (map_end < sym_map_start) {
+ if (map_start < map_end) {
+ InsertMap(*maps, MapEntry(map_start, map_end - map_start, map_start, dso, false, 0));
+ }
+ map_start = sym_map_start;
+ }
+ if (map_end < sym_map_end) {
+ map_end = sym_map_end;
+ }
+ }
+
+ if (map_start < map_end) {
+ InsertMap(*maps, MapEntry(map_start, map_end - map_start, map_start, dso, false, 0));
+ }
+}
+
Dso* ThreadTree::FindUserDsoOrNew(const std::string& filename, uint64_t start_addr,
DsoType dso_type) {
auto it = user_dso_tree_.find(filename);
@@ -144,6 +204,16 @@ Dso* ThreadTree::FindUserDsoOrNew(const std::string& filename, uint64_t start_ad
return it->second.get();
}
+void ThreadTree::AddSymbolsForProcess(int pid, std::vector<Symbol>* symbols) {
+ auto name = GetSymbolMapDsoName(pid);
+
+ auto dso = FindUserDsoOrNew(name, 0, DSO_SYMBOL_MAP_FILE);
+ dso->SetSymbols(symbols);
+
+ auto thread = FindThreadOrNew(pid, pid);
+ AddThreadMapsForDsoSymbols(thread, dso);
+}
+
const MapEntry* ThreadTree::AllocateMap(const MapEntry& entry) {
map_storage_.emplace_back(new MapEntry(entry));
return map_storage_.back().get();
@@ -225,8 +295,8 @@ const MapEntry* ThreadTree::FindMap(const ThreadEntry* thread, uint64_t ip) {
return result != nullptr ? result : &unknown_map_;
}
-const Symbol* ThreadTree::FindSymbol(const MapEntry* map, uint64_t ip,
- uint64_t* pvaddr_in_file, Dso** pdso) {
+const Symbol* ThreadTree::FindSymbol(const MapEntry* map, uint64_t ip, uint64_t* pvaddr_in_file,
+ Dso** pdso) {
uint64_t vaddr_in_file = 0;
const Symbol* symbol = nullptr;
Dso* dso = map->dso;
@@ -240,15 +310,15 @@ const Symbol* ThreadTree::FindSymbol(const MapEntry* map, uint64_t ip,
// If the ip address hits the vmlinux, or hits a kernel module, but we can't find its symbol
// in the kernel module file, then find its symbol in /proc/kallsyms or vmlinux.
vaddr_in_file = ip;
- dso = kernel_dso_.get();
+ dso = FindKernelDsoOrNew();
symbol = dso->FindSymbol(vaddr_in_file);
}
if (symbol == nullptr) {
if (show_ip_for_unknown_symbol_) {
- std::string name = android::base::StringPrintf(
- "%s%s[+%" PRIx64 "]", (show_mark_for_unknown_symbol_ ? "*" : ""),
- dso->FileName().c_str(), vaddr_in_file);
+ std::string name = android::base::StringPrintf("%s%s[+%" PRIx64 "]",
+ (show_mark_for_unknown_symbol_ ? "*" : ""),
+ dso->FileName().c_str(), vaddr_in_file);
dso->AddUnknownSymbol(vaddr_in_file, name);
symbol = dso->FindSymbol(vaddr_in_file);
CHECK(symbol != nullptr);
@@ -277,20 +347,19 @@ void ThreadTree::ClearThreadAndMap() {
map_storage_.clear();
}
-void ThreadTree::AddDsoInfo(const std::string& file_path, uint32_t file_type,
- uint64_t min_vaddr, uint64_t file_offset_of_min_vaddr,
- std::vector<Symbol>* symbols,
- const std::vector<uint64_t>& dex_file_offsets) {
- DsoType dso_type = static_cast<DsoType>(file_type);
+void ThreadTree::AddDsoInfo(FileFeature& file) {
+ DsoType dso_type = file.type;
Dso* dso = nullptr;
- if (dso_type == DSO_KERNEL || dso_type == DSO_KERNEL_MODULE) {
- dso = FindKernelDsoOrNew(file_path);
+ if (dso_type == DSO_KERNEL) {
+ dso = FindKernelDsoOrNew();
+ } else if (dso_type == DSO_KERNEL_MODULE) {
+ dso = FindKernelModuleDsoOrNew(file.path, 0, 0);
} else {
- dso = FindUserDsoOrNew(file_path, 0, dso_type);
+ dso = FindUserDsoOrNew(file.path, 0, dso_type);
}
- dso->SetMinExecutableVaddr(min_vaddr, file_offset_of_min_vaddr);
- dso->SetSymbols(symbols);
- for (uint64_t offset : dex_file_offsets) {
+ dso->SetMinExecutableVaddr(file.min_vaddr, file.file_offset_of_min_vaddr);
+ dso->SetSymbols(&file.symbols);
+ for (uint64_t offset : file.dex_file_offsets) {
dso->AddDexFileOffset(offset);
}
}
@@ -313,9 +382,8 @@ void ThreadTree::Update(const Record& record) {
if (r.InKernel()) {
AddKernelMap(r.data->addr, r.data->len, r.data->pgoff, r.filename);
} else {
- std::string filename = (r.filename == DEFAULT_EXECNAME_FOR_THREAD_MMAP)
- ? "[unknown]"
- : r.filename;
+ std::string filename =
+ (r.filename == DEFAULT_EXECNAME_FOR_THREAD_MMAP) ? "[unknown]" : r.filename;
AddThreadMap(r.data->pid, r.data->tid, r.data->addr, r.data->len, r.data->pgoff, filename,
r.data->prot);
}
@@ -336,7 +404,9 @@ void ThreadTree::Update(const Record& record) {
std::vector<Dso*> ThreadTree::GetAllDsos() const {
std::vector<Dso*> result;
- result.push_back(kernel_dso_.get());
+ if (kernel_dso_) {
+ result.push_back(kernel_dso_.get());
+ }
for (auto& p : module_dso_tree_) {
result.push_back(p.second.get());
}
diff --git a/simpleperf/thread_tree.h b/simpleperf/thread_tree.h
index 76d3403d..1024b8e2 100644
--- a/simpleperf/thread_tree.h
+++ b/simpleperf/thread_tree.h
@@ -26,15 +26,13 @@
#include "dso.h"
+namespace simpleperf {
+
struct Record;
constexpr char DEFAULT_KERNEL_MMAP_NAME[] = "[kernel.kallsyms]";
-// Seen in perf.data file generated by perf.
-constexpr char DEFAULT_KERNEL_MMAP_NAME_PERF[] = "[kernel.kallsyms]_text";
constexpr char DEFAULT_EXECNAME_FOR_THREAD_MMAP[] = "//anon";
-namespace simpleperf {
-
namespace map_flags {
constexpr uint32_t PROT_JIT_SYMFILE_MAP = 0x4000;
} // namespace map_flags
@@ -47,8 +45,8 @@ struct MapEntry {
bool in_kernel;
uint32_t flags;
- MapEntry(uint64_t start_addr, uint64_t len, uint64_t pgoff,
- Dso* dso, bool in_kernel, uint32_t flags = 0)
+ MapEntry(uint64_t start_addr, uint64_t len, uint64_t pgoff, Dso* dso, bool in_kernel,
+ uint32_t flags = 0)
: start_addr(start_addr),
len(len),
pgoff(pgoff),
@@ -59,9 +57,7 @@ struct MapEntry {
uint64_t get_end_addr() const { return start_addr + len; }
- uint64_t Contains(uint64_t addr) const {
- return addr >= start_addr && addr < get_end_addr();
- }
+ uint64_t Contains(uint64_t addr) const { return addr >= start_addr && addr < get_end_addr(); }
uint64_t GetVaddrInFile(uint64_t addr) const {
if (Contains(addr)) {
@@ -73,7 +69,7 @@ struct MapEntry {
struct MapSet {
std::map<uint64_t, const MapEntry*> maps; // Map from start_addr to a MapEntry.
- uint64_t version = 0u; // incremented each time changing maps
+ uint64_t version = 0u; // incremented each time changing maps
const MapEntry* FindMapByAddr(uint64_t addr) const;
};
@@ -81,10 +77,12 @@ struct MapSet {
struct ThreadEntry {
int pid;
int tid;
- const char* comm; // It always refers to the latest comm.
+ const char* comm; // It always refers to the latest comm.
std::shared_ptr<MapSet> maps; // maps is shared by threads in the same process.
};
+struct FileFeature;
+
// ThreadTree contains thread information (in ThreadEntry) and mmap information
// (in MapEntry) of the monitored threads. It also has interface to access
// symbols in executable binaries mapped in the monitored threads.
@@ -93,32 +91,34 @@ class ThreadTree {
ThreadTree()
: show_ip_for_unknown_symbol_(false),
show_mark_for_unknown_symbol_(false),
- unknown_symbol_("unknown", 0,
- std::numeric_limits<unsigned long long>::max()) {
+ unknown_symbol_("unknown", 0, std::numeric_limits<unsigned long long>::max()) {
unknown_dso_ = Dso::CreateDso(DSO_UNKNOWN_FILE, "unknown");
- unknown_map_ = MapEntry(0, std::numeric_limits<unsigned long long>::max(),
- 0, unknown_dso_.get(), false);
- kernel_dso_ = Dso::CreateDso(DSO_KERNEL, DEFAULT_KERNEL_MMAP_NAME);
+ unknown_map_ =
+ MapEntry(0, std::numeric_limits<unsigned long long>::max(), 0, unknown_dso_.get(), false);
// We can't dump comm for pid 0 from /proc, so add it's name here.
SetThreadName(0, 0, "swapper");
}
+ virtual ~ThreadTree() {}
void SetThreadName(int pid, int tid, const std::string& comm);
void ForkThread(int pid, int tid, int ppid, int ptid);
- ThreadEntry* FindThread(int tid);
+ virtual ThreadEntry* FindThread(int tid) const;
ThreadEntry* FindThreadOrNew(int pid, int tid);
void ExitThread(int pid, int tid);
- void AddKernelMap(uint64_t start_addr, uint64_t len, uint64_t pgoff,
- const std::string& filename);
+ void AddKernelMap(uint64_t start_addr, uint64_t len, uint64_t pgoff, const std::string& filename);
const MapSet& GetKernelMaps() { return kernel_maps_; }
- void AddThreadMap(int pid, int tid, uint64_t start_addr, uint64_t len,
- uint64_t pgoff, const std::string& filename, uint32_t flags = 0);
- const MapEntry* FindMap(const ThreadEntry* thread, uint64_t ip,
- bool in_kernel);
+ void AddThreadMap(int pid, int tid, uint64_t start_addr, uint64_t len, uint64_t pgoff,
+ const std::string& filename, uint32_t flags = 0);
+
+ // Add process symbols that do not correspond to any real dso.
+ // For example, these might be symbols generated by a JIT.
+ void AddSymbolsForProcess(int pid, std::vector<Symbol>* symbols);
+
+ const MapEntry* FindMap(const ThreadEntry* thread, uint64_t ip, bool in_kernel);
// Find map for an ip address when we don't know whether it is in kernel.
const MapEntry* FindMap(const ThreadEntry* thread, uint64_t ip);
- const Symbol* FindSymbol(const MapEntry* map, uint64_t ip,
- uint64_t* pvaddr_in_file, Dso** pdso = nullptr);
+ const Symbol* FindSymbol(const MapEntry* map, uint64_t ip, uint64_t* pvaddr_in_file,
+ Dso** pdso = nullptr);
const Symbol* FindKernelSymbol(uint64_t ip);
bool IsUnknownDso(const Dso* dso) const { return dso == unknown_dso_.get(); }
const Symbol* UnknownSymbol() const { return &unknown_symbol_; }
@@ -131,10 +131,7 @@ class ThreadTree {
// Clear thread and map information, but keep loaded dso information. It saves
// the time to reload dso information.
void ClearThreadAndMap();
-
- void AddDsoInfo(const std::string& file_path, uint32_t file_type,
- uint64_t min_vaddr, uint64_t file_offset_of_min_vaddr,
- std::vector<Symbol>* symbols, const std::vector<uint64_t>& dex_file_offsets);
+ void AddDsoInfo(FileFeature& file);
void AddDexFileOffset(const std::string& file_path, uint64_t dex_file_offset);
// Update thread tree with information provided by record.
@@ -144,12 +141,17 @@ class ThreadTree {
private:
ThreadEntry* CreateThread(int pid, int tid);
- Dso* FindKernelDsoOrNew(const std::string& filename);
+ Dso* FindKernelDsoOrNew();
+ Dso* FindKernelModuleDsoOrNew(const std::string& filename, uint64_t memory_start,
+ uint64_t memory_end);
Dso* FindUserDsoOrNew(const std::string& filename, uint64_t start_addr = 0,
DsoType dso_type = DSO_ELF_FILE);
const MapEntry* AllocateMap(const MapEntry& entry);
void InsertMap(MapSet& maps, const MapEntry& entry);
+ // Add thread maps to cover symbols in dso.
+ void AddThreadMapsForDsoSymbols(ThreadEntry* thread, Dso* dso);
+
std::unordered_map<int, std::unique_ptr<ThreadEntry>> thread_tree_;
std::vector<std::unique_ptr<std::string>> thread_comm_storage_;
@@ -168,8 +170,4 @@ class ThreadTree {
} // namespace simpleperf
-using MapEntry = simpleperf::MapEntry;
-using ThreadEntry = simpleperf::ThreadEntry;
-using ThreadTree = simpleperf::ThreadTree;
-
#endif // SIMPLE_PERF_THREAD_TREE_H_
diff --git a/simpleperf/thread_tree_test.cpp b/simpleperf/thread_tree_test.cpp
index d00ebcb9..cefa3f74 100644
--- a/simpleperf/thread_tree_test.cpp
+++ b/simpleperf/thread_tree_test.cpp
@@ -18,6 +18,8 @@
#include <gtest/gtest.h>
+#include "read_symbol_map.h"
+
using namespace simpleperf;
class ThreadTreeTest : public ::testing::Test {
@@ -64,6 +66,12 @@ class ThreadTreeTest : public ::testing::Test {
}
}
+ const Symbol* FindSymbol(int pid, int tid, uint64_t ip, bool in_kernel = false) {
+ auto thread = thread_tree_.FindThreadOrNew(pid, tid);
+ auto map = thread_tree_.FindMap(thread, ip, in_kernel);
+ return thread_tree_.FindSymbol(map, ip, nullptr, nullptr);
+ }
+
std::vector<std::string> expected_names_;
ThreadTree thread_tree_;
};
@@ -120,3 +128,18 @@ TEST_F(ThreadTreeTest, reused_tid_without_thread_exit) {
thread_tree_.ForkThread(1, 2, 1, 1);
thread_tree_.ForkThread(2, 2, 1, 1);
}
+
+TEST_F(ThreadTreeTest, add_symbols_for_process) {
+ std::string symbol_map(
+ "0x2000 0x20 two\n"
+ "0x1000 0x10 one\n"
+ "0x3000 0x30 three\n");
+
+ auto symbols = ReadSymbolMapFromString(symbol_map);
+
+ thread_tree_.AddSymbolsForProcess(1, &symbols);
+
+ ASSERT_STREQ("one", FindSymbol(1, 1, 0x1000)->Name());
+ ASSERT_STREQ("two", FindSymbol(1, 1, 0x2010)->Name());
+ ASSERT_STREQ("three", FindSymbol(1, 1, 0x302f)->Name());
+}
diff --git a/simpleperf/tracing.cpp b/simpleperf/tracing.cpp
index 5b7a5a43..cb6a862c 100644
--- a/simpleperf/tracing.cpp
+++ b/simpleperf/tracing.cpp
@@ -16,9 +16,12 @@
#include "tracing.h"
+#include <stdlib.h>
#include <string.h>
#include <map>
+#include <optional>
+#include <regex>
#include <string>
#include <vector>
@@ -32,8 +35,21 @@
#include "perf_event.h"
#include "utils.h"
-const char TRACING_INFO_MAGIC[10] = {23, 8, 68, 't', 'r',
- 'a', 'c', 'i', 'n', 'g'};
+using android::base::Split;
+using android::base::StartsWith;
+
+namespace simpleperf {
+
+template <>
+void MoveFromBinaryFormat(std::string& data, const char*& p) {
+ data.clear();
+ while (*p != '\0') {
+ data.push_back(*p++);
+ }
+ p++;
+}
+
+const char TRACING_INFO_MAGIC[10] = {23, 8, 68, 't', 'r', 'a', 'c', 'i', 'n', 'g'};
template <class T>
void AppendData(std::vector<char>& data, const T& s) {
@@ -50,15 +66,6 @@ void AppendData(std::vector<char>& data, const std::string& s) {
data.insert(data.end(), s.c_str(), s.c_str() + s.size() + 1);
}
-template <>
-void MoveFromBinaryFormat(std::string& data, const char*& p) {
- data.clear();
- while (*p != '\0') {
- data.push_back(*p++);
- }
- p++;
-}
-
static void AppendFile(std::vector<char>& data, const std::string& file,
uint32_t file_size_bytes = 8) {
if (file_size_bytes == 8) {
@@ -71,8 +78,7 @@ static void AppendFile(std::vector<char>& data, const std::string& file,
data.insert(data.end(), file.begin(), file.end());
}
-static void DetachFile(const char*& p, std::string& file,
- uint32_t file_size_bytes = 8) {
+static void DetachFile(const char*& p, std::string& file, uint32_t file_size_bytes = 8) {
uint64_t file_size = ConvertBytesToValue(p, file_size_bytes);
p += file_size_bytes;
file.clear();
@@ -80,6 +86,25 @@ static void DetachFile(const char*& p, std::string& file,
p += file_size;
}
+static bool ReadTraceFsFile(const std::string& path, std::string* content,
+ bool report_error = true) {
+ const char* tracefs_dir = GetTraceFsDir();
+ if (tracefs_dir == nullptr) {
+ if (report_error) {
+ LOG(ERROR) << "tracefs doesn't exist";
+ }
+ return false;
+ }
+ std::string full_path = tracefs_dir + path;
+ if (!android::base::ReadFileToString(full_path, content)) {
+ if (report_error) {
+ PLOG(ERROR) << "failed to read " << full_path;
+ }
+ return false;
+ }
+ return true;
+}
+
struct TraceType {
std::string system;
std::string name;
@@ -101,24 +126,6 @@ class TracingFile {
uint32_t GetPageSize() const { return page_size; }
private:
- bool ReadTraceFsFile(const std::string& path, std::string* content, bool report_error = true) {
- const char* tracefs_dir = GetTraceFsDir();
- if (tracefs_dir == nullptr) {
- if (report_error) {
- LOG(ERROR) << "tracefs doesn't exist";
- }
- return false;
- }
- std::string full_path = tracefs_dir + path;
- if (!android::base::ReadFileToString(full_path, content)) {
- if (report_error) {
- PLOG(ERROR) << "failed to read " << full_path;
- }
- return false;
- }
- return true;
- }
-
char magic[10];
std::string version;
char endian;
@@ -139,8 +146,8 @@ TracingFile::TracingFile() {
memcpy(magic, TRACING_INFO_MAGIC, sizeof(TRACING_INFO_MAGIC));
version = "0.5";
endian = 0;
- size_of_long = static_cast<int>(sizeof(long)); // NOLINT(google-runtime-int)
- page_size = static_cast<uint32_t>(::GetPageSize());
+ size_of_long = static_cast<int>(sizeof(long)); // NOLINT(google-runtime-int)
+ page_size = static_cast<uint32_t>(simpleperf::GetPageSize());
}
bool TracingFile::RecordHeaderFiles() {
@@ -260,13 +267,11 @@ void TracingFile::Dump(size_t indent) const {
}
for (size_t i = 0; i < event_format_files.size(); ++i) {
PrintIndented(indent + 1, "event format file %zu/%zu %s:\n%s\n\n", i + 1,
- event_format_files.size(),
- event_format_files[i].first.c_str(),
+ event_format_files.size(), event_format_files[i].first.c_str(),
event_format_files[i].second.c_str());
}
PrintIndented(indent + 1, "kallsyms:\n%s\n\n", kallsyms_file.c_str());
- PrintIndented(indent + 1, "printk_formats:\n%s\n\n",
- printk_formats_file.c_str());
+ PrintIndented(indent + 1, "printk_formats:\n%s\n\n", printk_formats_file.c_str());
}
enum class FormatParsingState {
@@ -279,82 +284,89 @@ enum class FormatParsingState {
// Parse lines like: field:char comm[16]; offset:8; size:16; signed:1;
static TracingField ParseTracingField(const std::string& s) {
TracingField field;
- size_t start = 0;
std::string name;
std::string value;
- for (size_t i = 0; i < s.size(); ++i) {
- if (!isspace(s[i]) && (i == 0 || isspace(s[i - 1]))) {
- start = i;
- } else if (s[i] == ':') {
- name = s.substr(start, i - start);
- start = i + 1;
- } else if (s[i] == ';') {
- value = s.substr(start, i - start);
- if (name == "field") {
- // Parse value with brackets like "comm[16]", or just a field name.
- size_t left_bracket_pos = value.find('[');
- if (left_bracket_pos == std::string::npos) {
- field.name = value;
- field.elem_count = 1;
- } else {
- field.name = value.substr(0, left_bracket_pos);
- field.elem_count = 1;
- size_t right_bracket_pos = value.find(']', left_bracket_pos);
- if (right_bracket_pos != std::string::npos) {
- size_t len = right_bracket_pos - left_bracket_pos - 1;
- size_t elem_count;
- // Array size may not be a number, like field:u32 rates[IEEE80211_NUM_BANDS].
- if (android::base::ParseUint(value.substr(left_bracket_pos + 1, len), &elem_count)) {
- field.elem_count = elem_count;
- }
+ std::regex re(R"((\w+):(.+?);)");
+
+ std::sregex_iterator match_it(s.begin(), s.end(), re);
+ std::sregex_iterator match_end;
+ while (match_it != match_end) {
+ std::smatch match = *match_it++;
+ std::string name = match.str(1);
+ std::string value = match.str(2);
+
+ if (name == "field") {
+ std::string last_value_part = Split(value, " \t").back();
+
+ if (StartsWith(value, "__data_loc char[]")) {
+ // Parse value like "__data_loc char[] name".
+ field.name = last_value_part;
+ field.elem_count = 1;
+ field.is_dynamic = true;
+ } else if (auto left_bracket_pos = last_value_part.find('[');
+ left_bracket_pos != std::string::npos) {
+ // Parse value with brackets like "char comm[16]".
+ field.name = last_value_part.substr(0, left_bracket_pos);
+ field.elem_count = 1;
+ if (size_t right_bracket_pos = last_value_part.find(']', left_bracket_pos);
+ right_bracket_pos != std::string::npos) {
+ size_t len = right_bracket_pos - left_bracket_pos - 1;
+ 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)) {
+ field.elem_count = elem_count;
}
}
- } else if (name == "offset") {
- field.offset =
- static_cast<size_t>(strtoull(value.c_str(), nullptr, 10));
- } else if (name == "size") {
- size_t size = static_cast<size_t>(strtoull(value.c_str(), nullptr, 10));
- CHECK_EQ(size % field.elem_count, 0u);
- field.elem_size = size / field.elem_count;
- } else if (name == "signed") {
- int is_signed = static_cast<int>(strtoull(value.c_str(), nullptr, 10));
- field.is_signed = (is_signed == 1);
+ } else {
+ // Parse value like "int common_pid".
+ field.name = last_value_part;
+ field.elem_count = 1;
}
+ } else if (name == "offset") {
+ field.offset = static_cast<size_t>(strtoull(value.c_str(), nullptr, 10));
+ } else if (name == "size") {
+ size_t size = static_cast<size_t>(strtoull(value.c_str(), nullptr, 10));
+ CHECK_EQ(size % field.elem_count, 0u);
+ field.elem_size = size / field.elem_count;
+ } else if (name == "signed") {
+ int is_signed = static_cast<int>(strtoull(value.c_str(), nullptr, 10));
+ field.is_signed = (is_signed == 1);
}
}
return field;
}
-std::vector<TracingFormat> TracingFile::LoadTracingFormatsFromEventFiles()
- const {
+TracingFormat ParseTracingFormat(const std::string& data) {
+ TracingFormat format;
+ std::vector<std::string> strs = Split(data, "\n");
+ FormatParsingState state = FormatParsingState::READ_NAME;
+ for (const auto& s : strs) {
+ if (state == FormatParsingState::READ_NAME) {
+ if (size_t pos = s.find("name:"); pos != std::string::npos) {
+ format.name = android::base::Trim(s.substr(pos + strlen("name:")));
+ state = FormatParsingState::READ_ID;
+ }
+ } else if (state == FormatParsingState::READ_ID) {
+ if (size_t pos = s.find("ID:"); pos != std::string::npos) {
+ format.id = strtoull(s.substr(pos + strlen("ID:")).c_str(), nullptr, 10);
+ state = FormatParsingState::READ_FIELDS;
+ }
+ } 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);
+ }
+ }
+ }
+ return format;
+}
+
+std::vector<TracingFormat> TracingFile::LoadTracingFormatsFromEventFiles() const {
std::vector<TracingFormat> formats;
for (const auto& pair : event_format_files) {
- TracingFormat format;
+ TracingFormat format = ParseTracingFormat(pair.second);
format.system_name = pair.first;
- std::vector<std::string> strs = android::base::Split(pair.second, "\n");
- FormatParsingState state = FormatParsingState::READ_NAME;
- for (const auto& s : strs) {
- if (state == FormatParsingState::READ_NAME) {
- size_t pos = s.find("name:");
- if (pos != std::string::npos) {
- format.name = android::base::Trim(s.substr(pos + strlen("name:")));
- state = FormatParsingState::READ_ID;
- }
- } else if (state == FormatParsingState::READ_ID) {
- size_t pos = s.find("ID:");
- if (pos != std::string::npos) {
- format.id =
- strtoull(s.substr(pos + strlen("ID:")).c_str(), nullptr, 10);
- state = FormatParsingState::READ_FIELDS;
- }
- } else if (state == FormatParsingState::READ_FIELDS) {
- size_t pos = s.find("field:");
- if (pos != std::string::npos) {
- TracingField field = ParseTracingField(s);
- format.fields.push_back(field);
- }
- }
- }
formats.push_back(format);
}
return formats;
@@ -365,9 +377,13 @@ Tracing::Tracing(const std::vector<char>& data) {
tracing_file_->LoadFromBinary(data);
}
-Tracing::~Tracing() { delete tracing_file_; }
+Tracing::~Tracing() {
+ delete tracing_file_;
+}
-void Tracing::Dump(size_t indent) { tracing_file_->Dump(indent); }
+void Tracing::Dump(size_t indent) {
+ tracing_file_->Dump(indent);
+}
TracingFormat Tracing::GetTracingFormatHavingId(uint64_t trace_event_id) {
if (tracing_formats_.empty()) {
@@ -388,8 +404,7 @@ std::string Tracing::GetTracingEventNameHavingId(uint64_t trace_event_id) {
}
for (const auto& format : tracing_formats_) {
if (format.id == trace_event_id) {
- return android::base::StringPrintf("%s:%s", format.system_name.c_str(),
- format.name.c_str());
+ return android::base::StringPrintf("%s:%s", format.system_name.c_str(), format.name.c_str());
}
}
return "";
@@ -399,10 +414,11 @@ const std::string& Tracing::GetKallsyms() const {
return tracing_file_->GetKallsymsFile();
}
-uint32_t Tracing::GetPageSize() const { return tracing_file_->GetPageSize(); }
+uint32_t Tracing::GetPageSize() const {
+ return tracing_file_->GetPageSize();
+}
-bool GetTracingData(const std::vector<const EventType*>& event_types,
- std::vector<char>* data) {
+bool GetTracingData(const std::vector<const EventType*>& event_types, std::vector<char>* data) {
data->clear();
std::vector<TraceType> trace_types;
for (const auto& type : event_types) {
@@ -429,3 +445,196 @@ bool GetTracingData(const std::vector<const EventType*>& event_types,
*data = tracing_file.BinaryFormat();
return true;
}
+
+namespace {
+
+// Briefly check if the filter format is acceptable by the kernel, which is described in
+// Documentation/trace/events.rst in the kernel. Also adjust quotes in string operands.
+//
+// filter := predicate_expr [logical_operator predicate_expr]*
+// predicate_expr := predicate | '!' predicate_expr | '(' filter ')'
+// predicate := field_name relational_operator value
+//
+// logical_operator := '&&' | '||'
+// relational_operator := numeric_operator | string_operator
+// numeric_operator := '==' | '!=' | '<' | '<=' | '>' | '>=' | '&'
+// string_operator := '==' | '!=' | '~'
+// value := int or string
+struct FilterFormatAdjuster {
+ FilterFormatAdjuster(bool use_quote) : use_quote(use_quote) {}
+
+ bool MatchFilter(const char*& p) {
+ bool ok = MatchPredicateExpr(p);
+ while (ok && *p != '\0') {
+ RemoveSpace(p);
+ if (strncmp(p, "||", 2) == 0 || strncmp(p, "&&", 2) == 0) {
+ CopyBytes(p, 2);
+ ok = MatchPredicateExpr(p);
+ } else {
+ break;
+ }
+ }
+ RemoveSpace(p);
+ return ok;
+ }
+
+ void RemoveSpace(const char*& p) {
+ size_t i = 0;
+ while (isspace(p[i])) {
+ i++;
+ }
+ if (i > 0) {
+ CopyBytes(p, i);
+ }
+ }
+
+ bool MatchPredicateExpr(const char*& p) {
+ RemoveSpace(p);
+ if (*p == '!') {
+ CopyBytes(p, 1);
+ return MatchPredicateExpr(p);
+ }
+ if (*p == '(') {
+ CopyBytes(p, 1);
+ bool ok = MatchFilter(p);
+ if (!ok) {
+ return false;
+ }
+ RemoveSpace(p);
+ if (*p != ')') {
+ return false;
+ }
+ CopyBytes(p, 1);
+ return true;
+ }
+ return MatchPredicate(p);
+ }
+
+ bool MatchPredicate(const char*& p) {
+ return MatchFieldName(p) && MatchRelationalOperator(p) && MatchValue(p);
+ }
+
+ bool MatchFieldName(const char*& p) {
+ RemoveSpace(p);
+ std::string name;
+ for (size_t i = 0; isalnum(p[i]) || p[i] == '_'; i++) {
+ name.push_back(p[i]);
+ }
+ CopyBytes(p, name.size());
+ if (name.empty()) {
+ return false;
+ }
+ used_fields.emplace(std::move(name));
+ return true;
+ }
+
+ bool MatchRelationalOperator(const char*& p) {
+ RemoveSpace(p);
+ // "==", "!=", "<", "<=", ">", ">=", "&", "~"
+ if (*p == '=' || *p == '!' || *p == '<' || *p == '>') {
+ if (p[1] == '=') {
+ CopyBytes(p, 2);
+ return true;
+ }
+ }
+ if (*p == '<' || *p == '>' || *p == '&' || *p == '~') {
+ CopyBytes(p, 1);
+ return true;
+ }
+ return false;
+ }
+
+ bool MatchValue(const char*& p) {
+ RemoveSpace(p);
+ // Match a string with quotes.
+ if (*p == '\'' || *p == '"') {
+ char quote = *p;
+ size_t len = 1;
+ while (p[len] != quote && p[len] != '\0') {
+ len++;
+ }
+ if (p[len] != quote) {
+ return false;
+ }
+ len++;
+ if (use_quote) {
+ CopyBytes(p, len);
+ } else {
+ p++;
+ CopyBytes(p, len - 2);
+ p++;
+ }
+ return true;
+ }
+ // Match an int value.
+ char* end;
+ errno = 0;
+ if (*p == '-') {
+ strtoll(p, &end, 0);
+ } else {
+ strtoull(p, &end, 0);
+ }
+ if (errno == 0 && end != p) {
+ CopyBytes(p, end - p);
+ return true;
+ }
+ // Match a string without quotes, stopping at ), &&, || or space.
+ size_t len = 0;
+ while (p[len] != '\0' && strchr(")&| \t", p[len]) == nullptr) {
+ len++;
+ }
+ if (len == 0) {
+ return false;
+ }
+ if (use_quote) {
+ adjusted_filter += '"';
+ }
+ CopyBytes(p, len);
+ if (use_quote) {
+ adjusted_filter += '"';
+ }
+ return true;
+ }
+
+ void CopyBytes(const char*& p, size_t len) {
+ adjusted_filter.append(p, len);
+ p += len;
+ }
+
+ const bool use_quote;
+ std::string adjusted_filter;
+ FieldNameSet used_fields;
+};
+
+} // namespace
+
+std::optional<std::string> AdjustTracepointFilter(const std::string& filter, bool use_quote,
+ FieldNameSet* used_fields) {
+ FilterFormatAdjuster adjuster(use_quote);
+ const char* p = filter.c_str();
+ if (!adjuster.MatchFilter(p) || *p != '\0') {
+ LOG(ERROR) << "format error in filter \"" << filter << "\" starting from \"" << p << "\"";
+ return std::nullopt;
+ }
+ *used_fields = std::move(adjuster.used_fields);
+ return std::move(adjuster.adjusted_filter);
+}
+
+std::optional<FieldNameSet> GetFieldNamesForTracepointEvent(const EventType& event) {
+ std::vector<std::string> strs = Split(event.name, ":");
+ if (strs.size() != 2) {
+ return {};
+ }
+ std::string data;
+ if (!ReadTraceFsFile("/events/" + strs[0] + "/" + strs[1] + "/format", &data, false)) {
+ return {};
+ }
+ TracingFormat format = ParseTracingFormat(data);
+ FieldNameSet names;
+ for (auto& field : format.fields) {
+ names.emplace(std::move(field.name));
+ }
+ return names;
+}
+
+} // namespace simpleperf
diff --git a/simpleperf/tracing.h b/simpleperf/tracing.h
index db3f04af..101ac555 100644
--- a/simpleperf/tracing.h
+++ b/simpleperf/tracing.h
@@ -17,6 +17,8 @@
#ifndef SIMPLE_PERF_TRACING_H_
#define SIMPLE_PERF_TRACING_H_
+#include <optional>
+#include <set>
#include <vector>
#include <android-base/logging.h>
@@ -24,12 +26,21 @@
#include "event_type.h"
#include "utils.h"
+namespace simpleperf {
+
struct TracingField {
std::string name;
- size_t offset;
- size_t elem_size;
- size_t elem_count;
- bool is_signed;
+ size_t offset = 0;
+ size_t elem_size = 0;
+ size_t elem_count = 1;
+ bool is_signed = false;
+ bool is_dynamic = false;
+
+ bool operator==(const TracingField& other) const {
+ return name == other.name && offset == other.offset && elem_size == other.elem_size &&
+ elem_count == other.elem_count && is_signed == other.is_signed &&
+ is_dynamic == other.is_dynamic;
+ }
};
struct TracingFieldPlace {
@@ -78,8 +89,7 @@ struct TracingFormat {
return field;
}
}
- LOG(FATAL) << "Couldn't find field " << name << "in TracingFormat of "
- << this->name;
+ LOG(FATAL) << "Couldn't find field " << name << "in TracingFormat of " << this->name;
return fields[0];
}
};
@@ -101,7 +111,17 @@ class Tracing {
std::vector<TracingFormat> tracing_formats_;
};
-bool GetTracingData(const std::vector<const EventType*>& event_types,
- std::vector<char>* data);
+bool GetTracingData(const std::vector<const EventType*>& event_types, std::vector<char>* data);
+
+// use_quote: whether or not to use quotes in string operands
+// used_fields: field names used in the filter
+// Return adjusted filter on success, otherwise return std::nullopt.
+using FieldNameSet = std::set<std::string>;
+std::optional<std::string> AdjustTracepointFilter(const std::string& filter, bool use_quote,
+ FieldNameSet* used_fields);
+std::optional<FieldNameSet> GetFieldNamesForTracepointEvent(const EventType& event);
+TracingFormat ParseTracingFormat(const std::string& data);
+
+} // namespace simpleperf
#endif // SIMPLE_PERF_TRACING_H_
diff --git a/simpleperf/tracing_test.cpp b/simpleperf/tracing_test.cpp
new file mode 100644
index 00000000..0ed82763
--- /dev/null
+++ b/simpleperf/tracing_test.cpp
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+#include "tracing.h"
+
+#include <gtest/gtest.h>
+
+#include <android-base/strings.h>
+
+using namespace simpleperf;
+
+static void CheckAdjustFilter(const std::string& filter, bool use_quote,
+ const std::string& adjusted_filter,
+ const std::string used_field_str) {
+ FieldNameSet used_fields;
+ auto value = AdjustTracepointFilter(filter, use_quote, &used_fields);
+ ASSERT_TRUE(value.has_value());
+ ASSERT_EQ(value.value(), adjusted_filter);
+ ASSERT_EQ(android::base::Join(used_fields, ","), used_field_str);
+}
+
+TEST(tracing, adjust_tracepoint_filter) {
+ std::string filter = "((sig >= 1 && sig < 20) || sig == 32) && comm != \"bash\"";
+ CheckAdjustFilter(filter, true, filter, "comm,sig");
+ CheckAdjustFilter(filter, false, "((sig >= 1 && sig < 20) || sig == 32) && comm != bash",
+ "comm,sig");
+
+ filter = "pid != 3 && !(comm ~ *bash)";
+ CheckAdjustFilter(filter, true, "pid != 3 && !(comm ~ \"*bash\")", "comm,pid");
+ CheckAdjustFilter(filter, false, filter, "comm,pid");
+
+ filter = "mask & 3";
+ CheckAdjustFilter(filter, true, filter, "mask");
+ CheckAdjustFilter(filter, false, filter, "mask");
+
+ filter = "addr > 0 && addr != 0xFFFFFFFFFFFFFFFF || value > -5";
+ CheckAdjustFilter(filter, true, filter, "addr,value");
+ CheckAdjustFilter(filter, false, filter, "addr,value");
+
+ // unmatched paren
+ FieldNameSet used_fields;
+ ASSERT_FALSE(AdjustTracepointFilter("(pid > 3", true, &used_fields).has_value());
+ ASSERT_FALSE(AdjustTracepointFilter("pid > 3)", true, &used_fields).has_value());
+ // unknown operator
+ ASSERT_FALSE(AdjustTracepointFilter("pid ^ 3", true, &used_fields).has_value());
+}
+
+namespace simpleperf {
+std::ostream& operator<<(std::ostream& os, const TracingField& field) {
+ os << "field (" << field.name << ", off " << field.offset << ", elem size " << field.elem_size
+ << ", elem_count " << field.elem_count << ", is_signed " << field.is_signed << ", is_dynamic "
+ << field.is_dynamic << ")";
+ return os;
+}
+} // namespace simpleperf
+
+TEST(tracing, ParseTracingFormat) {
+ std::string data =
+ "name: sched_wakeup_new\n"
+ "ID: 94\n"
+ "format:\n"
+ "\tfield:unsigned short common_type; offset:0; size:2; signed:0;\n"
+ "\tfield:unsigned char common_flags; offset:2; size:1; signed:0;\n"
+ "\tfield:unsigned char common_preempt_count; offset:3; size:1; signed:0;\n"
+ "\tfield:int common_pid; offset:4; size:4; signed:1;\n"
+ "\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);
+ ASSERT_EQ(format.name, "sched_wakeup_new");
+ ASSERT_EQ(format.id, 94);
+ ASSERT_EQ(format.fields.size(), 6);
+ ASSERT_EQ(format.fields[0], TracingField({.name = "common_type", .offset = 0, .elem_size = 2}));
+ ASSERT_EQ(format.fields[1], TracingField({.name = "common_flags", .offset = 2, .elem_size = 1}));
+ ASSERT_EQ(format.fields[2],
+ TracingField({.name = "common_preempt_count", .offset = 3, .elem_size = 1}));
+ ASSERT_EQ(format.fields[3],
+ TracingField({.name = "common_pid", .offset = 4, .elem_size = 4, .is_signed = true}));
+ ASSERT_EQ(
+ format.fields[4],
+ TracingField(
+ {.name = "comm", .offset = 8, .elem_size = 1, .elem_count = 16, .is_signed = true}));
+ ASSERT_EQ(format.fields[5], TracingField({.name = "name",
+ .offset = 24,
+ .elem_size = 4,
+ .elem_count = 1,
+ .is_signed = true,
+ .is_dynamic = true}));
+}
diff --git a/simpleperf/utils.cpp b/simpleperf/utils.cpp
index 407c5f08..069676a6 100644
--- a/simpleperf/utils.cpp
+++ b/simpleperf/utils.cpp
@@ -31,13 +31,21 @@
#include <android-base/file.h>
#include <android-base/logging.h>
+#include <android-base/parseint.h>
#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
#include <build/version.h>
#include <7zCrc.h>
#include <Xz.h>
#include <XzCrc64.h>
+namespace simpleperf {
+
+using android::base::ParseInt;
+using android::base::Split;
+using android::base::StringPrintf;
+
void OneTimeFreeAllocator::Clear() {
for (auto& p : v_) {
delete[] p;
@@ -56,21 +64,21 @@ const char* OneTimeFreeAllocator::AllocateString(std::string_view s) {
cur_ = p;
end_ = p + alloc_size;
}
- strcpy(cur_, s.data());
+ memcpy(cur_, s.data(), s.size());
+ cur_[s.size()] = '\0';
const char* result = cur_;
cur_ += size;
return result;
}
-
android::base::unique_fd FileHelper::OpenReadOnly(const std::string& filename) {
- int fd = TEMP_FAILURE_RETRY(open(filename.c_str(), O_RDONLY | O_BINARY));
- return android::base::unique_fd(fd);
+ int fd = TEMP_FAILURE_RETRY(open(filename.c_str(), O_RDONLY | O_BINARY));
+ return android::base::unique_fd(fd);
}
android::base::unique_fd FileHelper::OpenWriteOnly(const std::string& filename) {
- int fd = TEMP_FAILURE_RETRY(open(filename.c_str(), O_WRONLY | O_BINARY | O_CREAT, 0644));
- return android::base::unique_fd(fd);
+ int fd = TEMP_FAILURE_RETRY(open(filename.c_str(), O_WRONLY | O_BINARY | O_CREAT, 0644));
+ return android::base::unique_fd(fd);
}
std::unique_ptr<ArchiveHelper> ArchiveHelper::CreateInstance(const std::string& filename) {
@@ -82,7 +90,7 @@ std::unique_ptr<ArchiveHelper> ArchiveHelper::CreateInstance(const std::string&
// files than zip files in a process map. In order to detect invalid zip files fast, we add a
// check of magic number here. Note that OpenArchiveFd() detects invalid zip files in a thorough
// way, but it usually needs reading at least 64K file data.
- static const char zip_preamble[] = {0x50, 0x4b, 0x03, 0x04 };
+ static const char zip_preamble[] = {0x50, 0x4b, 0x03, 0x04};
char buf[4];
if (!android::base::ReadFully(fd, buf, 4) || memcmp(buf, zip_preamble, 4) != 0) {
return nullptr;
@@ -296,12 +304,9 @@ bool XzDecompress(const std::string& compressed_data, std::string* decompressed_
}
static std::map<std::string, android::base::LogSeverity> log_severity_map = {
- {"verbose", android::base::VERBOSE},
- {"debug", android::base::DEBUG},
- {"info", android::base::INFO},
- {"warning", android::base::WARNING},
- {"error", android::base::ERROR},
- {"fatal", android::base::FATAL},
+ {"verbose", android::base::VERBOSE}, {"debug", android::base::DEBUG},
+ {"info", android::base::INFO}, {"warning", android::base::WARNING},
+ {"error", android::base::ERROR}, {"fatal", android::base::FATAL},
};
bool GetLogSeverity(const std::string& name, android::base::LogSeverity* severity) {
auto it = log_severity_map.find(name);
@@ -334,47 +339,6 @@ bool IsRoot() {
return is_root == 1;
}
-bool ProcessKernelSymbols(std::string& symbol_data,
- const std::function<bool(const KernelSymbol&)>& callback) {
- char* p = &symbol_data[0];
- char* data_end = p + symbol_data.size();
- while (p < data_end) {
- char* line_end = strchr(p, '\n');
- if (line_end != nullptr) {
- *line_end = '\0';
- }
- size_t line_size = (line_end != nullptr) ? (line_end - p) : (data_end - p);
- // Parse line like: ffffffffa005c4e4 d __warned.41698 [libsas]
- char name[line_size];
- char module[line_size];
- strcpy(module, "");
-
- KernelSymbol symbol;
- int ret = sscanf(p, "%" PRIx64 " %c %s%s", &symbol.addr, &symbol.type, name, module);
- if (line_end != nullptr) {
- *line_end = '\n';
- p = line_end + 1;
- } else {
- p = data_end;
- }
- if (ret >= 3) {
- symbol.name = name;
- size_t module_len = strlen(module);
- if (module_len > 2 && module[0] == '[' && module[module_len - 1] == ']') {
- module[module_len - 1] = '\0';
- symbol.module = &module[1];
- } else {
- symbol.module = nullptr;
- }
-
- if (callback(symbol)) {
- return true;
- }
- }
- }
- return false;
-}
-
size_t GetPageSize() {
#if defined(__linux__)
return sysconf(_SC_PAGE_SIZE);
@@ -407,34 +371,57 @@ timeval SecondToTimeval(double time_in_sec) {
constexpr int SIMPLEPERF_VERSION = 1;
std::string GetSimpleperfVersion() {
- return android::base::StringPrintf("%d.build.%s", SIMPLEPERF_VERSION,
- android::build::GetBuildNumber().c_str());
+ return StringPrintf("%d.build.%s", SIMPLEPERF_VERSION, android::build::GetBuildNumber().c_str());
}
-std::vector<int> GetCpusFromString(const std::string& s) {
- std::set<int> cpu_set;
- bool have_dash = false;
- const char* p = s.c_str();
- char* endp;
- int last_cpu;
- int cpu;
- // Parse line like: 0,1-3, 5, 7-8
- while ((cpu = static_cast<int>(strtol(p, &endp, 10))) != 0 || endp != p) {
- if (have_dash && !cpu_set.empty()) {
- for (int t = last_cpu + 1; t < cpu; ++t) {
- cpu_set.insert(t);
- }
+// Parse a line like: 0,1-3, 5, 7-8
+std::optional<std::set<int>> GetCpusFromString(const std::string& s) {
+ std::string str;
+ for (char c : s) {
+ if (!isspace(c)) {
+ str += c;
}
- have_dash = false;
- cpu_set.insert(cpu);
- last_cpu = cpu;
- p = endp;
- while (!isdigit(*p) && *p != '\0') {
- if (*p == '-') {
- have_dash = true;
+ }
+ std::set<int> cpus;
+ int cpu1;
+ int cpu2;
+ for (const std::string& p : Split(str, ",")) {
+ size_t split_pos = p.find('-');
+ if (split_pos == std::string::npos) {
+ if (!ParseInt(p, &cpu1, 0)) {
+ LOG(ERROR) << "failed to parse cpu: " << p;
+ return std::nullopt;
+ }
+ cpus.insert(cpu1);
+ } else {
+ if (!ParseInt(p.substr(0, split_pos), &cpu1, 0) ||
+ !ParseInt(p.substr(split_pos + 1), &cpu2, 0) || cpu1 > cpu2) {
+ LOG(ERROR) << "failed to parse cpu: " << p;
+ return std::nullopt;
+ }
+ while (cpu1 <= cpu2) {
+ cpus.insert(cpu1++);
}
- ++p;
}
}
- return std::vector<int>(cpu_set.begin(), cpu_set.end());
+ return cpus;
}
+
+std::optional<std::set<pid_t>> GetTidsFromString(const std::string& s, bool check_if_exists) {
+ std::set<pid_t> tids;
+ for (const auto& p : Split(s, ",")) {
+ int tid;
+ if (!ParseInt(p.c_str(), &tid, 0)) {
+ LOG(ERROR) << "Invalid tid '" << p << "'";
+ return std::nullopt;
+ }
+ if (check_if_exists && !IsDir(StringPrintf("/proc/%d", tid))) {
+ LOG(ERROR) << "Non existing thread '" << tid << "'";
+ return std::nullopt;
+ }
+ tids.insert(tid);
+ }
+ return tids;
+}
+
+} // namespace simpleperf
diff --git a/simpleperf/utils.h b/simpleperf/utils.h
index 6bb51d3c..ec2303f0 100644
--- a/simpleperf/utils.h
+++ b/simpleperf/utils.h
@@ -18,20 +18,31 @@
#define SIMPLE_PERF_UTILS_H_
#include <stddef.h>
+#include <stdio.h>
#include <time.h>
+#include <fstream>
#include <functional>
+#include <optional>
#include <set>
#include <string>
#include <vector>
#include <android-base/logging.h>
#include <android-base/macros.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <ziparchive/zip_archive.h>
+namespace simpleperf {
+
+static inline uint64_t AlignDown(uint64_t value, uint64_t alignment) {
+ return value & ~(alignment - 1);
+}
+
static inline uint64_t Align(uint64_t value, uint64_t alignment) {
- return (value + alignment - 1) & ~(alignment - 1);
+ return AlignDown(value + alignment - 1, alignment);
}
#ifdef _WIN32
@@ -47,12 +58,9 @@ static inline uint64_t Align(uint64_t value, uint64_t alignment) {
class OneTimeFreeAllocator {
public:
explicit OneTimeFreeAllocator(size_t unit_size = 8192u)
- : unit_size_(unit_size), cur_(nullptr), end_(nullptr) {
- }
+ : unit_size_(unit_size), cur_(nullptr), end_(nullptr) {}
- ~OneTimeFreeAllocator() {
- Clear();
- }
+ ~OneTimeFreeAllocator() { Clear(); }
void Clear();
const char* AllocateString(std::string_view s);
@@ -64,6 +72,19 @@ class OneTimeFreeAllocator {
char* end_;
};
+class LineReader {
+ public:
+ explicit LineReader(std::string_view file_path) : ifs_(file_path) {}
+ // Return true if open file successfully.
+ bool Ok() const { return ifs_.good(); }
+ // If available, return next line content with new line, otherwise return nullptr.
+ std::string* ReadLine() { return (std::getline(ifs_, buf_)) ? &buf_ : nullptr; }
+
+ private:
+ std::ifstream ifs_;
+ std::string buf_;
+};
+
class FileHelper {
public:
static android::base::unique_fd OpenReadOnly(const std::string& filename);
@@ -146,16 +167,6 @@ std::string GetLogSeverityName();
bool IsRoot();
-struct KernelSymbol {
- uint64_t addr;
- char type;
- const char* name;
- const char* module; // If nullptr, the symbol is not in a kernel module.
-};
-
-bool ProcessKernelSymbols(std::string& symbol_data,
- const std::function<bool(const KernelSymbol&)>& callback);
-
size_t GetPageSize();
uint64_t ConvertBytesToValue(const char* bytes, uint32_t size);
@@ -164,16 +175,29 @@ timeval SecondToTimeval(double time_in_sec);
std::string GetSimpleperfVersion();
-std::vector<int> GetCpusFromString(const std::string& s);
+std::optional<std::set<int>> GetCpusFromString(const std::string& s);
+std::optional<std::set<pid_t>> GetTidsFromString(const std::string& s, bool check_if_exists);
-namespace {
+template <typename T>
+std::optional<std::set<T>> ParseUintVector(const std::string& s) {
+ std::set<T> result;
+ T value;
+ for (const auto& p : android::base::Split(s, ",")) {
+ if (!android::base::ParseUint(p.c_str(), &value, std::numeric_limits<T>::max())) {
+ LOG(ERROR) << "Invalid Uint '" << p << "' in " << s;
+ return std::nullopt;
+ }
+ result.insert(value);
+ }
+ return result;
+}
// from boost::hash_combine
template <typename T>
-void HashCombine(size_t& seed, const T& val) {
+static inline void HashCombine(size_t& seed, const T& val) {
seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
-} // namespace
+} // namespace simpleperf
#endif // SIMPLE_PERF_UTILS_H_
diff --git a/simpleperf/utils_test.cpp b/simpleperf/utils_test.cpp
index 878f8aae..725ae4db 100644
--- a/simpleperf/utils_test.cpp
+++ b/simpleperf/utils_test.cpp
@@ -16,53 +16,12 @@
#include <gtest/gtest.h>
+#include <android-base/file.h>
+
#include "get_test_data.h"
#include "utils.h"
-static bool ModulesMatch(const char* p, const char* q) {
- if (p == nullptr && q == nullptr) {
- return true;
- }
- if (p != nullptr && q != nullptr) {
- return strcmp(p, q) == 0;
- }
- return false;
-}
-
-static bool KernelSymbolsMatch(const KernelSymbol& sym1,
- const KernelSymbol& sym2) {
- return sym1.addr == sym2.addr && sym1.type == sym2.type &&
- strcmp(sym1.name, sym2.name) == 0 &&
- ModulesMatch(sym1.module, sym2.module);
-}
-
-TEST(utils, ProcessKernelSymbols) {
- std::string data =
- "ffffffffa005c4e4 d __warned.41698 [libsas]\n"
- "aaaaaaaaaaaaaaaa T _text\n"
- "cccccccccccccccc c ccccc\n";
- KernelSymbol expected_symbol;
- expected_symbol.addr = 0xffffffffa005c4e4ULL;
- expected_symbol.type = 'd';
- expected_symbol.name = "__warned.41698";
- expected_symbol.module = "libsas";
- ASSERT_TRUE(ProcessKernelSymbols(
- data,
- std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol)));
-
- expected_symbol.addr = 0xaaaaaaaaaaaaaaaaULL;
- expected_symbol.type = 'T';
- expected_symbol.name = "_text";
- expected_symbol.module = nullptr;
- ASSERT_TRUE(ProcessKernelSymbols(
- data,
- std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol)));
-
- expected_symbol.name = "non_existent_symbol";
- ASSERT_FALSE(ProcessKernelSymbols(
- data,
- std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol)));
-}
+using namespace simpleperf;
TEST(utils, ConvertBytesToValue) {
char buf[8];
@@ -101,8 +60,32 @@ TEST(utils, ArchiveHelper) {
}
TEST(utils, GetCpusFromString) {
- ASSERT_EQ(GetCpusFromString(""), std::vector<int>());
- ASSERT_EQ(GetCpusFromString("0-2"), std::vector<int>({0, 1, 2}));
- ASSERT_EQ(GetCpusFromString("0,2-3"), std::vector<int>({0, 2, 3}));
- ASSERT_EQ(GetCpusFromString("1,0-3,3,4"), std::vector<int>({0, 1, 2, 3, 4}));
+ 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}));
+ ASSERT_EQ(GetCpusFromString("1,0-3,3,4"), std::make_optional<std::set<int>>({0, 1, 2, 3, 4}));
+ ASSERT_EQ(GetCpusFromString("0,1-3, 5, 7-8"),
+ std::make_optional<std::set<int>>({0, 1, 2, 3, 5, 7, 8}));
+ ASSERT_EQ(GetCpusFromString(""), std::nullopt);
+ ASSERT_EQ(GetCpusFromString("-3"), std::nullopt);
+ ASSERT_EQ(GetCpusFromString("3,2-1"), std::nullopt);
+}
+
+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);
+}
+
+TEST(utils, LineReader) {
+ TemporaryFile tmpfile;
+ close(tmpfile.release());
+ ASSERT_TRUE(android::base::WriteStringToFile("line1\nline2", tmpfile.path));
+ LineReader reader(tmpfile.path);
+ ASSERT_TRUE(reader.Ok());
+ std::string* line = reader.ReadLine();
+ ASSERT_TRUE(line != nullptr);
+ ASSERT_EQ(*line, "line1");
+ line = reader.ReadLine();
+ ASSERT_TRUE(line != nullptr);
+ ASSERT_EQ(*line, "line2");
+ ASSERT_TRUE(reader.ReadLine() == nullptr);
}
diff --git a/simpleperf/workload.cpp b/simpleperf/workload.cpp
index 60c9ed15..e82e47b6 100644
--- a/simpleperf/workload.cpp
+++ b/simpleperf/workload.cpp
@@ -25,15 +25,17 @@
#include <android-base/logging.h>
#include <android-base/strings.h>
+namespace simpleperf {
+
std::unique_ptr<Workload> Workload::CreateWorkload(const std::vector<std::string>& args) {
- std::unique_ptr<Workload> workload(new Workload(args, std::function<void ()>()));
+ std::unique_ptr<Workload> workload(new Workload(args, std::function<void()>()));
if (workload != nullptr && workload->CreateNewProcess()) {
return workload;
}
return nullptr;
}
-std::unique_ptr<Workload> Workload::CreateWorkload(const std::function<void ()>& function) {
+std::unique_ptr<Workload> Workload::CreateWorkload(const std::function<void()>& function) {
std::unique_ptr<Workload> workload(new Workload(std::vector<std::string>(), function));
if (workload != nullptr && workload->CreateNewProcess()) {
return workload;
@@ -51,16 +53,14 @@ bool Workload::RunCmd(const std::vector<std::string>& args, bool report_error) {
return ret == 0;
}
-Workload::Workload(const std::vector<std::string>& args, const std::function<void ()>& function)
+Workload::Workload(const std::vector<std::string>& args, const std::function<void()>& function)
: work_state_(NotYetCreateNewProcess),
child_proc_args_(args),
child_proc_function_(function),
work_pid_(-1),
start_signal_fd_(-1),
exec_child_fd_(-1) {
- kill_function_ = [](pid_t pid) {
- kill(pid, SIGKILL);
- };
+ kill_function_ = [](pid_t pid) { kill(pid, SIGKILL); };
}
Workload::~Workload() {
@@ -204,3 +204,5 @@ bool Workload::WaitChildProcess(bool wait_forever, bool is_child_killed, int* ex
}
return finished;
}
+
+} // namespace simpleperf
diff --git a/simpleperf/workload.h b/simpleperf/workload.h
index 4ca93a42..22718a99 100644
--- a/simpleperf/workload.h
+++ b/simpleperf/workload.h
@@ -25,6 +25,8 @@
#include <android-base/macros.h>
+namespace simpleperf {
+
class Workload {
private:
enum WorkState {
@@ -36,28 +38,24 @@ class Workload {
public:
static std::unique_ptr<Workload> CreateWorkload(const std::vector<std::string>& args);
- static std::unique_ptr<Workload> CreateWorkload(const std::function<void ()>& function);
+ static std::unique_ptr<Workload> CreateWorkload(const std::function<void()>& function);
static bool RunCmd(const std::vector<std::string>& args, bool report_error = true);
~Workload();
bool Start();
- bool IsStarted() {
- return work_state_ == Started;
- }
- pid_t GetPid() {
- return work_pid_;
- }
+ bool IsStarted() { return work_state_ == Started; }
+ pid_t GetPid() { return work_pid_; }
bool WaitChildProcess(int* exit_code);
// Set the function used to kill the workload process in ~Workload().
- void SetKillFunction(const std::function<void (pid_t)>& kill_function) {
+ void SetKillFunction(const std::function<void(pid_t)>& kill_function) {
kill_function_ = kill_function;
}
private:
- explicit Workload(const std::vector<std::string>& args, const std::function<void ()>& function);
+ explicit Workload(const std::vector<std::string>& args, const std::function<void()>& function);
bool CreateNewProcess();
void ChildProcessFn(int start_signal_fd, int exec_child_fd);
@@ -66,13 +64,15 @@ class Workload {
WorkState work_state_;
// The child process either executes child_proc_args or run child_proc_function.
std::vector<std::string> child_proc_args_;
- std::function<void ()> child_proc_function_;
+ std::function<void()> child_proc_function_;
pid_t work_pid_;
int start_signal_fd_; // The parent process writes 1 to start workload in the child process.
int exec_child_fd_; // The child process writes 1 to notify that execvp() failed.
- std::function<void (pid_t)> kill_function_;
+ std::function<void(pid_t)> kill_function_;
DISALLOW_COPY_AND_ASSIGN(Workload);
};
+} // namespace simpleperf
+
#endif // SIMPLE_PERF_WORKLOAD_H_
diff --git a/simpleperf/workload_test.cpp b/simpleperf/workload_test.cpp
index 98241432..f99ec753 100644
--- a/simpleperf/workload_test.cpp
+++ b/simpleperf/workload_test.cpp
@@ -22,11 +22,11 @@
#include "utils.h"
#include "workload.h"
+using namespace simpleperf;
+
TEST(workload, success) {
IOEventLoop loop;
- ASSERT_TRUE(loop.AddSignalEvent(SIGCHLD, [&]() {
- return loop.ExitLoop();
- }));
+ ASSERT_TRUE(loop.AddSignalEvent(SIGCHLD, [&]() { return loop.ExitLoop(); }));
auto workload = Workload::CreateWorkload({"sleep", "1"});
ASSERT_TRUE(workload != nullptr);
ASSERT_TRUE(workload->GetPid() != 0);
@@ -43,9 +43,7 @@ TEST(workload, execvp_failure) {
static void run_signaled_workload() {
{
IOEventLoop loop;
- ASSERT_TRUE(loop.AddSignalEvent(SIGCHLD, [&]() {
- return loop.ExitLoop();
- }));
+ ASSERT_TRUE(loop.AddSignalEvent(SIGCHLD, [&]() { return loop.ExitLoop(); }));
auto workload = Workload::CreateWorkload({"sleep", "10"});
ASSERT_TRUE(workload != nullptr);
ASSERT_TRUE(workload->Start());
@@ -64,9 +62,7 @@ TEST(workload, signaled_warning) {
static void run_exit_nonzero_workload() {
{
IOEventLoop loop;
- ASSERT_TRUE(loop.AddSignalEvent(SIGCHLD, [&]() {
- return loop.ExitLoop();
- }));
+ ASSERT_TRUE(loop.AddSignalEvent(SIGCHLD, [&]() { return loop.ExitLoop(); }));
auto workload = Workload::CreateWorkload({"ls", "nonexistdir"});
ASSERT_TRUE(workload != nullptr);
ASSERT_TRUE(workload->Start());
diff --git a/slideshow/Android.bp b/slideshow/Android.bp
new file mode 100644
index 00000000..b6c3922a
--- /dev/null
+++ b/slideshow/Android.bp
@@ -0,0 +1,36 @@
+// Copyright 2015 The Android Open Source Project
+
+package {
+ default_applicable_licenses: ["system_extras_slideshow_license"],
+}
+
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_slideshow_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
+cc_binary {
+ name: "slideshow",
+ srcs: ["slideshow.cpp"],
+ cflags: [
+ "-D__STDC_LIMIT_MACROS",
+ ],
+ shared_libs: [
+ "libminui",
+ "libpng",
+ "libbase",
+ "libz",
+ "libutils",
+ "libcutils",
+ "liblog",
+ "libm",
+ "libc",
+ ],
+}
diff --git a/slideshow/Android.mk b/slideshow/Android.mk
deleted file mode 100644
index 192e64fb..00000000
--- a/slideshow/Android.mk
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2015 The Android Open Source Project
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := slideshow.cpp
-LOCAL_MODULE := slideshow
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_CFLAGS := -D__STDC_LIMIT_MACROS -Werror
-LOCAL_SHARED_LIBRARIES := \
- libminui \
- libpng \
- libbase \
- libz \
- libutils \
- libcutils \
- liblog \
- libm \
- libc
-include $(BUILD_EXECUTABLE)
diff --git a/sound/Android.bp b/sound/Android.bp
index 9c6b1905..2b0e5f98 100644
--- a/sound/Android.bp
+++ b/sound/Android.bp
@@ -1,3 +1,20 @@
+package {
+ default_applicable_licenses: ["system_extras_sound_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_sound_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_binary {
name: "sound",
srcs: ["playwav.c"],
diff --git a/squashfs_utils/Android.bp b/squashfs_utils/Android.bp
index c4c675c3..4a6fb5fa 100644
--- a/squashfs_utils/Android.bp
+++ b/squashfs_utils/Android.bp
@@ -1,5 +1,22 @@
// Copyright 2015 The Android Open Source Project
+package {
+ default_applicable_licenses: ["system_extras_squashfs_utils_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_squashfs_utils_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_library {
name: "libsquashfs_utils",
cflags: ["-Werror"],
diff --git a/su/Android.mk b/su/Android.mk
index e3da4f21..18493992 100644
--- a/su/Android.mk
+++ b/su/Android.mk
@@ -6,6 +6,11 @@ LOCAL_CFLAGS := -Wall -Werror
LOCAL_SRC_FILES:= su.cpp
LOCAL_MODULE:= su
+LOCAL_LICENSE_KINDS:= SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS:= notice
+LOCAL_NOTICE_FILE:= $(LOCAL_PATH)/NOTICE
+
+LOCAL_HEADER_LIBRARIES := libcutils_headers
LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
diff --git a/taskstats/Android.bp b/taskstats/Android.bp
index 3f2a5732..ad9f88ce 100644
--- a/taskstats/Android.bp
+++ b/taskstats/Android.bp
@@ -1,5 +1,22 @@
// Copyright 2013 The Android Open Source Project
+package {
+ default_applicable_licenses: ["system_extras_taskstats_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_taskstats_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_binary {
name: "taskstats",
diff --git a/tests/Android.bp b/tests/Android.bp
new file mode 100644
index 00000000..af3b8b2f
--- /dev/null
+++ b/tests/Android.bp
@@ -0,0 +1,31 @@
+//
+// 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 {
+ default_applicable_licenses: ["system_extras_tests_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_tests_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
diff --git a/tests/audio/alsa/Android.bp b/tests/audio/alsa/Android.bp
index 60dbe87e..0bcc15eb 100644
--- a/tests/audio/alsa/Android.bp
+++ b/tests/audio/alsa/Android.bp
@@ -14,6 +14,15 @@
// 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_tests_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_extras_tests_license"],
+}
+
cc_test {
name: "pcmtest",
srcs: ["pcmtest.cpp"],
@@ -27,5 +36,5 @@ cc_test {
"liblog",
"libtinyalsa",
],
- static_libs: ["libtestUtil"],
+ vendor: true,
}
diff --git a/tests/audio/alsa/pcmtest.cpp b/tests/audio/alsa/pcmtest.cpp
index 090334e1..883aa972 100644
--- a/tests/audio/alsa/pcmtest.cpp
+++ b/tests/audio/alsa/pcmtest.cpp
@@ -29,12 +29,17 @@
#define LOG_TAG "pcmtest"
#include <utils/Log.h>
-#include <testUtil.h>
#define PCM_PREFIX "pcm"
#define MIXER_PREFIX "control"
#define TIMER_PREFIX "timer"
+#define MAXSTR 200
+#define testPrintI(...) \
+ do { \
+ testPrint(stdout, __VA_ARGS__); \
+ } while (0)
+
const char kSoundDir[] = "/dev/snd";
typedef struct PCM_NODE {
@@ -50,6 +55,22 @@ static unsigned int cards;
static unsigned int mixers;
static unsigned int timers;
+void testPrint(FILE* stream, const char* fmt, ...) {
+ char line[MAXSTR];
+ va_list args;
+
+ va_start(args, fmt);
+ vsnprintf(line, sizeof(line), fmt, args);
+ if (stream == stderr) {
+ ALOG(LOG_ERROR, LOG_TAG, "%s", line);
+ } else {
+ ALOG(LOG_INFO, LOG_TAG, "%s", line);
+ }
+ vfprintf(stream, fmt, args);
+ va_end(args);
+ fputc('\n', stream);
+}
+
unsigned int getPcmNodes(void)
{
DIR *d;
diff --git a/tests/binder/benchmarks/Android.bp b/tests/binder/benchmarks/Android.bp
index 5ac0895a..6a550688 100644
--- a/tests/binder/benchmarks/Android.bp
+++ b/tests/binder/benchmarks/Android.bp
@@ -14,6 +14,15 @@
// 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_tests_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_extras_tests_license"],
+}
+
cc_benchmark {
name: "binderAddInts",
diff --git a/tests/bootloader/Android.mk b/tests/bootloader/Android.mk
index ab311fdc..a5421a50 100644
--- a/tests/bootloader/Android.mk
+++ b/tests/bootloader/Android.mk
@@ -6,6 +6,9 @@ LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := bootloader_unit_test
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE
LOCAL_MODULE_TAGS := tests
bootloader_py_files := $(call find-subdir-files, *.py)
diff --git a/tests/cpueater/Android.bp b/tests/cpueater/Android.bp
index 315a18b0..72c0e49b 100644
--- a/tests/cpueater/Android.bp
+++ b/tests/cpueater/Android.bp
@@ -13,6 +13,23 @@
// limitations under the License.
// Copyright The Android Open Source Project
+package {
+ default_applicable_licenses: ["system_extras_tests_cpueater_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_tests_cpueater_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_binary {
name: "cpueater",
srcs: ["cpueater.c"],
diff --git a/tests/crypto/Android.bp b/tests/crypto/Android.bp
index 61cb8299..d0326d47 100644
--- a/tests/crypto/Android.bp
+++ b/tests/crypto/Android.bp
@@ -1,5 +1,14 @@
// Copyright 2013 The Android Open Source Project
+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"],
+}
+
cc_binary {
name: "get_dm_versions",
srcs: ["get_dm_versions.c"],
diff --git a/tests/directiotest/Android.bp b/tests/directiotest/Android.bp
index 489e3c82..ab2545ce 100644
--- a/tests/directiotest/Android.bp
+++ b/tests/directiotest/Android.bp
@@ -1,3 +1,12 @@
+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"],
+}
+
cc_binary {
name: "directiotest",
srcs: ["directiotest.c"],
diff --git a/tests/ext4/Android.mk b/tests/ext4/Android.mk
index a9a684d2..0a384842 100644
--- a/tests/ext4/Android.mk
+++ b/tests/ext4/Android.mk
@@ -7,6 +7,9 @@ include $(CLEAR_VARS)
LOCAL_SRC_FILES:= rand_emmc_perf.c
LOCAL_CFLAGS := -Wall -Werror
LOCAL_MODULE:= rand_emmc_perf
+LOCAL_LICENSE_KINDS:= SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS:= notice
+LOCAL_NOTICE_FILE:= $(LOCAL_PATH)/../NOTICE
LOCAL_MULTILIB := both
LOCAL_MODULE_STEM_32:= rand_emmc_perf
LOCAL_MODULE_STEM_64:= rand_emmc_perf64
@@ -16,4 +19,3 @@ LOCAL_FORCE_STATIC_EXECUTABLE := true
LOCAL_STATIC_LIBRARIES := libm libc
include $(BUILD_EXECUTABLE)
-
diff --git a/tests/framebuffer/Android.bp b/tests/framebuffer/Android.bp
index f072a59a..eda69dec 100644
--- a/tests/framebuffer/Android.bp
+++ b/tests/framebuffer/Android.bp
@@ -1,3 +1,12 @@
+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"],
+}
+
cc_binary {
name: "test-fb-refresh",
srcs: ["refresh.c"],
diff --git a/tests/fstest/Android.bp b/tests/fstest/Android.bp
index febbb2f7..8c9ce68e 100644
--- a/tests/fstest/Android.bp
+++ b/tests/fstest/Android.bp
@@ -12,6 +12,15 @@
// 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_tests_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_extras_tests_license"],
+}
+
cc_test {
name: "recovery_test",
srcs: ["recovery_test.cpp"],
diff --git a/tests/icachetest/Android.bp b/tests/icachetest/Android.bp
index e8c72855..4e9d0d05 100644
--- a/tests/icachetest/Android.bp
+++ b/tests/icachetest/Android.bp
@@ -1,5 +1,14 @@
// Copyright 2006 The Android Open Source Project
+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"],
+}
+
cc_binary {
name: "icache",
diff --git a/tests/iptables/qtaguid/Android.bp b/tests/iptables/qtaguid/Android.bp
index 9c853c8b..935c0b41 100644
--- a/tests/iptables/qtaguid/Android.bp
+++ b/tests/iptables/qtaguid/Android.bp
@@ -14,6 +14,15 @@
// 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_tests_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_extras_tests_license"],
+}
+
cc_test {
name: "socketTag",
srcs: ["socketTag.cpp"],
diff --git a/tests/kernel.config/Android.mk b/tests/kernel.config/Android.mk
index a622bf56..cff19bac 100644
--- a/tests/kernel.config/Android.mk
+++ b/tests/kernel.config/Android.mk
@@ -31,6 +31,9 @@ test_src_files := \
include $(CLEAR_VARS)
LOCAL_MODULE := kernel-config-unit-tests
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE
LOCAL_MODULE_TAGS := tests
LOCAL_CFLAGS := $(test_c_flags)
LOCAL_CFLAGS += -DHAS_KCMP
@@ -40,6 +43,9 @@ include $(BUILD_NATIVE_TEST)
include $(CLEAR_VARS)
LOCAL_MODULE := CtsKernelConfigTestCases
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE
LOCAL_MODULE_TAGS := optional
LOCAL_CFLAGS := $(test_c_flags)
LOCAL_CFLAGS += -DHAS_KCMP
@@ -60,5 +66,8 @@ LOCAL_SRC_FILES := \
scrape_mmap_addr.cpp
LOCAL_MODULE := scrape_mmap_addr
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE
LOCAL_CFLAGS := -Wall -Werror
include $(BUILD_NATIVE_TEST)
diff --git a/tests/lib/Android.bp b/tests/lib/Android.bp
index 7a8ee5d0..f41419e3 100644
--- a/tests/lib/Android.bp
+++ b/tests/lib/Android.bp
@@ -1 +1,10 @@
-subdirs = [ "*" ]
+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"],
+}
+
+subdirs = ["*"]
diff --git a/tests/lib/testUtil/Android.bp b/tests/lib/testUtil/Android.bp
index 8ac90a6e..39fad294 100644
--- a/tests/lib/testUtil/Android.bp
+++ b/tests/lib/testUtil/Android.bp
@@ -14,10 +14,22 @@
// 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_tests_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_extras_tests_license"],
+}
+
cc_library_static {
name: "libtestUtil",
srcs: ["testUtil.c"],
- cflags: ["-Wall", "-Werror"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
export_include_dirs: ["include"],
shared_libs: [
"libcutils",
diff --git a/tests/lib/testUtil/testUtil.c b/tests/lib/testUtil/testUtil.c
index 6cfff9f3..f2236d54 100644
--- a/tests/lib/testUtil/testUtil.c
+++ b/tests/lib/testUtil/testUtil.c
@@ -153,6 +153,7 @@ void testPrint(FILE *stream, const char *fmt, ...)
ALOG(LOG_INFO, logCatTag, "%s", line);
}
vfprintf(stream, fmt, args);
+ va_end(args);
fputc('\n', stream);
}
diff --git a/tests/memeater/Android.bp b/tests/memeater/Android.bp
new file mode 100644
index 00000000..dfeaea70
--- /dev/null
+++ b/tests/memeater/Android.bp
@@ -0,0 +1,38 @@
+// 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.
+// 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.
+// Copyright The Android Open Source Project
+
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["system_extras_tests_memeater_license"],
+}
+
+license {
+ name: "system_extras_tests_memeater_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
+cc_binary {
+ name: "memeater",
+ srcs: ["memeater.c"],
+ cflags: [
+ "-Wno-unused-parameter",
+ ],
+}
diff --git a/tests/pagingtest/Android.mk b/tests/pagingtest/Android.mk
index de952ce3..69fea9d0 100644
--- a/tests/pagingtest/Android.mk
+++ b/tests/pagingtest/Android.mk
@@ -9,6 +9,9 @@ LOCAL_SRC_FILES:= \
thrashing_test.c
LOCAL_MODULE:= pagingtest
+LOCAL_LICENSE_KINDS:= SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS:= notice
+LOCAL_NOTICE_FILE:= $(LOCAL_PATH)/../NOTICE
LOCAL_CFLAGS := -Wall -Werror
diff --git a/tests/pftest/Android.bp b/tests/pftest/Android.bp
index dcd8b5b9..2a9ef6ba 100644
--- a/tests/pftest/Android.bp
+++ b/tests/pftest/Android.bp
@@ -1,5 +1,14 @@
// Copyright 2010 The Android Open Source Project
+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"],
+}
+
cc_binary {
name: "pftest",
diff --git a/tests/schedtest/Android.bp b/tests/schedtest/Android.bp
index 28ece031..ac10b117 100644
--- a/tests/schedtest/Android.bp
+++ b/tests/schedtest/Android.bp
@@ -1,3 +1,12 @@
+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"],
+}
+
cc_binary {
name: "schedtest",
srcs: ["schedtest.c"],
diff --git a/tests/storage/Android.bp b/tests/storage/Android.bp
index ba9bf1fe..9bbd24ea 100644
--- a/tests/storage/Android.bp
+++ b/tests/storage/Android.bp
@@ -1,5 +1,14 @@
// Copyright 2013 The Android Open Source Project
+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"],
+}
+
cc_binary {
name: "opentest",
srcs: ["opentest.c"],
diff --git a/tests/suspend_stress/Android.bp b/tests/suspend_stress/Android.bp
index e7a0d94c..bceff117 100644
--- a/tests/suspend_stress/Android.bp
+++ b/tests/suspend_stress/Android.bp
@@ -1,3 +1,12 @@
+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"],
+}
+
cc_binary {
name: "suspend_stress",
srcs: ["suspend_stress.cpp"],
diff --git a/tests/tcp_nuke_addr/Android.bp b/tests/tcp_nuke_addr/Android.bp
index 316fd3a1..1a504395 100644
--- a/tests/tcp_nuke_addr/Android.bp
+++ b/tests/tcp_nuke_addr/Android.bp
@@ -1,3 +1,12 @@
+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"],
+}
+
cc_test {
name: "tcp_nuke_addr_test",
srcs: ["tcp_nuke_addr_test.cpp"],
diff --git a/tests/timetest/Android.bp b/tests/timetest/Android.bp
index 481610b7..c4f361cb 100644
--- a/tests/timetest/Android.bp
+++ b/tests/timetest/Android.bp
@@ -1,5 +1,14 @@
// Copyright 2006 The Android Open Source Project
+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"],
+}
+
cc_test {
name: "time-unit-tests",
cflags: [
diff --git a/tests/uevents/Android.bp b/tests/uevents/Android.bp
index a292ebcf..4548b456 100644
--- a/tests/uevents/Android.bp
+++ b/tests/uevents/Android.bp
@@ -1,3 +1,12 @@
+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"],
+}
+
cc_binary {
name: "uevents",
srcs: ["uevents.c"],
diff --git a/toolchain-extras/.clang-format b/toolchain-extras/.clang-format
new file mode 120000
index 00000000..fd0645fd
--- /dev/null
+++ b/toolchain-extras/.clang-format
@@ -0,0 +1 @@
+../.clang-format-2 \ No newline at end of file
diff --git a/toolchain-extras/Android.bp b/toolchain-extras/Android.bp
index 04a5a8ec..220f3e34 100644
--- a/toolchain-extras/Android.bp
+++ b/toolchain-extras/Android.bp
@@ -1,3 +1,7 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_defaults {
name: "libprofile-defaults",
srcs: [
@@ -9,14 +13,16 @@ cc_defaults {
cc_library_static {
name: "libprofile-extras",
- defaults: ["libprofile-defaults",],
+ defaults: ["libprofile-defaults"],
native_bridge_supported: true,
vendor_available: true,
+ product_available: true,
vndk: {
enabled: true,
},
ramdisk_available: true,
+ vendor_ramdisk_available: true,
recovery_available: true,
stl: "none",
@@ -26,9 +32,10 @@ cc_library_static {
cc_library_static {
name: "libprofile-extras_ndk",
- defaults: ["libprofile-defaults",],
+ defaults: ["libprofile-defaults"],
native_bridge_supported: true,
vendor_available: true,
+ product_available: true,
vndk: {
enabled: true,
},
@@ -40,8 +47,12 @@ cc_defaults {
name: "libprofile-clang-defaults",
srcs: [
"profile-clang-extras.cpp",
+ "profile-clang-openat.cpp",
],
native_coverage: false,
+ sanitize: {
+ blocklist: "libprofile_clang_extras_blocklist.txt",
+ },
}
cc_library_static {
@@ -50,10 +61,12 @@ 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,
stl: "none",
@@ -66,6 +79,7 @@ cc_library_static {
defaults: ["libprofile-clang-defaults"],
native_bridge_supported: true,
vendor_available: true,
+ product_available: true,
vndk: {
enabled: true,
},
@@ -73,10 +87,35 @@ cc_library_static {
sdk_version: "minimum",
}
+cc_library_static {
+ name: "libprofile-clang-extras_cfi_support",
+ defaults: ["libprofile-clang-defaults"],
+
+ native_bridge_supported: true,
+ vendor_available: true,
+ product_available: true,
+ vndk: {
+ enabled: true,
+ },
+ ramdisk_available: true,
+ vendor_ramdisk_available: true,
+ recovery_available: true,
+
+ stl: "none",
+ system_shared_libs: [],
+ header_libs: ["libc_headers"],
+ sanitize: {
+ cfi: true,
+ config: {
+ cfi_assembly_support: true,
+ },
+ },
+}
+
cc_test {
name: "libprofile-extras-test",
srcs: [
- "profile-extras-test.cpp"
+ "profile-extras-test.cpp",
],
static_libs: [
"libprofile-extras",
@@ -84,3 +123,17 @@ cc_test {
ldflags: ["-uinit_profile_extras"],
native_coverage: false,
}
+
+cc_test {
+ name: "libprofile-clang-extras-test",
+ srcs: [
+ "profile-clang-extras-test.cpp",
+ ],
+ whole_static_libs: [
+ "libprofile-clang-extras",
+ ],
+ ldflags: [
+ "-Wl,--wrap,open",
+ ],
+ native_coverage: false,
+}
diff --git a/toolchain-extras/libprofile_clang_extras_blocklist.txt b/toolchain-extras/libprofile_clang_extras_blocklist.txt
new file mode 100644
index 00000000..f1d8b891
--- /dev/null
+++ b/toolchain-extras/libprofile_clang_extras_blocklist.txt
@@ -0,0 +1,2 @@
+[cfi]
+fun:_ZL19llvm_signal_handleri
diff --git a/toolchain-extras/profile-clang-extras-test.cpp b/toolchain-extras/profile-clang-extras-test.cpp
index 2bb7bdbb..0c746478 100644
--- a/toolchain-extras/profile-clang-extras-test.cpp
+++ b/toolchain-extras/profile-clang-extras-test.cpp
@@ -14,7 +14,10 @@
* limitations under the License.
*/
+#include <fcntl.h>
#include <gtest/gtest.h>
+#include <sys/stat.h>
+
#include "profile-extras.h"
static int flush_count = 0;
@@ -30,7 +33,23 @@ TEST(profile_extras, smoke) {
flush_count = 0;
ASSERT_EQ(0, flush_count);
- kill(getpid(), GCOV_FLUSH_SIGNAL);
+ kill(getpid(), COVERAGE_FLUSH_SIGNAL);
sleep(2);
ASSERT_EQ(1, flush_count);
}
+
+static const char* OPEN_AT_TEST_FNAME = "/data/misc/trace/test.profraw";
+TEST(profile_extras, openat) {
+ mode_t old_umask = umask(0077);
+ unlink(OPEN_AT_TEST_FNAME);
+
+ int fd = open(OPEN_AT_TEST_FNAME, O_RDWR | O_CREAT, 0666);
+ ASSERT_NE(fd, -1);
+ close(fd);
+ umask(old_umask);
+
+ struct stat stat_buf;
+ ASSERT_EQ(stat(OPEN_AT_TEST_FNAME, &stat_buf), 0);
+ ASSERT_EQ(stat_buf.st_mode & 0777, 0666);
+ unlink(OPEN_AT_TEST_FNAME);
+}
diff --git a/toolchain-extras/profile-clang-extras.cpp b/toolchain-extras/profile-clang-extras.cpp
index 89c18b2a..bb713e18 100644
--- a/toolchain-extras/profile-clang-extras.cpp
+++ b/toolchain-extras/profile-clang-extras.cpp
@@ -36,24 +36,15 @@ static void llvm_signal_handler(__unused int signum) {
}
}
-__attribute__((weak)) int init_profile_extras_once = 0;
-
// Initialize libprofile-extras:
-// - Install a signal handler that triggers __llvm_profile_write_file on <COVERAGE_FLUSH_SIGNAL>.
-//
-// We want this initializer to run during load time.
//
-// Just marking init_profile_extras() with __attribute__((constructor)) isn't
-// enough since the linker drops it from its output since no other symbol from
-// this static library is referenced.
+// - Install a signal handler that triggers __llvm_profile_write_file on
+// <COVERAGE_FLUSH_SIGNAL>.
//
-// We force the linker to include init_profile_extras() by passing
-// '-uinit_profile_extras' to the linker (in build/soong).
-__attribute__((constructor)) int init_profile_extras(void) {
- if (init_profile_extras_once)
- return 0;
- init_profile_extras_once = 1;
-
+// We want this initializer to run during load time. In addition to marking
+// this function as a constructor, we link this library with `--whole-archive`
+// to force this function to be included in the output.
+static __attribute__((constructor)) int init_profile_extras(void) {
if (chained_signal_handler != SIG_ERR) {
return -1;
}
diff --git a/toolchain-extras/profile-clang-openat.cpp b/toolchain-extras/profile-clang-openat.cpp
new file mode 100644
index 00000000..45c1acc5
--- /dev/null
+++ b/toolchain-extras/profile-clang-openat.cpp
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+// This file provides a wrapper for open.
+
+extern "C" {
+
+int __real_open(const char* pathname, int flags, ...);
+
+static bool needs_mode(int flags) {
+ return ((flags & O_CREAT) == O_CREAT) || ((flags & O_TMPFILE) == O_TMPFILE);
+}
+
+static const char* PROFRAW_START = "/data/misc/trace/";
+static bool is_coverage_trace(const char* pathname) {
+ if (strncmp(pathname, PROFRAW_START, strlen(PROFRAW_START)) == 0) return true;
+ return false;
+}
+
+__attribute__((weak)) int __wrap_open(const char* pathname, int flags, ...) {
+ if (!needs_mode(flags)) {
+ return __real_open(pathname, flags);
+ }
+
+ va_list args;
+ va_start(args, flags);
+ mode_t mode = static_cast<mode_t>(va_arg(args, int));
+ va_end(args);
+
+ int ret = __real_open(pathname, flags, mode);
+ if (ret != -1 && is_coverage_trace(pathname)) fchmod(ret, mode);
+ return ret;
+}
+}
diff --git a/toolchain-extras/profile-extras-test.cpp b/toolchain-extras/profile-extras-test.cpp
index 38acf734..aef007c5 100644
--- a/toolchain-extras/profile-extras-test.cpp
+++ b/toolchain-extras/profile-extras-test.cpp
@@ -20,19 +20,26 @@
#include "profile-extras.h"
-static int flush_count = 0;
+static int dump_count = 0;
+static int reset_count = 0;
extern "C" {
-void __gcov_flush() {
- flush_count++;
+void __gcov_dump() {
+ dump_count++;
+}
+
+void __gcov_reset() {
+ reset_count++;
}
}
TEST(profile_extras, smoke) {
- flush_count = 0;
+ dump_count = 0;
+ reset_count = 0;
- ASSERT_EQ(0, flush_count);
+ ASSERT_EQ(0, dump_count);
kill(getpid(), COVERAGE_FLUSH_SIGNAL);
sleep(2);
- ASSERT_EQ(1, flush_count);
+ ASSERT_EQ(1, dump_count);
+ ASSERT_EQ(1, reset_count);
}
diff --git a/toolchain-extras/profile-extras.cpp b/toolchain-extras/profile-extras.cpp
index 93f7f8f1..55a9b991 100644
--- a/toolchain-extras/profile-extras.cpp
+++ b/toolchain-extras/profile-extras.cpp
@@ -29,13 +29,15 @@
extern "C" {
-void __gcov_flush(void);
+void __gcov_dump(void);
+void __gcov_reset(void);
// storing SIG_ERR helps us detect (unlikely) looping.
static sighandler_t chained_gcov_signal_handler = SIG_ERR;
static void gcov_signal_handler(int signum) {
- __gcov_flush();
+ __gcov_dump();
+ __gcov_reset();
if (chained_gcov_signal_handler != SIG_ERR &&
chained_gcov_signal_handler != SIG_IGN &&
chained_gcov_signal_handler != SIG_DFL) {
diff --git a/vbmeta_tools/Android.bp b/vbmeta_tools/Android.bp
index 73a9eb7f..56b62b91 100644
--- a/vbmeta_tools/Android.bp
+++ b/vbmeta_tools/Android.bp
@@ -14,6 +14,10 @@
// limitations under the License.
//
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_binary {
name: "vbmake",
host_supported: true,
@@ -23,4 +27,4 @@ cc_binary {
srcs: [
"vbmake.cc",
],
-} \ No newline at end of file
+}
diff --git a/verity/Android.bp b/verity/Android.bp
index 5d0a80c0..fc2b827c 100644
--- a/verity/Android.bp
+++ b/verity/Android.bp
@@ -1,3 +1,20 @@
+package {
+ default_applicable_licenses: ["system_extras_verity_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "system_extras_verity_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_binary_host {
name: "generate_verity_key",
srcs: ["generate_verity_key.c"],
diff --git a/verity/build_verity_tree_test.cpp b/verity/build_verity_tree_test.cpp
index 8e1f114a..74fe9c06 100644
--- a/verity/build_verity_tree_test.cpp
+++ b/verity/build_verity_tree_test.cpp
@@ -101,6 +101,18 @@ TEST_F(BuildVerityTreeTest, HashSingleLevel) {
HashTreeBuilder::BytesArrayToString(builder->root_hash()));
}
+TEST_F(BuildVerityTreeTest, HashSingleLevel_blake2b256) {
+ std::vector<unsigned char> data(128 * 4096, 0x0);
+
+ builder.reset(
+ new HashTreeBuilder(4096, HashTreeBuilder::HashFunction("blake2b-256")));
+
+ GenerateHashTree(data, salt_hex);
+ ASSERT_EQ(1u, verity_tree().size());
+ ASSERT_EQ("6d5b006af5308523f7db6956c60b2650ff3e7edab1e2194cc8ee19b1a1398c03",
+ HashTreeBuilder::BytesArrayToString(builder->root_hash()));
+}
+
TEST_F(BuildVerityTreeTest, HashMultipleLevels) {
std::vector<unsigned char> data(129 * 4096, 0xff);
diff --git a/verity/fec/Android.bp b/verity/fec/Android.bp
index 4bcecb09..46c2e465 100644
--- a/verity/fec/Android.bp
+++ b/verity/fec/Android.bp
@@ -1,6 +1,24 @@
+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_verity_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_extras_verity_license"],
+}
+
cc_binary_host {
name: "fec",
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "",
+ },
+ },
target: {
linux_glibc: {
sanitize: {
diff --git a/verity/hash_tree_builder.cpp b/verity/hash_tree_builder.cpp
index 79d30405..1cd2e291 100644
--- a/verity/hash_tree_builder.cpp
+++ b/verity/hash_tree_builder.cpp
@@ -17,6 +17,7 @@
#include "verity/hash_tree_builder.h"
#include <algorithm>
+#include <functional>
#include <memory>
#include <android-base/file.h>
@@ -41,6 +42,9 @@ const EVP_MD* HashTreeBuilder::HashFunction(const std::string& hash_name) {
if (android::base::EqualsIgnoreCase(hash_name, "sha512")) {
return EVP_sha512();
}
+ if (android::base::EqualsIgnoreCase(hash_name, "blake2b-256")) {
+ return EVP_blake2b256();
+ }
LOG(ERROR) << "Unsupported hash algorithm " << hash_name;
return nullptr;
@@ -92,18 +96,19 @@ bool HashTreeBuilder::ParseBytesArrayFromString(
return true;
}
-uint64_t HashTreeBuilder::CalculateSize(uint64_t input_size) const {
+uint64_t HashTreeBuilder::CalculateSize(
+ uint64_t input_size, size_t block_size, size_t hash_size) {
uint64_t verity_blocks = 0;
size_t level_blocks;
size_t levels = 0;
do {
level_blocks =
- verity_tree_blocks(input_size, block_size_, hash_size_, levels);
+ verity_tree_blocks(input_size, block_size, hash_size, levels);
levels++;
verity_blocks += level_blocks;
} while (level_blocks > 1);
- return verity_blocks * block_size_;
+ return verity_blocks * block_size;
}
bool HashTreeBuilder::Initialize(int64_t expected_data_size,
@@ -297,19 +302,14 @@ bool HashTreeBuilder::WriteHashTreeToFile(const std::string& output) const {
return WriteHashTreeToFd(output_fd, 0);
}
-bool HashTreeBuilder::WriteHashTreeToFd(int fd, uint64_t offset) const {
+bool HashTreeBuilder::WriteHashTree(
+ std::function<bool(const void*, size_t)> callback) const {
CHECK(!verity_tree_.empty());
- if (lseek(fd, offset, SEEK_SET) != offset) {
- PLOG(ERROR) << "Failed to seek the output fd, offset: " << offset;
- return false;
- }
-
// Reads reversely to output the verity tree top-down.
for (size_t i = verity_tree_.size(); i > 0; i--) {
const auto& level_blocks = verity_tree_[i - 1];
- if (!android::base::WriteFully(fd, level_blocks.data(),
- level_blocks.size())) {
+ if (!callback(level_blocks.data(), level_blocks.size())) {
PLOG(ERROR) << "Failed to write the hash tree level " << i;
return false;
}
@@ -318,6 +318,19 @@ bool HashTreeBuilder::WriteHashTreeToFd(int fd, uint64_t offset) const {
return true;
}
+bool HashTreeBuilder::WriteHashTreeToFd(int fd, uint64_t offset) const {
+ CHECK(!verity_tree_.empty());
+
+ if (lseek(fd, offset, SEEK_SET) != offset) {
+ PLOG(ERROR) << "Failed to seek the output fd, offset: " << offset;
+ return false;
+ }
+
+ return WriteHashTree([fd](auto data, auto size) {
+ return android::base::WriteFully(fd, data, size);
+ });
+}
+
void HashTreeBuilder::AppendPaddings(std::vector<unsigned char>* data) {
size_t remainder = data->size() % block_size_;
if (remainder != 0) {
diff --git a/verity/include/verity/hash_tree_builder.h b/verity/include/verity/hash_tree_builder.h
index 7e870bf4..a601d1d9 100644
--- a/verity/include/verity/hash_tree_builder.h
+++ b/verity/include/verity/hash_tree_builder.h
@@ -20,6 +20,7 @@
#include <inttypes.h>
#include <stddef.h>
+#include <functional>
#include <string>
#include <vector>
@@ -34,7 +35,10 @@ class HashTreeBuilder {
public:
HashTreeBuilder(size_t block_size, const EVP_MD* md);
// Returns the size of the verity tree in bytes given the input data size.
- uint64_t CalculateSize(uint64_t input_size) const;
+ uint64_t CalculateSize(uint64_t input_size) const {
+ return CalculateSize(input_size, block_size_, hash_size_);
+ }
+ static uint64_t CalculateSize(uint64_t input_size, size_t block_size, size_t hash_size);
// Gets ready for the hash tree computation. We expect |expected_data_size|
// bytes source data.
bool Initialize(int64_t expected_data_size,
@@ -51,6 +55,7 @@ class HashTreeBuilder {
// Writes the computed hash tree top-down to |output|.
bool WriteHashTreeToFile(const std::string& output) const;
bool WriteHashTreeToFd(int fd, uint64_t offset) const;
+ bool WriteHashTree(std::function<bool(const void*, size_t)> callback) const;
size_t hash_size() const { return hash_size_; }
const std::vector<unsigned char>& root_hash() const { return root_hash_; }
diff --git a/zram-perf/Android.bp b/zram-perf/Android.bp
index 7b20537a..5b46c684 100644
--- a/zram-perf/Android.bp
+++ b/zram-perf/Android.bp
@@ -1,3 +1,7 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_binary {
name: "zram-perf",
cflags: [