summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2020-04-28 20:26:44 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2020-04-28 20:26:44 +0000
commit30a4df025ab854a247b9714fbf6840bd7f8211fd (patch)
tree8c64e6ae9306cdbdbda36580bc247a36a051e1fa
parentc36ecc0803cb1df95f2daf90b100370f0631f9fb (diff)
parent29e06f5526a2dd48c9f19ef7f238804f84c03c30 (diff)
downloadextras-android12-mainline-tzdata-release.tar.gz
Change-Id: Ie3b2616d0777d5fa31840df0790cc48856698478
-rw-r--r--bootctl/Android.bp2
-rw-r--r--boottime_tools/bootio/bootio_collector.cpp8
-rwxr-xr-xcppreopts/cppreopts.sh2
-rw-r--r--ext4_utils/Android.bp12
-rw-r--r--ext4_utils/mkuserimg_mke2fs.py25
-rw-r--r--ioshark/ioshark_bench_subr.c29
l---------libfscrypt/.clang-format1
-rw-r--r--libfscrypt/Android.bp1
-rw-r--r--libfscrypt/fscrypt.cpp427
-rw-r--r--libfscrypt/fscrypt_init_extensions.cpp146
-rw-r--r--libfscrypt/include/fscrypt/fscrypt.h41
-rw-r--r--libfscrypt/include/fscrypt/fscrypt_init_extensions.h (renamed from memory_replay/Utils.h)30
-rw-r--r--libfscrypt/tests/fscrypt_test.cpp138
-rw-r--r--libjsonpb/parse/jsonpb.cpp5
-rw-r--r--libjsonpb/verify/test.cpp26
-rw-r--r--libperfmgr/tools/ConfigVerifier.cc49
-rw-r--r--memory_replay/Action.cpp200
-rw-r--r--memory_replay/Action.h45
-rw-r--r--memory_replay/Alloc.cpp178
-rw-r--r--memory_replay/Alloc.h49
-rw-r--r--memory_replay/Android.bp98
-rw-r--r--memory_replay/File.cpp171
-rw-r--r--memory_replay/LineBuffer.cpp59
-rw-r--r--memory_replay/LineBuffer.h (renamed from memory_replay/File.h)25
-rw-r--r--memory_replay/NativeInfo.cpp100
-rw-r--r--memory_replay/NativeInfo.h15
-rw-r--r--memory_replay/Pointers.cpp10
-rw-r--r--memory_replay/Pointers.h15
-rw-r--r--memory_replay/Thread.cpp5
-rw-r--r--memory_replay/Thread.h19
-rw-r--r--memory_replay/Threads.cpp21
-rw-r--r--memory_replay/Threads.h6
-rw-r--r--memory_replay/TraceBenchmark.cpp368
-rw-r--r--memory_replay/dumps/README (renamed from memory_replay/traces/README)2
-rw-r--r--memory_replay/dumps/camera.zip (renamed from memory_replay/traces/camera.zip)bin9622035 -> 9622035 bytes
-rw-r--r--memory_replay/dumps/gmail.zip (renamed from memory_replay/traces/gmail.zip)bin12189897 -> 12189897 bytes
-rw-r--r--memory_replay/dumps/maps.zip (renamed from memory_replay/traces/maps.zip)bin8784596 -> 8784596 bytes
-rw-r--r--memory_replay/dumps/surfaceflinger.zip (renamed from memory_replay/traces/surfaceflinger.zip)bin4043232 -> 4043232 bytes
-rw-r--r--memory_replay/dumps/system_server.zip (renamed from memory_replay/traces/system_server.zip)bin34244259 -> 34244259 bytes
-rw-r--r--memory_replay/dumps/systemui.zip (renamed from memory_replay/traces/systemui.zip)bin1879288 -> 1879288 bytes
-rw-r--r--memory_replay/dumps/youtube.zip (renamed from memory_replay/traces/youtube.zip)bin6799540 -> 6799540 bytes
-rw-r--r--memory_replay/main.cpp151
-rw-r--r--memory_replay/tests/ActionTest.cpp168
-rw-r--r--memory_replay/tests/AllocTest.cpp146
-rw-r--r--memory_replay/tests/FileTest.cpp112
-rw-r--r--memory_replay/tests/LineBufferTest.cpp241
-rw-r--r--memory_replay/tests/NativeInfoTest.cpp91
-rw-r--r--memory_replay/tests/ThreadTest.cpp17
-rw-r--r--memory_replay/tests/ThreadsTest.cpp29
-rw-r--r--memory_replay/tests/test.txt2
-rw-r--r--memory_replay/tests/test.zipbin201 -> 0 bytes
-rw-r--r--memory_replay/traces/TRACES80
-rw-r--r--memory_replay/traces/angry_birds2.zipbin27375861 -> 0 bytes
-rw-r--r--memory_replay/traces/candy_crush_saga.zipbin27847350 -> 0 bytes
-rw-r--r--memory_replay/traces/photos.zipbin9184675 -> 0 bytes
-rw-r--r--memory_replay/traces/pubg.zipbin7860330 -> 0 bytes
-rw-r--r--partition_tools/lpdump.cc278
-rw-r--r--partition_tools/lpmake.cc14
-rw-r--r--perfprofd/Android.bp267
-rw-r--r--perfprofd/NOTICE190
-rw-r--r--perfprofd/OWNERS2
-rw-r--r--perfprofd/TEST_MAPPING7
-rw-r--r--perfprofd/binder_interface/Android.bp (renamed from libfscrypt/tests/Android.bp)34
-rw-r--r--perfprofd/binder_interface/aidl/android/os/IPerfProfd.aidl45
-rw-r--r--perfprofd/binder_interface/perfprofd_binder.cc377
-rw-r--r--perfprofd/binder_interface/perfprofd_binder.h31
-rw-r--r--perfprofd/config.h128
-rw-r--r--perfprofd/configreader.cc620
-rw-r--r--perfprofd/configreader.h83
-rw-r--r--perfprofd/cpuconfig.cc113
-rw-r--r--perfprofd/cpuconfig.h50
-rw-r--r--perfprofd/dropbox/Android.bp52
-rw-r--r--perfprofd/dropbox/dropbox.cc129
-rw-r--r--perfprofd/dropbox/dropbox.h37
-rw-r--r--perfprofd/dropbox/dropbox_host.cc35
-rw-r--r--perfprofd/map_utils.h129
-rw-r--r--perfprofd/perf_data_converter.cc197
-rw-r--r--perfprofd/perf_data_converter.h23
-rw-r--r--perfprofd/perf_profile.proto131
-rw-r--r--perfprofd/perfprofd.conf24
-rw-r--r--perfprofd/perfprofd.rc5
-rw-r--r--perfprofd/perfprofd_cmdline.cc258
-rw-r--r--perfprofd/perfprofd_cmdline.h39
-rw-r--r--perfprofd/perfprofd_config.proto96
-rw-r--r--perfprofd/perfprofd_io.cc310
-rw-r--r--perfprofd/perfprofd_io.h38
-rw-r--r--perfprofd/perfprofd_perf.cc332
-rw-r--r--perfprofd/perfprofd_perf.h57
-rw-r--r--perfprofd/perfprofd_record-fwd.h31
-rw-r--r--perfprofd/perfprofd_record.proto58
-rw-r--r--perfprofd/perfprofd_threaded_handler.h198
-rw-r--r--perfprofd/perfprofdcore.cc732
-rw-r--r--perfprofd/perfprofdcore.h91
-rw-r--r--perfprofd/perfprofdmain.cc60
-rw-r--r--perfprofd/quipper_helper.h146
-rw-r--r--perfprofd/scripts/Android.bp89
-rw-r--r--perfprofd/scripts/perf_config_proto.py193
-rw-r--r--perfprofd/scripts/perf_proto_json2sqlite.py167
-rw-r--r--perfprofd/scripts/perf_proto_stack.py576
-rw-r--r--perfprofd/scripts/perf_proto_stack_sqlite_flame.py272
-rw-r--r--perfprofd/scripts/sorted_collection.py147
-rw-r--r--perfprofd/symbolizer.cc179
-rw-r--r--perfprofd/symbolizer.h35
-rw-r--r--perfprofd/tests/Android.bp73
-rw-r--r--perfprofd/tests/AndroidTest.xml29
-rw-r--r--perfprofd/tests/README.txt64
-rw-r--r--perfprofd/tests/callchain.canned.perf.databin0 -> 256412 bytes
-rw-r--r--perfprofd/tests/canned.perf.databin0 -> 1366208 bytes
-rw-r--r--perfprofd/tests/perfprofd_test.cc1743
-rw-r--r--perfprofd/tests/perfprofdmockutils.cc101
-rw-r--r--perfprofd/tests/perfprofdmockutils.h31
-rw-r--r--puncture_fs/Android.bp2
-rw-r--r--puncture_fs/puncture_fs.c (renamed from puncture_fs/puncture_fs.cpp)6
-rw-r--r--simpleperf/Android.bp75
-rw-r--r--simpleperf/Android.mk5
-rw-r--r--simpleperf/ETMDecoder.cpp582
-rw-r--r--simpleperf/ETMDecoder.h66
-rw-r--r--simpleperf/ETMRecorder.cpp225
-rw-r--r--simpleperf/ETMRecorder.h77
-rw-r--r--simpleperf/JITDebugReader.cpp213
-rw-r--r--simpleperf/JITDebugReader.h22
-rw-r--r--simpleperf/OfflineUnwinder.cpp72
-rw-r--r--simpleperf/OfflineUnwinder.h49
-rw-r--r--simpleperf/README.md24
-rw-r--r--simpleperf/RecordReadThread.cpp139
-rw-r--r--simpleperf/RecordReadThread.h25
-rw-r--r--simpleperf/RecordReadThread_test.cpp162
-rw-r--r--simpleperf/SampleComparator.h4
-rw-r--r--simpleperf/SampleDisplayer.h4
-rw-r--r--simpleperf/app_api/cpp/simpleperf.cpp94
-rw-r--r--simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java44
-rw-r--r--simpleperf/cmd_debug_unwind.cpp12
-rw-r--r--simpleperf/cmd_debug_unwind_test.cpp22
-rw-r--r--simpleperf/cmd_dumprecord.cpp98
-rw-r--r--simpleperf/cmd_dumprecord_test.cpp12
-rw-r--r--simpleperf/cmd_inject.cpp213
-rw-r--r--simpleperf/cmd_inject_test.cpp52
-rw-r--r--simpleperf/cmd_kmem.cpp2
-rw-r--r--simpleperf/cmd_list.cpp47
-rw-r--r--simpleperf/cmd_record.cpp161
-rw-r--r--simpleperf/cmd_record_test.cpp307
-rw-r--r--simpleperf/cmd_report.cpp52
-rw-r--r--simpleperf/cmd_report_sample.cpp50
-rw-r--r--simpleperf/cmd_report_sample_test.cpp1
-rw-r--r--simpleperf/cmd_stat.cpp194
-rw-r--r--simpleperf/cmd_stat_test.cpp8
-rw-r--r--simpleperf/cmd_trace_sched.cpp11
-rw-r--r--simpleperf/command.cpp7
-rw-r--r--simpleperf/demo/CppApi/app/src/main/cpp/native-lib.cpp20
-rw-r--r--simpleperf/demo/JavaApi/app/src/main/java/simpleperf/demo/java_api/MainActivity.java9
-rw-r--r--simpleperf/doc/README.md1153
-rw-r--r--simpleperf/doc/android_application_profiling.md272
-rw-r--r--simpleperf/doc/android_platform_profiling.md36
-rw-r--r--simpleperf/doc/executable_commands_reference.md583
-rw-r--r--simpleperf/doc/scripts_reference.md233
-rw-r--r--simpleperf/dso.cpp18
-rw-r--r--simpleperf/dso_test.cpp29
-rw-r--r--simpleperf/environment.cpp4
-rw-r--r--simpleperf/event_attr.cpp6
-rw-r--r--simpleperf/event_fd.cpp73
-rw-r--r--simpleperf/event_fd.h26
-rw-r--r--simpleperf/event_selection_set.cpp74
-rw-r--r--simpleperf/event_selection_set.h21
-rw-r--r--simpleperf/event_type.cpp23
-rw-r--r--simpleperf/event_type.h2
-rw-r--r--simpleperf/event_type_table.h199
-rwxr-xr-xsimpleperf/generate_event_type_table.py215
-rw-r--r--simpleperf/get_test_data.h3
-rw-r--r--simpleperf/gtest_main.cpp6
-rw-r--r--simpleperf/nonlinux_support/nonlinux_support.cpp16
-rw-r--r--simpleperf/record.cpp107
-rw-r--r--simpleperf/record.h76
-rw-r--r--simpleperf/record_file.h30
-rw-r--r--simpleperf/record_file_format.h1
-rw-r--r--simpleperf/record_file_reader.cpp154
-rw-r--r--simpleperf/record_file_test.cpp4
-rw-r--r--simpleperf/record_file_writer.cpp27
-rw-r--r--simpleperf/report_lib_interface.cpp52
-rw-r--r--simpleperf/sample_tree.h8
-rw-r--r--simpleperf/sample_tree_test.cpp2
-rwxr-xr-xsimpleperf/scripts/app_profiler.py22
-rwxr-xr-xsimpleperf/scripts/bin/android/arm/simpleperfbin2571732 -> 2482640 bytes
-rwxr-xr-xsimpleperf/scripts/bin/android/arm64/simpleperfbin3520480 -> 3355032 bytes
-rwxr-xr-xsimpleperf/scripts/bin/android/x86/simpleperfbin4261104 -> 4083312 bytes
-rwxr-xr-xsimpleperf/scripts/bin/android/x86_64/simpleperfbin4172872 -> 4007320 bytes
-rwxr-xr-xsimpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylibbin6274128 -> 6113016 bytes
-rwxr-xr-xsimpleperf/scripts/bin/darwin/x86_64/simpleperfbin7779556 -> 6758024 bytes
-rwxr-xr-xsimpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.sobin4978264 -> 4913592 bytes
-rwxr-xr-xsimpleperf/scripts/bin/linux/x86_64/simpleperfbin6224008 -> 5798520 bytes
-rwxr-xr-xsimpleperf/scripts/bin/windows/x86/libsimpleperf_report.dllbin3943424 -> 3851264 bytes
-rwxr-xr-xsimpleperf/scripts/bin/windows/x86/libwinpthread-1.dllbin231082 -> 211018 bytes
-rwxr-xr-xsimpleperf/scripts/bin/windows/x86/simpleperf.exebin3878912 -> 3385856 bytes
-rwxr-xr-xsimpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dllbin4185600 -> 4128768 bytes
-rwxr-xr-xsimpleperf/scripts/bin/windows/x86_64/libwinpthread-1.dllbin570473 -> 550120 bytes
-rwxr-xr-xsimpleperf/scripts/bin/windows/x86_64/simpleperf.exebin4191232 -> 3385856 bytes
-rwxr-xr-xsimpleperf/scripts/binary_cache_builder.py8
-rw-r--r--simpleperf/scripts/inferno/data_types.py13
-rwxr-xr-xsimpleperf/scripts/inferno/inferno.py24
-rwxr-xr-xsimpleperf/scripts/pprof_proto_generator.py87
-rwxr-xr-xsimpleperf/scripts/report_html.py90
-rw-r--r--simpleperf/scripts/script_testdata/aggregatable_perf1.databin2202763 -> 0 bytes
-rw-r--r--simpleperf/scripts/script_testdata/aggregatable_perf2.databin2024113 -> 0 bytes
-rw-r--r--simpleperf/scripts/script_testdata/cpp_api-debug_Q.apkbin2791612 -> 0 bytes
-rw-r--r--simpleperf/scripts/script_testdata/cpp_api-debug_prev_Q.apkbin2791656 -> 0 bytes
-rw-r--r--simpleperf/scripts/script_testdata/cpp_api-profile_Q.apkbin5079732 -> 0 bytes
-rw-r--r--simpleperf/scripts/script_testdata/cpp_api-profile_prev_Q.apkbin5078484 -> 0 bytes
-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
-rw-r--r--simpleperf/scripts/script_testdata/two_process_perf.databin53289 -> 0 bytes
-rw-r--r--simpleperf/scripts/simpleperf_report_lib.py12
-rwxr-xr-xsimpleperf/scripts/test.py764
-rwxr-xr-xsimpleperf/scripts/update.py76
-rw-r--r--simpleperf/scripts/utils.py101
-rw-r--r--simpleperf/testdata/DisplayBitmaps.apkbin2166441 -> 0 bytes
-rw-r--r--simpleperf/testdata/DisplayBitmapsTest.apkbin2886160 -> 0 bytes
-rw-r--r--simpleperf/testdata/EndlessTunnel.apkbin615556 -> 0 bytes
-rw-r--r--simpleperf/testdata/data/app/simpleperf.demo.cpp_api/base.apkbin1329667 -> 0 bytes
-rw-r--r--simpleperf/testdata/data/symfs_with_build_id_list/build_id_list1
-rw-r--r--simpleperf/testdata/data/symfs_with_build_id_list/elf_for_build_id_checkbin8466 -> 0 bytes
-rw-r--r--simpleperf/testdata/data/symfs_with_wrong_build_id_list/build_id_list1
-rw-r--r--simpleperf/testdata/etm/etm_test_loopbin11312 -> 0 bytes
-rw-r--r--simpleperf/testdata/etm/perf.databin14584 -> 0 bytes
-rw-r--r--simpleperf/testdata/perf_unwind_embedded_lib_in_apk.databin215075 -> 0 bytes
-rw-r--r--simpleperf/thread_tree.cpp53
-rw-r--r--simpleperf/thread_tree.h6
-rw-r--r--simpleperf/thread_tree_test.cpp15
-rw-r--r--simpleperf/utils.cpp9
-rw-r--r--simpleperf/utils.h10
-rw-r--r--slideshow/Android.mk5
-rw-r--r--squashfs_utils/Android.bp4
-rw-r--r--taskstats/taskstats.c7
-rw-r--r--tests/fstest/recovery_test.cpp10
-rw-r--r--toolchain-extras/Android.bp2
-rw-r--r--toolchain-extras/profile-extras.cpp15
-rw-r--r--vbmeta_tools/Android.bp26
-rw-r--r--vbmeta_tools/vbmake.cc96
-rw-r--r--verity/Android.bp39
-rwxr-xr-xverity/boot_signer8
-rwxr-xr-x[-rw-r--r--]verity/build_verity_metadata.py0
-rw-r--r--verity/hash_tree_builder.cpp4
-rwxr-xr-xverity/verity_signer8
243 files changed, 12560 insertions, 8088 deletions
diff --git a/bootctl/Android.bp b/bootctl/Android.bp
index e5804154..a58723c0 100644
--- a/bootctl/Android.bp
+++ b/bootctl/Android.bp
@@ -9,6 +9,8 @@ cc_binary {
],
shared_libs: [
"libhidlbase",
+ "libhidltransport",
+ "libhwbinder",
"libutils",
"android.hardware.boot@1.0",
],
diff --git a/boottime_tools/bootio/bootio_collector.cpp b/boottime_tools/bootio/bootio_collector.cpp
index dc13525e..495a9aa4 100644
--- a/boottime_tools/bootio/bootio_collector.cpp
+++ b/boottime_tools/bootio/bootio_collector.cpp
@@ -275,8 +275,10 @@ void PrintPids(DataContainer& data, std::unordered_map<int, uint64_t>& cpuDataMa
stats.rbytes += (newerSample->readbytes() - olderSample->readbytes());
stats.wbytes += (newerSample->writebytes() - olderSample->writebytes());
-#define NUMBER "%-13" PRId64
- printf("%5" PRId64 " - %-5" PRId64 " " NUMBER NUMBER NUMBER NUMBER NUMBER NUMBER "%-9.2f\n",
+ // Note that all of these are explicitly `long long`s, not int64_t,
+ // so we can't use PRId64 here.
+#define NUMBER "%-13lld"
+ printf("%5lld - %-5lld " NUMBER NUMBER NUMBER NUMBER NUMBER NUMBER "%-9.2f\n",
#undef NUMBER
olderSample->uptime(),
newerSample->uptime(),
@@ -290,7 +292,7 @@ void PrintPids(DataContainer& data, std::unordered_map<int, uint64_t>& cpuDataMa
isFirstSample = false;
}
printf("-----------------------------------------------------------------------------\n");
-#define NUMBER "%-13" PRId64
+#define NUMBER "%-13lld"
printf("%-15s" NUMBER NUMBER NUMBER NUMBER NUMBER NUMBER "\n",
#undef NUMBER
"Total",
diff --git a/cppreopts/cppreopts.sh b/cppreopts/cppreopts.sh
index d409db8d..3416e675 100755
--- a/cppreopts/cppreopts.sh
+++ b/cppreopts/cppreopts.sh
@@ -53,7 +53,7 @@ if [ $# -eq 1 ]; then
# NOTE: this implementation will break in any path with spaces to favor
# background copy tasks
for file in $(find ${mountpoint} -type f -name "*.odex" -o -type f -name "*.vdex" -o -type f -name "*.art"); do
- real_name=${file/${mountpoint}/}
+ real_name=${file/${mountpoint}/\/system}
dest_name=$(preopt2cachename ${real_name})
if ! test $? -eq 0 ; then
log -p i -t cppreopts "Unable to figure out destination for ${file}"
diff --git a/ext4_utils/Android.bp b/ext4_utils/Android.bp
index 4efecf7d..e9c5965f 100644
--- a/ext4_utils/Android.bp
+++ b/ext4_utils/Android.bp
@@ -17,10 +17,17 @@ cc_library {
export_include_dirs: ["include"],
shared_libs: [
"libbase",
+ "libsparse",
"libz",
],
target: {
+ host: {
+ static_libs: ["libsparse"],
+ },
+ not_windows: {
+ static_libs: ["libselinux"],
+ },
windows: {
host_ldlibs: ["-lws2_32"],
enabled: true,
@@ -29,6 +36,7 @@ cc_library {
android: {
shared_libs: [
"libbase",
+ "libselinux",
],
shared: {
@@ -48,10 +56,6 @@ python_binary_host {
"mkuserimg_mke2fs.py",
],
- data: [
- "mke2fs.conf",
- ],
-
version: {
py2: {
enabled: true,
diff --git a/ext4_utils/mkuserimg_mke2fs.py b/ext4_utils/mkuserimg_mke2fs.py
index 4633426b..538ffaac 100644
--- a/ext4_utils/mkuserimg_mke2fs.py
+++ b/ext4_utils/mkuserimg_mke2fs.py
@@ -17,10 +17,8 @@
import argparse
import logging
import os
-import pkgutil
import subprocess
import sys
-import tempfile
def RunCommand(cmd, env):
@@ -214,20 +212,15 @@ def main(argv):
output.truncate()
# run mke2fs
- with tempfile.NamedTemporaryFile() as conf_file:
- conf_data = pkgutil.get_data('mkuserimg_mke2fs', 'mke2fs.conf')
- conf_file.write(conf_data)
- conf_file.flush()
- mke2fs_env = {"MKE2FS_CONFIG" : conf_file.name}
-
- if args.timestamp:
- mke2fs_env["E2FSPROGS_FAKE_TIME"] = args.timestamp
-
- output, ret = RunCommand(mke2fs_cmd, mke2fs_env)
- print(output)
- if ret != 0:
- logging.error("Failed to run mke2fs: " + output)
- sys.exit(4)
+ mke2fs_env = {"MKE2FS_CONFIG" : "./system/extras/ext4_utils/mke2fs.conf"}
+ if args.timestamp:
+ mke2fs_env["E2FSPROGS_FAKE_TIME"] = args.timestamp
+
+ output, ret = RunCommand(mke2fs_cmd, mke2fs_env)
+ print(output)
+ if ret != 0:
+ logging.error("Failed to run mke2fs: " + output)
+ sys.exit(4)
# run e2fsdroid
e2fsdroid_env = {}
diff --git a/ioshark/ioshark_bench_subr.c b/ioshark/ioshark_bench_subr.c
index 1f740616..8bc702ee 100644
--- a/ioshark/ioshark_bench_subr.c
+++ b/ioshark/ioshark_bench_subr.c
@@ -22,7 +22,7 @@
#include <signal.h>
#include <string.h>
#include <sys/stat.h>
-#include <errno.h>
+#include <sys/errno.h>
#include <fcntl.h>
#include <string.h>
#include <assert.h>
@@ -121,9 +121,10 @@ files_db_fsync_discard_files(void *handle)
openflags);
if (fd < 0) {
fprintf(stderr,
- "%s: open(%s %x): %m\n",
+ "%s: open(%s %x) error %d\n",
progname, db_node->filename,
- openflags);
+ openflags,
+ errno);
exit(EXIT_FAILURE);
}
db_node->fd = fd;
@@ -206,8 +207,9 @@ files_db_unlink_files(void *handle)
db_node->fd = -1;
if (is_readonly_mount(db_node->filename, db_node->size) == 0) {
if (unlink(db_node->filename) < 0) {
- fprintf(stderr, "%s: Cannot unlink %s: %m\n",
- __func__, db_node->filename);
+ fprintf(stderr, "%s: Cannot unlink %s:%s\n",
+ __func__, db_node->filename,
+ strerror(errno));
exit(EXIT_FAILURE);
}
}
@@ -276,7 +278,8 @@ create_file(char *path, size_t size, struct rw_bytes_s *rw_bytes)
fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0644);
if (fd < 0) {
- fprintf(stderr, "%s Cannot create file %s: %m\n", progname, path);
+ fprintf(stderr, "%s Cannot create file %s, error = %d\n",
+ progname, path, errno);
exit(EXIT_FAILURE);
}
while (size > 0) {
@@ -284,7 +287,8 @@ create_file(char *path, size_t size, struct rw_bytes_s *rw_bytes)
buf = get_buf(&buf, &buflen, n, 1);
if (write(fd, buf, n) < n) {
fprintf(stderr,
- "%s Cannot write file %s: %m\n", progname, path);
+ "%s Cannot write file %s, error = %d\n",
+ progname, path, errno);
free(buf);
exit(EXIT_FAILURE);
}
@@ -293,12 +297,14 @@ create_file(char *path, size_t size, struct rw_bytes_s *rw_bytes)
}
free(buf);
if (fsync(fd) < 0) {
- fprintf(stderr, "%s Cannot fsync file %s: %m\n", progname, path);
+ fprintf(stderr, "%s Cannot fsync file %s, error = %d\n",
+ progname, path, errno);
exit(EXIT_FAILURE);
}
- if ((errno = posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED)) != 0) {
+ if (posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED) < 0) {
fprintf(stderr,
- "%s Cannot fadvise(DONTNEED) file %s: %m\n", progname, path);
+ "%s Cannot fadvise(DONTNEED) file %s, error = %d\n",
+ progname, path, errno);
exit(EXIT_FAILURE);
}
close(fd);
@@ -602,7 +608,8 @@ init_filename_cache(void)
MAP_SHARED | MAP_LOCKED | MAP_POPULATE,
fd, 0);
if (filename_cache == MAP_FAILED) {
- fprintf(stderr, "%s Can't fstat ioshark_filenames file: %m\n", progname);
+ fprintf(stderr, "%s Can't fstat ioshark_filenames file: %s\n",
+ progname, strerror(errno));
exit(EXIT_FAILURE);
}
close(fd);
diff --git a/libfscrypt/.clang-format b/libfscrypt/.clang-format
deleted file mode 120000
index 973b2fab..00000000
--- a/libfscrypt/.clang-format
+++ /dev/null
@@ -1 +0,0 @@
-../../../build/soong/scripts/system-clang-format \ No newline at end of file
diff --git a/libfscrypt/Android.bp b/libfscrypt/Android.bp
index 65b94ed9..cca38235 100644
--- a/libfscrypt/Android.bp
+++ b/libfscrypt/Android.bp
@@ -5,6 +5,7 @@ cc_library {
recovery_available: true,
srcs: [
"fscrypt.cpp",
+ "fscrypt_init_extensions.cpp",
],
export_include_dirs: ["include"],
shared_libs: [
diff --git a/libfscrypt/fscrypt.cpp b/libfscrypt/fscrypt.cpp
index b76f0b17..adeb66aa 100644
--- a/libfscrypt/fscrypt.cpp
+++ b/libfscrypt/fscrypt.cpp
@@ -16,101 +16,42 @@
#include "fscrypt/fscrypt.h"
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/strings.h>
-#include <android-base/unique_fd.h>
+#include <array>
+
#include <asm/ioctl.h>
-#include <cutils/properties.h>
+#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/fs.h>
-#include <logwrap/logwrap.h>
#include <string.h>
#include <sys/stat.h>
+#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
-#include <utils/misc.h>
-
-#include <array>
-#include <string>
-#include <vector>
-
-using namespace std::string_literals;
-
-// TODO: switch to <linux/fscrypt.h> once it's in Bionic
-#ifndef FSCRYPT_POLICY_V1
-// Careful: due to an API quirk this is actually 0, not 1. We use 1 everywhere
-// else, so make sure to only use this constant in the ioctl itself.
-#define FSCRYPT_POLICY_V1 0
-#define FSCRYPT_KEY_DESCRIPTOR_SIZE 8
-struct fscrypt_policy_v1 {
- __u8 version;
- __u8 contents_encryption_mode;
- __u8 filenames_encryption_mode;
- __u8 flags;
- __u8 master_key_descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE];
-};
-
-#define FSCRYPT_POLICY_V2 2
-#define FSCRYPT_KEY_IDENTIFIER_SIZE 16
-struct fscrypt_policy_v2 {
- __u8 version;
- __u8 contents_encryption_mode;
- __u8 filenames_encryption_mode;
- __u8 flags;
- __u8 __reserved[4];
- __u8 master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
-};
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <cutils/properties.h>
+#include <logwrap/logwrap.h>
+#include <utils/misc.h>
-#endif /* FSCRYPT_POLICY_V1 */
+#define FS_KEY_DESCRIPTOR_SIZE_HEX (2 * FS_KEY_DESCRIPTOR_SIZE + 1)
/* modes not supported by upstream kernel, so not in <linux/fs.h> */
#define FS_ENCRYPTION_MODE_AES_256_HEH 126
#define FS_ENCRYPTION_MODE_PRIVATE 127
-#define HEX_LOOKUP "0123456789abcdef"
-
-struct ModeLookupEntry {
- std::string name;
- int id;
-};
-
-static const auto contents_modes = std::vector<ModeLookupEntry>{
- {"aes-256-xts"s, FS_ENCRYPTION_MODE_AES_256_XTS},
- {"software"s, FS_ENCRYPTION_MODE_AES_256_XTS},
- {"adiantum"s, FS_ENCRYPTION_MODE_ADIANTUM},
- {"ice"s, FS_ENCRYPTION_MODE_PRIVATE},
-};
+/* new definition, not yet in Bionic's <linux/fs.h> */
+#ifndef FS_ENCRYPTION_MODE_ADIANTUM
+#define FS_ENCRYPTION_MODE_ADIANTUM 9
+#endif
-static const auto filenames_modes = std::vector<ModeLookupEntry>{
- {"aes-256-cts"s, FS_ENCRYPTION_MODE_AES_256_CTS},
- {"aes-256-heh"s, FS_ENCRYPTION_MODE_AES_256_HEH},
- {"adiantum"s, FS_ENCRYPTION_MODE_ADIANTUM},
-};
-
-static bool LookupModeByName(const std::vector<struct ModeLookupEntry>& modes,
- const std::string& name, int* result) {
- for (const auto& e : modes) {
- if (e.name == name) {
- *result = e.id;
- return true;
- }
- }
- return false;
-}
+/* new definition, not yet in Bionic's <linux/fs.h> */
+#ifndef FS_POLICY_FLAG_DIRECT_KEY
+#define FS_POLICY_FLAG_DIRECT_KEY 0x4
+#endif
-static bool LookupModeById(const std::vector<struct ModeLookupEntry>& modes, int id,
- std::string* result) {
- for (const auto& e : modes) {
- if (e.id == id) {
- *result = e.name;
- return true;
- }
- }
- return false;
-}
+#define HEX_LOOKUP "0123456789abcdef"
bool fscrypt_is_native() {
char value[PROPERTY_VALUE_MAX];
@@ -118,14 +59,11 @@ bool fscrypt_is_native() {
return !strcmp(value, "file");
}
-namespace android {
-namespace fscrypt {
-
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);
+ android_fork_execvp(argv.size(), const_cast<char**>(argv.data()), &status, false, true);
if (res != 0) {
PLOG(ERROR) << argv[0] << " " << argv[1] << " " << argv[2] << "failed";
return;
@@ -142,197 +80,204 @@ static void log_ls(const char* dirname) {
}
}
-void BytesToHex(const std::string& bytes, std::string* hex) {
- hex->clear();
- for (char c : bytes) {
- *hex += HEX_LOOKUP[(c & 0xF0) >> 4];
- *hex += HEX_LOOKUP[c & 0x0F];
+static void policy_to_hex(const char* policy, char* hex) {
+ for (size_t i = 0, j = 0; i < FS_KEY_DESCRIPTOR_SIZE; i++) {
+ hex[j++] = HEX_LOOKUP[(policy[i] & 0xF0) >> 4];
+ hex[j++] = HEX_LOOKUP[policy[i] & 0x0F];
}
+ hex[FS_KEY_DESCRIPTOR_SIZE_HEX - 1] = '\0';
}
-static bool fscrypt_is_encrypted(int fd) {
- fscrypt_policy_v1 policy;
-
- // success => encrypted with v1 policy
- // EINVAL => encrypted with v2 policy
- // ENODATA => not encrypted
- return ioctl(fd, FS_IOC_GET_ENCRYPTION_POLICY, &policy) == 0 || errno == EINVAL;
+static bool is_dir_empty(const char *dirname, bool *is_empty)
+{
+ int n = 0;
+ auto dirp = std::unique_ptr<DIR, int (*)(DIR*)>(opendir(dirname), closedir);
+ if (!dirp) {
+ PLOG(ERROR) << "Unable to read directory: " << dirname;
+ return false;
+ }
+ for (;;) {
+ errno = 0;
+ auto entry = readdir(dirp.get());
+ if (!entry) {
+ if (errno) {
+ PLOG(ERROR) << "Unable to read directory: " << dirname;
+ return false;
+ }
+ break;
+ }
+ if (strcmp(entry->d_name, "lost+found") != 0) { // Skip lost+found
+ ++n;
+ if (n > 2) {
+ *is_empty = false;
+ return true;
+ }
+ }
+ }
+ *is_empty = true;
+ return true;
}
-bool OptionsToString(const EncryptionOptions& options, std::string* options_string) {
- std::string contents_mode, filenames_mode;
- if (!LookupModeById(contents_modes, options.contents_mode, &contents_mode)) {
- return false;
+static uint8_t fscrypt_get_policy_flags(int filenames_encryption_mode) {
+ if (filenames_encryption_mode == FS_ENCRYPTION_MODE_AES_256_CTS) {
+ // Use legacy padding with our original filenames encryption mode.
+ return FS_POLICY_FLAGS_PAD_4;
+ } else if (filenames_encryption_mode == FS_ENCRYPTION_MODE_ADIANTUM) {
+ // Use DIRECT_KEY for Adiantum, since it's much more efficient but just
+ // as secure since Android doesn't reuse the same master key for
+ // multiple encryption modes
+ return (FS_POLICY_FLAGS_PAD_16 | FS_POLICY_FLAG_DIRECT_KEY);
}
- if (!LookupModeById(filenames_modes, options.filenames_mode, &filenames_mode)) {
+ // With a new mode we can use the better padding flag without breaking existing devices: pad
+ // filenames with zeroes to the next 16-byte boundary. This is more secure (helps hide the
+ // length of filenames) and makes the inputs evenly divisible into blocks which is more
+ // efficient for encryption and decryption.
+ return FS_POLICY_FLAGS_PAD_16;
+}
+
+static bool fscrypt_policy_set(const char *directory, const char *policy,
+ size_t policy_length,
+ int contents_encryption_mode,
+ int filenames_encryption_mode) {
+ if (policy_length != FS_KEY_DESCRIPTOR_SIZE) {
+ LOG(ERROR) << "Policy wrong length: " << policy_length;
return false;
}
- *options_string = contents_mode + ":" + filenames_mode + ":v" + std::to_string(options.version);
- if ((options.flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64)) {
- *options_string += "+inlinecrypt_optimized";
- }
- EncryptionOptions options_check;
- if (!ParseOptions(*options_string, &options_check)) {
- LOG(ERROR) << "Internal error serializing options as string: " << *options_string;
+ char policy_hex[FS_KEY_DESCRIPTOR_SIZE_HEX];
+ policy_to_hex(policy, policy_hex);
+
+ int fd = open(directory, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
+ if (fd == -1) {
+ PLOG(ERROR) << "Failed to open directory " << directory;
return false;
}
- if (memcmp(&options, &options_check, sizeof(options_check)) != 0) {
- LOG(ERROR) << "Internal error serializing options as string, round trip failed: "
- << *options_string;
+
+ fscrypt_policy fp;
+ fp.version = 0;
+ fp.contents_encryption_mode = contents_encryption_mode;
+ fp.filenames_encryption_mode = filenames_encryption_mode;
+ fp.flags = fscrypt_get_policy_flags(filenames_encryption_mode);
+ memcpy(fp.master_key_descriptor, policy, FS_KEY_DESCRIPTOR_SIZE);
+ if (ioctl(fd, FS_IOC_SET_ENCRYPTION_POLICY, &fp)) {
+ PLOG(ERROR) << "Failed to set encryption policy for " << directory << " to " << policy_hex
+ << " modes " << contents_encryption_mode << "/" << filenames_encryption_mode;
+ close(fd);
return false;
}
+ close(fd);
+
+ LOG(INFO) << "Policy for " << directory << " set to " << policy_hex
+ << " modes " << contents_encryption_mode << "/" << filenames_encryption_mode;
return true;
}
-bool ParseOptions(const std::string& options_string, EncryptionOptions* options) {
- memset(options, '\0', sizeof(*options));
- auto parts = android::base::Split(options_string, ":");
- if (parts.size() < 1 || parts.size() > 3) {
+static bool fscrypt_policy_get(const char *directory, char *policy,
+ size_t policy_length,
+ int contents_encryption_mode,
+ int filenames_encryption_mode) {
+ if (policy_length != FS_KEY_DESCRIPTOR_SIZE) {
+ LOG(ERROR) << "Policy wrong length: " << policy_length;
return false;
}
- if (!LookupModeByName(contents_modes, parts[0], &options->contents_mode)) {
- LOG(ERROR) << "Invalid file contents encryption mode: " << parts[0];
+
+ int fd = open(directory, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
+ if (fd == -1) {
+ PLOG(ERROR) << "Failed to open directory " << directory;
return false;
}
- if (parts.size() >= 2) {
- if (!LookupModeByName(filenames_modes, parts[1], &options->filenames_mode)) {
- LOG(ERROR) << "Invalid file names encryption mode: " << parts[1];
- return false;
- }
- } else if (options->contents_mode == FS_ENCRYPTION_MODE_ADIANTUM) {
- options->filenames_mode = FS_ENCRYPTION_MODE_ADIANTUM;
- } else {
- options->filenames_mode = FS_ENCRYPTION_MODE_AES_256_CTS;
- }
- options->version = 1;
- options->flags = 0;
- if (parts.size() >= 3) {
- auto flags = android::base::Split(parts[2], "+");
- for (const auto& flag : flags) {
- if (flag == "v1") {
- options->version = 1;
- } else if (flag == "v2") {
- options->version = 2;
- } else if (flag == "inlinecrypt_optimized") {
- options->flags |= FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64;
- } else {
- LOG(ERROR) << "Unknown flag: " << flag;
- return false;
- }
- }
- }
- // In the original setting of v1 policies and AES-256-CTS we used 4-byte
- // padding of filenames, so we have to retain that for compatibility.
- //
- // 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 (options->version == 1 && options->filenames_mode == FS_ENCRYPTION_MODE_AES_256_CTS) {
- options->flags |= FS_POLICY_FLAGS_PAD_4;
- } else {
- options->flags |= FS_POLICY_FLAGS_PAD_16;
+ fscrypt_policy fp;
+ memset(&fp, 0, sizeof(fscrypt_policy));
+ if (ioctl(fd, FS_IOC_GET_ENCRYPTION_POLICY, &fp) != 0) {
+ PLOG(ERROR) << "Failed to get encryption policy for " << directory;
+ close(fd);
+ log_ls(directory);
+ return false;
}
-
- // Use DIRECT_KEY for Adiantum, since it's much more efficient but just as
- // secure since Android doesn't reuse the same master key for multiple
- // encryption modes.
- if (options->filenames_mode == FS_ENCRYPTION_MODE_ADIANTUM) {
- options->flags |= FS_POLICY_FLAG_DIRECT_KEY;
+ close(fd);
+
+ if ((fp.version != 0)
+ || (fp.contents_encryption_mode != contents_encryption_mode)
+ || (fp.filenames_encryption_mode != filenames_encryption_mode)
+ || (fp.flags !=
+ fscrypt_get_policy_flags(filenames_encryption_mode))) {
+ LOG(ERROR) << "Failed to find matching encryption policy for " << directory;
+ return false;
}
- return true;
-}
+ memcpy(policy, fp.master_key_descriptor, FS_KEY_DESCRIPTOR_SIZE);
-static std::string PolicyDebugString(const EncryptionPolicy& policy) {
- std::stringstream ss;
- std::string ref_hex;
- BytesToHex(policy.key_raw_ref, &ref_hex);
- ss << ref_hex;
- ss << " v" << policy.options.version;
- ss << " modes " << policy.options.contents_mode << "/" << policy.options.filenames_mode;
- ss << std::hex << " flags 0x" << policy.options.flags;
- return ss.str();
+ return true;
}
-bool EnsurePolicy(const EncryptionPolicy& policy, const std::string& directory) {
- union {
- fscrypt_policy_v1 v1;
- fscrypt_policy_v2 v2;
- } kern_policy;
- memset(&kern_policy, 0, sizeof(kern_policy));
-
- switch (policy.options.version) {
- case 1:
- if (policy.key_raw_ref.size() != FSCRYPT_KEY_DESCRIPTOR_SIZE) {
- LOG(ERROR) << "Invalid key descriptor length for v1 policy: "
- << policy.key_raw_ref.size();
- return false;
- }
- // Careful: FSCRYPT_POLICY_V1 is actually 0 in the API, so make sure
- // to use it here instead of a literal 1.
- kern_policy.v1.version = FSCRYPT_POLICY_V1;
- kern_policy.v1.contents_encryption_mode = policy.options.contents_mode;
- kern_policy.v1.filenames_encryption_mode = policy.options.filenames_mode;
- kern_policy.v1.flags = policy.options.flags;
- policy.key_raw_ref.copy(reinterpret_cast<char*>(kern_policy.v1.master_key_descriptor),
- FSCRYPT_KEY_DESCRIPTOR_SIZE);
- break;
- case 2:
- if (policy.key_raw_ref.size() != FSCRYPT_KEY_IDENTIFIER_SIZE) {
- LOG(ERROR) << "Invalid key identifier length for v2 policy: "
- << policy.key_raw_ref.size();
- return false;
- }
- kern_policy.v2.version = FSCRYPT_POLICY_V2;
- kern_policy.v2.contents_encryption_mode = policy.options.contents_mode;
- kern_policy.v2.filenames_encryption_mode = policy.options.filenames_mode;
- kern_policy.v2.flags = policy.options.flags;
- policy.key_raw_ref.copy(reinterpret_cast<char*>(kern_policy.v2.master_key_identifier),
- FSCRYPT_KEY_IDENTIFIER_SIZE);
- break;
- default:
- LOG(ERROR) << "Invalid encryption policy version: " << policy.options.version;
- return false;
+static bool fscrypt_policy_check(const char *directory, const char *policy,
+ size_t policy_length,
+ int contents_encryption_mode,
+ int filenames_encryption_mode) {
+ if (policy_length != FS_KEY_DESCRIPTOR_SIZE) {
+ LOG(ERROR) << "Policy wrong length: " << policy_length;
+ return false;
}
-
- android::base::unique_fd fd(open(directory.c_str(), O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC));
- if (fd == -1) {
- PLOG(ERROR) << "Failed to open directory " << directory;
+ char existing_policy[FS_KEY_DESCRIPTOR_SIZE];
+ if (!fscrypt_policy_get(directory, existing_policy, FS_KEY_DESCRIPTOR_SIZE,
+ contents_encryption_mode,
+ filenames_encryption_mode)) return false;
+ char existing_policy_hex[FS_KEY_DESCRIPTOR_SIZE_HEX];
+
+ policy_to_hex(existing_policy, existing_policy_hex);
+
+ if (memcmp(policy, existing_policy, FS_KEY_DESCRIPTOR_SIZE) != 0) {
+ char policy_hex[FS_KEY_DESCRIPTOR_SIZE_HEX];
+ policy_to_hex(policy, policy_hex);
+ LOG(ERROR) << "Found policy " << existing_policy_hex << " at " << directory
+ << " which doesn't match expected value " << policy_hex;
+ log_ls(directory);
return false;
}
+ LOG(INFO) << "Found policy " << existing_policy_hex << " at " << directory
+ << " which matches expected value";
+ return true;
+}
- bool already_encrypted = fscrypt_is_encrypted(fd);
+int fscrypt_policy_ensure(const char *directory, const char *policy,
+ size_t policy_length,
+ const char *contents_encryption_mode,
+ const char *filenames_encryption_mode) {
+ int contents_mode = 0;
+ int filenames_mode = 0;
+
+ if (!strcmp(contents_encryption_mode, "software") ||
+ !strcmp(contents_encryption_mode, "aes-256-xts")) {
+ contents_mode = FS_ENCRYPTION_MODE_AES_256_XTS;
+ } else if (!strcmp(contents_encryption_mode, "adiantum")) {
+ contents_mode = FS_ENCRYPTION_MODE_ADIANTUM;
+ } else if (!strcmp(contents_encryption_mode, "ice")) {
+ contents_mode = FS_ENCRYPTION_MODE_PRIVATE;
+ } else {
+ LOG(ERROR) << "Invalid file contents encryption mode: "
+ << contents_encryption_mode;
+ return -1;
+ }
- // FS_IOC_SET_ENCRYPTION_POLICY will set the policy if the directory is
- // unencrypted; otherwise it will verify that the existing policy matches.
- // Setting the policy will fail if the directory is already nonempty.
- if (ioctl(fd, FS_IOC_SET_ENCRYPTION_POLICY, &kern_policy) != 0) {
- std::string reason;
- switch (errno) {
- case EEXIST:
- reason = "The directory already has a different encryption policy.";
- break;
- default:
- reason = strerror(errno);
- break;
- }
- LOG(ERROR) << "Failed to set encryption policy of " << directory << " to "
- << PolicyDebugString(policy) << ": " << reason;
- if (errno == ENOTEMPTY) {
- log_ls(directory.c_str());
- }
- return false;
+ if (!strcmp(filenames_encryption_mode, "aes-256-cts")) {
+ filenames_mode = FS_ENCRYPTION_MODE_AES_256_CTS;
+ } else if (!strcmp(filenames_encryption_mode, "aes-256-heh")) {
+ filenames_mode = FS_ENCRYPTION_MODE_AES_256_HEH;
+ } else if (!strcmp(filenames_encryption_mode, "adiantum")) {
+ filenames_mode = FS_ENCRYPTION_MODE_ADIANTUM;
+ } else {
+ LOG(ERROR) << "Invalid file names encryption mode: "
+ << filenames_encryption_mode;
+ return -1;
}
- if (already_encrypted) {
- LOG(INFO) << "Verified that " << directory << " has the encryption policy "
- << PolicyDebugString(policy);
+ bool is_empty;
+ if (!is_dir_empty(directory, &is_empty)) return -1;
+ if (is_empty) {
+ if (!fscrypt_policy_set(directory, policy, policy_length,
+ contents_mode, filenames_mode)) return -1;
} else {
- LOG(INFO) << "Encryption policy of " << directory << " set to "
- << PolicyDebugString(policy);
+ if (!fscrypt_policy_check(directory, policy, policy_length,
+ contents_mode, filenames_mode)) return -1;
}
- return true;
+ return 0;
}
-
-} // namespace fscrypt
-} // namespace android
diff --git a/libfscrypt/fscrypt_init_extensions.cpp b/libfscrypt/fscrypt_init_extensions.cpp
new file mode 100644
index 00000000..2fd70e79
--- /dev/null
+++ b/libfscrypt/fscrypt_init_extensions.cpp
@@ -0,0 +1,146 @@
+/*
+ * 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 "fscrypt/fscrypt.h"
+#include "fscrypt/fscrypt_init_extensions.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <cutils/properties.h>
+#include <cutils/sockets.h>
+#include <keyutils.h>
+#include <logwrap/logwrap.h>
+
+#define TAG "fscrypt"
+
+static const std::string arbitrary_sequence_number = "42";
+
+static int set_policy_on(char const* ref_basename, char const* dir);
+
+int fscrypt_install_keyring()
+{
+ key_serial_t device_keyring = add_key("keyring", "fscrypt", 0, 0,
+ KEY_SPEC_SESSION_KEYRING);
+
+ if (device_keyring == -1) {
+ PLOG(ERROR) << "Failed to create keyring";
+ return -1;
+ }
+
+ LOG(INFO) << "Keyring created with id " << device_keyring << " in process " << getpid();
+
+ return 0;
+}
+
+int fscrypt_set_directory_policy(const char* dir)
+{
+ if (!dir || strncmp(dir, "/data/", 6)) {
+ return 0;
+ }
+
+ // Special-case /data/media/obb per b/64566063
+ if (strcmp(dir, "/data/media/obb") == 0) {
+ // Try to set policy on this directory, but if it is non-empty this may fail.
+ set_policy_on(fscrypt_key_ref, dir);
+ return 0;
+ }
+
+ // Only set policy on first level /data directories
+ // To make this less restrictive, consider using a policy file.
+ // However this is overkill for as long as the policy is simply
+ // to apply a global policy to all /data folders created via makedir
+ if (strchr(dir + 6, '/')) {
+ return 0;
+ }
+
+ // Special case various directories that must not be encrypted,
+ // often because their subdirectories must be encrypted.
+ // This isn't a nice way to do this, see b/26641735
+ std::vector<std::string> directories_to_exclude = {
+ "lost+found",
+ "system_ce", "system_de",
+ "misc_ce", "misc_de",
+ "vendor_ce", "vendor_de",
+ "media",
+ "data", "user", "user_de",
+ "apex", "preloads", "app-staging",
+ "gsi",
+ };
+ std::string prefix = "/data/";
+ for (const auto& d: directories_to_exclude) {
+ if ((prefix + d) == dir) {
+ LOG(INFO) << "Not setting policy on " << dir;
+ return 0;
+ }
+ }
+ std::vector<std::string> per_boot_directories = {
+ "per_boot",
+ };
+ for (const auto& d : per_boot_directories) {
+ if ((prefix + d) == dir) {
+ LOG(INFO) << "Setting per_boot key on " << dir;
+ return set_policy_on(fscrypt_key_per_boot_ref, dir);
+ }
+ }
+ return set_policy_on(fscrypt_key_ref, dir);
+}
+
+static int set_policy_on(char const* ref_basename, char const* dir) {
+ std::string ref_filename = std::string("/data") + ref_basename;
+ std::string policy;
+ if (!android::base::ReadFileToString(ref_filename, &policy)) {
+ LOG(ERROR) << "Unable to read system policy to set on " << dir;
+ return -1;
+ }
+
+ auto type_filename = std::string("/data") + fscrypt_key_mode;
+ std::string modestring;
+ if (!android::base::ReadFileToString(type_filename, &modestring)) {
+ LOG(ERROR) << "Cannot read mode";
+ }
+
+ std::vector<std::string> modes = android::base::Split(modestring, ":");
+
+ if (modes.size() < 1 || modes.size() > 2) {
+ LOG(ERROR) << "Invalid encryption mode string: " << modestring;
+ return -1;
+ }
+
+ LOG(INFO) << "Setting policy on " << dir;
+ int result = fscrypt_policy_ensure(dir, policy.c_str(), policy.length(),
+ modes[0].c_str(),
+ modes.size() >= 2 ?
+ modes[1].c_str() : "aes-256-cts");
+ if (result) {
+ LOG(ERROR) << android::base::StringPrintf(
+ "Setting %02x%02x%02x%02x policy on %s failed!",
+ policy[0], policy[1], policy[2], policy[3], dir);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/libfscrypt/include/fscrypt/fscrypt.h b/libfscrypt/include/fscrypt/fscrypt.h
index 2b809866..ff82d47a 100644
--- a/libfscrypt/include/fscrypt/fscrypt.h
+++ b/libfscrypt/include/fscrypt/fscrypt.h
@@ -17,45 +17,24 @@
#ifndef _FSCRYPT_H_
#define _FSCRYPT_H_
-#include <string>
+#include <sys/cdefs.h>
+#include <stdbool.h>
+#include <cutils/multiuser.h>
-// TODO: switch to <linux/fscrypt.h> once it's in Bionic
-#define FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 0x08
+__BEGIN_DECLS
bool fscrypt_is_native();
+int fscrypt_policy_ensure(const char *directory, const char *policy,
+ size_t policy_length,
+ const char *contents_encryption_mode,
+ const char *filenames_encryption_mode);
+
static const char* fscrypt_unencrypted_folder = "/unencrypted";
static const char* fscrypt_key_ref = "/unencrypted/ref";
static const char* fscrypt_key_per_boot_ref = "/unencrypted/per_boot_ref";
static const char* fscrypt_key_mode = "/unencrypted/mode";
-namespace android {
-namespace fscrypt {
-
-struct EncryptionOptions {
- int version;
- int contents_mode;
- int filenames_mode;
- int flags;
-
- // Ensure that "version" is not valid on creation and so must be explicitly set
- EncryptionOptions() : version(0) {}
-};
-
-struct EncryptionPolicy {
- EncryptionOptions options;
- std::string key_raw_ref;
-};
-
-void BytesToHex(const std::string& bytes, std::string* hex);
-
-bool OptionsToString(const EncryptionOptions& options, std::string* options_string);
-
-bool ParseOptions(const std::string& options_string, EncryptionOptions* options);
-
-bool EnsurePolicy(const EncryptionPolicy& policy, const std::string& directory);
-
-} // namespace fscrypt
-} // namespace android
+__END_DECLS
#endif // _FSCRYPT_H_
diff --git a/memory_replay/Utils.h b/libfscrypt/include/fscrypt/fscrypt_init_extensions.h
index 44ce93ac..2b6c46e1 100644
--- a/memory_replay/Utils.h
+++ b/libfscrypt/include/fscrypt/fscrypt_init_extensions.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,20 +14,20 @@
* limitations under the License.
*/
-#pragma once
+#ifndef _FSCRYPT_INIT_EXTENSIONS_H_
+#define _FSCRYPT_INIT_EXTENSIONS_H_
-#include <stdint.h>
-#include <time.h>
+#include <sys/cdefs.h>
+#include <stdbool.h>
+#include <cutils/multiuser.h>
-static __always_inline uint64_t Nanotime() {
- struct timespec t = {};
- clock_gettime(CLOCK_MONOTONIC, &t);
- return static_cast<uint64_t>(t.tv_sec) * 1000000000LL + t.tv_nsec;
-}
+__BEGIN_DECLS
-static __always_inline void MakeAllocationResident(void* ptr, size_t nbytes, int pagesize) {
- uint8_t* data = reinterpret_cast<uint8_t*>(ptr);
- for (size_t i = 0; i < nbytes; i += pagesize) {
- data[i] = 1;
- }
-}
+// These functions assume they are being called from init
+// They will not operate properly outside of init
+int fscrypt_install_keyring();
+int fscrypt_set_directory_policy(const char* path);
+
+__END_DECLS
+
+#endif // _FSCRYPT_INIT_EXTENSIONS_H_
diff --git a/libfscrypt/tests/fscrypt_test.cpp b/libfscrypt/tests/fscrypt_test.cpp
deleted file mode 100644
index 677f0f22..00000000
--- a/libfscrypt/tests/fscrypt_test.cpp
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2019 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 <linux/fs.h>
-
-#include <fscrypt/fscrypt.h>
-
-#include <gtest/gtest.h>
-
-using namespace android::fscrypt;
-
-/* modes not supported by upstream kernel, so not in <linux/fs.h> */
-#define FS_ENCRYPTION_MODE_AES_256_HEH 126
-#define FS_ENCRYPTION_MODE_PRIVATE 127
-
-TEST(fscrypt, ParseOptions) {
- EncryptionOptions options;
- std::string options_string;
-
- EXPECT_FALSE(ParseOptions("", &options));
- EXPECT_FALSE(ParseOptions("blah", &options));
-
- EXPECT_TRUE(ParseOptions("software", &options));
- EXPECT_EQ(1, options.version);
- EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_XTS, options.contents_mode);
- EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_CTS, options.filenames_mode);
- EXPECT_EQ(FS_POLICY_FLAGS_PAD_4, options.flags);
- EXPECT_TRUE(OptionsToString(options, &options_string));
- EXPECT_EQ("aes-256-xts:aes-256-cts:v1", options_string);
-
- EXPECT_TRUE(ParseOptions("aes-256-xts", &options));
- EXPECT_EQ(1, options.version);
- EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_XTS, options.contents_mode);
- EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_CTS, options.filenames_mode);
- EXPECT_EQ(FS_POLICY_FLAGS_PAD_4, options.flags);
- EXPECT_TRUE(OptionsToString(options, &options_string));
- EXPECT_EQ("aes-256-xts:aes-256-cts:v1", options_string);
-
- EXPECT_TRUE(ParseOptions("adiantum", &options));
- EXPECT_EQ(1, options.version);
- EXPECT_EQ(FS_ENCRYPTION_MODE_ADIANTUM, options.contents_mode);
- EXPECT_EQ(FS_ENCRYPTION_MODE_ADIANTUM, options.filenames_mode);
- EXPECT_EQ(FS_POLICY_FLAGS_PAD_16 | FS_POLICY_FLAG_DIRECT_KEY, options.flags);
- EXPECT_TRUE(OptionsToString(options, &options_string));
- EXPECT_EQ("adiantum:adiantum:v1", options_string);
-
- EXPECT_TRUE(ParseOptions("adiantum:aes-256-heh", &options));
- EXPECT_EQ(1, options.version);
- EXPECT_EQ(FS_ENCRYPTION_MODE_ADIANTUM, options.contents_mode);
- EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_HEH, options.filenames_mode);
- EXPECT_EQ(FS_POLICY_FLAGS_PAD_16, options.flags);
- EXPECT_TRUE(OptionsToString(options, &options_string));
- EXPECT_EQ("adiantum:aes-256-heh:v1", options_string);
-
- EXPECT_TRUE(ParseOptions("ice", &options));
- EXPECT_EQ(1, options.version);
- EXPECT_EQ(FS_ENCRYPTION_MODE_PRIVATE, options.contents_mode);
- EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_CTS, options.filenames_mode);
- EXPECT_EQ(FS_POLICY_FLAGS_PAD_4, options.flags);
- EXPECT_TRUE(OptionsToString(options, &options_string));
- EXPECT_EQ("ice:aes-256-cts:v1", options_string);
-
- EXPECT_FALSE(ParseOptions("ice:blah", &options));
-
- EXPECT_TRUE(ParseOptions("ice:aes-256-cts", &options));
- EXPECT_EQ(1, options.version);
- EXPECT_EQ(FS_ENCRYPTION_MODE_PRIVATE, options.contents_mode);
- EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_CTS, options.filenames_mode);
- EXPECT_EQ(FS_POLICY_FLAGS_PAD_4, options.flags);
- EXPECT_TRUE(OptionsToString(options, &options_string));
- EXPECT_EQ("ice:aes-256-cts:v1", options_string);
-
- EXPECT_TRUE(ParseOptions("ice:aes-256-heh", &options));
- EXPECT_EQ(1, options.version);
- EXPECT_EQ(FS_ENCRYPTION_MODE_PRIVATE, options.contents_mode);
- EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_HEH, options.filenames_mode);
- EXPECT_EQ(FS_POLICY_FLAGS_PAD_16, options.flags);
- EXPECT_TRUE(OptionsToString(options, &options_string));
- EXPECT_EQ("ice:aes-256-heh:v1", options_string);
-
- EXPECT_TRUE(ParseOptions("ice:adiantum", &options));
- EXPECT_EQ(1, options.version);
- EXPECT_EQ(FS_ENCRYPTION_MODE_PRIVATE, options.contents_mode);
- EXPECT_EQ(FS_ENCRYPTION_MODE_ADIANTUM, options.filenames_mode);
- EXPECT_EQ(FS_POLICY_FLAGS_PAD_16 | FS_POLICY_FLAG_DIRECT_KEY, options.flags);
- EXPECT_TRUE(OptionsToString(options, &options_string));
- EXPECT_EQ("ice:adiantum:v1", options_string);
-
- EXPECT_TRUE(ParseOptions("aes-256-xts:aes-256-cts", &options));
- EXPECT_EQ(1, options.version);
- EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_XTS, options.contents_mode);
- EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_CTS, options.filenames_mode);
- EXPECT_EQ(FS_POLICY_FLAGS_PAD_4, options.flags);
- EXPECT_TRUE(OptionsToString(options, &options_string));
- EXPECT_EQ("aes-256-xts:aes-256-cts:v1", options_string);
-
- EXPECT_TRUE(ParseOptions("aes-256-xts:aes-256-cts:v1", &options));
- EXPECT_EQ(1, options.version);
- EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_XTS, options.contents_mode);
- EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_CTS, options.filenames_mode);
- EXPECT_EQ(FS_POLICY_FLAGS_PAD_4, options.flags);
- EXPECT_TRUE(OptionsToString(options, &options_string));
- EXPECT_EQ("aes-256-xts:aes-256-cts:v1", options_string);
-
- EXPECT_TRUE(ParseOptions("aes-256-xts:aes-256-cts:v2", &options));
- EXPECT_EQ(2, options.version);
- EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_XTS, options.contents_mode);
- EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_CTS, options.filenames_mode);
- EXPECT_EQ(FS_POLICY_FLAGS_PAD_16, options.flags);
- EXPECT_TRUE(OptionsToString(options, &options_string));
- EXPECT_EQ("aes-256-xts:aes-256-cts:v2", options_string);
-
- EXPECT_TRUE(ParseOptions("aes-256-xts:aes-256-cts:v2+inlinecrypt_optimized", &options));
- EXPECT_EQ(2, options.version);
- EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_XTS, options.contents_mode);
- EXPECT_EQ(FS_ENCRYPTION_MODE_AES_256_CTS, options.filenames_mode);
- EXPECT_EQ(FS_POLICY_FLAGS_PAD_16 | FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64, options.flags);
- EXPECT_TRUE(OptionsToString(options, &options_string));
- EXPECT_EQ("aes-256-xts:aes-256-cts:v2+inlinecrypt_optimized", options_string);
-
- EXPECT_FALSE(ParseOptions("aes-256-xts:aes-256-cts:v2:", &options));
- EXPECT_FALSE(ParseOptions("aes-256-xts:aes-256-cts:v2:foo", &options));
- EXPECT_FALSE(ParseOptions("aes-256-xts:aes-256-cts:blah", &options));
- EXPECT_FALSE(ParseOptions("aes-256-xts:aes-256-cts:vblah", &options));
-}
diff --git a/libjsonpb/parse/jsonpb.cpp b/libjsonpb/parse/jsonpb.cpp
index d7feb670..bd95dbdf 100644
--- a/libjsonpb/parse/jsonpb.cpp
+++ b/libjsonpb/parse/jsonpb.cpp
@@ -27,6 +27,7 @@ namespace jsonpb {
using google::protobuf::DescriptorPool;
using google::protobuf::Message;
+using google::protobuf::scoped_ptr;
using google::protobuf::util::NewTypeResolverForDescriptorPool;
using google::protobuf::util::TypeResolver;
@@ -37,7 +38,7 @@ std::string GetTypeUrl(const Message& message) {
}
ErrorOr<std::string> MessageToJsonString(const Message& message) {
- std::unique_ptr<TypeResolver> resolver(
+ scoped_ptr<TypeResolver> resolver(
NewTypeResolverForDescriptorPool(kTypeUrlPrefix, DescriptorPool::generated_pool()));
google::protobuf::util::JsonOptions options;
@@ -55,7 +56,7 @@ ErrorOr<std::string> MessageToJsonString(const Message& message) {
namespace internal {
ErrorOr<std::monostate> JsonStringToMessage(const std::string& content, Message* message) {
- std::unique_ptr<TypeResolver> resolver(
+ scoped_ptr<TypeResolver> resolver(
NewTypeResolverForDescriptorPool(kTypeUrlPrefix, DescriptorPool::generated_pool()));
std::string binary;
diff --git a/libjsonpb/verify/test.cpp b/libjsonpb/verify/test.cpp
index cb98f47f..2ffc923a 100644
--- a/libjsonpb/verify/test.cpp
+++ b/libjsonpb/verify/test.cpp
@@ -122,9 +122,9 @@ TEST_F(JsonKeyTest, NoJsonNameOk) {
EXPECT_TRUE(AllFieldsAreKnown(*object, json, &error)) << error;
}
-// JSON field name is lower/UpperCamelCase of Proto field name;
-// AllFieldsAreKnown should return false. Although the lower/UpperCamelCase name
-// is a valid key accepted by Protobuf's JSON parser, we explicitly disallow the
+// JSON field name is lowerCamelCase of Proto field name;
+// AllFieldsAreKnown should return false. Although the lowerCamelCase name is a
+// valid key accepted by Protobuf's JSON parser, we explicitly disallow the
// behavior.
TEST_F(JsonKeyTest, NoJsonNameFooBar) {
EXPECT_EQ("fooBar", GetFieldJsonName<NoJsonName>("foo_bar"));
@@ -137,13 +137,13 @@ TEST_F(JsonKeyTest, NoJsonNameBarBaz) {
}
TEST_F(JsonKeyTest, NoJsonNameBazQux) {
- EXPECT_EQ("BazQux", GetFieldJsonName<NoJsonName>("BazQux"));
- // No test for BazQux because its JSON name is the same as field_name
+ EXPECT_EQ("bazQux", GetFieldJsonName<NoJsonName>("BazQux"));
+ TestParseOkWithUnknownKey<NoJsonName>("BazQux", "bazQux");
}
TEST_F(JsonKeyTest, NoJsonNameQuxQuux) {
- EXPECT_EQ("QUXQUUX", GetFieldJsonName<NoJsonName>("QUX_QUUX"));
- TestParseOkWithUnknownKey<NoJsonName>("QUX_QUUX", "QUXQUUX");
+ EXPECT_EQ("qUXQUUX", GetFieldJsonName<NoJsonName>("QUX_QUUX"));
+ TestParseOkWithUnknownKey<NoJsonName>("QUX_QUUX", "qUXQUUX");
}
class EmbeddedJsonKeyTest : public LibJsonpbVerifyTest {
@@ -198,16 +198,16 @@ TEST_F(EmbeddedJsonKeyTest, BarBaz) {
EXPECT_EQ("test", object->repeated_with_json_name().begin()->barbaz());
}
-TEST_F(EmbeddedJsonKeyTest, NoJsonName) {
- auto object = TestEmbeddedError(
- "{\"no_json_name\": {\"QUXQUUX\": \"test\"}}", "QUXQUUX");
+TEST_F(EmbeddedJsonKeyTest, BazQux) {
+ auto object =
+ TestEmbeddedError("{\"no_json_name\": {\"bazQux\": \"test\"}}", "bazQux");
ASSERT_TRUE(object.ok()) << object.error();
- EXPECT_EQ("test", object->no_json_name().qux_quux());
+ EXPECT_EQ("test", object->no_json_name().bazqux());
}
TEST_F(EmbeddedJsonKeyTest, QuxQuux) {
auto object = TestEmbeddedError(
- "{\"repeated_no_json_name\": [{\"QUXQUUX\": \"test\"}]}", "QUXQUUX");
+ "{\"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());
@@ -287,7 +287,7 @@ static const std::vector<ScalarTestErrorParam> gScalarTestErrorParams = {
{"{\"e\": 1}", "Should not allow integers for enums"},
};
-INSTANTIATE_TEST_SUITE_P(Jsonpb, ScalarTestError,
+INSTANTIATE_TEST_SUITE_P(, ScalarTestError,
::testing::ValuesIn(gScalarTestErrorParams));
int main(int argc, char** argv) {
diff --git a/libperfmgr/tools/ConfigVerifier.cc b/libperfmgr/tools/ConfigVerifier.cc
index 2e25e950..af3a7146 100644
--- a/libperfmgr/tools/ConfigVerifier.cc
+++ b/libperfmgr/tools/ConfigVerifier.cc
@@ -44,6 +44,23 @@ class NodeVerifier : public HintManager {
return false;
}
+ for (const auto& node : nodes) {
+ std::vector<std::string> values = node->GetValues();
+ std::string default_value = values[node->GetDefaultIndex()];
+ // Always set to default first
+ values.insert(values.begin(), default_value);
+ // And reset to default after test
+ values.push_back(default_value);
+ for (const auto& value : values) {
+ if (!android::base::WriteStringToFile(value, node->GetPath())) {
+ LOG(ERROR) << "Failed to write to node: " << node->GetPath()
+ << " with value: " << value;
+ return false;
+ }
+ LOG(VERBOSE) << "Wrote to node: " << node->GetPath()
+ << " with value: " << value;
+ }
+ }
return true;
}
@@ -67,14 +84,10 @@ static void printUsage(const char* exec_name) {
" [options]\n"
"\n"
"Options:\n"
- " --config, -c [PATH]\n"
+ " --config [PATH], -c\n"
" path to Json config file\n\n"
" --exec_hint, -e\n"
" do hints in Json config\n\n"
- " --hint_name, -i\n"
- " do only the specific hint\n\n"
- " --hint_duration, -d [duration]\n"
- " duration in ms for each hint\n\n"
" --help, -h\n"
" print this message\n\n"
" --verbose, -v\n"
@@ -83,7 +96,7 @@ static void printUsage(const char* exec_name) {
LOG(INFO) << usage;
}
-static void execConfig(const std::string& json_file, const std::string& hint_name, unsigned long hint_duration) {
+static void execConfig(const std::string& json_file) {
std::unique_ptr<android::perfmgr::HintManager> hm =
android::perfmgr::HintManager::GetFromJSON(json_file);
if (!hm.get() || !hm->IsRunning()) {
@@ -91,43 +104,37 @@ static void execConfig(const std::string& json_file, const std::string& hint_nam
}
std::vector<std::string> hints = hm->GetHints();
for (const auto& hint : hints) {
- if (!hint_name.empty() && hint_name != hint)
- continue;
LOG(INFO) << "Do hint: " << hint;
- hm->DoHint(hint, std::chrono::milliseconds(hint_duration));
+ hm->DoHint(hint);
std::this_thread::yield();
- std::this_thread::sleep_for(std::chrono::milliseconds(hint_duration));
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
LOG(INFO) << "End hint: " << hint;
hm->EndHint(hint);
std::this_thread::yield();
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
int main(int argc, char* argv[]) {
- android::base::InitLogging(argv, android::base::StdioLogger);
+ android::base::InitLogging(argv, android::base::StderrLogger);
if (getuid() == 0) {
LOG(WARNING) << "Running as root might mask node permission";
}
std::string config_path;
- std::string hint_name;
bool exec_hint = false;
- unsigned long hint_duration = 100;
-
while (true) {
static struct option opts[] = {
{"config", required_argument, nullptr, 'c'},
{"exec_hint", no_argument, nullptr, 'e'},
- {"hint_name", required_argument, nullptr, 'i'},
- {"hint_duration", required_argument, nullptr, 'd'},
{"help", no_argument, nullptr, 'h'},
{"verbose", no_argument, nullptr, 'v'},
{0, 0, 0, 0} // termination of the option list
};
int option_index = 0;
- int c = getopt_long(argc, argv, "c:ei:d:hv", opts, &option_index);
+ int c = getopt_long(argc, argv, "c:ehv", opts, &option_index);
if (c == -1) {
break;
}
@@ -139,12 +146,6 @@ int main(int argc, char* argv[]) {
case 'e':
exec_hint = true;
break;
- case 'i':
- hint_name = optarg;
- break;
- case 'd':
- hint_duration = strtoul(optarg, NULL, 10);
- break;
case 'v':
android::base::SetMinimumLogSeverity(android::base::VERBOSE);
break;
@@ -164,7 +165,7 @@ int main(int argc, char* argv[]) {
}
if (exec_hint) {
- execConfig(config_path, hint_name, hint_duration);
+ execConfig(config_path);
return 0;
}
diff --git a/memory_replay/Action.cpp b/memory_replay/Action.cpp
new file mode 100644
index 00000000..216ff9da
--- /dev/null
+++ b/memory_replay/Action.cpp
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+#include <malloc.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <time.h>
+
+#include <new>
+
+#include "Action.h"
+#include "Threads.h"
+#include "Pointers.h"
+
+static uint64_t nanotime() {
+ struct timespec t;
+ t.tv_sec = t.tv_nsec = 0;
+ clock_gettime(CLOCK_MONOTONIC, &t);
+ return static_cast<uint64_t>(t.tv_sec) * 1000000000LL + t.tv_nsec;
+}
+
+class EndThreadAction : public Action {
+ public:
+ EndThreadAction() {}
+
+ bool EndThread() override { return true; }
+
+ uint64_t Execute(Pointers*) override { return 0; }
+};
+
+class AllocAction : public Action {
+ public:
+ explicit AllocAction(uintptr_t key_pointer) : key_pointer_(key_pointer) {}
+
+ protected:
+ uintptr_t key_pointer_ = 0;
+ size_t size_ = 0;
+};
+
+class MallocAction : public AllocAction {
+ public:
+ MallocAction(uintptr_t key_pointer, const char* line) : AllocAction(key_pointer) {
+ if (sscanf(line, "%zu", &size_) != 1) {
+ is_error_ = true;
+ }
+ }
+
+ uint64_t Execute(Pointers* pointers) override {
+ uint64_t time_nsecs = nanotime();
+ void* memory = malloc(size_);
+ time_nsecs = nanotime() - time_nsecs;
+
+ memset(memory, 1, size_);
+ pointers->Add(key_pointer_, memory);
+
+ return time_nsecs;
+ }
+};
+
+class CallocAction : public AllocAction {
+ public:
+ CallocAction(uintptr_t key_pointer, const char* line) : AllocAction(key_pointer) {
+ if (sscanf(line, "%zu %zu", &n_elements_, &size_) != 2) {
+ is_error_ = true;
+ }
+ }
+
+ uint64_t Execute(Pointers* pointers) override {
+ uint64_t time_nsecs = nanotime();
+ void* memory = calloc(n_elements_, size_);
+ time_nsecs = nanotime() - time_nsecs;
+
+ memset(memory, 0, n_elements_ * size_);
+ pointers->Add(key_pointer_, memory);
+
+ return time_nsecs;
+ }
+
+ private:
+ size_t n_elements_ = 0;
+};
+
+class ReallocAction : public AllocAction {
+ public:
+ ReallocAction(uintptr_t key_pointer, const char* line) : AllocAction(key_pointer) {
+ if (sscanf(line, "%" SCNxPTR " %zu", &old_pointer_, &size_) != 2) {
+ is_error_ = true;
+ }
+ }
+
+ bool DoesFree() override { return old_pointer_ != 0; }
+
+ uint64_t Execute(Pointers* pointers) override {
+ void* old_memory = nullptr;
+ if (old_pointer_ != 0) {
+ old_memory = pointers->Remove(old_pointer_);
+ }
+
+ uint64_t time_nsecs = nanotime();
+ void* memory = realloc(old_memory, size_);
+ time_nsecs = nanotime() - time_nsecs;
+
+ memset(memory, 1, size_);
+ pointers->Add(key_pointer_, memory);
+
+ return time_nsecs;
+ }
+
+ private:
+ uintptr_t old_pointer_ = 0;
+};
+
+class MemalignAction : public AllocAction {
+ public:
+ MemalignAction(uintptr_t key_pointer, const char* line) : AllocAction(key_pointer) {
+ if (sscanf(line, "%zu %zu", &align_, &size_) != 2) {
+ is_error_ = true;
+ }
+ }
+
+ uint64_t Execute(Pointers* pointers) override {
+ uint64_t time_nsecs = nanotime();
+ void* memory = memalign(align_, size_);
+ time_nsecs = nanotime() - time_nsecs;
+
+ memset(memory, 1, size_);
+ pointers->Add(key_pointer_, memory);
+
+ return time_nsecs;
+ }
+
+ private:
+ size_t align_ = 0;
+};
+
+class FreeAction : public AllocAction {
+ public:
+ explicit FreeAction(uintptr_t key_pointer) : AllocAction(key_pointer) {
+ }
+
+ bool DoesFree() override { return key_pointer_ != 0; }
+
+ uint64_t Execute(Pointers* pointers) override {
+ if (key_pointer_) {
+ void* memory = pointers->Remove(key_pointer_);
+ uint64_t time_nsecs = nanotime();
+ free(memory);
+ return nanotime() - time_nsecs;
+ }
+ return 0;
+ }
+};
+
+size_t Action::MaxActionSize() {
+ size_t max = MAX(sizeof(EndThreadAction), sizeof(MallocAction));
+ max = MAX(max, sizeof(CallocAction));
+ max = MAX(max, sizeof(ReallocAction));
+ max = MAX(max, sizeof(MemalignAction));
+ return MAX(max, sizeof(FreeAction));
+}
+
+Action* Action::CreateAction(uintptr_t key_pointer, const char* type,
+ const char* line, void* action_memory) {
+ Action* action = nullptr;
+ if (strcmp(type, "malloc") == 0) {
+ action = new (action_memory) MallocAction(key_pointer, line);
+ } else if (strcmp(type, "free") == 0) {
+ action = new (action_memory) FreeAction(key_pointer);
+ } else if (strcmp(type, "calloc") == 0) {
+ action = new (action_memory) CallocAction(key_pointer, line);
+ } else if (strcmp(type, "realloc") == 0) {
+ action = new (action_memory) ReallocAction(key_pointer, line);
+ } else if (strcmp(type, "memalign") == 0) {
+ action = new (action_memory) MemalignAction(key_pointer, line);
+ } else if (strcmp(type, "thread_done") == 0) {
+ action = new (action_memory) EndThreadAction();
+ }
+
+ if (action == nullptr || action->IsError()) {
+ return nullptr;
+ }
+ return action;
+}
diff --git a/memory_replay/Action.h b/memory_replay/Action.h
new file mode 100644
index 00000000..f498f526
--- /dev/null
+++ b/memory_replay/Action.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MEMORY_REPLAY_ACTION_H
+#define _MEMORY_REPLAY_ACTION_H
+
+#include <stdint.h>
+
+class Pointers;
+
+class Action {
+ public:
+ Action() {}
+ virtual ~Action() {}
+
+ virtual uint64_t Execute(Pointers* pointers) = 0;
+
+ bool IsError() { return is_error_; };
+
+ virtual bool EndThread() { return false; }
+
+ virtual bool DoesFree() { return false; }
+
+ static size_t MaxActionSize();
+ static Action* CreateAction(uintptr_t key_pointer, const char* type,
+ const char* line, void* action_memory);
+
+ protected:
+ bool is_error_ = false;
+};
+
+#endif // _MEMORY_REPLAY_ACTION_H
diff --git a/memory_replay/Alloc.cpp b/memory_replay/Alloc.cpp
deleted file mode 100644
index af94ee5d..00000000
--- a/memory_replay/Alloc.cpp
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <err.h>
-#include <inttypes.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <unistd.h>
-
-#include <string>
-
-#include "Alloc.h"
-#include "Pointers.h"
-#include "Utils.h"
-
-void AllocGetData(const std::string& line, AllocEntry* entry) {
- int line_pos = 0;
- char name[128];
- // All lines have this format:
- // TID: ALLOCATION_TYPE POINTER
- // where
- // TID is the thread id of the thread doing the operation.
- // ALLOCATION_TYPE is one of malloc, calloc, memalign, realloc, free, thread_done
- // POINTER is the hex value of the actual pointer
- if (sscanf(line.c_str(), "%d: %127s %" SCNx64 " %n", &entry->tid, name, &entry->ptr, &line_pos) !=
- 3) {
- errx(1, "File Error: Failed to process %s", line.c_str());
- }
- const char* line_end = &line[line_pos];
- std::string type(name);
- if (type == "malloc") {
- // Format:
- // TID: malloc POINTER SIZE_OF_ALLOCATION
- if (sscanf(line_end, "%zu", &entry->size) != 1) {
- errx(1, "File Error: Failed to read malloc data %s", line.c_str());
- }
- entry->type = MALLOC;
- } else if (type == "free") {
- // Format:
- // TID: free POINTER
- entry->type = FREE;
- } else if (type == "calloc") {
- // Format:
- // TID: calloc POINTER ITEM_COUNT ITEM_SIZE
- if (sscanf(line_end, "%" SCNd64 " %zu", &entry->u.n_elements, &entry->size) != 2) {
- errx(1, "File Error: Failed to read calloc data %s", line.c_str());
- }
- entry->type = CALLOC;
- } else if (type == "realloc") {
- // Format:
- // TID: calloc POINTER NEW_SIZE OLD_POINTER
- if (sscanf(line_end, "%" SCNx64 " %zu", &entry->u.old_ptr, &entry->size) != 2) {
- errx(1, "File Error: Failed to read realloc data %s", line.c_str());
- }
- entry->type = REALLOC;
- } else if (type == "memalign") {
- // Format:
- // TID: memalign POINTER ALIGNMENT SIZE
- if (sscanf(line_end, "%" SCNd64 " %zu", &entry->u.align, &entry->size) != 2) {
- errx(1, "File Error: Failed to read memalign data %s", line.c_str());
- }
- entry->type = MEMALIGN;
- } else if (type == "thread_done") {
- entry->type = THREAD_DONE;
- } else {
- errx(1, "File Error: Unknown type %s", type.c_str());
- }
-}
-
-bool AllocDoesFree(const AllocEntry& entry) {
- switch (entry.type) {
- case MALLOC:
- case CALLOC:
- case MEMALIGN:
- case THREAD_DONE:
- return false;
-
- case FREE:
- return entry.ptr != 0;
-
- case REALLOC:
- return entry.u.old_ptr != 0;
- }
-}
-
-static uint64_t MallocExecute(const AllocEntry& entry, Pointers* pointers) {
- int pagesize = getpagesize();
- uint64_t time_nsecs = Nanotime();
- void* memory = malloc(entry.size);
- MakeAllocationResident(memory, entry.size, pagesize);
- time_nsecs = Nanotime() - time_nsecs;
-
- pointers->Add(entry.ptr, memory);
-
- return time_nsecs;
-}
-
-static uint64_t CallocExecute(const AllocEntry& entry, Pointers* pointers) {
- int pagesize = getpagesize();
- uint64_t time_nsecs = Nanotime();
- void* memory = calloc(entry.u.n_elements, entry.size);
- MakeAllocationResident(memory, entry.u.n_elements * entry.size, pagesize);
- time_nsecs = Nanotime() - time_nsecs;
-
- pointers->Add(entry.ptr, memory);
-
- return time_nsecs;
-}
-
-static uint64_t ReallocExecute(const AllocEntry& entry, Pointers* pointers) {
- void* old_memory = nullptr;
- if (entry.u.old_ptr != 0) {
- old_memory = pointers->Remove(entry.u.old_ptr);
- }
-
- int pagesize = getpagesize();
- uint64_t time_nsecs = Nanotime();
- void* memory = realloc(old_memory, entry.size);
- MakeAllocationResident(memory, entry.size, pagesize);
- time_nsecs = Nanotime() - time_nsecs;
-
- pointers->Add(entry.ptr, memory);
-
- return time_nsecs;
-}
-
-static uint64_t MemalignExecute(const AllocEntry& entry, Pointers* pointers) {
- int pagesize = getpagesize();
- uint64_t time_nsecs = Nanotime();
- void* memory = memalign(entry.u.align, entry.size);
- MakeAllocationResident(memory, entry.size, pagesize);
- time_nsecs = Nanotime() - time_nsecs;
-
- pointers->Add(entry.ptr, memory);
-
- return time_nsecs;
-}
-
-static uint64_t FreeExecute(const AllocEntry& entry, Pointers* pointers) {
- if (entry.ptr == 0) {
- return 0;
- }
-
- void* memory = pointers->Remove(entry.ptr);
- uint64_t time_nsecs = Nanotime();
- free(memory);
- return Nanotime() - time_nsecs;
-}
-
-uint64_t AllocExecute(const AllocEntry& entry, Pointers* pointers) {
- switch (entry.type) {
- case MALLOC:
- return MallocExecute(entry, pointers);
- case CALLOC:
- return CallocExecute(entry, pointers);
- case REALLOC:
- return ReallocExecute(entry, pointers);
- case MEMALIGN:
- return MemalignExecute(entry, pointers);
- case FREE:
- return FreeExecute(entry, pointers);
- default:
- return 0;
- }
-}
diff --git a/memory_replay/Alloc.h b/memory_replay/Alloc.h
deleted file mode 100644
index d590fcba..00000000
--- a/memory_replay/Alloc.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2019 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>
-
-// Forward Declarations.
-class Pointers;
-
-enum AllocEnum : uint8_t {
- MALLOC = 0,
- CALLOC,
- MEMALIGN,
- REALLOC,
- FREE,
- THREAD_DONE,
-};
-
-struct AllocEntry {
- pid_t tid;
- AllocEnum type;
- uint64_t ptr = 0;
- size_t size = 0;
- union {
- uint64_t old_ptr = 0;
- uint64_t n_elements;
- uint64_t align;
- } u;
-};
-
-void AllocGetData(const std::string& line, AllocEntry* entry);
-
-bool AllocDoesFree(const AllocEntry& entry);
-
-uint64_t AllocExecute(const AllocEntry& entry, Pointers* pointers);
diff --git a/memory_replay/Android.bp b/memory_replay/Android.bp
index ab5a380f..824ac125 100644
--- a/memory_replay/Android.bp
+++ b/memory_replay/Android.bp
@@ -1,61 +1,29 @@
-//
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-cc_defaults {
- name: "memory_flag_defaults",
- host_supported: false,
-
- cflags: [
- "-Wall",
- "-Wextra",
- "-Werror",
- ],
-
- compile_multilib: "both",
-}
-
cc_defaults {
name: "memory_replay_defaults",
- defaults: ["memory_flag_defaults"],
+ host_supported: true,
srcs: [
- "Alloc.cpp",
- "File.cpp",
+ "Action.cpp",
+ "LineBuffer.cpp",
"NativeInfo.cpp",
"Pointers.cpp",
"Thread.cpp",
"Threads.cpp",
],
-
- shared_libs: [
- "libbase",
- "libziparchive",
- ],
-
- static_libs: [
- "libasync_safe",
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
],
-}
-
-cc_binary {
- name: "memory_replay",
- defaults: ["memory_replay_defaults"],
+ shared_libs: ["libbase"],
- srcs: ["main.cpp"],
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+ compile_multilib: "both",
multilib: {
lib32: {
suffix: "32",
@@ -64,16 +32,23 @@ cc_binary {
suffix: "64",
},
},
+
+}
+
+cc_binary {
+ name: "memory_replay",
+ defaults: ["memory_replay_defaults"],
+
+ srcs: ["main.cpp"],
}
cc_test {
name: "memory_replay_tests",
defaults: ["memory_replay_defaults"],
- isolated: true,
srcs: [
- "tests/AllocTest.cpp",
- "tests/FileTest.cpp",
+ "tests/ActionTest.cpp",
+ "tests/LineBufferTest.cpp",
"tests/NativeInfoTest.cpp",
"tests/PointersTest.cpp",
"tests/ThreadTest.cpp",
@@ -87,29 +62,4 @@ cc_test {
test_suites: ["device-tests"],
},
},
-
- data: [
- "tests/test.txt",
- "tests/test.zip",
- ],
-}
-
-cc_benchmark {
- name: "trace_benchmark",
- defaults: ["memory_flag_defaults"],
-
- srcs: [
- "Alloc.cpp",
- "TraceBenchmark.cpp",
- "File.cpp",
- ],
-
- shared_libs: [
- "libbase",
- "libziparchive",
- ],
-
- data: [
- "traces/*.zip",
- ],
}
diff --git a/memory_replay/File.cpp b/memory_replay/File.cpp
deleted file mode 100644
index 8bcd9041..00000000
--- a/memory_replay/File.cpp
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <err.h>
-#include <errno.h>
-#include <stdint.h>
-#include <sys/mman.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include <string>
-
-#include <android-base/file.h>
-#include <android-base/strings.h>
-#include <ziparchive/zip_archive.h>
-
-#include "Alloc.h"
-#include "File.h"
-
-std::string ZipGetContents(const char* filename) {
- ZipArchiveHandle archive;
- if (OpenArchive(filename, &archive) != 0) {
- return "";
- }
-
- // It is assumed that the archive contains only a single entry.
- void* cookie;
- std::string contents;
- if (StartIteration(archive, &cookie) == 0) {
- ZipEntry entry;
- std::string name;
- if (Next(cookie, &entry, &name) == 0) {
- contents.resize(entry.uncompressed_length);
- if (ExtractToMemory(archive, &entry, reinterpret_cast<uint8_t*>(contents.data()),
- entry.uncompressed_length) != 0) {
- contents = "";
- }
- }
- }
-
- CloseArchive(archive);
- return contents;
-}
-
-static void WaitPid(pid_t pid) {
- int wstatus;
- pid_t wait_pid = TEMP_FAILURE_RETRY(waitpid(pid, &wstatus, 0));
- if (wait_pid != pid) {
- if (wait_pid == -1) {
- err(1, "waitpid() failed");
- } else {
- errx(1, "Unexpected pid from waitpid(): expected %d, returned %d", pid, wait_pid);
- }
- }
- if (!WIFEXITED(wstatus)) {
- errx(1, "Forked process did not terminate with exit() call");
- }
- if (WEXITSTATUS(wstatus) != 0) {
- errx(1, "Bad exit value from forked process: returned %d", WEXITSTATUS(wstatus));
- }
-}
-
-// This function should not do any memory allocations in the main function.
-// Any true allocation should happen in fork'd code.
-void GetUnwindInfo(const char* filename, AllocEntry** entries, size_t* num_entries) {
- void* mem =
- mmap(nullptr, sizeof(size_t), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
- if (mem == MAP_FAILED) {
- err(1, "Unable to allocate a shared map of size %zu", sizeof(size_t));
- }
- *reinterpret_cast<size_t*>(mem) = 0;
-
- pid_t pid;
- if ((pid = fork()) == 0) {
- // First get the number of lines in the trace file. It is assumed
- // that there are no blank lines, and every line contains a valid
- // allocation operation.
- std::string contents;
- if (android::base::EndsWith(filename, ".zip")) {
- contents = ZipGetContents(filename);
- } else if (!android::base::ReadFileToString(filename, &contents)) {
- errx(1, "Unable to get contents of %s", filename);
- }
- if (contents.empty()) {
- errx(1, "Unable to get contents of %s", filename);
- }
-
- size_t lines = 0;
- size_t index = 0;
- while (true) {
- index = contents.find('\n', index);
- if (index == std::string::npos) {
- break;
- }
- index++;
- lines++;
- }
- if (contents[contents.size() - 1] != '\n') {
- // Add one since the last line doesn't end in '\n'.
- lines++;
- }
- *reinterpret_cast<size_t*>(mem) = lines;
- _exit(0);
- } else if (pid == -1) {
- err(1, "fork() call failed");
- }
- WaitPid(pid);
- *num_entries = *reinterpret_cast<size_t*>(mem);
- munmap(mem, sizeof(size_t));
-
- mem = mmap(nullptr, *num_entries * sizeof(AllocEntry), PROT_READ | PROT_WRITE,
- MAP_ANONYMOUS | MAP_SHARED, -1, 0);
- if (mem == MAP_FAILED) {
- err(1, "Unable to allocate a shared map of size %zu", *num_entries * sizeof(AllocEntry));
- }
- *entries = reinterpret_cast<AllocEntry*>(mem);
-
- if ((pid = fork()) == 0) {
- std::string contents;
- if (android::base::EndsWith(filename, ".zip")) {
- contents = ZipGetContents(filename);
- } else if (!android::base::ReadFileToString(filename, &contents)) {
- errx(1, "Unable to get contents of %s", filename);
- }
- if (contents.empty()) {
- errx(1, "Contents of zip file %s is empty.", filename);
- }
-
- size_t entry_idx = 0;
- size_t start_str = 0;
- size_t end_str = 0;
- while (true) {
- end_str = contents.find('\n', start_str);
- if (end_str == std::string::npos) {
- break;
- }
- if (entry_idx == *num_entries) {
- errx(1, "Too many entries, stopped at entry %zu", entry_idx);
- }
- contents[end_str] = '\0';
- AllocGetData(&contents[start_str], &(*entries)[entry_idx++]);
- start_str = end_str + 1;
- }
- if (entry_idx != *num_entries) {
- errx(1, "Mismatched number of entries found: expected %zu, found %zu", *num_entries,
- entry_idx);
- }
- _exit(0);
- } else if (pid == -1) {
- err(1, "fork() call failed");
- }
- WaitPid(pid);
-}
-
-void FreeEntries(AllocEntry* entries, size_t num_entries) {
- munmap(entries, num_entries * sizeof(AllocEntry));
-}
diff --git a/memory_replay/LineBuffer.cpp b/memory_replay/LineBuffer.cpp
new file mode 100644
index 00000000..5e65ad62
--- /dev/null
+++ b/memory_replay/LineBuffer.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "LineBuffer.h"
+
+LineBuffer::LineBuffer(int fd, char* buffer, size_t buffer_len) : fd_(fd), buffer_(buffer), buffer_len_(buffer_len) {
+}
+
+bool LineBuffer::GetLine(char** line, size_t* line_len) {
+ while (true) {
+ if (bytes_ > 0) {
+ char* newline = reinterpret_cast<char*>(memchr(buffer_ + start_, '\n', bytes_));
+ if (newline != nullptr) {
+ *newline = '\0';
+ *line = buffer_ + start_;
+ start_ = newline - buffer_ + 1;
+ bytes_ -= newline - *line + 1;
+ *line_len = newline - *line;
+ return true;
+ }
+ }
+ if (start_ > 0) {
+ // Didn't find anything, copy the current to the front of the buffer.
+ memmove(buffer_, buffer_ + start_, bytes_);
+ start_ = 0;
+ }
+ ssize_t bytes = TEMP_FAILURE_RETRY(read(fd_, buffer_ + bytes_, buffer_len_ - bytes_ - 1));
+ if (bytes <= 0) {
+ if (bytes_ > 0) {
+ // The read data might not contain a nul terminator, so add one.
+ buffer_[bytes_] = '\0';
+ *line = buffer_ + start_;
+ *line_len = bytes_;
+ bytes_ = 0;
+ start_ = 0;
+ return true;
+ }
+ return false;
+ }
+ bytes_ += bytes;
+ }
+}
diff --git a/memory_replay/File.h b/memory_replay/LineBuffer.h
index c1447bba..934d3021 100644
--- a/memory_replay/File.h
+++ b/memory_replay/LineBuffer.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,18 +14,23 @@
* limitations under the License.
*/
-#pragma once
+#ifndef _MEMORY_REPLAY_LINE_BUFFER_H
+#define _MEMORY_REPLAY_LINE_BUFFER_H
#include <stdint.h>
-#include <string>
+class LineBuffer {
+ public:
+ LineBuffer(int fd, char* buffer, size_t buffer_len);
-// Forward Declarations.
-struct AllocEntry;
+ bool GetLine(char** line, size_t* line_len);
-std::string ZipGetContents(const char* filename);
+ private:
+ int fd_;
+ char* buffer_ = nullptr;
+ size_t buffer_len_ = 0;
+ size_t start_ = 0;
+ size_t bytes_ = 0;
+};
-// If filename ends with .zip, treat as a zip file to decompress.
-void GetUnwindInfo(const char* filename, AllocEntry** entries, size_t* num_entries);
-
-void FreeEntries(AllocEntry* entries, size_t num_entries);
+#endif // _MEMORY_REPLAY_LINE_BUFFER_H
diff --git a/memory_replay/NativeInfo.cpp b/memory_replay/NativeInfo.cpp
index ec131ddc..18c832bb 100644
--- a/memory_replay/NativeInfo.cpp
+++ b/memory_replay/NativeInfo.cpp
@@ -18,89 +18,51 @@
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
-#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
-#include <sys/stat.h>
#include <sys/types.h>
+#include <sys/stat.h>
#include <unistd.h>
#include <android-base/unique_fd.h>
-#include <async_safe/log.h>
+#include "LineBuffer.h"
#include "NativeInfo.h"
-void NativePrintf(const char* fmt, ...) {
- va_list args;
- va_start(args, fmt);
- char buffer[512];
- int buffer_len = async_safe_format_buffer_va_list(buffer, sizeof(buffer), fmt, args);
- va_end(args);
-
- (void)write(STDOUT_FILENO, buffer, buffer_len);
-}
-
-void NativeFormatFloat(char* buffer, size_t buffer_len, uint64_t value, uint64_t divisor) {
- uint64_t hundreds = ((((value % divisor) * 1000) / divisor) + 5) / 10;
- async_safe_format_buffer(buffer, buffer_len, "%" PRIu64 ".%02" PRIu64, value / divisor, hundreds);
-}
-
// This function is not re-entrant since it uses a static buffer for
// the line data.
-void NativeGetInfo(int smaps_fd, size_t* rss_bytes, size_t* va_bytes) {
- size_t total_rss_bytes = 0;
+void GetNativeInfo(int smaps_fd, size_t* pss_bytes, size_t* va_bytes) {
+ static char map_buffer[65535];
+ LineBuffer line_buf(smaps_fd, map_buffer, sizeof(map_buffer));
+ char* line;
+ size_t total_pss_bytes = 0;
size_t total_va_bytes = 0;
+ size_t line_len;
bool native_map = false;
-
- char buf[1024];
- size_t buf_start = 0;
- size_t buf_bytes = 0;
- while (true) {
- ssize_t bytes =
- TEMP_FAILURE_RETRY(read(smaps_fd, buf + buf_bytes, sizeof(buf) - buf_bytes - 1));
- if (bytes <= 0) {
- break;
- }
- buf_bytes += bytes;
- while (buf_bytes > 0) {
- char* newline = reinterpret_cast<char*>(memchr(&buf[buf_start], '\n', buf_bytes));
- if (newline == nullptr) {
- break;
- }
- *newline = '\0';
- uintptr_t start, end;
- int name_pos;
- size_t native_rss_kB;
- if (sscanf(&buf[buf_start], "%" SCNxPTR "-%" SCNxPTR " %*4s %*x %*x:%*x %*d %n", &start, &end,
- &name_pos) == 2) {
- char* map_name = &buf[buf_start + name_pos];
- if (strcmp(map_name, "[anon:libc_malloc]") == 0 || strcmp(map_name, "[heap]") == 0 ||
- strncmp(map_name, "[anon:scudo:", 12) == 0) {
- total_va_bytes += end - start;
- native_map = true;
- } else {
- native_map = false;
- }
- } else if (native_map && sscanf(&buf[buf_start], "Rss: %zu", &native_rss_kB) == 1) {
- total_rss_bytes += native_rss_kB * 1024;
+ while (line_buf.GetLine(&line, &line_len)) {
+ uintptr_t start, end;
+ int name_pos;
+ size_t native_pss_kB;
+ if (sscanf(line, "%" SCNxPTR "-%" SCNxPTR " %*4s %*x %*x:%*x %*d %n",
+ &start, &end, &name_pos) == 2) {
+ if (strcmp(line + name_pos, "[anon:libc_malloc]") == 0 ||
+ strcmp(line + name_pos, "[heap]") == 0) {
+ total_va_bytes += end - start;
+ native_map = true;
+ } else {
+ native_map = false;
}
- buf_bytes -= newline - &buf[buf_start] + 1;
- buf_start = newline - buf + 1;
- }
- if (buf_start > 0) {
- if (buf_bytes > 0) {
- memmove(buf, &buf[buf_start], buf_bytes);
- }
- buf_start = 0;
+ } else if (native_map && sscanf(line, "Pss: %zu", &native_pss_kB) == 1) {
+ total_pss_bytes += native_pss_kB * 1024;
}
}
- *rss_bytes = total_rss_bytes;
+ *pss_bytes = total_pss_bytes;
*va_bytes = total_va_bytes;
}
-void NativePrintInfo(const char* preamble) {
- size_t rss_bytes;
+void PrintNativeInfo(const char* preamble) {
+ size_t pss_bytes;
size_t va_bytes;
android::base::unique_fd smaps_fd(open("/proc/self/smaps", O_RDONLY));
@@ -108,12 +70,8 @@ void NativePrintInfo(const char* preamble) {
err(1, "Cannot open /proc/self/smaps: %s\n", strerror(errno));
}
- NativeGetInfo(smaps_fd, &rss_bytes, &va_bytes);
-
- // Avoid any allocations, so use special non-allocating printfs.
- char buffer[256];
- NativeFormatFloat(buffer, sizeof(buffer), rss_bytes, 1024 * 1024);
- NativePrintf("%sNative RSS: %zu bytes %sMB\n", preamble, rss_bytes, buffer);
- NativeFormatFloat(buffer, sizeof(buffer), va_bytes, 1024 * 1024);
- NativePrintf("%sNative VA Space: %zu bytes %sMB\n", preamble, va_bytes, buffer);
+ GetNativeInfo(smaps_fd, &pss_bytes, &va_bytes);
+ printf("%sNative PSS: %zu bytes %0.2fMB\n", preamble, pss_bytes, pss_bytes/(1024*1024.0));
+ printf("%sNative VA Space: %zu bytes %0.2fMB\n", preamble, va_bytes, va_bytes/(1024*1024.0));
+ fflush(stdout);
}
diff --git a/memory_replay/NativeInfo.h b/memory_replay/NativeInfo.h
index c91eec29..59536954 100644
--- a/memory_replay/NativeInfo.h
+++ b/memory_replay/NativeInfo.h
@@ -14,14 +14,13 @@
* limitations under the License.
*/
-#pragma once
+#ifndef _MEMORY_REPLAY_NATIVE_INFO_H
+#define _MEMORY_REPLAY_NATIVE_INFO_H
-void NativeGetInfo(int smaps_fd, size_t* rss_bytes, size_t* va_bytes);
+// This function is not re-entrant.
+void GetNativeInfo(int smaps_fd, size_t* pss_bytes, size_t* va_bytes);
-void NativePrintInfo(const char* preamble);
+// This function is not re-entrant.
+void PrintNativeInfo(const char* preamble);
-// Does not support any floating point specifiers.
-void NativePrintf(const char* fmt, ...) __printflike(1, 2);
-
-// Fill buffer as if %0.2f was chosen for value / divisor.
-void NativeFormatFloat(char* buffer, size_t buffer_len, uint64_t value, uint64_t divisor);
+#endif // _MEMORY_REPLAY_NATIVE_INFO_H
diff --git a/memory_replay/Pointers.cpp b/memory_replay/Pointers.cpp
index 6335dc2c..b9604f06 100644
--- a/memory_replay/Pointers.cpp
+++ b/memory_replay/Pointers.cpp
@@ -32,12 +32,12 @@ Pointers::Pointers(size_t max_allocs) {
// Align to a page.
pointers_size_ = (max_allocs * 4 * sizeof(pointer_data) + pagesize - 1) & ~(pagesize - 1);
max_pointers_ = pointers_size_ / sizeof(pointer_data);
- void* memory =
- mmap(nullptr, pointers_size_, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
+ void* memory = mmap(nullptr, pointers_size_, PROT_READ | PROT_WRITE,
+ MAP_ANON | MAP_PRIVATE, -1, 0);
if (memory == MAP_FAILED) {
err(1, "Unable to allocate data for pointer hash: %zu total_allocs\n", max_allocs);
}
- // Set all of the pointers to be empty.
+ // Make sure that all of the PSS for this is counted right away.
memset(memory, 0, pointers_size_);
pointers_ = reinterpret_cast<pointer_data*>(memory);
}
@@ -74,7 +74,7 @@ void* Pointers::Remove(uintptr_t key_pointer) {
return pointer;
}
-Pointers::pointer_data* Pointers::Find(uintptr_t key_pointer) {
+pointer_data* Pointers::Find(uintptr_t key_pointer) {
size_t index = GetHash(key_pointer);
for (size_t entries = max_pointers_; entries != 0; entries--) {
if (atomic_load(&pointers_[index].key_pointer) == key_pointer) {
@@ -87,7 +87,7 @@ Pointers::pointer_data* Pointers::Find(uintptr_t key_pointer) {
return nullptr;
}
-Pointers::pointer_data* Pointers::FindEmpty(uintptr_t key_pointer) {
+pointer_data* Pointers::FindEmpty(uintptr_t key_pointer) {
size_t index = GetHash(key_pointer);
for (size_t entries = 0; entries < max_pointers_; entries++) {
uintptr_t empty = 0;
diff --git a/memory_replay/Pointers.h b/memory_replay/Pointers.h
index 040027bf..c7cd825a 100644
--- a/memory_replay/Pointers.h
+++ b/memory_replay/Pointers.h
@@ -14,18 +14,19 @@
* limitations under the License.
*/
-#pragma once
+#ifndef _MEMORY_REPLAY_POINTERS_H
+#define _MEMORY_REPLAY_POINTERS_H
#include <stdatomic.h>
#include <stdint.h>
+struct pointer_data {
+ std::atomic_uintptr_t key_pointer;
+ void* pointer;
+};
+
class Pointers {
public:
- struct pointer_data {
- std::atomic_uintptr_t key_pointer;
- void* pointer;
- };
-
explicit Pointers(size_t max_allocs);
virtual ~Pointers();
@@ -46,3 +47,5 @@ class Pointers {
size_t pointers_size_ = 0;
size_t max_pointers_ = 0;
};
+
+#endif // _MEMORY_REPLAY_POINTERS_H
diff --git a/memory_replay/Thread.cpp b/memory_replay/Thread.cpp
index 4f0a6d54..497b288e 100644
--- a/memory_replay/Thread.cpp
+++ b/memory_replay/Thread.cpp
@@ -16,6 +16,7 @@
#include <pthread.h>
+#include "Action.h"
#include "Thread.h"
Thread::Thread() {
@@ -55,3 +56,7 @@ void Thread::ClearPending() {
pthread_mutex_unlock(&mutex_);
pthread_cond_signal(&cond_);
}
+
+Action* Thread::CreateAction(uintptr_t key_pointer, const char* type, const char* line) {
+ return Action::CreateAction(key_pointer, type, line, action_memory_);
+}
diff --git a/memory_replay/Thread.h b/memory_replay/Thread.h
index ffab01b4..7724c12e 100644
--- a/memory_replay/Thread.h
+++ b/memory_replay/Thread.h
@@ -14,16 +14,18 @@
* limitations under the License.
*/
-#pragma once
+#ifndef _MEMORY_REPLAY_THREAD_H
+#define _MEMORY_REPLAY_THREAD_H
#include <pthread.h>
#include <stdint.h>
#include <sys/types.h>
-// Forward Declarations.
-struct AllocEntry;
+class Action;
class Pointers;
+constexpr size_t ACTION_MEMORY_SIZE = 128;
+
class Thread {
public:
Thread();
@@ -34,13 +36,13 @@ class Thread {
void SetPending();
void ClearPending();
+ Action* CreateAction(uintptr_t key_pointer, const char* type, const char* line);
void AddTimeNsecs(uint64_t nsecs) { total_time_nsecs_ += nsecs; }
void set_pointers(Pointers* pointers) { pointers_ = pointers; }
Pointers* pointers() { return pointers_; }
- void SetAllocEntry(const AllocEntry* entry) { entry_ = entry; }
- const AllocEntry& GetAllocEntry() { return *entry_; }
+ Action* GetAction() { return reinterpret_cast<Action*>(action_memory_); }
private:
pthread_mutex_t mutex_ = PTHREAD_MUTEX_INITIALIZER;
@@ -53,7 +55,12 @@ class Thread {
Pointers* pointers_ = nullptr;
- const AllocEntry* entry_;
+ // Per thread memory for an Action. Only one action can be processed.
+ // at a time.
+ static constexpr size_t ACTION_SIZE = 128;
+ uint8_t action_memory_[ACTION_SIZE];
friend class Threads;
};
+
+#endif // _MEMORY_REPLAY_THREAD_H
diff --git a/memory_replay/Threads.cpp b/memory_replay/Threads.cpp
index 61f950ee..81a679e9 100644
--- a/memory_replay/Threads.cpp
+++ b/memory_replay/Threads.cpp
@@ -26,8 +26,7 @@
#include <new>
-#include "Alloc.h"
-#include "Pointers.h"
+#include "Action.h"
#include "Thread.h"
#include "Threads.h"
@@ -35,11 +34,11 @@ void* ThreadRunner(void* data) {
Thread* thread = reinterpret_cast<Thread*>(data);
while (true) {
thread->WaitForPending();
- const AllocEntry& entry = thread->GetAllocEntry();
- thread->AddTimeNsecs(AllocExecute(entry, thread->pointers()));
- bool thread_done = entry.type == THREAD_DONE;
+ Action* action = thread->GetAction();
+ thread->AddTimeNsecs(action->Execute(thread->pointers()));
+ bool end_thread = action->EndThread();
thread->ClearPending();
- if (thread_done) {
+ if (end_thread) {
break;
}
}
@@ -58,6 +57,11 @@ Threads::Threads(Pointers* pointers, size_t max_threads)
data_size_, max_threads_);
}
+ if (Thread::ACTION_SIZE < Action::MaxActionSize()) {
+ err(1, "Thread action size is too small: ACTION_SIZE %zu, max size %zu\n",
+ Thread::ACTION_SIZE, Action::MaxActionSize());
+ }
+
threads_ = new (memory) Thread[max_threads_];
}
@@ -81,7 +85,7 @@ Thread* Threads::CreateThread(pid_t tid) {
thread->tid_ = tid;
thread->pointers_ = pointers_;
thread->total_time_nsecs_ = 0;
- if ((errno = pthread_create(&thread->thread_id_, nullptr, ThreadRunner, thread)) != 0) {
+ if (pthread_create(&thread->thread_id_, nullptr, ThreadRunner, thread) == -1) {
err(1, "Failed to create thread %d: %s\n", tid, strerror(errno));
}
@@ -145,10 +149,9 @@ void Threads::Finish(Thread* thread) {
}
void Threads::FinishAll() {
- AllocEntry thread_done = {.type = THREAD_DONE};
for (size_t i = 0; i < max_threads_; i++) {
if (threads_[i].tid_ != 0) {
- threads_[i].SetAllocEntry(&thread_done);
+ threads_[i].CreateAction(0, "thread_done", nullptr);
threads_[i].SetPending();
Finish(threads_ + i);
}
diff --git a/memory_replay/Threads.h b/memory_replay/Threads.h
index 3bb4a320..4778bff3 100644
--- a/memory_replay/Threads.h
+++ b/memory_replay/Threads.h
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-#pragma once
+#ifndef _MEMORY_REPLAY_THREADS_H
+#define _MEMORY_REPLAY_THREADS_H
#include <stdint.h>
#include <sys/types.h>
-// Forward Declarations.
class Pointers;
class Thread;
@@ -53,3 +53,5 @@ class Threads {
friend Thread;
};
+
+#endif // _MEMORY_REPLAY_THREADS_H
diff --git a/memory_replay/TraceBenchmark.cpp b/memory_replay/TraceBenchmark.cpp
deleted file mode 100644
index da886721..00000000
--- a/memory_replay/TraceBenchmark.cpp
+++ /dev/null
@@ -1,368 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <err.h>
-#include <inttypes.h>
-#include <malloc.h>
-#include <sched.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <algorithm>
-#include <stack>
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-#include <android-base/file.h>
-#include <android-base/strings.h>
-#include <benchmark/benchmark.h>
-
-#include "Alloc.h"
-#include "File.h"
-#include "Utils.h"
-
-struct TraceAllocEntry {
- TraceAllocEntry(AllocEnum type, size_t idx, size_t size, size_t last_arg)
- : type(type), idx(idx), size(size) {
- u.old_idx = last_arg;
- }
- AllocEnum type;
- size_t idx;
- size_t size;
- union {
- size_t old_idx = 0;
- size_t align;
- size_t n_elements;
- } u;
-};
-
-static size_t GetIndex(std::stack<size_t>& free_indices, size_t* max_index) {
- if (free_indices.empty()) {
- return (*max_index)++;
- }
- size_t index = free_indices.top();
- free_indices.pop();
- return index;
-}
-
-static std::vector<TraceAllocEntry>* GetTraceData(const char* filename, size_t* max_ptrs) {
- // Only keep last trace encountered cached.
- static std::string cached_filename;
- static std::vector<TraceAllocEntry> cached_entries;
- static size_t cached_max_ptrs;
-
- if (cached_filename == filename) {
- *max_ptrs = cached_max_ptrs;
- return &cached_entries;
- }
-
- cached_entries.clear();
- cached_max_ptrs = 0;
- cached_filename = filename;
-
- std::string content(ZipGetContents(filename));
- if (content.empty()) {
- errx(1, "Internal Error: Empty zip file %s", filename);
- }
- std::vector<std::string> lines(android::base::Split(content, "\n"));
-
- *max_ptrs = 0;
- std::stack<size_t> free_indices;
- std::unordered_map<uint64_t, size_t> ptr_to_index;
- std::vector<TraceAllocEntry>* entries = &cached_entries;
- for (const std::string& line : lines) {
- if (line.empty()) {
- continue;
- }
- AllocEntry entry;
- AllocGetData(line, &entry);
-
- switch (entry.type) {
- case MALLOC: {
- size_t idx = GetIndex(free_indices, max_ptrs);
- ptr_to_index[entry.ptr] = idx;
- entries->emplace_back(MALLOC, idx, entry.size, 0);
- break;
- }
- case CALLOC: {
- size_t idx = GetIndex(free_indices, max_ptrs);
- ptr_to_index[entry.ptr] = idx;
- entries->emplace_back(CALLOC, idx, entry.u.n_elements, entry.size);
- break;
- }
- case MEMALIGN: {
- size_t idx = GetIndex(free_indices, max_ptrs);
- ptr_to_index[entry.ptr] = idx;
- entries->emplace_back(MEMALIGN, idx, entry.size, entry.u.align);
- break;
- }
- case REALLOC: {
- size_t old_pointer_idx = 0;
- if (entry.u.old_ptr != 0) {
- auto idx_entry = ptr_to_index.find(entry.u.old_ptr);
- if (idx_entry == ptr_to_index.end()) {
- errx(1, "File Error: Failed to find realloc pointer %" PRIu64, entry.u.old_ptr);
- }
- old_pointer_idx = idx_entry->second;
- free_indices.push(old_pointer_idx);
- }
- size_t idx = GetIndex(free_indices, max_ptrs);
- ptr_to_index[entry.ptr] = idx;
- entries->emplace_back(REALLOC, idx, entry.size, old_pointer_idx + 1);
- break;
- }
- case FREE:
- if (entry.ptr != 0) {
- auto idx_entry = ptr_to_index.find(entry.ptr);
- if (idx_entry == ptr_to_index.end()) {
- errx(1, "File Error: Unable to find free pointer %" PRIu64, entry.ptr);
- }
- free_indices.push(idx_entry->second);
- entries->emplace_back(FREE, idx_entry->second + 1, 0, 0);
- } else {
- entries->emplace_back(FREE, 0, 0, 0);
- }
- break;
- case THREAD_DONE:
- // Ignore these.
- break;
- }
- }
-
- cached_max_ptrs = *max_ptrs;
- return entries;
-}
-
-static void RunTrace(benchmark::State& state, std::vector<TraceAllocEntry>& entries,
- size_t max_ptrs) {
- std::vector<void*> ptrs(max_ptrs, nullptr);
-
- int pagesize = getpagesize();
- void* ptr;
- uint64_t total_ns = 0;
- uint64_t start_ns;
- for (auto& entry : entries) {
- switch (entry.type) {
- case MALLOC:
- start_ns = Nanotime();
- ptr = malloc(entry.size);
- if (ptr == nullptr) {
- errx(1, "malloc returned nullptr");
- }
- MakeAllocationResident(ptr, entry.size, pagesize);
- total_ns += Nanotime() - start_ns;
-
- if (ptrs[entry.idx] != nullptr) {
- errx(1, "Internal Error: malloc pointer being replaced is not nullptr");
- }
- ptrs[entry.idx] = ptr;
- break;
-
- case CALLOC:
- start_ns = Nanotime();
- ptr = calloc(entry.u.n_elements, entry.size);
- if (ptr == nullptr) {
- errx(1, "calloc returned nullptr");
- }
- MakeAllocationResident(ptr, entry.size, pagesize);
- total_ns += Nanotime() - start_ns;
-
- if (ptrs[entry.idx] != nullptr) {
- errx(1, "Internal Error: calloc pointer being replaced is not nullptr");
- }
- ptrs[entry.idx] = ptr;
- break;
-
- case MEMALIGN:
- start_ns = Nanotime();
- ptr = memalign(entry.u.align, entry.size);
- if (ptr == nullptr) {
- errx(1, "memalign returned nullptr");
- }
- MakeAllocationResident(ptr, entry.size, pagesize);
- total_ns += Nanotime() - start_ns;
-
- if (ptrs[entry.idx] != nullptr) {
- errx(1, "Internal Error: memalign pointer being replaced is not nullptr");
- }
- ptrs[entry.idx] = ptr;
- break;
-
- case REALLOC:
- start_ns = Nanotime();
- if (entry.u.old_idx == 0) {
- ptr = realloc(nullptr, entry.size);
- } else {
- ptr = realloc(ptrs[entry.u.old_idx - 1], entry.size);
- ptrs[entry.u.old_idx - 1] = nullptr;
- }
- if (entry.size > 0) {
- if (ptr == nullptr) {
- errx(1, "realloc returned nullptr");
- }
- MakeAllocationResident(ptr, entry.size, pagesize);
- }
- total_ns += Nanotime() - start_ns;
-
- if (ptrs[entry.idx] != nullptr) {
- errx(1, "Internal Error: realloc pointer being replaced is not nullptr");
- }
- ptrs[entry.idx] = ptr;
- break;
-
- case FREE:
- if (entry.idx != 0) {
- ptr = ptrs[entry.idx - 1];
- ptrs[entry.idx - 1] = nullptr;
- } else {
- ptr = nullptr;
- }
- start_ns = Nanotime();
- free(ptr);
- total_ns += Nanotime() - start_ns;
- break;
-
- case THREAD_DONE:
- break;
- }
- }
- state.SetIterationTime(total_ns / double(1000000000.0));
-
- std::for_each(ptrs.begin(), ptrs.end(), [](void* ptr) { free(ptr); });
-}
-
-// Run a trace as if all of the allocations occurred in a single thread.
-// This is not completely realistic, but it is a possible worst case that
-// could happen in an app.
-static void BenchmarkTrace(benchmark::State& state, const char* filename) {
- std::string full_filename(android::base::GetExecutableDirectory() + "/traces/" + filename);
- size_t max_ptrs;
- std::vector<TraceAllocEntry>* entries = GetTraceData(full_filename.c_str(), &max_ptrs);
- if (entries == nullptr) {
- errx(1, "ERROR: Failed to get trace data for %s.", full_filename.c_str());
- }
-
-#if defined(__BIONIC__)
- // Need to set the decay time the same as how an app would operate.
- mallopt(M_DECAY_TIME, 1);
-#endif
-
- for (auto _ : state) {
- RunTrace(state, *entries, max_ptrs);
- }
-}
-
-#define BENCH_OPTIONS \
- UseManualTime() \
- ->Unit(benchmark::kMicrosecond) \
- ->MinTime(15.0) \
- ->Repetitions(4) \
- ->ReportAggregatesOnly(true)
-
-static void BM_angry_birds2(benchmark::State& state) {
- BenchmarkTrace(state, "angry_birds2.zip");
-}
-BENCHMARK(BM_angry_birds2)->BENCH_OPTIONS;
-
-static void BM_camera(benchmark::State& state) {
- BenchmarkTrace(state, "camera.zip");
-}
-BENCHMARK(BM_camera)->BENCH_OPTIONS;
-
-static void BM_candy_crush_saga(benchmark::State& state) {
- BenchmarkTrace(state, "candy_crush_saga.zip");
-}
-BENCHMARK(BM_candy_crush_saga)->BENCH_OPTIONS;
-
-void BM_gmail(benchmark::State& state) {
- BenchmarkTrace(state, "gmail.zip");
-}
-BENCHMARK(BM_gmail)->BENCH_OPTIONS;
-
-void BM_maps(benchmark::State& state) {
- BenchmarkTrace(state, "maps.zip");
-}
-BENCHMARK(BM_maps)->BENCH_OPTIONS;
-
-void BM_photos(benchmark::State& state) {
- BenchmarkTrace(state, "photos.zip");
-}
-BENCHMARK(BM_photos)->BENCH_OPTIONS;
-
-void BM_pubg(benchmark::State& state) {
- BenchmarkTrace(state, "pubg.zip");
-}
-BENCHMARK(BM_pubg)->BENCH_OPTIONS;
-
-void BM_surfaceflinger(benchmark::State& state) {
- BenchmarkTrace(state, "surfaceflinger.zip");
-}
-BENCHMARK(BM_surfaceflinger)->BENCH_OPTIONS;
-
-void BM_system_server(benchmark::State& state) {
- BenchmarkTrace(state, "system_server.zip");
-}
-BENCHMARK(BM_system_server)->BENCH_OPTIONS;
-
-void BM_systemui(benchmark::State& state) {
- BenchmarkTrace(state, "systemui.zip");
-}
-BENCHMARK(BM_systemui)->BENCH_OPTIONS;
-
-void BM_youtube(benchmark::State& state) {
- BenchmarkTrace(state, "youtube.zip");
-}
-BENCHMARK(BM_youtube)->BENCH_OPTIONS;
-
-int main(int argc, char** argv) {
- std::vector<char*> args;
- args.push_back(argv[0]);
-
- // Look for the --cpu=XX option.
- for (int i = 1; i < argc; i++) {
- if (strncmp(argv[i], "--cpu=", 6) == 0) {
- char* endptr;
- int cpu = strtol(&argv[i][6], &endptr, 10);
- if (argv[i][0] == '\0' || endptr == nullptr || *endptr != '\0') {
- printf("Invalid format of --cpu option, '%s' must be an integer value.\n", argv[i] + 6);
- return 1;
- }
- cpu_set_t cpuset;
- CPU_ZERO(&cpuset);
- CPU_SET(cpu, &cpuset);
- if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) {
- if (errno == EINVAL) {
- printf("Invalid cpu %d\n", cpu);
- return 1;
- }
- perror("sched_setaffinity failed");
- return 1;
- }
- printf("Locking to cpu %d\n", cpu);
- } else {
- args.push_back(argv[i]);
- }
- }
-
- argc = args.size();
- ::benchmark::Initialize(&argc, args.data());
- if (::benchmark::ReportUnrecognizedArguments(argc, args.data())) return 1;
- ::benchmark::RunSpecifiedBenchmarks();
-}
diff --git a/memory_replay/traces/README b/memory_replay/dumps/README
index 88b7b59b..d306b9a4 100644
--- a/memory_replay/traces/README
+++ b/memory_replay/dumps/README
@@ -11,7 +11,7 @@ Format of dumps:
<tid>
The pid_t value that is the gettid() value recorded during the run.
-<action_name>
+<action_name>
One of:
malloc - Allocate memory using the malloc function.
calloc - Allocate memory using the calloc function.
diff --git a/memory_replay/traces/camera.zip b/memory_replay/dumps/camera.zip
index b9d1fb10..b9d1fb10 100644
--- a/memory_replay/traces/camera.zip
+++ b/memory_replay/dumps/camera.zip
Binary files differ
diff --git a/memory_replay/traces/gmail.zip b/memory_replay/dumps/gmail.zip
index 45e0d040..45e0d040 100644
--- a/memory_replay/traces/gmail.zip
+++ b/memory_replay/dumps/gmail.zip
Binary files differ
diff --git a/memory_replay/traces/maps.zip b/memory_replay/dumps/maps.zip
index 17cf4e76..17cf4e76 100644
--- a/memory_replay/traces/maps.zip
+++ b/memory_replay/dumps/maps.zip
Binary files differ
diff --git a/memory_replay/traces/surfaceflinger.zip b/memory_replay/dumps/surfaceflinger.zip
index 495f2f2d..495f2f2d 100644
--- a/memory_replay/traces/surfaceflinger.zip
+++ b/memory_replay/dumps/surfaceflinger.zip
Binary files differ
diff --git a/memory_replay/traces/system_server.zip b/memory_replay/dumps/system_server.zip
index bb43baf6..bb43baf6 100644
--- a/memory_replay/traces/system_server.zip
+++ b/memory_replay/dumps/system_server.zip
Binary files differ
diff --git a/memory_replay/traces/systemui.zip b/memory_replay/dumps/systemui.zip
index 8909face..8909face 100644
--- a/memory_replay/traces/systemui.zip
+++ b/memory_replay/dumps/systemui.zip
Binary files differ
diff --git a/memory_replay/traces/youtube.zip b/memory_replay/dumps/youtube.zip
index bea68abb..bea68abb 100644
--- a/memory_replay/traces/youtube.zip
+++ b/memory_replay/dumps/youtube.zip
Binary files differ
diff --git a/memory_replay/main.cpp b/memory_replay/main.cpp
index 9873ec70..42b52983 100644
--- a/memory_replay/main.cpp
+++ b/memory_replay/main.cpp
@@ -18,7 +18,6 @@
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
-#include <malloc.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -27,67 +26,95 @@
#include <sys/types.h>
#include <unistd.h>
-#include "Alloc.h"
-#include "File.h"
+#include "Action.h"
+#include "LineBuffer.h"
#include "NativeInfo.h"
#include "Pointers.h"
#include "Thread.h"
#include "Threads.h"
-constexpr size_t kDefaultMaxThreads = 512;
+static char g_buffer[65535];
-static size_t GetMaxAllocs(const AllocEntry* entries, size_t num_entries) {
+size_t GetMaxAllocs(int fd) {
+ lseek(fd, 0, SEEK_SET);
+ LineBuffer line_buf(fd, g_buffer, sizeof(g_buffer));
+ char* line;
+ size_t line_len;
size_t num_allocs = 0;
- for (size_t i = 0; i < num_entries; i++) {
- switch (entries[i].type) {
- case THREAD_DONE:
- break;
- case MALLOC:
- case CALLOC:
- case MEMALIGN:
- case REALLOC:
- num_allocs++;
- break;
- case FREE:
+ while (line_buf.GetLine(&line, &line_len)) {
+ char* word = reinterpret_cast<char*>(memchr(line, ':', line_len));
+ if (word == nullptr) {
+ continue;
+ }
+
+ word++;
+ while (*word++ == ' ');
+ // This will treat a realloc as an allocation, even if it frees
+ // another allocation. Since reallocs are relatively rare, this
+ // shouldn't inflate the numbers that much.
+ if (*word == 'f') {
+ // Check if this is a free of zero.
+ uintptr_t pointer;
+ if (sscanf(word, "free %" SCNxPTR, &pointer) == 1 && pointer != 0) {
num_allocs--;
- break;
+ }
+ } else if (*word != 't') {
+ // Skip the thread_done message.
+ num_allocs++;
}
}
return num_allocs;
}
-static void ProcessDump(const AllocEntry* entries, size_t num_entries, size_t max_threads) {
- // Do a pass to get the maximum number of allocations used at one
- // time to allow a single mmap that can hold the maximum number of
- // pointers needed at once.
- size_t max_allocs = GetMaxAllocs(entries, num_entries);
+void ProcessDump(int fd, size_t max_allocs, size_t max_threads) {
+ lseek(fd, 0, SEEK_SET);
Pointers pointers(max_allocs);
Threads threads(&pointers, max_threads);
- NativePrintf("Maximum threads available: %zu\n", threads.max_threads());
- NativePrintf("Maximum allocations in dump: %zu\n", max_allocs);
- NativePrintf("Total pointers available: %zu\n\n", pointers.max_pointers());
-
- NativePrintInfo("Initial ");
-
- for (size_t i = 0; i < num_entries; i++) {
- if (((i + 1) % 100000) == 0) {
- NativePrintf(" At line %zu:\n", i + 1);
- NativePrintInfo(" ");
+ printf("Maximum threads available: %zu\n", threads.max_threads());
+ printf("Maximum allocations in dump: %zu\n", max_allocs);
+ printf("Total pointers available: %zu\n", pointers.max_pointers());
+ printf("\n");
+
+ PrintNativeInfo("Initial ");
+
+ LineBuffer line_buf(fd, g_buffer, sizeof(g_buffer));
+ char* line;
+ size_t line_len;
+ size_t line_number = 0;
+ while (line_buf.GetLine(&line, &line_len)) {
+ pid_t tid;
+ int line_pos = 0;
+ char type[128];
+ uintptr_t key_pointer;
+
+ // Every line is of this format:
+ // <tid>: <action_type> <pointer>
+ // Some actions have extra arguments which will be used and verified
+ // when creating the Action object.
+ if (sscanf(line, "%d: %s %" SCNxPTR " %n", &tid, type, &key_pointer, &line_pos) != 3) {
+ err(1, "Unparseable line found: %s\n", line);
}
- const AllocEntry& entry = entries[i];
- Thread* thread = threads.FindThread(entry.tid);
+ line_number++;
+ if ((line_number % 100000) == 0) {
+ printf(" At line %zu:\n", line_number);
+ PrintNativeInfo(" ");
+ }
+ Thread* thread = threads.FindThread(tid);
if (thread == nullptr) {
- thread = threads.CreateThread(entry.tid);
+ thread = threads.CreateThread(tid);
}
// Wait for the thread to complete any previous actions before handling
// the next action.
thread->WaitForReady();
- thread->SetAllocEntry(&entry);
+ Action* action = thread->CreateAction(key_pointer, type, line + line_pos);
+ if (action == nullptr) {
+ err(1, "Cannot create action from line: %s\n", line);
+ }
- bool does_free = AllocDoesFree(entry);
+ bool does_free = action->DoesFree();
if (does_free) {
// Make sure that any other threads doing allocations are complete
// before triggering the action. Otherwise, another thread could
@@ -98,7 +125,7 @@ static void ProcessDump(const AllocEntry* entries, size_t num_entries, size_t ma
// Tell the thread to execute the action.
thread->SetPending();
- if (entries[i].type == THREAD_DONE) {
+ if (action->EndThread()) {
// Wait for the thread to finish and clear the thread entry.
threads.Finish(thread);
}
@@ -113,7 +140,7 @@ static void ProcessDump(const AllocEntry* entries, size_t num_entries, size_t ma
// Wait for all threads to stop processing actions.
threads.WaitForAllToQuiesce();
- NativePrintInfo("Final ");
+ PrintNativeInfo("Final ");
// Free any outstanding pointers.
// This allows us to run a tool like valgrind to verify that no memory
@@ -122,12 +149,12 @@ static void ProcessDump(const AllocEntry* entries, size_t num_entries, size_t ma
pointers.FreeAll();
// Print out the total time making all allocation calls.
- char buffer[256];
- uint64_t total_nsecs = threads.total_time_nsecs();
- NativeFormatFloat(buffer, sizeof(buffer), total_nsecs, 1000000000);
- NativePrintf("Total Allocation/Free Time: %" PRIu64 "ns %ss\n", total_nsecs, buffer);
+ printf("Total Allocation/Free Time: %" PRIu64 "ns %0.2fs\n",
+ threads.total_time_nsecs(), threads.total_time_nsecs()/1000000000.0);
}
+constexpr size_t DEFAULT_MAX_THREADS = 512;
+
int main(int argc, char** argv) {
if (argc != 2 && argc != 3) {
if (argc > 3) {
@@ -136,41 +163,29 @@ int main(int argc, char** argv) {
fprintf(stderr, "Requires at least one argument.\n");
}
fprintf(stderr, "Usage: %s MEMORY_LOG_FILE [MAX_THREADS]\n", basename(argv[0]));
- fprintf(stderr, " MEMORY_LOG_FILE\n");
- fprintf(stderr, " This can either be a text file or a zipped text file.\n");
- fprintf(stderr, " MAX_THREADs\n");
- fprintf(stderr, " The maximum number of threads in the trace. The default is %zu.\n",
- kDefaultMaxThreads);
- fprintf(stderr, " This pre-allocates the memory for thread data to avoid allocating\n");
- fprintf(stderr, " while the trace is being replayed.\n");
return 1;
}
-#if defined(__LP64__)
- NativePrintf("64 bit environment.\n");
-#else
- NativePrintf("32 bit environment.\n");
-#endif
-
-#if defined(__BIONIC__)
- NativePrintf("Setting decay time to 1\n");
- mallopt(M_DECAY_TIME, 1);
-#endif
-
- size_t max_threads = kDefaultMaxThreads;
+ size_t max_threads = DEFAULT_MAX_THREADS;
if (argc == 3) {
max_threads = atoi(argv[2]);
}
- AllocEntry* entries;
- size_t num_entries;
- GetUnwindInfo(argv[1], &entries, &num_entries);
+ int dump_fd = open(argv[1], O_RDONLY);
+ if (dump_fd == -1) {
+ fprintf(stderr, "Failed to open %s: %s\n", argv[1], strerror(errno));
+ return 1;
+ }
- NativePrintf("Processing: %s\n", argv[1]);
+ printf("Processing: %s\n", argv[1]);
- ProcessDump(entries, num_entries, max_threads);
+ // Do a first pass to get the total number of allocations used at one
+ // time to allow a single mmap that can hold the maximum number of
+ // pointers needed at once.
+ size_t max_allocs = GetMaxAllocs(dump_fd);
+ ProcessDump(dump_fd, max_allocs, max_threads);
- FreeEntries(entries, num_entries);
+ close(dump_fd);
return 0;
}
diff --git a/memory_replay/tests/ActionTest.cpp b/memory_replay/tests/ActionTest.cpp
new file mode 100644
index 00000000..cd72c24e
--- /dev/null
+++ b/memory_replay/tests/ActionTest.cpp
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "Action.h"
+#include "Pointers.h"
+
+TEST(ActionTest, malloc) {
+ uint8_t memory[Action::MaxActionSize()];
+ const char* line = "1024";
+ Action* action = Action::CreateAction(0x1234, "malloc", line, memory);
+ ASSERT_TRUE(action != NULL);
+ ASSERT_FALSE(action->DoesFree());
+ ASSERT_FALSE(action->EndThread());
+
+ Pointers pointers(1);
+ action->Execute(&pointers);
+ void* pointer = pointers.Remove(0x1234);
+ ASSERT_TRUE(pointer != nullptr);
+ free(pointer);
+}
+
+TEST(ActionTest, malloc_malformed) {
+ uint8_t memory[128];
+ const char* line = "";
+ Action* action = Action::CreateAction(0x1234, "malloc", line, memory);
+ ASSERT_FALSE(action != NULL);
+}
+
+TEST(ActionTest, free) {
+ uint8_t memory[128];
+ const char* line = "";
+ Action* action = Action::CreateAction(0x1234, "free", line, memory);
+ ASSERT_TRUE(action != NULL);
+ ASSERT_TRUE(action->DoesFree());
+ ASSERT_FALSE(action->EndThread());
+
+ Pointers pointers(1);
+ pointers.Add(0x1234, malloc(10));
+ action->Execute(&pointers);
+}
+
+TEST(ActionTest, calloc) {
+ uint8_t memory[128];
+ const char* line = "100 10";
+ Action* action = Action::CreateAction(0x1234, "calloc", line, memory);
+ ASSERT_TRUE(action != NULL);
+ ASSERT_FALSE(action->DoesFree());
+ ASSERT_FALSE(action->EndThread());
+
+ Pointers pointers(1);
+ action->Execute(&pointers);
+ void* pointer = pointers.Remove(0x1234);
+ ASSERT_TRUE(pointer != nullptr);
+ free(pointer);
+}
+
+TEST(ActionTest, free_zero) {
+ uint8_t memory[128];
+ const char* line = "";
+ Action* action = Action::CreateAction(0, "free", line, memory);
+ ASSERT_TRUE(action != NULL);
+ ASSERT_FALSE(action->DoesFree());
+ ASSERT_FALSE(action->EndThread());
+ // Should be a nop.
+ action->Execute(nullptr);
+}
+
+TEST(ActionTest, calloc_malformed) {
+ uint8_t memory[128];
+ const char* line1 = "100";
+ Action* action = Action::CreateAction(0x1234, "calloc", line1, memory);
+ ASSERT_FALSE(action != NULL);
+
+ const char* line2 = "";
+ action = Action::CreateAction(0x1234, "calloc", line2, memory);
+ ASSERT_FALSE(action != NULL);
+}
+
+TEST(ActionTest, realloc) {
+ uint8_t memory[128];
+ const char* line = "0xabcd 100";
+ Action* action = Action::CreateAction(0x1234, "realloc", line, memory);
+ ASSERT_TRUE(action != NULL);
+ ASSERT_TRUE(action->DoesFree());
+ ASSERT_FALSE(action->EndThread());
+
+ Pointers pointers(1);
+ pointers.Add(0xabcd, malloc(10));
+ action->Execute(&pointers);
+ void* pointer = pointers.Remove(0x1234);
+ ASSERT_TRUE(pointer != nullptr);
+ free(pointer);
+
+ const char* null_line = "0x0 100";
+ action = Action::CreateAction(0x1234, "realloc", null_line, memory);
+ ASSERT_FALSE(action->DoesFree());
+ ASSERT_FALSE(action->EndThread());
+
+ action->Execute(&pointers);
+ pointer = pointers.Remove(0x1234);
+ ASSERT_TRUE(pointer != nullptr);
+ free(pointer);
+}
+
+TEST(ActionTest, realloc_malformed) {
+ uint8_t memory[128];
+ const char* line1 = "0x100";
+ Action* action = Action::CreateAction(0x1234, "realloc", line1, memory);
+ ASSERT_FALSE(action != NULL);
+
+ const char* line2 = "";
+ action = Action::CreateAction(0x1234, "realloc", line2, memory);
+ ASSERT_FALSE(action != NULL);
+}
+
+TEST(ActionTest, memalign) {
+ uint8_t memory[128];
+ const char* line = "16 300";
+ Action* action = Action::CreateAction(0x1234, "memalign", line, memory);
+ ASSERT_TRUE(action != NULL);
+ ASSERT_FALSE(action->DoesFree());
+ ASSERT_FALSE(action->EndThread());
+
+ Pointers pointers(1);
+ action->Execute(&pointers);
+ void* pointer = pointers.Remove(0x1234);
+ ASSERT_TRUE(pointer != nullptr);
+ free(pointer);
+}
+
+TEST(ActionTest, memalign_malformed) {
+ uint8_t memory[128];
+ const char* line1 = "100";
+ Action* action = Action::CreateAction(0x1234, "memalign", line1, memory);
+ ASSERT_FALSE(action != NULL);
+
+ const char* line2 = "";
+ action = Action::CreateAction(0x1234, "memalign", line2, memory);
+ ASSERT_FALSE(action != NULL);
+}
+
+TEST(ActionTest, endthread) {
+ uint8_t memory[128];
+ const char* line = "";
+ Action* action = Action::CreateAction(0x0, "thread_done", line, memory);
+ ASSERT_TRUE(action != NULL);
+ ASSERT_FALSE(action->DoesFree());
+ ASSERT_TRUE(action->EndThread());
+
+ action->Execute(nullptr);
+}
diff --git a/memory_replay/tests/AllocTest.cpp b/memory_replay/tests/AllocTest.cpp
deleted file mode 100644
index d5dd0573..00000000
--- a/memory_replay/tests/AllocTest.cpp
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2019 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 <stdint.h>
-
-#include <string>
-
-#include <gtest/gtest.h>
-
-#include "Alloc.h"
-
-TEST(AllocTest, malloc_valid) {
- std::string line = "1234: malloc 0xabd0000 20";
- AllocEntry entry;
- AllocGetData(line, &entry);
- EXPECT_EQ(MALLOC, entry.type);
- EXPECT_EQ(1234, entry.tid);
- EXPECT_EQ(0xabd0000U, entry.ptr);
- EXPECT_EQ(20U, entry.size);
- EXPECT_EQ(0U, entry.u.align);
-}
-
-TEST(AllocTest, malloc_invalid) {
- std::string line = "1234: malloc 0xabd0000";
- AllocEntry entry;
- EXPECT_DEATH(AllocGetData(line, &entry), "");
-
- line = "1234: malloc";
- EXPECT_DEATH(AllocGetData(line, &entry), "");
-}
-
-TEST(AllocTest, free_valid) {
- std::string line = "1235: free 0x5000";
- AllocEntry entry;
- AllocGetData(line, &entry);
- EXPECT_EQ(FREE, entry.type);
- EXPECT_EQ(1235, entry.tid);
- EXPECT_EQ(0x5000U, entry.ptr);
- EXPECT_EQ(0U, entry.size);
- EXPECT_EQ(0U, entry.u.align);
-}
-
-TEST(AllocTest, free_invalid) {
- std::string line = "1234: free";
- AllocEntry entry;
- EXPECT_DEATH(AllocGetData(line, &entry), "");
-}
-
-TEST(AllocTest, calloc_valid) {
- std::string line = "1236: calloc 0x8000 50 30";
- AllocEntry entry;
- AllocGetData(line, &entry);
- EXPECT_EQ(CALLOC, entry.type);
- EXPECT_EQ(1236, entry.tid);
- EXPECT_EQ(0x8000U, entry.ptr);
- EXPECT_EQ(30U, entry.size);
- EXPECT_EQ(50U, entry.u.n_elements);
-}
-
-TEST(AllocTest, calloc_invalid) {
- std::string line = "1236: calloc 0x8000 50";
- AllocEntry entry;
- EXPECT_DEATH(AllocGetData(line, &entry), "");
-
- line = "1236: calloc 0x8000";
- EXPECT_DEATH(AllocGetData(line, &entry), "");
-
- line = "1236: calloc";
- EXPECT_DEATH(AllocGetData(line, &entry), "");
-}
-
-TEST(AllocTest, realloc_valid) {
- std::string line = "1237: realloc 0x9000 0x4000 80";
- AllocEntry entry;
- AllocGetData(line, &entry);
- EXPECT_EQ(REALLOC, entry.type);
- EXPECT_EQ(1237, entry.tid);
- EXPECT_EQ(0x9000U, entry.ptr);
- EXPECT_EQ(80U, entry.size);
- EXPECT_EQ(0x4000U, entry.u.old_ptr);
-}
-
-TEST(AllocTest, realloc_invalid) {
- std::string line = "1237: realloc 0x9000 0x4000";
- AllocEntry entry;
- EXPECT_DEATH(AllocGetData(line, &entry), "");
-
- line = "1237: realloc 0x9000";
- EXPECT_DEATH(AllocGetData(line, &entry), "");
-
- line = "1237: realloc";
- EXPECT_DEATH(AllocGetData(line, &entry), "");
-}
-
-TEST(AllocTest, memalign_valid) {
- std::string line = "1238: memalign 0xa000 16 89";
- AllocEntry entry;
- AllocGetData(line, &entry);
- EXPECT_EQ(MEMALIGN, entry.type);
- EXPECT_EQ(1238, entry.tid);
- EXPECT_EQ(0xa000U, entry.ptr);
- EXPECT_EQ(89U, entry.size);
- EXPECT_EQ(16U, entry.u.align);
-}
-
-TEST(AllocTest, memalign_invalid) {
- std::string line = "1238: memalign 0xa000 16";
- AllocEntry entry;
- EXPECT_DEATH(AllocGetData(line, &entry), "");
-
- line = "1238: memalign 0xa000";
- EXPECT_DEATH(AllocGetData(line, &entry), "");
-
- line = "1238: memalign";
- EXPECT_DEATH(AllocGetData(line, &entry), "");
-}
-
-TEST(AllocTest, thread_done_valid) {
- std::string line = "1239: thread_done 0x0";
- AllocEntry entry;
- AllocGetData(line, &entry);
- EXPECT_EQ(THREAD_DONE, entry.type);
- EXPECT_EQ(1239, entry.tid);
- EXPECT_EQ(0U, entry.ptr);
- EXPECT_EQ(0U, entry.size);
- EXPECT_EQ(0U, entry.u.old_ptr);
-}
-
-TEST(AllocTest, thread_done_invalid) {
- std::string line = "1240: thread_done";
- AllocEntry entry;
- EXPECT_DEATH(AllocGetData(line, &entry), "");
-}
diff --git a/memory_replay/tests/FileTest.cpp b/memory_replay/tests/FileTest.cpp
deleted file mode 100644
index 77c0593d..00000000
--- a/memory_replay/tests/FileTest.cpp
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2019 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 <malloc.h>
-#include <stdint.h>
-
-#include <string>
-
-#include <android-base/file.h>
-#include <gtest/gtest.h>
-
-#include "Alloc.h"
-#include "File.h"
-
-static std::string GetTestDirectory() {
- return android::base::GetExecutableDirectory() + "/tests";
-}
-
-static std::string GetTestZip() {
- return GetTestDirectory() + "/test.zip";
-}
-
-TEST(FileTest, zip_get_contents) {
- EXPECT_EQ("12345: malloc 0x1000 16\n12345: free 0x1000\n", ZipGetContents(GetTestZip().c_str()));
-}
-
-TEST(FileTest, zip_get_contents_bad_file) {
- EXPECT_EQ("", ZipGetContents("/does/not/exist.zip"));
-}
-
-TEST(FileTest, get_unwind_info_from_zip_file) {
- // This will allocate, so do it before getting mallinfo.
- std::string file_name = GetTestZip();
-
- size_t mallinfo_before = mallinfo().uordblks;
- AllocEntry* entries;
- size_t num_entries;
- GetUnwindInfo(file_name.c_str(), &entries, &num_entries);
- size_t mallinfo_after = mallinfo().uordblks;
-
- // Verify no memory is allocated.
- EXPECT_EQ(mallinfo_after, mallinfo_before);
-
- ASSERT_EQ(2U, num_entries);
- EXPECT_EQ(12345, entries[0].tid);
- EXPECT_EQ(MALLOC, entries[0].type);
- EXPECT_EQ(0x1000U, entries[0].ptr);
- EXPECT_EQ(16U, entries[0].size);
- EXPECT_EQ(0U, entries[0].u.old_ptr);
-
- EXPECT_EQ(12345, entries[1].tid);
- EXPECT_EQ(FREE, entries[1].type);
- EXPECT_EQ(0x1000U, entries[1].ptr);
- EXPECT_EQ(0U, entries[1].size);
- EXPECT_EQ(0U, entries[1].u.old_ptr);
-
- FreeEntries(entries, num_entries);
-}
-
-TEST(FileTest, get_unwind_info_bad_zip_file) {
- AllocEntry* entries;
- size_t num_entries;
- EXPECT_DEATH(GetUnwindInfo("/does/not/exist.zip", &entries, &num_entries), "");
-}
-
-TEST(FileTest, get_unwind_info_from_text_file) {
- // This will allocate, so do it before getting mallinfo.
- std::string file_name = GetTestDirectory() + "/test.txt";
-
- size_t mallinfo_before = mallinfo().uordblks;
- AllocEntry* entries;
- size_t num_entries;
- GetUnwindInfo(file_name.c_str(), &entries, &num_entries);
- size_t mallinfo_after = mallinfo().uordblks;
-
- // Verify no memory is allocated.
- EXPECT_EQ(mallinfo_after, mallinfo_before);
-
- ASSERT_EQ(2U, num_entries);
- EXPECT_EQ(98765, entries[0].tid);
- EXPECT_EQ(MEMALIGN, entries[0].type);
- EXPECT_EQ(0xa000U, entries[0].ptr);
- EXPECT_EQ(124U, entries[0].size);
- EXPECT_EQ(16U, entries[0].u.align);
-
- EXPECT_EQ(98765, entries[1].tid);
- EXPECT_EQ(FREE, entries[1].type);
- EXPECT_EQ(0xa000U, entries[1].ptr);
- EXPECT_EQ(0U, entries[1].size);
- EXPECT_EQ(0U, entries[1].u.old_ptr);
-
- FreeEntries(entries, num_entries);
-}
-
-TEST(FileTest, get_unwind_info_bad_file) {
- AllocEntry* entries;
- size_t num_entries;
- EXPECT_DEATH(GetUnwindInfo("/does/not/exist", &entries, &num_entries), "");
-}
diff --git a/memory_replay/tests/LineBufferTest.cpp b/memory_replay/tests/LineBufferTest.cpp
new file mode 100644
index 00000000..1a310226
--- /dev/null
+++ b/memory_replay/tests/LineBufferTest.cpp
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include <string>
+
+#include <android-base/file.h>
+
+#include "LineBuffer.h"
+
+class LineBufferTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ tmp_file_ = new TemporaryFile();
+ ASSERT_TRUE(tmp_file_->fd != -1);
+ }
+
+ void TearDown() override {
+ delete tmp_file_;
+ }
+
+ TemporaryFile* tmp_file_ = nullptr;
+};
+
+TEST_F(LineBufferTest, single_line) {
+ std::string line_data;
+ line_data += "Single line with newline.\n";
+ ASSERT_TRUE(TEMP_FAILURE_RETRY(
+ write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1);
+ ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+ char buffer[100];
+ LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer));
+
+ char* line;
+ size_t line_len;
+ ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+ ASSERT_STREQ("Single line with newline.", line);
+ ASSERT_EQ(sizeof("Single line with newline.") - 1, line_len);
+
+ ASSERT_FALSE(line_buf.GetLine(&line, &line_len));
+}
+
+TEST_F(LineBufferTest, single_line_no_newline) {
+ std::string line_data;
+ line_data += "Single line with no newline.";
+ ASSERT_TRUE(TEMP_FAILURE_RETRY(
+ write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1);
+ ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+ char buffer[100];
+ LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer));
+
+ char* line;
+ size_t line_len;
+ ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+ ASSERT_STREQ("Single line with no newline.", line);
+ ASSERT_EQ(sizeof("Single line with no newline.") - 1, line_len);
+
+ ASSERT_FALSE(line_buf.GetLine(&line, &line_len));
+}
+
+TEST_F(LineBufferTest, single_read) {
+ std::string line_data;
+ line_data += "The first line.\n";
+ line_data += "Second line here.\n";
+ line_data += "Third line is last.\n";
+ ASSERT_TRUE(TEMP_FAILURE_RETRY(
+ write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1);
+ ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+ char buffer[100];
+ LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer));
+
+ char* line;
+ size_t line_len;
+ ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+ ASSERT_STREQ("The first line.", line);
+ ASSERT_EQ(sizeof("The first line.") - 1, line_len);
+
+ ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+ ASSERT_STREQ("Second line here.", line);
+ ASSERT_EQ(sizeof("Second line here.") - 1, line_len);
+
+ ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+ ASSERT_STREQ("Third line is last.", line);
+ ASSERT_EQ(sizeof("Third line is last.") - 1, line_len);
+
+ ASSERT_FALSE(line_buf.GetLine(&line, &line_len));
+}
+
+TEST_F(LineBufferTest, single_read_no_end_newline) {
+ std::string line_data;
+ line_data += "The first line.\n";
+ line_data += "Second line here.\n";
+ line_data += "Third line is last no newline.";
+ ASSERT_TRUE(TEMP_FAILURE_RETRY(
+ write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1);
+ ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+ char buffer[100];
+ LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer));
+
+ char* line;
+ size_t line_len;
+ ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+ ASSERT_STREQ("The first line.", line);
+ ASSERT_EQ(sizeof("The first line.") - 1, line_len);
+
+ ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+ ASSERT_STREQ("Second line here.", line);
+ ASSERT_EQ(sizeof("Second line here.") - 1, line_len);
+
+ ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+ ASSERT_STREQ("Third line is last no newline.", line);
+ ASSERT_EQ(sizeof("Third line is last no newline.") - 1, line_len);
+
+ ASSERT_FALSE(line_buf.GetLine(&line, &line_len));
+}
+
+TEST_F(LineBufferTest, one_line_per_read) {
+ std::string line_data;
+ line_data += "The first line.\n";
+ line_data += "Second line here.\n";
+ line_data += "Third line is last.\n";
+ line_data += "The fourth line.\n";
+ ASSERT_TRUE(TEMP_FAILURE_RETRY(
+ write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1);
+ ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+ char buffer[24];
+ LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer));
+
+ char* line;
+ size_t line_len;
+ ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+ ASSERT_STREQ("The first line.", line);
+ ASSERT_EQ(sizeof("The first line.") - 1, line_len);
+
+ ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+ ASSERT_STREQ("Second line here.", line);
+ ASSERT_EQ(sizeof("Second line here.") - 1, line_len);
+
+ ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+ ASSERT_STREQ("Third line is last.", line);
+ ASSERT_EQ(sizeof("Third line is last.") - 1, line_len);
+
+ line_data += "The fourth line.\n";
+ ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+ ASSERT_STREQ("The fourth line.", line);
+ ASSERT_EQ(sizeof("The fourth line.") - 1, line_len);
+
+ ASSERT_FALSE(line_buf.GetLine(&line, &line_len));
+}
+
+TEST_F(LineBufferTest, multiple_line_per_read_multiple_reads) {
+ std::string line_data;
+ line_data += "The first line.\n";
+ line_data += "Second line here.\n";
+ line_data += "Third line is last.\n";
+ line_data += "The fourth line.\n";
+ ASSERT_TRUE(TEMP_FAILURE_RETRY(
+ write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1);
+ ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+ char buffer[60];
+ LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer));
+
+ char* line;
+ size_t line_len;
+ ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+ ASSERT_STREQ("The first line.", line);
+ ASSERT_EQ(sizeof("The first line.") - 1, line_len);
+
+ ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+ ASSERT_STREQ("Second line here.", line);
+ ASSERT_EQ(sizeof("Second line here.") - 1, line_len);
+
+ ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+ ASSERT_STREQ("Third line is last.", line);
+ ASSERT_EQ(sizeof("Third line is last.") - 1, line_len);
+
+ line_data += "The fourth line.\n";
+ ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+ ASSERT_STREQ("The fourth line.", line);
+ ASSERT_EQ(sizeof("The fourth line.") - 1, line_len);
+
+ ASSERT_FALSE(line_buf.GetLine(&line, &line_len));
+}
+
+TEST_F(LineBufferTest, line_larger_than_buffer) {
+ std::string line_data;
+ line_data += "The first line.\n";
+ line_data += "Second line here.\n";
+ line_data += "This is a really, really, really, kind of long.\n";
+ line_data += "The fourth line.\n";
+ ASSERT_TRUE(TEMP_FAILURE_RETRY(
+ write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1);
+ ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+ char buffer[25];
+ LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer));
+
+ char* line;
+ size_t line_len;
+ ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+ ASSERT_STREQ("The first line.", line);
+ ASSERT_EQ(sizeof("The first line.") - 1, line_len);
+
+ ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+ ASSERT_STREQ("Second line here.", line);
+ ASSERT_EQ(sizeof("Second line here.") - 1, line_len);
+
+ ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+ ASSERT_STREQ("This is a really, really", line);
+ ASSERT_EQ(sizeof(buffer) - 1, line_len);
+ ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+ ASSERT_STREQ(", really, kind of long.", line);
+ ASSERT_EQ(sizeof(", really, kind of long.") - 1, line_len);
+
+ line_data += "The fourth line.\n";
+ ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+ ASSERT_STREQ("The fourth line.", line);
+ ASSERT_EQ(sizeof("The fourth line.") - 1, line_len);
+
+ ASSERT_FALSE(line_buf.GetLine(&line, &line_len));
+}
diff --git a/memory_replay/tests/NativeInfoTest.cpp b/memory_replay/tests/NativeInfoTest.cpp
index 845ec043..e0dea509 100644
--- a/memory_replay/tests/NativeInfoTest.cpp
+++ b/memory_replay/tests/NativeInfoTest.cpp
@@ -14,12 +14,12 @@
* limitations under the License.
*/
+#include <gtest/gtest.h>
#include <stdint.h>
#include <string>
#include <android-base/file.h>
-#include <gtest/gtest.h>
#include "NativeInfo.h"
@@ -41,8 +41,8 @@ TEST_F(NativeInfoTest, no_matching) {
std::string smaps_data =
"b6f1a000-b6f1c000 rw-p 00000000 00:00 0 [anon:thread signal stack]\n"
"Size: 8 kB\n"
- "Rss: 12 kB\n"
- "Pss: 0 kB\n"
+ "Rss: 0 kB\n"
+ "Pss: 12 kB\n"
"Shared_Clean: 0 kB\n"
"Shared_Dirty: 0 kB\n"
"Private_Clean: 0 kB\n"
@@ -59,10 +59,10 @@ TEST_F(NativeInfoTest, no_matching) {
write(tmp_file_->fd, smaps_data.c_str(), smaps_data.size())) != -1);
ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
- size_t rss_bytes = 1;
+ size_t pss_bytes = 1;
size_t va_bytes = 1;
- NativeGetInfo(tmp_file_->fd, &rss_bytes, &va_bytes);
- ASSERT_EQ(0U, rss_bytes);
+ GetNativeInfo(tmp_file_->fd, &pss_bytes, &va_bytes);
+ ASSERT_EQ(0U, pss_bytes);
ASSERT_EQ(0U, va_bytes);
}
@@ -70,8 +70,8 @@ TEST_F(NativeInfoTest, multiple_anons) {
std::string smaps_data =
"b6f1a000-b6f1c000 rw-p 00000000 00:00 0 [anon:libc_malloc]\n"
"Size: 8 kB\n"
- "Rss: 12 kB\n"
- "Pss: 0 kB\n"
+ "Rss: 0 kB\n"
+ "Pss: 12 kB\n"
"Shared_Clean: 0 kB\n"
"Shared_Dirty: 0 kB\n"
"Private_Clean: 0 kB\n"
@@ -86,8 +86,8 @@ TEST_F(NativeInfoTest, multiple_anons) {
"Name: [anon:libc_malloc]\n"
"b6f1e000-b6f1f000 rw-p 00000000 00:00 0 [anon:libc_malloc]\n"
"Size: 8 kB\n"
- "Rss: 20 kB\n"
- "Pss: 0 kB\n"
+ "Rss: 0 kB\n"
+ "Pss: 20 kB\n"
"Shared_Clean: 0 kB\n"
"Shared_Dirty: 0 kB\n"
"Private_Clean: 0 kB\n"
@@ -102,8 +102,8 @@ TEST_F(NativeInfoTest, multiple_anons) {
"Name: [anon:libc_malloc]\n"
"b6f2e000-b6f2f000 rw-p 00000000 00:00 0\n"
"Size: 8 kB\n"
- "Rss: 24 kB\n"
- "Pss: 0 kB\n"
+ "Rss: 0 kB\n"
+ "Pss: 24 kB\n"
"Shared_Clean: 0 kB\n"
"Shared_Dirty: 0 kB\n"
"Private_Clean: 0 kB\n"
@@ -120,10 +120,10 @@ TEST_F(NativeInfoTest, multiple_anons) {
write(tmp_file_->fd, smaps_data.c_str(), smaps_data.size())) != -1);
ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
- size_t rss_bytes = 1;
+ size_t pss_bytes = 1;
size_t va_bytes = 1;
- NativeGetInfo(tmp_file_->fd, &rss_bytes, &va_bytes);
- ASSERT_EQ(32768U, rss_bytes);
+ GetNativeInfo(tmp_file_->fd, &pss_bytes, &va_bytes);
+ ASSERT_EQ(32768U, pss_bytes);
ASSERT_EQ(12288U, va_bytes);
}
@@ -131,8 +131,8 @@ TEST_F(NativeInfoTest, multiple_heaps) {
std::string smaps_data =
"b6f1a000-b6f1c000 rw-p 00000000 00:00 0 [heap]\n"
"Size: 8 kB\n"
- "Rss: 24 kB\n"
- "Pss: 0 kB\n"
+ "Rss: 0 kB\n"
+ "Pss: 24 kB\n"
"Shared_Clean: 0 kB\n"
"Shared_Dirty: 0 kB\n"
"Private_Clean: 0 kB\n"
@@ -147,8 +147,8 @@ TEST_F(NativeInfoTest, multiple_heaps) {
"Name: [heap]\n"
"b6f1e000-b6f1f000 rw-p 00000000 00:00 0 [heap]\n"
"Size: 8 kB\n"
- "Rss: 20 kB\n"
- "Pss: 0 kB\n"
+ "Rss: 0 kB\n"
+ "Pss: 20 kB\n"
"Shared_Clean: 0 kB\n"
"Shared_Dirty: 0 kB\n"
"Private_Clean: 0 kB\n"
@@ -163,8 +163,8 @@ TEST_F(NativeInfoTest, multiple_heaps) {
"Name: [heap]\n"
"b6f2e000-b6f2f000 rw-p 00000000 00:00 0\n"
"Size: 8 kB\n"
- "Rss: 24 kB\n"
- "Pss: 0 kB\n"
+ "Rss: 0 kB\n"
+ "Pss: 24 kB\n"
"Shared_Clean: 0 kB\n"
"Shared_Dirty: 0 kB\n"
"Private_Clean: 0 kB\n"
@@ -181,10 +181,10 @@ TEST_F(NativeInfoTest, multiple_heaps) {
write(tmp_file_->fd, smaps_data.c_str(), smaps_data.size())) != -1);
ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
- size_t rss_bytes = 1;
+ size_t pss_bytes = 1;
size_t va_bytes = 1;
- NativeGetInfo(tmp_file_->fd, &rss_bytes, &va_bytes);
- ASSERT_EQ(45056U, rss_bytes);
+ GetNativeInfo(tmp_file_->fd, &pss_bytes, &va_bytes);
+ ASSERT_EQ(45056U, pss_bytes);
ASSERT_EQ(12288U, va_bytes);
}
@@ -192,8 +192,8 @@ TEST_F(NativeInfoTest, mix_heap_anon) {
std::string smaps_data =
"b6f1a000-b6f1c000 rw-p 00000000 00:00 0 [heap]\n"
"Size: 8 kB\n"
- "Rss: 32 kB\n"
- "Pss: 0 kB\n"
+ "Rss: 0 kB\n"
+ "Pss: 32 kB\n"
"Shared_Clean: 0 kB\n"
"Shared_Dirty: 0 kB\n"
"Private_Clean: 0 kB\n"
@@ -208,8 +208,8 @@ TEST_F(NativeInfoTest, mix_heap_anon) {
"Name: [heap]\n"
"b6f1e000-b6f1f000 rw-p 00000000 00:00 0 [anon:skip]\n"
"Size: 8 kB\n"
- "Rss: 32 kB\n"
- "Pss: 0 kB\n"
+ "Rss: 0 kB\n"
+ "Pss: 32 kB\n"
"Shared_Clean: 0 kB\n"
"Shared_Dirty: 0 kB\n"
"Private_Clean: 0 kB\n"
@@ -224,8 +224,8 @@ TEST_F(NativeInfoTest, mix_heap_anon) {
"Name: [anon:skip]\n"
"b6f2e000-b6f2f000 rw-p 00000000 00:00 0 [anon:libc_malloc]\n"
"Size: 8 kB\n"
- "Rss: 40 kB\n"
- "Pss: 0 kB\n"
+ "Rss: 0 kB\n"
+ "Pss: 40 kB\n"
"Shared_Clean: 0 kB\n"
"Shared_Dirty: 0 kB\n"
"Private_Clean: 0 kB\n"
@@ -240,8 +240,8 @@ TEST_F(NativeInfoTest, mix_heap_anon) {
"Name: [anon:libc_malloc]\n"
"b6f3e000-b6f3f000 rw-p 00000000 00:00 0\n"
"Size: 8 kB\n"
- "Rss: 24 kB\n"
- "Pss: 0 kB\n"
+ "Rss: 0 kB\n"
+ "Pss: 24 kB\n"
"Shared_Clean: 0 kB\n"
"Shared_Dirty: 0 kB\n"
"Private_Clean: 0 kB\n"
@@ -253,31 +253,14 @@ TEST_F(NativeInfoTest, mix_heap_anon) {
"KernelPageSize: 4 kB\n"
"MMUPageSize: 4 kB\n"
"Locked: 0 kB\n"
- "Name:\n"
- "b6f4e000-b6f6f000 rw-p 00000000 00:00 0 [anon:scudo:test]\n"
- "Size: 8 kB\n"
- "Rss: 52 kB\n"
- "Pss: 0 kB\n"
- "Shared_Clean: 0 kB\n"
- "Shared_Dirty: 0 kB\n"
- "Private_Clean: 0 kB\n"
- "Private_Dirty: 0 kB\n"
- "Referenced: 0 kB\n"
- "Anonymous: 0 kB\n"
- "AnonHugePages: 0 kB\n"
- "Swap: 0 kB\n"
- "KernelPageSize: 4 kB\n"
- "MMUPageSize: 4 kB\n"
- "Locked: 0 kB\n"
- "Name: [anon:scudo:test]\n";
-
+ "Name:\n";
ASSERT_TRUE(TEMP_FAILURE_RETRY(
write(tmp_file_->fd, smaps_data.c_str(), smaps_data.size())) != -1);
ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
- size_t rss_bytes = 1;
+ size_t pss_bytes = 1;
size_t va_bytes = 1;
- NativeGetInfo(tmp_file_->fd, &rss_bytes, &va_bytes);
- EXPECT_EQ(126976U, rss_bytes);
- EXPECT_EQ(147456U, va_bytes);
+ GetNativeInfo(tmp_file_->fd, &pss_bytes, &va_bytes);
+ ASSERT_EQ(73728U, pss_bytes);
+ ASSERT_EQ(12288U, va_bytes);
}
diff --git a/memory_replay/tests/ThreadTest.cpp b/memory_replay/tests/ThreadTest.cpp
index 4cecf189..72492905 100644
--- a/memory_replay/tests/ThreadTest.cpp
+++ b/memory_replay/tests/ThreadTest.cpp
@@ -14,13 +14,13 @@
* limitations under the License.
*/
+#include <gtest/gtest.h>
#include <pthread.h>
#include <unistd.h>
#include <utility>
-#include <gtest/gtest.h>
-
+#include "Action.h"
#include "Pointers.h"
#include "Thread.h"
@@ -99,3 +99,16 @@ TEST(ThreadTest, pointers) {
thread.set_pointers(&pointers);
ASSERT_TRUE(thread.pointers() == &pointers);
}
+
+TEST(ThreadTest, action) {
+ Thread thread;
+
+ Action* action = thread.CreateAction(0x1234, "thread_done", "");
+ ASSERT_EQ(action, thread.GetAction());
+
+ // Verify the action object is not garbage.
+ action->Execute(nullptr);
+
+ ASSERT_TRUE(action->EndThread());
+ ASSERT_FALSE(action->DoesFree());
+}
diff --git a/memory_replay/tests/ThreadsTest.cpp b/memory_replay/tests/ThreadsTest.cpp
index 990c9130..c2ba023c 100644
--- a/memory_replay/tests/ThreadsTest.cpp
+++ b/memory_replay/tests/ThreadsTest.cpp
@@ -16,7 +16,7 @@
#include <gtest/gtest.h>
-#include "Alloc.h"
+#include "Action.h"
#include "Pointers.h"
#include "Thread.h"
#include "Threads.h"
@@ -32,8 +32,7 @@ TEST(ThreadsTest, single_thread) {
Thread* found_thread = threads.FindThread(900);
ASSERT_EQ(thread, found_thread);
- AllocEntry thread_done = {.type = THREAD_DONE};
- thread->SetAllocEntry(&thread_done);
+ thread->CreateAction(0x1234, "thread_done", "");
thread->SetPending();
@@ -67,10 +66,9 @@ TEST(ThreadsTest, multiple_threads) {
Thread* found_thread3 = threads.FindThread(902);
ASSERT_EQ(thread3, found_thread3);
- AllocEntry thread_done = {.type = THREAD_DONE};
- thread1->SetAllocEntry(&thread_done);
- thread2->SetAllocEntry(&thread_done);
- thread3->SetAllocEntry(&thread_done);
+ thread1->CreateAction(0x1234, "thread_done", "");
+ thread2->CreateAction(0x1235, "thread_done", "");
+ thread3->CreateAction(0x1236, "thread_done", "");
thread1->SetPending();
threads.Finish(thread1);
@@ -95,26 +93,17 @@ TEST(ThreadsTest, verify_quiesce) {
// If WaitForAllToQuiesce is not correct, then this should provoke an error
// since we are overwriting the action data while it's being used.
- constexpr size_t kAllocEntries = 512;
- std::vector<AllocEntry> mallocs(kAllocEntries);
- std::vector<AllocEntry> frees(kAllocEntries);
- for (size_t i = 0; i < kAllocEntries; i++) {
- mallocs[i].type = MALLOC;
- mallocs[i].ptr = 0x1234 + i;
- mallocs[i].size = 100;
- thread->SetAllocEntry(&mallocs[i]);
+ for (size_t i = 0; i < 512; i++) {
+ thread->CreateAction(0x1234 + i, "malloc", "100");
thread->SetPending();
threads.WaitForAllToQuiesce();
- frees[i].type = FREE;
- frees[i].ptr = 0x1234 + i;
- thread->SetAllocEntry(&frees[i]);
+ thread->CreateAction(0x1234 + i, "free", "");
thread->SetPending();
threads.WaitForAllToQuiesce();
}
- AllocEntry thread_done = {.type = THREAD_DONE};
- thread->SetAllocEntry(&thread_done);
+ thread->CreateAction(0x1236, "thread_done", "");
thread->SetPending();
threads.Finish(thread);
ASSERT_EQ(0U, threads.num_threads());
diff --git a/memory_replay/tests/test.txt b/memory_replay/tests/test.txt
deleted file mode 100644
index c7f43f2c..00000000
--- a/memory_replay/tests/test.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-98765: memalign 0xa000 16 124
-98765: free 0xa000
diff --git a/memory_replay/tests/test.zip b/memory_replay/tests/test.zip
deleted file mode 100644
index baef5597..00000000
--- a/memory_replay/tests/test.zip
+++ /dev/null
Binary files differ
diff --git a/memory_replay/traces/TRACES b/memory_replay/traces/TRACES
deleted file mode 100644
index f8ca8979..00000000
--- a/memory_replay/traces/TRACES
+++ /dev/null
@@ -1,80 +0,0 @@
-This file describes how the traces were created.
-
-Original Trace Information (only limited information is available about their
-creation):
-
-camera.zip
- Trace of the native camera app from start-up.
-
-gmail.zip
- Trace of the Google Gmail app from start-up and through reading messages.
-
-maps.zip
- Trace of the Google Maps app from start-up while looking at various parts
- of the map.
-
-surfaceflinger.zip
- Trace of the system app process surfaceflinger from start-up while
- doing various tasks on the phone such as starting and closing apps.
-
-system_server.zip
- Trace of the system app process system_server from start-up while
- doing various tasks on the phone such as starting and closing apps.
-
-systemui.zip
- Trace of the system app process systemui from start-up while
- doing various tasks on the phone such as starting and closing apps.
-
-youtube.zip
- Trace of the Google YouTube app from start-up and while watching various
- videos.
-
-Trace Information:
-
-angry_birds2.zip
- Phone: Pixel 3a XL
- OS: Android Q
- Process Type: 64 bit
- Version: 2.31.0
- Traced process: com.rovio.baba
- Why: Top 500 app using Unity engine.
-
- Complete the tutorial. Restart the app with tracing enabled and play
- a level.
-
-candy_crush_saga.zip
- Phone: Pixel 3a XL
- OS: Android Q
- Process Type: 64 bit
- Version: 1.157.1.1
- Traced process: com.king.candycrushsaga
- Why: Top 100 game
-
- Play first level. Restart the app with tracing enabled and play a few
- levels.
-
-photos.zip
- Phone: Pixel 3a XL
- OS: Android Q
- Process Type: 64 bit
- Application: Google Photos
- Version: 4.21.0.259804562
- Traced process: com.google.android.apps.photos
- Why: Top 10 app
-
- Trace from app start while looking at different photos, scrolling the
- photos, switching between Photos/Albums/Assistant/Sharing views.
-
-pubg.zip
- Phone: Pixel 3a XL
- OS: Android Q
- Process Type: 32 bit
- Application: Pubg Mobile
- Version: 0.14.0
- Traced process: com.tencent.ig
- Why: Top 100 game
-
- Before tracing, create new guest character using the default settings
- for a male character, Spec Requirement set to High, resource pack
- downloaded. After this, kill the app and restart, then play a game for
- a few minutes.
diff --git a/memory_replay/traces/angry_birds2.zip b/memory_replay/traces/angry_birds2.zip
deleted file mode 100644
index d7ab5088..00000000
--- a/memory_replay/traces/angry_birds2.zip
+++ /dev/null
Binary files differ
diff --git a/memory_replay/traces/candy_crush_saga.zip b/memory_replay/traces/candy_crush_saga.zip
deleted file mode 100644
index c4b1cf07..00000000
--- a/memory_replay/traces/candy_crush_saga.zip
+++ /dev/null
Binary files differ
diff --git a/memory_replay/traces/photos.zip b/memory_replay/traces/photos.zip
deleted file mode 100644
index d418eb85..00000000
--- a/memory_replay/traces/photos.zip
+++ /dev/null
Binary files differ
diff --git a/memory_replay/traces/pubg.zip b/memory_replay/traces/pubg.zip
deleted file mode 100644
index 1bc96a39..00000000
--- a/memory_replay/traces/pubg.zip
+++ /dev/null
Binary files differ
diff --git a/partition_tools/lpdump.cc b/partition_tools/lpdump.cc
index 2eb9f1fe..1b607f8a 100644
--- a/partition_tools/lpdump.cc
+++ b/partition_tools/lpdump.cc
@@ -23,7 +23,6 @@
#include <sysexits.h>
#include <unistd.h>
-#include <algorithm>
#include <iostream>
#include <optional>
#include <regex>
@@ -56,11 +55,7 @@ static int usage(int /* argc */, char* argv[], std::ostream& cerr) {
"\n"
"Options:\n"
" -s, --slot=N Slot number or suffix.\n"
- " -j, --json Print in JSON format.\n"
- " -d, --dump-metadata-size\n"
- " Print the space reserved for metadata to stdout\n"
- " in bytes.\n"
- " -a, --all Dump all slots (not available in JSON mode).\n";
+ " -j, --json Print in JSON format.\n";
return EX_USAGE;
}
@@ -72,7 +67,6 @@ static std::string BuildAttributeString(uint32_t attrs) {
std::vector<std::string> strings;
if (attrs & LP_PARTITION_ATTR_READONLY) strings.emplace_back("readonly");
if (attrs & LP_PARTITION_ATTR_SLOT_SUFFIXED) strings.emplace_back("slot-suffixed");
- if (attrs & LP_PARTITION_ATTR_UPDATED) strings.emplace_back("updated");
return BuildFlagString(strings);
}
@@ -88,6 +82,11 @@ static std::string BuildBlockDeviceFlagString(uint32_t flags) {
return BuildFlagString(strings);
}
+static bool IsBlockDevice(const char* file) {
+ struct stat s;
+ return !stat(file, &s) && S_ISBLK(s.st_mode);
+}
+
// Reimplementation of fs_mgr_get_slot_suffix() without reading
// kernel commandline.
static std::string GetSlotSuffix() {
@@ -96,14 +95,11 @@ static std::string GetSlotSuffix() {
// Reimplementation of fs_mgr_get_super_partition_name() without reading
// kernel commandline. Always return the super partition at current slot.
-static std::string GetSuperPartitionName(const std::optional<uint32_t>& slot = {}) {
+static std::string GetSuperPartionName() {
std::string super_partition = base::GetProperty("ro.boot.super_partition", "");
if (super_partition.empty()) {
return LP_METADATA_DEFAULT_PARTITION_NAME;
}
- if (slot.has_value()) {
- return super_partition + SlotSuffixForSlotNumber(slot.value());
- }
return super_partition + GetSlotSuffix();
}
@@ -251,13 +247,6 @@ static int PrintJson(const LpMetadata* metadata, std::ostream& cout,
return EX_OK;
}
-static int DumpMetadataSize(const LpMetadata& metadata, std::ostream& cout) {
- auto super_device = GetMetadataSuperBlockDevice(metadata);
- uint64_t metadata_size = super_device->first_logical_sector * LP_SECTOR_SIZE;
- cout << metadata_size << std::endl;
- return EX_OK;
-}
-
class FileOrBlockDeviceOpener final : public PartitionOpener {
public:
android::base::unique_fd Open(const std::string& path, int flags) const override {
@@ -275,108 +264,12 @@ public:
}
};
-std::optional<std::tuple<std::string, uint64_t>>
-ParseLinearExtentData(const LpMetadata& pt, const LpMetadataExtent& extent) {
- if (extent.target_type != LP_TARGET_TYPE_LINEAR) {
- return std::nullopt;
- }
- const auto& block_device = pt.block_devices[extent.target_source];
- std::string device_name = GetBlockDevicePartitionName(block_device);
- return std::make_tuple(std::move(device_name), extent.target_data);
-}
-
-static void PrintMetadata(const LpMetadata& pt, std::ostream& cout) {
- cout << "Metadata version: " << pt.header.major_version << "." << pt.header.minor_version
- << "\n";
- cout << "Metadata size: " << (pt.header.header_size + pt.header.tables_size) << " bytes\n";
- cout << "Metadata max size: " << pt.geometry.metadata_max_size << " bytes\n";
- cout << "Metadata slot count: " << pt.geometry.metadata_slot_count << "\n";
- cout << "Partition table:\n";
- cout << "------------------------\n";
-
- std::vector<std::tuple<std::string, const LpMetadataExtent*>> extents;
-
- for (const auto& partition : pt.partitions) {
- std::string name = GetPartitionName(partition);
- std::string group_name = GetPartitionGroupName(pt.groups[partition.group_index]);
- cout << " Name: " << name << "\n";
- cout << " Group: " << group_name << "\n";
- cout << " Attributes: " << BuildAttributeString(partition.attributes) << "\n";
- cout << " Extents:\n";
- uint64_t first_sector = 0;
- for (size_t i = 0; i < partition.num_extents; i++) {
- const LpMetadataExtent& extent = pt.extents[partition.first_extent_index + i];
- cout << " " << first_sector << " .. " << (first_sector + extent.num_sectors - 1)
- << " ";
- first_sector += extent.num_sectors;
- if (extent.target_type == LP_TARGET_TYPE_LINEAR) {
- const auto& block_device = pt.block_devices[extent.target_source];
- std::string device_name = GetBlockDevicePartitionName(block_device);
- cout << "linear " << device_name.c_str() << " " << extent.target_data;
- } else if (extent.target_type == LP_TARGET_TYPE_ZERO) {
- cout << "zero";
- }
- extents.push_back(std::make_tuple(name, &extent));
- cout << "\n";
- }
- cout << "------------------------\n";
- }
-
- std::sort(extents.begin(), extents.end(), [&](const auto& x, const auto& y) {
- auto x_data = ParseLinearExtentData(pt, *std::get<1>(x));
- auto y_data = ParseLinearExtentData(pt, *std::get<1>(y));
- return x_data < y_data;
- });
-
- cout << "Super partition layout:\n";
- cout << "------------------------\n";
- for (auto&& [name, extent] : extents) {
- auto data = ParseLinearExtentData(pt, *extent);
- if (!data) continue;
- auto&& [block_device, offset] = *data;
- cout << block_device << ": " << offset << " .. " << (offset + extent->num_sectors)
- << ": " << name << " (" << extent->num_sectors << " sectors)\n";
- }
- cout << "------------------------\n";
-
- cout << "Block device table:\n";
- cout << "------------------------\n";
- for (const auto& block_device : pt.block_devices) {
- std::string partition_name = GetBlockDevicePartitionName(block_device);
- cout << " Partition name: " << partition_name << "\n";
- cout << " First sector: " << block_device.first_logical_sector << "\n";
- cout << " Size: " << block_device.size << " bytes\n";
- cout << " Flags: " << BuildBlockDeviceFlagString(block_device.flags) << "\n";
- cout << "------------------------\n";
- }
-
- cout << "Group table:\n";
- cout << "------------------------\n";
- for (const auto& group : pt.groups) {
- std::string group_name = GetPartitionGroupName(group);
- cout << " Name: " << group_name << "\n";
- cout << " Maximum size: " << group.maximum_size << " bytes\n";
- cout << " Flags: " << BuildGroupFlagString(group.flags) << "\n";
- cout << "------------------------\n";
- }
-}
-
-static std::unique_ptr<LpMetadata> ReadDeviceOrFile(const std::string& path, uint32_t slot) {
- if (IsEmptySuperImage(path)) {
- return ReadFromImageFile(path);
- }
- return ReadMetadata(path, slot);
-}
-
int LpdumpMain(int argc, char* argv[], std::ostream& cout, std::ostream& cerr) {
// clang-format off
struct option options[] = {
- { "all", no_argument, nullptr, 'a' },
{ "slot", required_argument, nullptr, 's' },
{ "help", no_argument, nullptr, 'h' },
{ "json", no_argument, nullptr, 'j' },
- { "dump-metadata-size", no_argument, nullptr, 'd' },
- { "is-super-empty", no_argument, nullptr, 'e' },
{ nullptr, 0, nullptr, 0 },
};
// clang-format on
@@ -386,87 +279,40 @@ int LpdumpMain(int argc, char* argv[], std::ostream& cout, std::ostream& cerr) {
int rv;
int index;
+ uint32_t slot = 0;
bool json = false;
- bool dump_metadata_size = false;
- bool dump_all = false;
- std::optional<uint32_t> slot;
- while ((rv = getopt_long_only(argc, argv, "s:jhde", options, &index)) != -1) {
+ while ((rv = getopt_long_only(argc, argv, "s:jh", options, &index)) != -1) {
switch (rv) {
- case 'a':
- dump_all = true;
- break;
case 'h':
- usage(argc, argv, cerr);
- return EX_OK;
- case 's': {
- uint32_t slot_arg;
- if (android::base::ParseUint(optarg, &slot_arg)) {
- slot = slot_arg;
- } else {
+ return usage(argc, argv, cerr);
+ case 's':
+ if (!android::base::ParseUint(optarg, &slot)) {
slot = SlotNumberForSlotSuffix(optarg);
}
break;
- }
- case 'e':
- // This is ignored, we now derive whether it's empty automatically.
- break;
- case 'd':
- dump_metadata_size = true;
- break;
case 'j':
json = true;
break;
- case '?':
- case ':':
- return usage(argc, argv, cerr);
}
}
- if (dump_all) {
- if (slot.has_value()) {
- cerr << "Cannot specify both --all and --slot.\n";
- return usage(argc, argv, cerr);
- }
- if (json) {
- cerr << "Cannot specify both --all and --json.\n";
- return usage(argc, argv, cerr);
+ std::unique_ptr<LpMetadata> pt;
+ if (optind < argc) {
+ FileOrBlockDeviceOpener opener;
+ const char* file = argv[optind++];
+ pt = ReadMetadata(opener, file, slot);
+ if (!pt && !IsBlockDevice(file)) {
+ pt = ReadFromImageFile(file);
}
-
- // When dumping everything always start from the first slot.
- slot = 0;
- }
-
-#ifdef __ANDROID__
- // Use the current slot as a default for A/B devices.
- auto current_slot_suffix = GetSlotSuffix();
- if (!slot.has_value() && !current_slot_suffix.empty()) {
- slot = SlotNumberForSlotSuffix(current_slot_suffix);
- }
-#endif
-
- // If we still haven't determined a slot yet, use the first one.
- if (!slot.has_value()) {
- slot = 0;
- }
-
- // Determine the path to the super partition (or image). If an explicit
- // path is given, we use it for everything. Otherwise, we will infer it
- // at the time we need to read metadata.
- std::string super_path;
- bool override_super_name = (optind < argc);
- if (override_super_name) {
- super_path = argv[optind++];
} else {
#ifdef __ANDROID__
- super_path = GetSuperPartitionName(slot);
+ auto slot_number = SlotNumberForSlotSuffix(GetSlotSuffix());
+ pt = ReadMetadata(GetSuperPartionName(), slot_number);
#else
- cerr << "Must specify a super partition image.\n";
return usage(argc, argv, cerr);
#endif
}
- auto pt = ReadDeviceOrFile(super_path, slot.value());
-
// --json option doesn't require metadata to be present.
if (json) {
return PrintJson(pt.get(), cout, cerr);
@@ -477,46 +323,60 @@ int LpdumpMain(int argc, char* argv[], std::ostream& cout, std::ostream& cerr) {
return EX_NOINPUT;
}
- if (dump_metadata_size) {
- return DumpMetadataSize(*pt.get(), cout);
- }
+ cout << "Metadata version: " << pt->header.major_version << "." << pt->header.minor_version
+ << "\n";
+ cout << "Metadata size: " << (pt->header.header_size + pt->header.tables_size) << " bytes\n";
+ cout << "Metadata max size: " << pt->geometry.metadata_max_size << " bytes\n";
+ cout << "Metadata slot count: " << pt->geometry.metadata_slot_count << "\n";
+ cout << "Partition table:\n";
+ cout << "------------------------\n";
- // When running on the device, we can check the slot count. Otherwise we
- // use the # of metadata slots. (There is an extra slot we don't want to
- // dump because it is currently unused.)
-#ifdef __ANDROID__
- uint32_t num_slots = current_slot_suffix.empty() ? 1 : 2;
- if (dump_all && num_slots > 1) {
- cout << "Current slot: " << current_slot_suffix << "\n";
- }
-#else
- uint32_t num_slots = pt->geometry.metadata_slot_count;
-#endif
- // Empty images only have one slot.
- if (IsEmptySuperImage(super_path)) {
- num_slots = 1;
+ for (const auto& partition : pt->partitions) {
+ std::string name = GetPartitionName(partition);
+ std::string group_name = GetPartitionGroupName(pt->groups[partition.group_index]);
+ cout << " Name: " << name << "\n";
+ cout << " Group: " << group_name << "\n";
+ cout << " Attributes: " << BuildAttributeString(partition.attributes) << "\n";
+ cout << " Extents:\n";
+ uint64_t first_sector = 0;
+ for (size_t i = 0; i < partition.num_extents; i++) {
+ const LpMetadataExtent& extent = pt->extents[partition.first_extent_index + i];
+ cout << " " << first_sector << " .. " << (first_sector + extent.num_sectors - 1)
+ << " ";
+ first_sector += extent.num_sectors;
+ if (extent.target_type == LP_TARGET_TYPE_LINEAR) {
+ const auto& block_device = pt->block_devices[extent.target_source];
+ std::string device_name = GetBlockDevicePartitionName(block_device);
+ cout << "linear " << device_name.c_str() << " " << extent.target_data;
+ } else if (extent.target_type == LP_TARGET_TYPE_ZERO) {
+ cout << "zero";
+ }
+ cout << "\n";
+ }
+ cout << "------------------------\n";
}
- if (num_slots > 1) {
- cout << "Slot " << slot.value() << ":\n";
+ cout << "Block device table:\n";
+ cout << "------------------------\n";
+ for (const auto& block_device : pt->block_devices) {
+ std::string partition_name = GetBlockDevicePartitionName(block_device);
+ cout << " Partition name: " << partition_name << "\n";
+ cout << " First sector: " << block_device.first_logical_sector << "\n";
+ cout << " Size: " << block_device.size << " bytes\n";
+ cout << " Flags: " << BuildBlockDeviceFlagString(block_device.flags) << "\n";
+ cout << "------------------------\n";
}
- PrintMetadata(*pt.get(), cout);
-
- if (dump_all) {
- for (uint32_t i = 1; i < num_slots; i++) {
- if (!override_super_name) {
- super_path = GetSuperPartitionName(i);
- }
-
- pt = ReadDeviceOrFile(super_path, i);
- if (!pt) {
- continue;
- }
- cout << "\nSlot " << i << ":\n";
- PrintMetadata(*pt.get(), cout);
- }
+ cout << "Group table:\n";
+ cout << "------------------------\n";
+ for (const auto& group : pt->groups) {
+ std::string group_name = GetPartitionGroupName(group);
+ cout << " Name: " << group_name << "\n";
+ cout << " Maximum size: " << group.maximum_size << " bytes\n";
+ cout << " Flags: " << BuildGroupFlagString(group.flags) << "\n";
+ cout << "------------------------\n";
}
+
return EX_OK;
}
diff --git a/partition_tools/lpmake.cc b/partition_tools/lpmake.cc
index 42814772..c9333bca 100644
--- a/partition_tools/lpmake.cc
+++ b/partition_tools/lpmake.cc
@@ -62,11 +62,6 @@ static int usage(int /* argc */, char* argv[]) {
" house the super partition.\n"
" -x,--auto-slot-suffixing Mark the block device and partition names needing\n"
" slot suffixes before being used.\n"
- " -F,--force-full-image Force a full image to be written even if no\n"
- " partition images were specified. Normally, this\n"
- " would produce a minimal super_empty.img which\n"
- " cannot be flashed; force-full-image will produce\n"
- " a flashable image.\n"
"\n"
"Partition data format:\n"
" <name>:<attributes>:<size>[:group]\n"
@@ -99,7 +94,6 @@ int main(int argc, char* argv[]) {
{ "device", required_argument, nullptr, 'D' },
{ "super-name", required_argument, nullptr, 'n' },
{ "auto-slot-suffixing", no_argument, nullptr, 'x' },
- { "force-full-image", no_argument, nullptr, 'F' },
{ nullptr, 0, nullptr, 0 },
};
@@ -118,11 +112,10 @@ int main(int argc, char* argv[]) {
bool output_sparse = false;
bool has_implied_super = false;
bool auto_slot_suffixing = false;
- bool force_full_image = false;
int rv;
int index;
- while ((rv = getopt_long_only(argc, argv, "d:m:s:p:o:h:FSx", options, &index)) != -1) {
+ while ((rv = getopt_long_only(argc, argv, "d:m:s:p:o:h", options, &index)) != -1) {
switch (rv) {
case 'h':
return usage(argc, argv);
@@ -225,9 +218,6 @@ int main(int argc, char* argv[]) {
case 'x':
auto_slot_suffixing = true;
break;
- case 'F':
- force_full_image = true;
- break;
default:
break;
}
@@ -357,7 +347,7 @@ int main(int argc, char* argv[]) {
}
std::unique_ptr<LpMetadata> metadata = builder->Export();
- if (!images.empty() || force_full_image) {
+ if (!images.empty()) {
if (block_devices.size() == 1) {
if (!WriteToImageFile(output_path.c_str(), *metadata.get(), block_size, images,
output_sparse)) {
diff --git a/perfprofd/Android.bp b/perfprofd/Android.bp
new file mode 100644
index 00000000..334590c1
--- /dev/null
+++ b/perfprofd/Android.bp
@@ -0,0 +1,267 @@
+//
+// 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.
+//
+
+cc_defaults {
+ name: "perfprofd_defaults",
+
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+
+ // 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",
+ ],
+ cppflags: [
+ "-Wno-sign-compare",
+ "-Wno-unused-parameter",
+ ],
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+}
+
+cc_defaults {
+ name: "perfprofd_debug_defaults",
+
+ cflags: [
+ "-O0",
+ "-g",
+ "-UNDEBUG",
+ ],
+
+ // Add sanitizers that work w/o extra libraries. This is important
+ // for atest etc to work.
+ sanitize: {
+ integer_overflow: true,
+ undefined: true,
+ },
+
+// TODO: Re-enable when ART's ASAN flags are correctly propagated.
+// target: {
+// // On the host add ASAN.
+// host: {
+// sanitize: {
+// address: true,
+// },
+// },
+// }
+}
+
+filegroup {
+ name: "perfprofd_record_proto",
+ srcs: [
+ "perfprofd_record.proto",
+ ],
+}
+
+// Static library for the record proto and its I/O.
+
+cc_library_static {
+ name: "libperfprofd_record_proto",
+ defaults: [
+ "perfprofd_defaults",
+ ],
+ host_supported: true,
+
+ static_libs: [
+ "libbase",
+ "libprotobuf-cpp-lite",
+ "libquipper",
+ "libz",
+ ],
+ srcs: [
+ "perfprofd_io.cc",
+ ":perfprofd_record_proto",
+ ],
+
+ proto: {
+ export_proto_headers: true,
+ include_dirs: ["external/perf_data_converter/src/quipper"],
+ type: "lite",
+ },
+
+ export_include_dirs: ["."], // Really only the -fwd.h.
+ export_static_lib_headers: ["libquipper"],
+}
+
+filegroup {
+ name: "perfprofd_config_proto",
+ srcs: [
+ "perfprofd_config.proto",
+ ],
+}
+
+cc_library_static {
+ name: "libperfprofd_proto_config",
+ defaults: [
+ "perfprofd_defaults",
+ ],
+ host_supported: true,
+
+ static_libs: [
+ "libprotobuf-cpp-lite",
+ ],
+ srcs: [
+ ":perfprofd_config_proto",
+ ],
+
+ proto: {
+ export_proto_headers: true,
+ type: "lite",
+ },
+
+ export_include_dirs: ["."], // Really only the -fwd.h.
+}
+
+//
+// Static library containing guts of AWP daemon.
+//
+
+cc_defaults {
+ name: "libperfprofdcore_defaults",
+ defaults: [
+ "perfprofd_defaults",
+ ],
+ host_supported: true,
+
+ static_libs: [
+ "libbase",
+ "libperfprofd_proto_config",
+ "libprotobuf-cpp-lite",
+ "libsimpleperf_dex_read",
+ "libsimpleperf_elf_read",
+ ],
+ whole_static_libs: [
+ "libperfprofd_dropbox",
+ "libperfprofd_record_proto",
+ "libquipper",
+ ],
+ srcs: [
+ "perf_data_converter.cc",
+ "configreader.cc",
+ "cpuconfig.cc",
+ "perfprofdcore.cc",
+ "perfprofd_cmdline.cc",
+ "perfprofd_perf.cc",
+ "symbolizer.cc"
+ ],
+
+ cflags: [
+ "-Wno-gnu-anonymous-struct",
+ ],
+
+ export_include_dirs: ["."],
+
+ target: {
+ android: {
+ static_libs: [
+ "libhealthhalutils",
+ ],
+ shared_libs: [
+ "android.hardware.health@2.0",
+ "libhidlbase",
+ ],
+ }
+ }
+}
+
+cc_library_static {
+ name: "libperfprofdcore",
+ defaults: [
+ "libart_static_defaults",
+ "libperfprofdcore_defaults",
+ ],
+}
+
+// Debug version.
+cc_library_static {
+ name: "libperfprofdcored",
+ defaults: [
+ "libartd_static_defaults",
+ "libperfprofdcore_defaults",
+ "perfprofd_debug_defaults",
+ ],
+}
+
+
+//
+// Main daemon
+//
+cc_binary {
+ name: "perfprofd",
+ defaults: [
+ "libart_static_defaults",
+ "perfprofd_defaults",
+ "libsimpleperf_dex_read_static_reqs_defaults",
+ "libsimpleperf_elf_read_static_reqs_defaults",
+ ],
+
+ srcs: [
+ "perfprofdmain.cc",
+ ],
+
+ static_libs: [
+ "libhealthhalutils",
+ "libperfprofdcore",
+ "libperfprofd_binder",
+ "libperfprofd_proto_config",
+ "libsimpleperf_dex_read",
+ "libsimpleperf_elf_read",
+ ],
+ group_static_libs: true,
+
+ shared_libs: [
+ "android.hardware.health@2.0",
+ "liblog",
+ "libprotobuf-cpp-lite",
+ "libbase",
+ "libbinder",
+ "libhidlbase",
+ "libservices",
+ "libutils",
+ ],
+
+ init_rc: ["perfprofd.rc"],
+
+ product_variables: {
+ pdk: {
+ enabled: false,
+ },
+ },
+
+ // We're technically independent, but ensure simpleperf is there.
+ required: [
+ "simpleperf",
+ ],
+}
+
+subdirs = [
+ "binder_interface",
+ "tests",
+]
diff --git a/perfprofd/NOTICE b/perfprofd/NOTICE
new file mode 100644
index 00000000..8530865d
--- /dev/null
+++ b/perfprofd/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2015, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ 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/perfprofd/OWNERS b/perfprofd/OWNERS
new file mode 100644
index 00000000..2c69ac95
--- /dev/null
+++ b/perfprofd/OWNERS
@@ -0,0 +1,2 @@
+agampe@google.com
+yabinc@google.com
diff --git a/perfprofd/TEST_MAPPING b/perfprofd/TEST_MAPPING
new file mode 100644
index 00000000..30101c2b
--- /dev/null
+++ b/perfprofd/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "perfprofd_test"
+ }
+ ]
+} \ No newline at end of file
diff --git a/libfscrypt/tests/Android.bp b/perfprofd/binder_interface/Android.bp
index 985b425f..8d6837f1 100644
--- a/libfscrypt/tests/Android.bp
+++ b/perfprofd/binder_interface/Android.bp
@@ -1,4 +1,5 @@
-// Copyright (C) 2019 The Android Open Source Project
+//
+// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,23 +12,36 @@
// 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.
+//
-cc_test {
- name: "libfscrypt_unit_test",
+//
+// Static library with binder service.
+//
+cc_library_static {
+ name: "libperfprofd_binder",
+ defaults: [
+ "perfprofd_defaults",
+ ],
+ export_include_dirs: ["."],
shared_libs: [
- "libbase",
+ "libbinder",
],
static_libs: [
- "libfscrypt",
+ "libbase",
+ "libperfprofdcore",
+ "libperfprofd_proto_config",
+ "libprotobuf-cpp-lite",
],
srcs: [
- "fscrypt_test.cpp",
+ "perfprofd_binder.cc",
+ ":perfprofd_aidl",
],
+}
- cflags: [
- "-Wall",
- "-Wextra",
- "-Werror",
+filegroup {
+ name: "perfprofd_aidl",
+ srcs: [
+ "aidl/android/os/IPerfProfd.aidl",
],
}
diff --git a/perfprofd/binder_interface/aidl/android/os/IPerfProfd.aidl b/perfprofd/binder_interface/aidl/android/os/IPerfProfd.aidl
new file mode 100644
index 00000000..e2628c71
--- /dev/null
+++ b/perfprofd/binder_interface/aidl/android/os/IPerfProfd.aidl
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+package android.os;
+
+/** {@hide} */
+interface IPerfProfd {
+ /**
+ * Start continuous profiling with the given parameters.
+ */
+ void startProfiling(int collectionInterval, int iterations,
+ int process, int samplingPeriod, int samplingFrequency,
+ int sampleDuration, boolean stackProfile,
+ boolean useElfSymbolizer, boolean sendToDropbox);
+
+ /**
+ * Start continuous profiling with the given encoded parameters.
+ * Parameters should be encoded in the ConfigReader syntax,
+ * separated by colons.
+ */
+ void startProfilingString(String config);
+
+ /**
+ * Start profiling with the parameters in the given protobuf.
+ */
+ void startProfilingProtobuf(in byte[] config_proto);
+
+ /**
+ * Stop an active profiling session.
+ */
+ void stopProfiling();
+}
diff --git a/perfprofd/binder_interface/perfprofd_binder.cc b/perfprofd/binder_interface/perfprofd_binder.cc
new file mode 100644
index 00000000..a1c77e24
--- /dev/null
+++ b/perfprofd/binder_interface/perfprofd_binder.cc
@@ -0,0 +1,377 @@
+/*
+**
+** Copyright 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.
+*/
+
+#include "perfprofd_binder.h"
+
+#include <cstdio>
+#include <cstdlib>
+#include <fstream>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <thread>
+
+#include <inttypes.h>
+#include <unistd.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <binder/BinderService.h>
+#include <binder/IResultReceiver.h>
+#include <binder/Status.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <utils/String16.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#include "android/os/BnPerfProfd.h"
+#include "perfprofd_config.pb.h"
+#include "perfprofd_record.pb.h"
+
+#include "config.h"
+#include "configreader.h"
+#include "perfprofdcore.h"
+#include "perfprofd_threaded_handler.h"
+
+namespace android {
+namespace perfprofd {
+namespace binder {
+
+namespace {
+
+using Status = ::android::binder::Status;
+
+class PerfProfdNativeService : public BinderService<PerfProfdNativeService>,
+ public ::android::os::BnPerfProfd,
+ public ThreadedHandler {
+ public:
+ static status_t start();
+ static int Main();
+
+ static char const* getServiceName() { return "perfprofd"; }
+
+ status_t dump(int fd, const Vector<String16> &args) override;
+
+ Status startProfiling(int32_t collectionInterval,
+ int32_t iterations,
+ int32_t process,
+ int32_t samplingPeriod,
+ int32_t samplingFrequency,
+ int32_t sampleDuration,
+ bool stackProfile,
+ bool useElfSymbolizer,
+ bool sendToDropbox) override;
+ Status startProfilingString(const String16& config) override;
+ Status startProfilingProtobuf(const std::vector<uint8_t>& config_proto) override;
+
+ Status stopProfiling() override;
+
+ // Override onTransact so we can handle shellCommand.
+ status_t onTransact(uint32_t _aidl_code,
+ const Parcel& _aidl_data,
+ Parcel* _aidl_reply,
+ uint32_t _aidl_flags = 0) override;
+
+ private:
+ status_t shellCommand(int /*in*/, int out, int err, Vector<String16>& args);
+
+ template <typename ProtoLoaderFn> Status StartProfilingProtobuf(ProtoLoaderFn fn);
+ Status StartProfilingProtobufFd(int fd);
+};
+
+status_t PerfProfdNativeService::start() {
+ IPCThreadState::self()->disableBackgroundScheduling(true);
+ status_t ret = BinderService<PerfProfdNativeService>::publish();
+ if (ret != android::OK) {
+ return ret;
+ }
+ sp<ProcessState> ps(ProcessState::self());
+ ps->startThreadPool();
+ ps->giveThreadPoolName();
+ return android::OK;
+}
+
+status_t PerfProfdNativeService::dump(int fd, const Vector<String16> &args) {
+ auto out = std::fstream(base::StringPrintf("/proc/self/fd/%d", fd));
+ auto print_config = [&out](bool is_profiling, const Config* config) {
+ if (is_profiling) {
+ out << "Profiling with config: " << ConfigReader::ConfigToString(*config);
+ } else {
+ out << "Not actively profiling.";
+ }
+ };
+ RunOnConfig(print_config);
+ out << std::endl;
+
+ return NO_ERROR;
+}
+
+Status PerfProfdNativeService::startProfiling(int32_t collectionInterval,
+ int32_t iterations,
+ int32_t process,
+ int32_t samplingPeriod,
+ int32_t samplingFrequency,
+ int32_t sampleDuration,
+ bool stackProfile,
+ bool useElfSymbolizer,
+ bool sendToDropbox) {
+ auto config_fn = [&](ThreadedConfig& config) {
+ config = ThreadedConfig(); // Reset to a default config.
+
+ if (collectionInterval >= 0) {
+ config.collection_interval_in_s = collectionInterval;
+ }
+ if (iterations >= 0) {
+ config.main_loop_iterations = iterations;
+ }
+ if (process >= 0) {
+ config.process = process;
+ }
+ if (samplingPeriod > 0) {
+ config.sampling_period = samplingPeriod;
+ }
+ if (samplingFrequency > 0) {
+ config.sampling_frequency = samplingFrequency;
+ }
+ if (sampleDuration > 0) {
+ config.sample_duration_in_s = sampleDuration;
+ }
+ config.stack_profile = stackProfile;
+ config.use_elf_symbolizer = useElfSymbolizer;
+ config.send_to_dropbox = sendToDropbox;
+ };
+ std::string error_msg;
+ if (!StartProfiling(config_fn, &error_msg)) {
+ return Status::fromExceptionCode(1, error_msg.c_str());
+ }
+ return Status::ok();
+}
+Status PerfProfdNativeService::startProfilingString(const String16& config) {
+ ConfigReader reader;
+ std::string error_msg;
+ // Split configuration along colon.
+ std::vector<std::string> args = base::Split(String8(config).string(), ":");
+ for (auto& arg : args) {
+ if (!reader.Read(arg, /* fail_on_error */ true, &error_msg)) {
+ std::string tmp = base::StringPrintf("Could not parse %s: %s",
+ arg.c_str(),
+ error_msg.c_str());
+ return Status::fromExceptionCode(1, tmp.c_str());
+ }
+ }
+ auto config_fn = [&](ThreadedConfig& config) {
+ config = ThreadedConfig(); // Reset to a default config.
+ reader.FillConfig(&config);
+ };
+ if (!StartProfiling(config_fn, &error_msg)) {
+ return Status::fromExceptionCode(1, error_msg.c_str());
+ }
+ return Status::ok();
+}
+Status PerfProfdNativeService::startProfilingProtobuf(const std::vector<uint8_t>& config_proto) {
+ auto proto_loader_fn = [&config_proto](ProfilingConfig& proto_config) {
+ return proto_config.ParseFromArray(config_proto.data(), config_proto.size());
+ };
+ return StartProfilingProtobuf(proto_loader_fn);
+}
+
+template <typename ProtoLoaderFn>
+Status PerfProfdNativeService::StartProfilingProtobuf(ProtoLoaderFn fn) {
+ ProfilingConfig proto_config;
+ if (!fn(proto_config)) {
+ return binder::Status::fromExceptionCode(2, "Could not read protobuf");
+ }
+ auto config_fn = [&proto_config](ThreadedConfig& config) {
+ config = ThreadedConfig(); // Reset to a default config.
+ ConfigReader::ProtoToConfig(proto_config, &config);
+ };
+ std::string error_msg;
+ if (!StartProfiling(config_fn, &error_msg)) {
+ return Status::fromExceptionCode(1, error_msg.c_str());
+ }
+ return Status::ok();
+}
+
+Status PerfProfdNativeService::StartProfilingProtobufFd(int fd) {
+ auto proto_loader_fn = [fd](ProfilingConfig& proto_config) {
+ struct IstreamCopyingInputStream : public google::protobuf::io::CopyingInputStream {
+ IstreamCopyingInputStream(int fd_in)
+ : stream(base::StringPrintf("/proc/self/fd/%d", fd_in),
+ std::ios::binary | std::ios::in) {
+ }
+
+ int Read(void* buffer, int size) override {
+ stream.read(reinterpret_cast<char*>(buffer), size);
+ size_t count = stream.gcount();
+ if (count > 0) {
+ return count;
+ }
+ return -1;
+ }
+
+ std::ifstream stream;
+ };
+ std::unique_ptr<IstreamCopyingInputStream> is(new IstreamCopyingInputStream(fd));
+ std::unique_ptr<google::protobuf::io::CopyingInputStreamAdaptor> is_adaptor(
+ new google::protobuf::io::CopyingInputStreamAdaptor(is.get()));
+ return proto_config.ParseFromZeroCopyStream(is_adaptor.get());
+ };
+ return StartProfilingProtobuf(proto_loader_fn);
+}
+
+Status PerfProfdNativeService::stopProfiling() {
+ std::string error_msg;
+ if (!StopProfiling(&error_msg)) {
+ Status::fromExceptionCode(1, error_msg.c_str());
+ }
+ return Status::ok();
+}
+
+status_t PerfProfdNativeService::shellCommand(int in,
+ int out,
+ int err_fd,
+ Vector<String16>& args) {
+ if (android::base::kEnableDChecks) {
+ LOG(VERBOSE) << "Perfprofd::shellCommand";
+
+ for (size_t i = 0, n = args.size(); i < n; i++) {
+ LOG(VERBOSE) << " arg[" << i << "]: '" << String8(args[i]).string() << "'";
+ }
+ }
+
+ auto err_str = std::fstream(base::StringPrintf("/proc/self/fd/%d", err_fd));
+
+ if (args.size() >= 1) {
+ if (args[0] == String16("dump")) {
+ dump(out, args);
+ return OK;
+ } else if (args[0] == String16("startProfiling")) {
+ ConfigReader reader;
+ for (size_t i = 1; i < args.size(); ++i) {
+ std::string error_msg;
+ if (!reader.Read(String8(args[i]).string(), /* fail_on_error */ true, &error_msg)) {
+ err_str << "Could not parse '" << String8(args[i]).string() << "': " << error_msg
+ << std::endl;
+ return BAD_VALUE;
+ }
+ }
+ auto config_fn = [&](ThreadedConfig& config) {
+ config = ThreadedConfig(); // Reset to a default config.
+ reader.FillConfig(&config);
+ };
+ std::string error_msg;
+ if (!StartProfiling(config_fn, &error_msg)) {
+ err_str << error_msg << std::endl;
+ return UNKNOWN_ERROR;
+ }
+ return OK;
+ } else if (args[0] == String16("startProfilingProto")) {
+ if (args.size() < 2) {
+ return BAD_VALUE;
+ }
+ int fd = -1;
+ if (args[1] == String16("-")) {
+ fd = in;
+ } else {
+ // TODO: Implement reading from disk?
+ }
+ if (fd < 0) {
+ err_str << "Bad file descriptor " << args[1] << std::endl;
+ return BAD_VALUE;
+ }
+ binder::Status status = StartProfilingProtobufFd(fd);
+ if (status.isOk()) {
+ return OK;
+ } else {
+ err_str << status.toString8() << std::endl;
+ return UNKNOWN_ERROR;
+ }
+ } else if (args[0] == String16("stopProfiling")) {
+ Status status = stopProfiling();
+ if (status.isOk()) {
+ return OK;
+ } else {
+ err_str << status.toString8() << std::endl;
+ return UNKNOWN_ERROR;
+ }
+ }
+ }
+ return BAD_VALUE;
+}
+
+status_t PerfProfdNativeService::onTransact(uint32_t _aidl_code,
+ const Parcel& _aidl_data,
+ Parcel* _aidl_reply,
+ uint32_t _aidl_flags) {
+ switch (_aidl_code) {
+ case IBinder::SHELL_COMMAND_TRANSACTION: {
+ int in = _aidl_data.readFileDescriptor();
+ int out = _aidl_data.readFileDescriptor();
+ int err = _aidl_data.readFileDescriptor();
+ int argc = _aidl_data.readInt32();
+ Vector<String16> args;
+ for (int i = 0; i < argc && _aidl_data.dataAvail() > 0; i++) {
+ args.add(_aidl_data.readString16());
+ }
+ sp<IBinder> unusedCallback;
+ sp<IResultReceiver> resultReceiver;
+ status_t status;
+ if ((status = _aidl_data.readNullableStrongBinder(&unusedCallback)) != OK)
+ return status;
+ if ((status = _aidl_data.readNullableStrongBinder(&resultReceiver)) != OK)
+ return status;
+ status = shellCommand(in, out, err, args);
+ if (resultReceiver != nullptr) {
+ resultReceiver->send(status);
+ }
+ return OK;
+ }
+
+ default:
+ return ::android::os::BnPerfProfd::onTransact(
+ _aidl_code, _aidl_data, _aidl_reply, _aidl_flags);
+ }
+}
+
+} // namespace
+
+int Main() {
+ {
+ struct DummyConfig : public Config {
+ void Sleep(size_t seconds) override {}
+ bool IsProfilingEnabled() const override { return false; }
+ };
+ DummyConfig config;
+ GlobalInit(config.perf_path);
+ }
+
+ android::status_t ret;
+ if ((ret = PerfProfdNativeService::start()) != android::OK) {
+ LOG(ERROR) << "Unable to start InstalldNativeService: %d" << ret;
+ exit(1);
+ }
+
+ android::IPCThreadState::self()->joinThreadPool();
+
+ LOG(INFO) << "Exiting perfprofd";
+ return 0;
+}
+
+} // namespace binder
+} // namespace perfprofd
+} // namespace android
diff --git a/perfprofd/binder_interface/perfprofd_binder.h b/perfprofd/binder_interface/perfprofd_binder.h
new file mode 100644
index 00000000..8ab6d091
--- /dev/null
+++ b/perfprofd/binder_interface/perfprofd_binder.h
@@ -0,0 +1,31 @@
+/*
+**
+** Copyright 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.
+*/
+
+#ifndef SYSTEM_EXTRAS_PERFPROFD_BINDER_INTERFACE_PERFPROFD_BINDER_H_
+#define SYSTEM_EXTRAS_PERFPROFD_BINDER_INTERFACE_PERFPROFD_BINDER_H_
+
+namespace android {
+namespace perfprofd {
+namespace binder {
+
+int Main();
+
+} // namespace binder
+} // namespace perfprofd
+} // namespace android
+
+#endif // SYSTEM_EXTRAS_PERFPROFD_BINDER_INTERFACE_PERFPROFD_BINDER_H_
diff --git a/perfprofd/config.h b/perfprofd/config.h
new file mode 100644
index 00000000..b5e5d5d4
--- /dev/null
+++ b/perfprofd/config.h
@@ -0,0 +1,128 @@
+/*
+ *
+ * Copyright 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SYSTEM_EXTRAS_PERFPROFD_CONFIG_H_
+#define SYSTEM_EXTRAS_PERFPROFD_CONFIG_H_
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+struct Config {
+ virtual ~Config() {}
+
+ // Average number of seconds between perf profile collections (if
+ // set to 100, then over time we want to see a perf profile
+ // collected every 100 seconds). The actual time within the interval
+ // for the collection is chosen randomly.
+ uint32_t collection_interval_in_s = 14400;
+ // Use the specified fixed seed for random number generation (unit
+ // testing)
+ uint32_t use_fixed_seed = 0;
+ // For testing purposes, number of times to iterate through main
+ // loop. Value of zero indicates that we should loop forever.
+ uint32_t main_loop_iterations = 0;
+
+ // The pid of the process to profile. May be negative, in which case
+ // the whole system will be profiled.
+ int32_t process = -1;
+
+ // Destination directory (where to write profiles).
+ std::string destination_directory = "/data/misc/perfprofd";
+ // Config directory (where to read configs).
+ std::string config_directory = "/data/data/com.google.android.gms/files";
+ // Full path to 'perf' executable.
+ std::string perf_path = "/system/bin/simpleperf";
+
+ // Desired sampling period (passed to perf -c option). Small
+ // sampling periods can perturb the collected profiles, so enforce
+ // min/max. A value of 0 means perf default. sampling_frequency
+ // takes priority.
+ uint32_t sampling_period = 0;
+ // Desired sampling frequency (passed to perf -f option). A value of 0
+ // means using sampling_period or default.
+ uint32_t sampling_frequency = 0;
+ // Length of time to collect samples (number of seconds for 'perf
+ // record -a' run).
+ uint32_t sample_duration_in_s = 2;
+
+ // If this parameter is non-zero it will cause perfprofd to
+ // exit immediately if the build type is not userdebug or eng.
+ // Currently defaults to 1 (true).
+ bool only_debug_build = true;
+
+ // If the "mpdecision" service is running at the point we are ready
+ // to kick off a profiling run, then temporarily disable the service
+ // and hard-wire all cores on prior to the collection run, provided
+ // that the duration of the recording is less than or equal to the value of
+ // 'hardwire_cpus_max_duration'.
+ bool hardwire_cpus = true;
+ uint32_t hardwire_cpus_max_duration_in_s = 5;
+
+ // Maximum number of unprocessed profiles we can accumulate in the
+ // destination directory. Once we reach this limit, we continue
+ // to collect, but we just overwrite the most recent profile.
+ uint32_t max_unprocessed_profiles = 10;
+
+ // If set to 1, pass the -g option when invoking 'perf' (requests
+ // stack traces as opposed to flat profile).
+ bool stack_profile = false;
+
+ // For unit testing only: if set to 1, emit info messages on config
+ // file parsing.
+ bool trace_config_read = false;
+
+ // Control collection of various additional profile tags
+ bool collect_cpu_utilization = true;
+ bool collect_charging_state = true;
+ bool collect_booting = true;
+ bool collect_camera_active = false;
+
+ // If true, use an ELF symbolizer to on-device symbolize.
+ bool use_elf_symbolizer = true;
+ // Whether to symbolize everything. If false, objects with build ID will be skipped.
+ bool symbolize_everything = false;
+
+ // If true, use libz to compress the output proto.
+ bool compress = true;
+
+ // If true, send the proto to dropbox instead to a file.
+ bool send_to_dropbox = false;
+
+ // Whether to fail or strip unsupported events.
+ bool fail_on_unsupported_events = false;
+
+ struct PerfCounterConfigElem {
+ std::vector<std::string> events;
+ bool group;
+ uint32_t sampling_period;
+ };
+ std::vector<PerfCounterConfigElem> event_config;
+
+ // Sleep for the given number of seconds.
+ virtual void Sleep(size_t seconds) = 0;
+
+ // Should the profiling be stopped immediately?
+ virtual bool ShouldStopProfiling() {
+ return false;
+ }
+
+ // Is profiling enabled?
+ virtual bool IsProfilingEnabled() const = 0;
+};
+
+#endif // SYSTEM_EXTRAS_PERFPROFD_CONFIG_H_
diff --git a/perfprofd/configreader.cc b/perfprofd/configreader.cc
new file mode 100644
index 00000000..1ad573bd
--- /dev/null
+++ b/perfprofd/configreader.cc
@@ -0,0 +1,620 @@
+/*
+**
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#include "configreader.h"
+
+#include <inttypes.h>
+
+#include <algorithm>
+#include <climits>
+#include <cstdlib>
+#include <cstring>
+#include <map>
+#include <sstream>
+#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 "perfprofd_config.pb.h"
+
+using android::base::StringPrintf;
+
+//
+// Config file path
+//
+static const char *config_file_path =
+ "/data/data/com.google.android.gms/files/perfprofd.conf";
+
+struct ConfigReader::Data {
+ struct values {
+ unsigned minv;
+ unsigned maxv;
+ };
+ std::map<std::string, values> u_info;
+ std::map<std::string, unsigned> u_entries;
+ std::map<std::string, std::string> s_entries;
+
+ struct events {
+ std::vector<std::string> names;
+ unsigned period;
+ bool group;
+ };
+ std::vector<events> e_entries;
+ bool trace_config_read;
+};
+
+ConfigReader::ConfigReader() : data_(new ConfigReader::Data())
+{
+ data_->trace_config_read = false;
+ addDefaultEntries();
+}
+
+ConfigReader::~ConfigReader()
+{
+}
+
+const char *ConfigReader::getConfigFilePath()
+{
+ return config_file_path;
+}
+
+void ConfigReader::setConfigFilePath(const char *path)
+{
+ config_file_path = strdup(path);
+ LOG(INFO) << "config file path set to " << config_file_path;
+}
+
+//
+// Populate the reader with the set of allowable entries
+//
+void ConfigReader::addDefaultEntries()
+{
+ struct DummyConfig : public Config {
+ void Sleep(size_t seconds) override {}
+ bool IsProfilingEnabled() const override { return false; }
+ };
+ DummyConfig config;
+
+ // Average number of seconds between perf profile collections (if
+ // set to 100, then over time we want to see a perf profile
+ // collected every 100 seconds). The actual time within the interval
+ // for the collection is chosen randomly.
+ addUnsignedEntry("collection_interval", config.collection_interval_in_s, 0, UINT32_MAX);
+
+ // Use the specified fixed seed for random number generation (unit
+ // testing)
+ addUnsignedEntry("use_fixed_seed", config.use_fixed_seed, 0, UINT32_MAX);
+
+ // For testing purposes, number of times to iterate through main
+ // loop. Value of zero indicates that we should loop forever.
+ addUnsignedEntry("main_loop_iterations", config.main_loop_iterations, 0, UINT32_MAX);
+
+ // Destination directory (where to write profiles).
+ addStringEntry("destination_directory", config.destination_directory.c_str());
+
+ // Config directory (where to read configs).
+ addStringEntry("config_directory", config.config_directory.c_str());
+
+ // Full path to 'perf' executable.
+ addStringEntry("perf_path", config.perf_path.c_str());
+
+ // Desired sampling period (passed to perf -c option).
+ addUnsignedEntry("sampling_period", config.sampling_period, 0, UINT32_MAX);
+ // Desired sampling frequency (passed to perf -f option).
+ addUnsignedEntry("sampling_frequency", config.sampling_frequency, 0, UINT32_MAX);
+
+ // Length of time to collect samples (number of seconds for 'perf
+ // record -a' run).
+ addUnsignedEntry("sample_duration", config.sample_duration_in_s, 1, 600);
+
+ // If this parameter is non-zero it will cause perfprofd to
+ // exit immediately if the build type is not userdebug or eng.
+ // Currently defaults to 1 (true).
+ addUnsignedEntry("only_debug_build", config.only_debug_build ? 1 : 0, 0, 1);
+
+ // If the "mpdecision" service is running at the point we are ready
+ // to kick off a profiling run, then temporarily disable the service
+ // and hard-wire all cores on prior to the collection run, provided
+ // that the duration of the recording is less than or equal to the value of
+ // 'hardwire_cpus_max_duration'.
+ addUnsignedEntry("hardwire_cpus", config.hardwire_cpus, 0, 1);
+ addUnsignedEntry("hardwire_cpus_max_duration",
+ config.hardwire_cpus_max_duration_in_s,
+ 1,
+ UINT32_MAX);
+
+ // Maximum number of unprocessed profiles we can accumulate in the
+ // destination directory. Once we reach this limit, we continue
+ // to collect, but we just overwrite the most recent profile.
+ addUnsignedEntry("max_unprocessed_profiles", config.max_unprocessed_profiles, 1, UINT32_MAX);
+
+ // If set to 1, pass the -g option when invoking 'perf' (requests
+ // stack traces as opposed to flat profile).
+ addUnsignedEntry("stack_profile", config.stack_profile ? 1 : 0, 0, 1);
+
+ // For unit testing only: if set to 1, emit info messages on config
+ // file parsing.
+ addUnsignedEntry("trace_config_read", config.trace_config_read ? 1 : 0, 0, 1);
+
+ // Control collection of various additional profile tags
+ addUnsignedEntry("collect_cpu_utilization", config.collect_cpu_utilization ? 1 : 0, 0, 1);
+ addUnsignedEntry("collect_charging_state", config.collect_charging_state ? 1 : 0, 0, 1);
+ addUnsignedEntry("collect_booting", config.collect_booting ? 1 : 0, 0, 1);
+ addUnsignedEntry("collect_camera_active", config.collect_camera_active ? 1 : 0, 0, 1);
+
+ // If true, use an ELF symbolizer to on-device symbolize.
+ addUnsignedEntry("use_elf_symbolizer", config.use_elf_symbolizer ? 1 : 0, 0, 1);
+ // Whether to symbolize everything. If false, objects with build ID will be skipped.
+ addUnsignedEntry("symbolize_everything", config.symbolize_everything ? 1 : 0, 0, 1);
+
+ // If true, use libz to compress the output proto.
+ addUnsignedEntry("compress", config.compress ? 1 : 0, 0, 1);
+
+ // If true, send the proto to dropbox instead of to a file.
+ addUnsignedEntry("dropbox", config.send_to_dropbox ? 1 : 0, 0, 1);
+
+ // The pid of the process to profile. May be negative, in which case
+ // the whole system will be profiled.
+ addUnsignedEntry("process", static_cast<uint32_t>(-1), 0, UINT32_MAX);
+
+ // Whether to fail or strip unsupported events.
+ addUnsignedEntry("fail_on_unsupported_events", config.fail_on_unsupported_events ? 1 : 0, 0, 1);
+}
+
+void ConfigReader::addUnsignedEntry(const char *key,
+ unsigned default_value,
+ unsigned min_value,
+ unsigned max_value)
+{
+ std::string ks(key);
+ CHECK(data_->u_entries.find(ks) == data_->u_entries.end() &&
+ data_->s_entries.find(ks) == data_->s_entries.end())
+ << "internal error -- duplicate entry for key " << key;
+ Data::values vals;
+ vals.minv = min_value;
+ vals.maxv = max_value;
+ data_->u_info[ks] = vals;
+ data_->u_entries[ks] = default_value;
+}
+
+void ConfigReader::addStringEntry(const char *key, const char *default_value)
+{
+ std::string ks(key);
+ CHECK(data_->u_entries.find(ks) == data_->u_entries.end() &&
+ data_->s_entries.find(ks) == data_->s_entries.end())
+ << "internal error -- duplicate entry for key " << key;
+ CHECK(default_value != nullptr) << "internal error -- bad default value for key " << key;
+ data_->s_entries[ks] = std::string(default_value);
+}
+
+unsigned ConfigReader::getUnsignedValue(const char *key) const
+{
+ std::string ks(key);
+ auto it = data_->u_entries.find(ks);
+ CHECK(it != data_->u_entries.end());
+ return it->second;
+}
+
+bool ConfigReader::getBoolValue(const char *key) const
+{
+ std::string ks(key);
+ auto it = data_->u_entries.find(ks);
+ CHECK(it != data_->u_entries.end());
+ return it->second != 0;
+}
+
+std::string ConfigReader::getStringValue(const char *key) const
+{
+ std::string ks(key);
+ auto it = data_->s_entries.find(ks);
+ CHECK(it != data_->s_entries.end());
+ return it->second;
+}
+
+void ConfigReader::overrideUnsignedEntry(const char *key, unsigned new_value)
+{
+ std::string ks(key);
+ auto it = data_->u_entries.find(ks);
+ CHECK(it != data_->u_entries.end());
+ Data::values vals;
+ auto iit = data_->u_info.find(key);
+ CHECK(iit != data_->u_info.end());
+ vals = iit->second;
+ CHECK(new_value >= vals.minv && new_value <= vals.maxv);
+ it->second = new_value;
+ LOG(INFO) << "option " << key << " overridden to " << new_value;
+}
+
+
+//
+// Parse a key=value pair read from the config file. This will issue
+// warnings or errors to the system logs if the line can't be
+// interpreted properly.
+//
+bool ConfigReader::parseLine(const std::string& key,
+ const std::string& value,
+ unsigned linecount,
+ std::string* error_msg)
+{
+ if (key.empty()) {
+ *error_msg = StringPrintf("line %u: Key is empty", linecount);
+ return false;
+ }
+ if (value.empty()) {
+ *error_msg = StringPrintf("line %u: Value for %s is empty", linecount, key.c_str());
+ return false;
+ }
+
+ auto uit = data_->u_entries.find(key);
+ if (uit != data_->u_entries.end()) {
+ uint64_t conv;
+ if (!android::base::ParseUint(value, &conv)) {
+ *error_msg = StringPrintf("line %u: value %s cannot be parsed", linecount, value.c_str());
+ return false;
+ }
+ Data::values vals;
+ auto iit = data_->u_info.find(key);
+ DCHECK(iit != data_->u_info.end());
+ vals = iit->second;
+ if (conv < vals.minv || conv > vals.maxv) {
+ *error_msg = StringPrintf("line %u: "
+ "specified value %" PRIu64 " for '%s' "
+ "outside permitted range [%u %u]",
+ linecount,
+ conv,
+ key.c_str(),
+ vals.minv,
+ vals.maxv);
+ return false;
+ } else {
+ if (data_->trace_config_read) {
+ LOG(INFO) << "option " << key << " set to " << conv;
+ }
+ uit->second = static_cast<unsigned>(conv);
+ }
+ data_->trace_config_read = (getUnsignedValue("trace_config_read") != 0);
+ return true;
+ }
+
+ auto sit = data_->s_entries.find(key);
+ if (sit != data_->s_entries.end()) {
+ if (data_->trace_config_read) {
+ LOG(INFO) << "option " << key << " set to " << value;
+ }
+ sit->second = std::string(value);
+ return true;
+ }
+
+ // Check whether this follows event syntax, and create an event entry, if necessary.
+ // -e_evtname(,evtname)*=period
+ // -g_evtname(,evtname)*=period
+ {
+ bool event_key = android::base::StartsWith(key, "-e_");
+ bool group_key = android::base::StartsWith(key, "-g_");
+ if (event_key || group_key) {
+ Data::events events;
+ events.group = group_key;
+
+ uint64_t conv;
+ if (!android::base::ParseUint(value, &conv)) {
+ *error_msg = StringPrintf("line %u: key %s cannot be parsed", linecount, key.c_str());
+ return false;
+ }
+ if (conv > std::numeric_limits<unsigned>::max()) {
+ *error_msg = StringPrintf("line %u: key %s: period too large", linecount, key.c_str());
+ return false;
+ }
+ events.period = static_cast<unsigned>(conv);
+
+ events.names = android::base::Split(key.substr(3), ",");
+ data_->e_entries.push_back(events);
+ return true;
+ }
+ }
+
+ *error_msg = StringPrintf("line %u: unknown option '%s'", linecount, key.c_str());
+ return false;
+}
+
+static bool isblank(const std::string &line)
+{
+ auto non_space = [](char c) { return isspace(c) == 0; };
+ return std::find_if(line.begin(), line.end(), non_space) == line.end();
+}
+
+
+
+bool ConfigReader::readFile()
+{
+ std::string contents;
+ if (! android::base::ReadFileToString(config_file_path, &contents)) {
+ return false;
+ }
+ std::string error_msg;
+ if (!Read(contents, /* fail_on_error */ false, &error_msg)) {
+ LOG(ERROR) << error_msg;
+ return false;
+ }
+ if (!error_msg.empty()) {
+ LOG(WARNING) << error_msg;
+ }
+ return true;
+}
+
+bool ConfigReader::Read(const std::string& content, bool fail_on_error, std::string* error_msg) {
+ std::stringstream ss(content);
+ std::string line;
+
+ auto append_error = [error_msg](const std::string& tmp) {
+ if (!error_msg->empty()) {
+ error_msg->append("\n");
+ error_msg->append(tmp);
+ } else {
+ *error_msg = tmp;
+ }
+ };
+
+ for (unsigned linecount = 1;
+ std::getline(ss,line,'\n');
+ linecount += 1)
+ {
+
+ // comment line?
+ if (line[0] == '#') {
+ continue;
+ }
+
+ // blank line?
+ if (isblank(line)) {
+ continue;
+ }
+
+ // look for X=Y assignment
+ auto efound = line.find('=');
+ if (efound == std::string::npos) {
+ append_error(StringPrintf("line %u: line malformed (no '=' found)", linecount));
+ if (fail_on_error) {
+ return false;
+ }
+ continue;
+ }
+
+ std::string key(line.substr(0, efound));
+ std::string value(line.substr(efound+1, std::string::npos));
+
+ std::string local_error_msg;
+ bool parse_success = parseLine(key, value, linecount, &local_error_msg);
+ if (!parse_success) {
+ append_error(local_error_msg);
+ if (fail_on_error) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+void ConfigReader::FillConfig(Config* config) {
+ config->collection_interval_in_s = getUnsignedValue("collection_interval");
+
+ config->use_fixed_seed = getUnsignedValue("use_fixed_seed");
+
+ config->main_loop_iterations = getUnsignedValue("main_loop_iterations");
+
+ config->destination_directory = getStringValue("destination_directory");
+
+ config->config_directory = getStringValue("config_directory");
+
+ config->perf_path = getStringValue("perf_path");
+
+ config->sampling_period = getUnsignedValue("sampling_period");
+ config->sampling_frequency = getUnsignedValue("sampling_frequency");
+
+ config->sample_duration_in_s = getUnsignedValue("sample_duration");
+
+ config->only_debug_build = getBoolValue("only_debug_build");
+
+ config->hardwire_cpus = getBoolValue("hardwire_cpus");
+ config->hardwire_cpus_max_duration_in_s = getUnsignedValue("hardwire_cpus_max_duration");
+
+ config->max_unprocessed_profiles = getUnsignedValue("max_unprocessed_profiles");
+
+ config->stack_profile = getBoolValue("stack_profile");
+
+ config->trace_config_read = getBoolValue("trace_config_read");
+
+ config->collect_cpu_utilization = getBoolValue("collect_cpu_utilization");
+ config->collect_charging_state = getBoolValue("collect_charging_state");
+ config->collect_booting = getBoolValue("collect_booting");
+ config->collect_camera_active = getBoolValue("collect_camera_active");
+
+ config->process = static_cast<int32_t>(getUnsignedValue("process"));
+ config->use_elf_symbolizer = getBoolValue("use_elf_symbolizer");
+ config->symbolize_everything = getBoolValue("symbolize_everything");
+ config->compress = getBoolValue("compress");
+ config->send_to_dropbox = getBoolValue("dropbox");
+ config->fail_on_unsupported_events = getBoolValue("fail_on_unsupported_events");
+
+ config->event_config.clear();
+ for (const auto& event : data_->e_entries) {
+ Config::PerfCounterConfigElem elem;
+ elem.events = event.names;
+ elem.group = event.group;
+ elem.sampling_period = event.period;
+ config->event_config.push_back(std::move(elem));
+ }
+}
+
+namespace {
+
+template <typename T>
+struct OssFormatter {
+};
+
+template <>
+struct OssFormatter<std::string> {
+ void Add(std::ostream& os, const std::string& val) {
+ os << val;
+ }
+};
+
+template <>
+struct OssFormatter<uint32_t> {
+ void Add(std::ostream& os, const uint32_t& val) {
+ os << val;
+ }
+};
+
+template <>
+struct OssFormatter<int32_t> {
+ void Add(std::ostream& os, const int32_t& val) {
+ os << val;
+ }
+};
+
+template <>
+struct OssFormatter<bool> {
+ void Add(std::ostream& os, const bool& val) {
+ os << (val ? 1 : 0);
+ }
+};
+
+
+} // namespace
+
+std::string ConfigReader::ConfigToString(const Config& config) {
+ std::ostringstream oss;
+
+ auto add = [&oss](const char* str, auto val) {
+ if (oss.tellp() != 0) {
+ oss << ' ';
+ }
+ oss << str << '=';
+ OssFormatter<decltype(val)> fmt;
+ fmt.Add(oss, val);
+ };
+
+ add("collection_interval", config.collection_interval_in_s);
+ add("use_fixed_seed", config.use_fixed_seed);
+ add("main_loop_iterations", config.main_loop_iterations);
+
+ add("destination_directory", config.destination_directory); // TODO: Escape.
+ add("config_directory", config.config_directory); // TODO: Escape.
+ add("perf_path", config.perf_path); // TODO: Escape.
+
+ add("sampling_period", config.sampling_period);
+ add("sampling_frequency", config.sampling_frequency);
+
+ add("sample_duration", config.sample_duration_in_s);
+
+ add("only_debug_build", config.only_debug_build);
+
+ add("hardwire_cpus", config.hardwire_cpus);
+
+ add("hardwire_cpus_max_duration", config.hardwire_cpus_max_duration_in_s);
+
+ add("max_unprocessed_profiles", config.max_unprocessed_profiles);
+
+ add("stack_profile", config.stack_profile);
+
+ add("trace_config_read", config.trace_config_read);
+
+ add("collect_cpu_utilization", config.collect_cpu_utilization);
+ add("collect_charging_state", config.collect_charging_state);
+ add("collect_booting", config.collect_booting);
+ add("collect_camera_active", config.collect_camera_active);
+
+ add("process", config.process);
+ add("use_elf_symbolizer", config.use_elf_symbolizer);
+ add("symbolize_everything", config.symbolize_everything);
+ add("compress", config.compress);
+ add("dropbox", config.send_to_dropbox);
+ add("fail_on_unsupported_events", config.fail_on_unsupported_events);
+
+ for (const auto& elem : config.event_config) {
+ std::ostringstream oss_elem;
+ oss_elem << '-' << (elem.group ? 'g' : 'e') << '_';
+ bool first = true;
+ for (const auto& event : elem.events) {
+ if (!first) {
+ oss_elem << ',';
+ }
+ oss_elem << event;
+ first = false;
+ }
+ add(oss_elem.str().c_str(), elem.sampling_period);
+ }
+
+ return oss.str();
+}
+
+void ConfigReader::ProtoToConfig(const android::perfprofd::ProfilingConfig& in, Config* out) {
+ // Copy base proto values.
+#define CHECK_AND_COPY_FROM_PROTO(name) \
+ if (in.has_ ## name()) { \
+ out->name = in.name(); \
+ }
+ CHECK_AND_COPY_FROM_PROTO(collection_interval_in_s)
+ CHECK_AND_COPY_FROM_PROTO(use_fixed_seed)
+ CHECK_AND_COPY_FROM_PROTO(main_loop_iterations)
+ CHECK_AND_COPY_FROM_PROTO(destination_directory)
+ CHECK_AND_COPY_FROM_PROTO(config_directory)
+ CHECK_AND_COPY_FROM_PROTO(perf_path)
+ CHECK_AND_COPY_FROM_PROTO(sampling_period)
+ CHECK_AND_COPY_FROM_PROTO(sampling_frequency)
+ CHECK_AND_COPY_FROM_PROTO(sample_duration_in_s)
+ CHECK_AND_COPY_FROM_PROTO(only_debug_build)
+ CHECK_AND_COPY_FROM_PROTO(hardwire_cpus)
+ CHECK_AND_COPY_FROM_PROTO(hardwire_cpus_max_duration_in_s)
+ CHECK_AND_COPY_FROM_PROTO(max_unprocessed_profiles)
+ CHECK_AND_COPY_FROM_PROTO(stack_profile)
+ CHECK_AND_COPY_FROM_PROTO(collect_cpu_utilization)
+ CHECK_AND_COPY_FROM_PROTO(collect_charging_state)
+ CHECK_AND_COPY_FROM_PROTO(collect_booting)
+ CHECK_AND_COPY_FROM_PROTO(collect_camera_active)
+ CHECK_AND_COPY_FROM_PROTO(process)
+ CHECK_AND_COPY_FROM_PROTO(use_elf_symbolizer)
+ CHECK_AND_COPY_FROM_PROTO(symbolize_everything)
+ CHECK_AND_COPY_FROM_PROTO(send_to_dropbox)
+ CHECK_AND_COPY_FROM_PROTO(compress)
+ CHECK_AND_COPY_FROM_PROTO(fail_on_unsupported_events)
+#undef CHECK_AND_COPY_FROM_PROTO
+
+ // Convert counters.
+ for (const auto& event_config : in.event_config()) {
+ Config::PerfCounterConfigElem config_elem;
+
+ if (event_config.counters_size() == 0) {
+ LOG(WARNING) << "Missing counters.";
+ continue;
+ }
+ config_elem.events.reserve(event_config.counters_size());
+ for (const std::string& str : event_config.counters()) {
+ config_elem.events.push_back(str);
+ }
+ config_elem.group = event_config.has_as_group() ? event_config.as_group() : false;
+ config_elem.sampling_period = event_config.has_sampling_period()
+ ? event_config.sampling_period()
+ : 0;
+ out->event_config.push_back(std::move(config_elem));
+ }
+}
diff --git a/perfprofd/configreader.h b/perfprofd/configreader.h
new file mode 100644
index 00000000..3d2243c5
--- /dev/null
+++ b/perfprofd/configreader.h
@@ -0,0 +1,83 @@
+/*
+ *
+ * Copyright 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SYSTEM_EXTRAS_PERFPROFD_CONFIGREADER_H_
+#define SYSTEM_EXTRAS_PERFPROFD_CONFIGREADER_H_
+
+#include <memory>
+#include <string>
+
+#include "config.h"
+
+namespace android {
+namespace perfprofd {
+class ProfilingConfig; // Config proto.
+} // namespace perfprofd
+} // namespace android
+
+//
+// This table describes the perfprofd config file syntax in terms of
+// key/value pairs. Values come in two flavors: strings, or unsigned
+// integers. In the latter case the reader sets allowable
+// minimum/maximum for the setting.
+//
+class ConfigReader {
+
+ public:
+ ConfigReader();
+ ~ConfigReader();
+
+ // Ask for the current setting of a config item
+ unsigned getUnsignedValue(const char *key) const;
+ bool getBoolValue(const char *key) const;
+ std::string getStringValue(const char *key) const;
+
+ // read the specified config file, applying any settings it contains
+ // returns true for successful read, false if conf file cannot be opened.
+ bool readFile();
+
+ bool Read(const std::string& data, bool fail_on_error, std::string* error_msg);
+
+ // set/get path to config file
+ static void setConfigFilePath(const char *path);
+ static const char *getConfigFilePath();
+
+ // override a config item (for unit testing purposes)
+ void overrideUnsignedEntry(const char *key, unsigned new_value);
+
+ void FillConfig(Config* config);
+ static std::string ConfigToString(const Config& config);
+
+ static void ProtoToConfig(const android::perfprofd::ProfilingConfig& in, Config* out);
+
+ private:
+ void addUnsignedEntry(const char *key,
+ unsigned default_value,
+ unsigned min_value,
+ unsigned max_value);
+ void addStringEntry(const char *key, const char *default_value);
+ void addDefaultEntries();
+ bool parseLine(const std::string& key,
+ const std::string& value,
+ unsigned linecount,
+ std::string* error_msg);
+
+ struct Data;
+ std::unique_ptr<Data> data_;
+};
+
+#endif
diff --git a/perfprofd/cpuconfig.cc b/perfprofd/cpuconfig.cc
new file mode 100644
index 00000000..0f23fd0f
--- /dev/null
+++ b/perfprofd/cpuconfig.cc
@@ -0,0 +1,113 @@
+/*
+**
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <string>
+#include <sstream>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <android-base/logging.h>
+#ifdef __BIONIC__
+#include <android-base/properties.h>
+#endif
+
+#include "cpuconfig.h"
+
+#define SYSFSCPU "/sys/devices/system/cpu"
+
+HardwireCpuHelper::HardwireCpuHelper(bool perform)
+ : mpdecision_stopped_(false)
+{
+ if (perform && GetMpdecisionRunning()) {
+ mpdecision_stopped_ = true;
+ StopMpdecision();
+ int ncores = GetNumCores();
+ for (int i = 0; i < ncores; ++i) {
+ OnlineCore(i, 1);
+ }
+ }
+}
+
+HardwireCpuHelper::~HardwireCpuHelper()
+{
+ if (mpdecision_stopped_) {
+ RestartMpdecision();
+ }
+}
+
+bool HardwireCpuHelper::GetMpdecisionRunning()
+{
+#ifdef __BIONIC__
+ return android::base::GetProperty("init.svc.mpdecision", "") == "running";
+#else
+ return false;
+#endif
+}
+
+
+int HardwireCpuHelper::GetNumCores()
+{
+ int ncores = -1;
+ std::string possible(SYSFSCPU "/possible");
+ FILE *fp = fopen(possible.c_str(), "re");
+ if (fp) {
+ unsigned lo = 0, hi = 0;
+ if (fscanf(fp, "%u-%u", &lo, &hi) == 2) {
+ ncores = hi - lo + 1;
+ }
+ fclose(fp);
+ }
+ return ncores;
+}
+
+void HardwireCpuHelper::OnlineCore(int i, int onoff)
+{
+ std::stringstream ss;
+ ss << SYSFSCPU "/cpu" << i << "/online";
+ FILE *fp = fopen(ss.str().c_str(), "we");
+ if (fp) {
+ fprintf(fp, onoff ? "1\n" : "0\n");
+ fclose(fp);
+ } else {
+ PLOG(WARNING) << "open failed for " << ss.str();
+ }
+}
+
+void HardwireCpuHelper::StopMpdecision()
+{
+#ifdef __BIONIC__
+ if (!android::base::SetProperty("ctl.stop", "mpdecision")) {
+ LOG(ERROR) << "setprop ctl.stop mpdecision failed";
+ }
+#endif
+}
+
+void HardwireCpuHelper::RestartMpdecision()
+{
+#ifdef __BIONIC__
+ // Don't try to offline the cores we previously onlined -- let
+ // mpdecision figure out what to do
+
+ if (!android::base::SetProperty("ctl.start", "mpdecision")) {
+ LOG(ERROR) << "setprop ctl.start mpdecision failed";
+ }
+#endif
+}
diff --git a/perfprofd/cpuconfig.h b/perfprofd/cpuconfig.h
new file mode 100644
index 00000000..bc5b5cf9
--- /dev/null
+++ b/perfprofd/cpuconfig.h
@@ -0,0 +1,50 @@
+/*
+**
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+//
+// Helper class to perform cpu setup (if needed) prior to a profile collection.
+//
+class HardwireCpuHelper {
+ public:
+
+ // The constructor for this class checks to see if the 'mpdecision'
+ // service is running; if so (and if 'perform' is TRUE), then it
+ // disables the service and on-lines all of the available cores/cpus
+ // (anything listed in /sys/devices/system/cpu/possible). The
+ // destructor will re-enable the mpdecision service if it was
+ // previously disabled.
+ explicit HardwireCpuHelper(bool perform);
+ virtual ~HardwireCpuHelper();
+
+ private:
+ bool mpdecision_stopped_;
+
+ // Collect the number of available cpus/cores from /sys/devices/system/cpu/possible
+ int GetNumCores();
+
+ // Returns TRUE if the system service 'mpdecision' is running
+ bool GetMpdecisionRunning();
+
+ // Online/offline the specified cpu
+ void OnlineCore(int whichCore, int onoff);
+
+ // Enable/disable the mpdecision service via the equivalent of
+ // setprop ctl.start mpdecision
+ // setprop ctl.stop mpdecision
+ void StopMpdecision();
+ void RestartMpdecision();
+};
diff --git a/perfprofd/dropbox/Android.bp b/perfprofd/dropbox/Android.bp
new file mode 100644
index 00000000..4ab3a11c
--- /dev/null
+++ b/perfprofd/dropbox/Android.bp
@@ -0,0 +1,52 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+//
+// Static library for dropbox submission.
+//
+cc_library_static {
+ name: "libperfprofd_dropbox",
+ defaults: [
+ "perfprofd_defaults",
+ ],
+ host_supported: true,
+
+ export_include_dirs: ["."],
+ static_libs: [
+ "libbase",
+ "libperfprofd_record_proto",
+ "libprotobuf-cpp-lite",
+ ],
+ target: {
+ android: {
+ srcs: [
+ "dropbox.cc",
+ ],
+ static_libs: [
+ "libutils",
+ ],
+ shared_libs: [
+ "libbinder",
+ "libservices",
+ ],
+ },
+ host: {
+ srcs: [
+ "dropbox_host.cc",
+ ],
+ },
+ },
+}
diff --git a/perfprofd/dropbox/dropbox.cc b/perfprofd/dropbox/dropbox.cc
new file mode 100644
index 00000000..2b1dc2ef
--- /dev/null
+++ b/perfprofd/dropbox/dropbox.cc
@@ -0,0 +1,129 @@
+/*
+ *
+ * Copyright 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.
+ */
+
+#include "dropbox.h"
+
+#include <cstdio>
+#include <cstdlib>
+#include <memory>
+
+#include <inttypes.h>
+#include <unistd.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <android/os/DropBoxManager.h>
+#include <binder/Status.h>
+#include <utils/String8.h>
+
+#include "perfprofd_record.pb.h"
+
+#include "perfprofd_io.h"
+
+namespace android {
+namespace perfprofd {
+namespace dropbox {
+
+namespace {
+
+bool WriteDropboxFile(android::perfprofd::PerfprofdRecord* encodedProfile,
+ const std::string& temp_dir,
+ std::string* error_msg) {
+ android::base::unique_fd tmp_fd;
+ {
+ char path[PATH_MAX];
+ snprintf(path, sizeof(path), "%s/dropboxtmp-XXXXXX", temp_dir.c_str());
+ tmp_fd.reset(mkstemp(path));
+ if (tmp_fd.get() == -1) {
+ *error_msg = android::base::StringPrintf("Could not create temp file %s: %s",
+ path,
+ strerror(errno));
+ return false;
+ }
+ if (unlink(path) != 0) {
+ PLOG(WARNING) << "Could not unlink binder temp file";
+ }
+ }
+
+ // Dropbox takes ownership of the fd, and if it is not readonly,
+ // a selinux violation will occur. Get a read-only version.
+ android::base::unique_fd read_only;
+ {
+ char fdpath[64];
+ snprintf(fdpath, arraysize(fdpath), "/proc/self/fd/%d", tmp_fd.get());
+ read_only.reset(open(fdpath, O_RDONLY | O_CLOEXEC));
+ if (read_only.get() < 0) {
+ *error_msg = android::base::StringPrintf("Could not create read-only fd: %s",
+ strerror(errno));
+ return false;
+ }
+ }
+
+ constexpr bool kCompress = true; // Ignore the config here. Dropbox will always end up
+ // compressing the data, might as well make the temp
+ // file smaller and help it out.
+ using DropBoxManager = android::os::DropBoxManager;
+ constexpr int kDropboxFlags = DropBoxManager::IS_GZIPPED;
+
+ if (!SerializeProtobuf(encodedProfile, std::move(tmp_fd), kCompress)) {
+ *error_msg = "Could not serialize to temp file";
+ return false;
+ }
+
+ sp<DropBoxManager> dropbox(new DropBoxManager());
+ android::binder::Status status = dropbox->addFile(String16("perfprofd"),
+ read_only.release(),
+ kDropboxFlags);
+ if (!status.isOk()) {
+ *error_msg = status.toString8();
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+bool SendToDropbox(android::perfprofd::PerfprofdRecord* profile,
+ const std::string& temp_directory,
+ std::string* error_msg) {
+ size_t size = profile->ByteSize();
+ if (size < 1024 * 1024) {
+ // For a small size, send as a byte buffer directly.
+ std::unique_ptr<uint8_t[]> data(new uint8_t[size]);
+ profile->SerializeWithCachedSizesToArray(data.get());
+
+ using DropBoxManager = android::os::DropBoxManager;
+ sp<DropBoxManager> dropbox(new DropBoxManager());
+ android::binder::Status status = dropbox->addData(String16("perfprofd"),
+ data.get(),
+ size,
+ 0);
+ if (!status.isOk()) {
+ *error_msg = status.toString8();
+ return false;
+ }
+ return true;
+ } else {
+ // For larger buffers, we need to go through the filesystem.
+ return WriteDropboxFile(profile, temp_directory, error_msg);
+ }
+}
+
+} // namespace dropbox
+} // namespace perfprofd
+} // namespace android
diff --git a/perfprofd/dropbox/dropbox.h b/perfprofd/dropbox/dropbox.h
new file mode 100644
index 00000000..b25d2cc2
--- /dev/null
+++ b/perfprofd/dropbox/dropbox.h
@@ -0,0 +1,37 @@
+/*
+ *
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SYSTEM_EXTRAS_PERFPROFD_DROPBOX_DROPBOX_H_
+#define SYSTEM_EXTRAS_PERFPROFD_DROPBOX_DROPBOX_H_
+
+#include <string>
+
+#include "perfprofd_record-fwd.h"
+
+namespace android {
+namespace perfprofd {
+namespace dropbox {
+
+bool SendToDropbox(android::perfprofd::PerfprofdRecord* profile,
+ const std::string& temp_directory,
+ std::string* error_msg);
+
+} // namespace dropbox
+} // namespace perfprofd
+} // namespace android
+
+#endif // SYSTEM_EXTRAS_PERFPROFD_DROPBOX_DROPBOX_H_
diff --git a/perfprofd/dropbox/dropbox_host.cc b/perfprofd/dropbox/dropbox_host.cc
new file mode 100644
index 00000000..5c08aa85
--- /dev/null
+++ b/perfprofd/dropbox/dropbox_host.cc
@@ -0,0 +1,35 @@
+/*
+ *
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "dropbox.h"
+
+#include <android-base/macros.h>
+
+namespace android {
+namespace perfprofd {
+namespace dropbox {
+
+bool SendToDropbox(android::perfprofd::PerfprofdRecord* profile,
+ const std::string& temp_directory ATTRIBUTE_UNUSED,
+ std::string* error_msg) {
+ *error_msg = "Dropbox not supported on host";
+ return false;
+}
+
+} // namespace dropbox
+} // namespace perfprofd
+} // namespace android
diff --git a/perfprofd/map_utils.h b/perfprofd/map_utils.h
new file mode 100644
index 00000000..2e3d97d9
--- /dev/null
+++ b/perfprofd/map_utils.h
@@ -0,0 +1,129 @@
+#ifndef SYSTEM_EXTRAS_PERFPROFD_MAP_UTILS_H_
+#define SYSTEM_EXTRAS_PERFPROFD_MAP_UTILS_H_
+
+#include <map>
+#include <set>
+
+#include <android-base/logging.h>
+
+namespace android {
+namespace perfprofd {
+
+template <typename T, typename U>
+decltype(static_cast<T*>(nullptr)->begin()) GetLeqIterator(T& map, U key) {
+ if (map.empty()) {
+ return map.end();
+ }
+ auto it = map.upper_bound(key);
+ if (it == map.begin()) {
+ return map.end();
+ }
+ --it;
+ return it;
+}
+
+template <typename SymType, typename ValType>
+class RangeMap {
+ public:
+ struct AggregatedSymbol {
+ SymType symbol;
+ std::set<ValType> offsets;
+ AggregatedSymbol(const SymType& sym, const ValType& offset) : symbol(sym) {
+ offsets.insert(offset);
+ }
+ };
+
+ public:
+ void Insert(const SymType& sym, const ValType& val) {
+ auto aggr_it = GetLeqIterator(map_, val);
+ if (aggr_it == map_.end()) {
+ // Maybe we need to extend the first one.
+ if (!map_.empty()) {
+ AggregatedSymbol& first = map_.begin()->second;
+ CHECK_LT(val, map_.begin()->first);
+ if (first.symbol == sym) {
+ ExtendLeft(map_.begin(), val);
+ return;
+ }
+ }
+ // Nope, new entry needed.
+ map_.emplace(val, AggregatedSymbol(sym, val));
+ return;
+ }
+
+ AggregatedSymbol& maybe_match = aggr_it->second;
+
+ if (maybe_match.symbol == sym) {
+ // Same symbol, just insert. This is true for overlap as well as extension.
+ maybe_match.offsets.insert(val);
+ return;
+ }
+
+ // Is there overlap?
+ if (*maybe_match.offsets.rbegin() < val) {
+ // No. See if it can be merged with the next one.
+ ++aggr_it;
+ if (aggr_it != map_.end() && aggr_it->second.symbol == sym) {
+ ExtendLeft(aggr_it, val);
+ return;
+ }
+
+ // Just add a new symbol entry.
+ map_.emplace(val, AggregatedSymbol(sym, val));
+ return;
+ }
+
+ // OK, we have an overlapping non-symbol-equal AggregatedSymbol. Need to break
+ // things up.
+ AggregatedSymbol left(maybe_match.symbol, *maybe_match.offsets.begin());
+ auto offset_it = maybe_match.offsets.begin();
+ for (; *offset_it < val; ++offset_it) {
+ left.offsets.insert(*offset_it);
+ }
+
+ if (*offset_it == val) {
+ // This should not happen.
+ LOG(ERROR) << "Unexpected overlap!";
+ return;
+ }
+
+ AggregatedSymbol right(maybe_match.symbol, *offset_it);
+ for (; offset_it != maybe_match.offsets.end(); ++offset_it) {
+ right.offsets.insert(*offset_it);
+ }
+
+ map_.erase(aggr_it);
+ map_.emplace(*left.offsets.begin(), std::move(left));
+ map_.emplace(val, AggregatedSymbol(sym, val));
+ map_.emplace(*right.offsets.begin(), std::move(right));
+ }
+
+ using RangeMapType = std::map<ValType, AggregatedSymbol>;
+
+ typename RangeMapType::const_iterator begin() const {
+ return map_.begin();
+ }
+ typename RangeMapType::const_iterator end() const {
+ return map_.end();
+ }
+
+ bool empty() const {
+ return map_.empty();
+ }
+
+ private:
+ void ExtendLeft(typename RangeMapType::iterator it, const ValType& val) {
+ CHECK(val < *it->second.offsets.begin());
+ AggregatedSymbol copy = std::move(it->second);
+ map_.erase(it);
+ copy.offsets.insert(val);
+ map_.emplace(val, std::move(copy));
+ }
+
+ RangeMapType map_;
+};
+
+} // namespace perfprofd
+} // namespace android
+
+#endif // SYSTEM_EXTRAS_PERFPROFD_MAP_UTILS_H_
diff --git a/perfprofd/perf_data_converter.cc b/perfprofd/perf_data_converter.cc
new file mode 100644
index 00000000..ea560f11
--- /dev/null
+++ b/perfprofd/perf_data_converter.cc
@@ -0,0 +1,197 @@
+
+#include "perf_data_converter.h"
+
+#include <algorithm>
+#include <limits>
+#include <map>
+#include <memory>
+#include <set>
+#include <unordered_map>
+
+#include <android-base/logging.h>
+#include <android-base/macros.h>
+#include <android-base/strings.h>
+#include <perf_data_utils.h>
+#include <perf_parser.h>
+#include <perf_protobuf_io.h>
+
+#include "perfprofd_record.pb.h"
+#include "perf_data.pb.h"
+
+#include "map_utils.h"
+#include "quipper_helper.h"
+#include "symbolizer.h"
+
+using std::map;
+
+namespace android {
+namespace perfprofd {
+
+namespace {
+
+void AddSymbolInfo(PerfprofdRecord* record,
+ ::quipper::PerfParser& perf_parser,
+ ::perfprofd::Symbolizer* symbolizer,
+ bool symbolize_everything) {
+ std::unordered_set<std::string> filenames_w_build_id;
+ if (!symbolize_everything) {
+ for (auto& perf_build_id : record->build_ids()) {
+ filenames_w_build_id.insert(perf_build_id.filename());
+ }
+ }
+
+ std::unordered_set<std::string> files_wo_build_id;
+ {
+ quipper::MmapEventIterator it(*record);
+ for (; it != it.end(); ++it) {
+ const ::quipper::PerfDataProto_MMapEvent* mmap_event = &it->mmap_event();
+ if (!mmap_event->has_filename() || !mmap_event->has_start() || !mmap_event->has_len()) {
+ // Don't care.
+ continue;
+ }
+ if (filenames_w_build_id.count(mmap_event->filename()) == 0) {
+ files_wo_build_id.insert(mmap_event->filename());
+ }
+ }
+ }
+ if (files_wo_build_id.empty()) {
+ return;
+ }
+
+ struct Dso {
+ uint64_t min_vaddr;
+ RangeMap<std::string, uint64_t> symbols;
+ explicit Dso(uint64_t min_vaddr_in) : min_vaddr(min_vaddr_in) {
+ }
+ };
+ std::unordered_map<std::string, Dso> files;
+
+ auto it = record->events().begin();
+ auto end = record->events().end();
+ auto parsed_it = perf_parser.parsed_events().begin();
+ auto parsed_end = perf_parser.parsed_events().end();
+ for (; it != end; ++it, ++parsed_it) {
+ CHECK(parsed_it != parsed_end);
+ if (!it->has_sample_event()) {
+ continue;
+ }
+
+ const ::quipper::PerfDataProto_SampleEvent& sample_event = it->sample_event();
+
+ if (android::base::kEnableDChecks) {
+ // Check that the parsed_event and sample_event are consistent.
+ CHECK_EQ(parsed_it->callchain.size(), sample_event.callchain_size());
+ }
+
+ auto check_address = [&](const std::string& dso_name, uint64_t offset) {
+ if (files_wo_build_id.count(dso_name) == 0) {
+ return;
+ }
+
+ // OK, that's a hit in the mmap segment (w/o build id).
+
+ Dso* dso_data;
+ {
+ auto dso_it = files.find(dso_name);
+ constexpr uint64_t kNoMinAddr = std::numeric_limits<uint64_t>::max();
+ if (dso_it == files.end()) {
+ uint64_t min_vaddr;
+ bool has_min_vaddr = symbolizer->GetMinExecutableVAddr(dso_name, &min_vaddr);
+ if (!has_min_vaddr) {
+ min_vaddr = kNoMinAddr;
+ }
+ auto it = files.emplace(dso_name, Dso(min_vaddr));
+ dso_data = &it.first->second;
+ } else {
+ dso_data = &dso_it->second;
+ }
+ if (dso_data->min_vaddr == kNoMinAddr) {
+ return;
+ }
+ }
+
+ // TODO: Is min_vaddr necessary here?
+ const uint64_t file_addr = offset;
+
+ std::string symbol = symbolizer->Decode(dso_name, file_addr);
+ if (symbol.empty()) {
+ return;
+ }
+
+ dso_data->symbols.Insert(symbol, file_addr);
+ };
+ if (sample_event.has_ip() && parsed_it->dso_and_offset.dso_info_ != nullptr) {
+ check_address(parsed_it->dso_and_offset.dso_info_->name, parsed_it->dso_and_offset.offset_);
+ }
+ if (sample_event.callchain_size() > 0) {
+ for (auto& callchain_data: parsed_it->callchain) {
+ if (callchain_data.dso_info_ == nullptr) {
+ continue;
+ }
+ check_address(callchain_data.dso_info_->name, callchain_data.offset_);
+ }
+ }
+ }
+
+ if (!files.empty()) {
+ // We have extra symbol info, create proto messages now.
+ size_t symbol_info_index = 0;
+ for (auto& file_data : files) {
+ const std::string& filename = file_data.first;
+ const Dso& dso = file_data.second;
+ if (dso.symbols.empty()) {
+ continue;
+ }
+
+ auto* symbol_info = record->AddExtension(::quipper::symbol_info);
+ symbol_info->set_filename(filename);
+ symbol_info->set_filename_md5_prefix(::quipper::Md5Prefix(filename));
+ symbol_info->set_min_vaddr(dso.min_vaddr);
+ for (auto& aggr_sym : dso.symbols) {
+ auto* symbol = symbol_info->add_symbols();
+ symbol->set_addr(*aggr_sym.second.offsets.begin());
+ symbol->set_size(*aggr_sym.second.offsets.rbegin() - *aggr_sym.second.offsets.begin() + 1);
+ symbol->set_name(aggr_sym.second.symbol);
+ symbol->set_name_md5_prefix(::quipper::Md5Prefix(aggr_sym.second.symbol));
+ }
+
+ ++symbol_info_index;
+ }
+ }
+}
+
+} // namespace
+
+PerfprofdRecord*
+RawPerfDataToAndroidPerfProfile(const string &perf_file,
+ ::perfprofd::Symbolizer* symbolizer,
+ bool symbolize_everything) {
+ std::unique_ptr<PerfprofdRecord> ret(new PerfprofdRecord());
+ ret->SetExtension(::quipper::id, 0); // TODO.
+
+ ::quipper::PerfParserOptions options = {};
+ options.do_remap = true;
+ options.discard_unused_events = true;
+ options.read_missing_buildids = true;
+
+ ::quipper::PerfReader reader;
+ if (!reader.ReadFile(perf_file)) return nullptr;
+
+ ::quipper::PerfParser parser(&reader, options);
+ if (!parser.ParseRawEvents()) return nullptr;
+
+ if (!reader.Serialize(ret.get())) return nullptr;
+
+ // Append parser stats to protobuf.
+ ::quipper::PerfSerializer::SerializeParserStats(parser.stats(), ret.get());
+
+ // TODO: Symbolization.
+ if (symbolizer != nullptr) {
+ AddSymbolInfo(ret.get(), parser, symbolizer, symbolize_everything);
+ }
+
+ return ret.release();
+}
+
+} // namespace perfprofd
+} // namespace android
diff --git a/perfprofd/perf_data_converter.h b/perfprofd/perf_data_converter.h
new file mode 100644
index 00000000..8b4ab9ff
--- /dev/null
+++ b/perfprofd/perf_data_converter.h
@@ -0,0 +1,23 @@
+#ifndef WIRELESS_ANDROID_LOGGING_AWP_PERF_DATA_CONVERTER_H_
+#define WIRELESS_ANDROID_LOGGING_AWP_PERF_DATA_CONVERTER_H_
+
+#include <string>
+
+#include "perfprofd_record-fwd.h"
+
+namespace perfprofd {
+struct Symbolizer;
+} // namespace perfprofd
+
+namespace android {
+namespace perfprofd {
+
+PerfprofdRecord*
+RawPerfDataToAndroidPerfProfile(const std::string &perf_file,
+ ::perfprofd::Symbolizer* symbolizer,
+ bool symbolize_everything);
+
+} // namespace perfprofd
+} // namespace android
+
+#endif // WIRELESS_ANDROID_LOGGING_AWP_PERF_DATA_CONVERTER_H_
diff --git a/perfprofd/perf_profile.proto b/perfprofd/perf_profile.proto
new file mode 100644
index 00000000..65c9c39a
--- /dev/null
+++ b/perfprofd/perf_profile.proto
@@ -0,0 +1,131 @@
+
+syntax = "proto2";
+
+option java_package = "com.google.common.logging";
+
+option optimize_for = LITE_RUNTIME;
+
+package wireless_android_play_playlog;
+
+// An entry of the map from a stack of addresses to count.
+// Address here is the offset of the instruction address to the load address
+// of the load_module.
+message AddressSample {
+ // List of addresses that represents a call stack.
+ // address[0] is the leaf of the call stack.
+ repeated uint64 address = 1;
+
+ // List of load_module_ids that represents a call stack.
+ // load_module_id[0] is the leaf of the call stack.
+ // This field can be set as empty if all frame share the same load_module_id
+ // with LoadModuleSamples.load_module_id.
+ repeated int32 load_module_id = 2;
+
+ // Total count that the address/address_range is sampled.
+ optional int64 count = 3;
+};
+
+// An entry of the map from address_range to count.
+// [start, end] represents the range of addresses, end->to represents the
+// taken branch that ends the range.
+message RangeSample {
+ // Start instruction address of a range.
+ optional uint64 start = 1;
+
+ // If "end" and "to" is not provided, "start" represents a single instruction.
+ optional uint64 end = 2;
+ optional uint64 to = 3;
+
+ // Total count that the address/address_range is sampled.
+ optional int64 count = 4;
+};
+
+// A load module.
+message LoadModule {
+ // Name of the load_module.
+ optional string name = 1;
+
+ // LoadModule's linker build_id.
+ optional string build_id = 2;
+
+ // On-device symbolized entries.
+ repeated string symbol = 3;
+}
+
+// All samples for a load_module.
+message LoadModuleSamples {
+ optional int32 load_module_id = 1;
+
+ // Map from a stack of addresses to count.
+ repeated AddressSample address_samples = 2;
+
+ // Map from a range triplet (start, end, to) to count.
+ repeated RangeSample range_samples = 3;
+}
+
+// A table of program names.
+message ProcessNames {
+ repeated string name = 1;
+}
+
+// All samples for a program.
+message ProgramSamples {
+ // Name of the program.
+ optional string name = 1;
+
+ // Load module profiles.
+ repeated LoadModuleSamples modules = 2;
+
+ // Index into ProcessNames for the name of the process.
+ optional uint32 process_name_id = 3;
+}
+
+// A compressed representation of a perf profile, which contains samples from
+// multiple binaries.
+message AndroidPerfProfile {
+
+ // Type of the hardware event.
+ enum EventType {
+ CYCLE = 0;
+ BRANCH = 1;
+ }
+ // Hardware event used in profiling.
+ optional EventType event = 1;
+
+ // Total number of samples in this profile.
+ // This is the sum of counts of address_samples and range_samples in all
+ // load_module_samples.
+ optional int64 total_samples = 2;
+
+ // Samples for all profiled programs.
+ repeated ProgramSamples programs = 3;
+
+ // List of all load modules.
+ repeated LoadModule load_modules = 4;
+
+ // Table of process names.
+ optional ProcessNames process_names = 11;
+
+ // is device screen on at point when profile is collected?
+ optional bool display_on = 5;
+
+ // system load at point when profile is collected; corresponds
+ // to first value from /proc/loadavg multiplied by 100 then
+ // converted to int32
+ optional int32 sys_load_average = 6;
+
+ // At the point when the profile was collected, was a camera active?
+ optional bool camera_active = 7;
+
+ // At the point when the profile was collected, was the device still booting?
+ optional bool booting = 8;
+
+ // At the point when the profile was collected, was the device plugged into
+ // a charger?
+ optional bool on_charger = 9;
+
+ // CPU utilization measured prior to profile collection (expressed as
+ // 100 minus the idle percentage).
+ optional int32 cpu_utilization = 10;
+
+}
diff --git a/perfprofd/perfprofd.conf b/perfprofd/perfprofd.conf
new file mode 100644
index 00000000..696c3de5
--- /dev/null
+++ b/perfprofd/perfprofd.conf
@@ -0,0 +1,24 @@
+#
+# Configuration file for perf profile collection daemon (perfprofd)
+#
+#------------------------------------------------------------------------
+#
+# Destination directory for profiles
+#
+destination_directory=/data/misc/perfprofd
+#
+# Config directory for perfprofd
+#
+config_directory=/data/data/com.google.android.gms/files
+#
+# Sampling period (for perf -c option)
+#
+sampling_period=500000
+#
+# Average interval to wait between profile collection attempts (seconds)
+#
+collection_interval=86400
+#
+# Number of seconds of profile data to collect
+#
+sample_duration=3
diff --git a/perfprofd/perfprofd.rc b/perfprofd/perfprofd.rc
new file mode 100644
index 00000000..937c407b
--- /dev/null
+++ b/perfprofd/perfprofd.rc
@@ -0,0 +1,5 @@
+service perfprofd /system/bin/perfprofd --binder
+ class late_start
+ user root
+ group root wakelock
+ writepid /dev/cpuset/system-background/tasks
diff --git a/perfprofd/perfprofd_cmdline.cc b/perfprofd/perfprofd_cmdline.cc
new file mode 100644
index 00000000..2c958c0a
--- /dev/null
+++ b/perfprofd/perfprofd_cmdline.cc
@@ -0,0 +1,258 @@
+/*
+ *
+ * Copyright 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfprofd_cmdline.h"
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <set>
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/macros.h>
+#include <android-base/stringprintf.h>
+
+#include "perfprofd_record.pb.h"
+
+#include "configreader.h"
+#include "dropbox.h"
+#include "perfprofdcore.h"
+#include "perfprofd_io.h"
+
+//
+// Perf profiling daemon -- collects system-wide profiles using
+//
+// simpleperf record -a
+//
+// and encodes them so that they can be uploaded by a separate service.
+//
+
+//
+
+//
+// Output file from 'perf record'.
+//
+#define PERF_OUTPUT "perf.data"
+
+//
+// Path to the perf file to convert and exit? Empty value is the default, daemon mode.
+//
+static std::string perf_file_to_convert = "";
+
+//
+// SIGHUP handler. Sending SIGHUP to the daemon can be used to break it
+// out of a sleep() call so as to trigger a new collection (debugging)
+//
+static void sig_hup(int /* signum */)
+{
+ LOG(WARNING) << "SIGHUP received";
+}
+
+//
+// Parse command line args. Currently supported flags:
+// * "-c PATH" sets the path of the config file to PATH.
+// * "-x PATH" reads PATH as a perf data file and saves it as a file in
+// perf_profile.proto format. ".encoded" suffix is appended to PATH to form
+// the output file path.
+//
+static void parse_args(int argc, char** argv)
+{
+ int ac;
+
+ for (ac = 1; ac < argc; ++ac) {
+ if (!strcmp(argv[ac], "-c")) {
+ if (ac >= argc-1) {
+ LOG(ERROR) << "malformed command line: -c option requires argument)";
+ continue;
+ }
+ ConfigReader::setConfigFilePath(argv[ac+1]);
+ ++ac;
+ } else if (!strcmp(argv[ac], "-x")) {
+ if (ac >= argc-1) {
+ LOG(ERROR) << "malformed command line: -x option requires argument)";
+ continue;
+ }
+ perf_file_to_convert = argv[ac+1];
+ ++ac;
+ } else {
+ LOG(ERROR) << "malformed command line: unknown option or arg " << argv[ac] << ")";
+ continue;
+ }
+ }
+}
+
+//
+// Post-processes after profile is collected and converted to protobuf.
+// * GMS core stores processed file sequence numbers in
+// /data/data/com.google.android.gms/files/perfprofd_processed.txt
+// * Update /data/misc/perfprofd/perfprofd_produced.txt to remove the sequence
+// numbers that have been processed and append the current seq number
+// Returns true if the current_seq should increment.
+//
+static bool post_process(const Config& config, int current_seq)
+{
+ const std::string& dest_dir = config.destination_directory;
+ std::string processed_file_path =
+ config.config_directory + "/" + PROCESSED_FILENAME;
+ std::string produced_file_path = dest_dir + "/" + PRODUCED_FILENAME;
+
+
+ std::set<int> processed;
+ FILE *fp = fopen(processed_file_path.c_str(), "r");
+ if (fp != NULL) {
+ int seq;
+ while(fscanf(fp, "%d\n", &seq) > 0) {
+ if (remove(android::base::StringPrintf(
+ "%s/perf.data.encoded.%d", dest_dir.c_str(),seq).c_str()) == 0) {
+ processed.insert(seq);
+ }
+ }
+ fclose(fp);
+ }
+
+ std::set<int> produced;
+ fp = fopen(produced_file_path.c_str(), "r");
+ if (fp != NULL) {
+ int seq;
+ while(fscanf(fp, "%d\n", &seq) > 0) {
+ if (processed.find(seq) == processed.end()) {
+ produced.insert(seq);
+ }
+ }
+ fclose(fp);
+ }
+
+ uint32_t maxLive = config.max_unprocessed_profiles;
+ if (produced.size() >= maxLive) {
+ return false;
+ }
+
+ produced.insert(current_seq);
+ fp = fopen(produced_file_path.c_str(), "w");
+ if (fp == NULL) {
+ PLOG(WARNING) << "Cannot write " << produced_file_path;
+ return false;
+ }
+ for (std::set<int>::const_iterator iter = produced.begin();
+ iter != produced.end(); ++iter) {
+ fprintf(fp, "%d\n", *iter);
+ }
+ fclose(fp);
+ chmod(produced_file_path.c_str(),
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
+ return true;
+}
+
+//
+// Initialization
+//
+
+static void init(ConfigReader &config)
+{
+ if (!config.readFile()) {
+ LOG(ERROR) << "unable to open configuration file " << config.getConfigFilePath();
+ }
+
+ CommonInit(static_cast<uint32_t>(config.getUnsignedValue("use_fixed_seed")),
+ config.getStringValue("destination_directory").c_str());
+
+ signal(SIGHUP, sig_hup);
+}
+
+//
+// Main routine:
+// 1. parse cmd line args
+// 2. read config file
+// 3. loop: {
+// sleep for a while
+// perform a profile collection
+// }
+//
+int perfprofd_main(int argc, char** argv, Config* config)
+{
+ LOG(INFO) << "starting Android Wide Profiling daemon";
+
+ parse_args(argc, argv);
+ {
+ ConfigReader config_reader;
+ init(config_reader);
+ config_reader.FillConfig(config);
+ }
+ GlobalInit(config->perf_path);
+
+ if (!perf_file_to_convert.empty()) {
+ std::string encoded_path = perf_file_to_convert + ".encoded";
+ encode_to_proto(perf_file_to_convert, encoded_path.c_str(), *config, 0, nullptr);
+ return 0;
+ }
+
+ // Early exit if we're not supposed to run on this build flavor
+ if (!IsDebugBuild() && config->only_debug_build) {
+ LOG(INFO) << "early exit due to inappropriate build type";
+ return 0;
+ }
+
+ auto config_fn = [config]() {
+ return config;
+ };
+ auto reread_config = [config]() {
+ // Reread config file -- the uploader may have rewritten it.
+ ConfigReader config_reader;
+ if (config_reader.readFile()) {
+ config_reader.FillConfig(config);
+ }
+ };
+ int seq = 0;
+ auto handler = [&seq](android::perfprofd::PerfprofdRecord* proto, Config* handler_config) {
+ if (proto == nullptr) {
+ return false;
+ }
+ if (handler_config->send_to_dropbox) {
+ std::string error_msg;
+ if (!android::perfprofd::dropbox::SendToDropbox(proto,
+ handler_config->destination_directory,
+ &error_msg)) {
+ LOG(ERROR) << "Failed dropbox submission: " << error_msg;
+ return false;
+ }
+ } else {
+ std::string data_file_path(handler_config->destination_directory);
+ data_file_path += "/";
+ data_file_path += PERF_OUTPUT;
+ std::string path = android::base::StringPrintf("%s.encoded.%d", data_file_path.c_str(), seq);
+ if (!android::perfprofd::SerializeProtobuf(proto, path.c_str(), handler_config->compress)) {
+ return false;
+ }
+ if (!post_process(*handler_config, seq)) {
+ return false;
+ }
+ }
+ seq++;
+ return true;
+ };
+ ProfilingLoop(config_fn, reread_config, handler);
+
+ LOG(INFO) << "finishing Android Wide Profiling daemon";
+ return 0;
+}
diff --git a/perfprofd/perfprofd_cmdline.h b/perfprofd/perfprofd_cmdline.h
new file mode 100644
index 00000000..5a6b766c
--- /dev/null
+++ b/perfprofd/perfprofd_cmdline.h
@@ -0,0 +1,39 @@
+/*
+ *
+ * Copyright 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SYSTEM_EXTRAS_PERFPROFD_PERFPROFD_CMDLINE_H_
+#define SYSTEM_EXTRAS_PERFPROFD_PERFPROFD_CMDLINE_H_
+
+// Semaphore file that indicates that the user is opting in
+#define SEMAPHORE_FILENAME "perf_profile_collection_enabled.txt"
+
+// File containing a list of sequence numbers corresponding to profiles
+// that have been processed/uploaded. Written by the GmsCore uploader,
+// within the GmsCore files directory.
+#define PROCESSED_FILENAME "perfprofd_processed.txt"
+
+// File containing a list of sequence numbers corresponding to profiles
+// that have been created by the perfprofd but not yet uploaded. Written
+// by perfprofd within the destination directory; consumed by GmsCore.
+#define PRODUCED_FILENAME "perfprofd_produced.txt"
+
+struct Config;
+
+// Main routine for perfprofd daemon
+int perfprofd_main(int argc, char **argv, Config* config);
+
+#endif // SYSTEM_EXTRAS_PERFPROFD_PERFPROFD_CMDLINE_H_
diff --git a/perfprofd/perfprofd_config.proto b/perfprofd/perfprofd_config.proto
new file mode 100644
index 00000000..3702b8d8
--- /dev/null
+++ b/perfprofd/perfprofd_config.proto
@@ -0,0 +1,96 @@
+
+syntax = "proto2";
+
+option java_package = "android.perfprofd";
+
+package android.perfprofd;
+
+message PerfConfigElement {
+ repeated string counters = 1;
+ optional bool as_group = 2 [ default = false ];
+ optional uint32 sampling_period = 3;
+};
+
+// The configuration for a profiling session.
+message ProfilingConfig {
+ // Average number of seconds between perf profile collections (if
+ // set to 100, then over time we want to see a perf profile
+ // collected every 100 seconds). The actual time within the interval
+ // for the collection is chosen randomly.
+ optional uint32 collection_interval_in_s = 1;
+
+ // Use the specified fixed seed for random number generation (unit
+ // testing)
+ optional uint32 use_fixed_seed = 2;
+
+ // Number of times to iterate through main
+ // loop. Value of zero indicates that we should loop forever.
+ optional uint32 main_loop_iterations = 3;
+
+ // Destination directory (where to write profiles).
+ optional string destination_directory = 4;
+ // Config directory (where to read configs).
+ optional string config_directory = 5;
+ // Full path to 'perf' executable.
+ optional string perf_path = 6;
+
+ // Desired sampling period (passed to perf -c option). Small
+ // sampling periods can perturb the collected profiles, so enforce
+ // min/max. A value of 0 means perf default. sampling_frequency
+ // takes priority.
+ optional uint32 sampling_period = 7;
+ // Desired sampling frequency (passed to perf -f option). A value of 0
+ // means using sampling_period or default.
+ optional uint32 sampling_frequency = 22;
+ // Length of time to collect samples (number of seconds for 'perf
+ // record -a' run).
+ optional uint32 sample_duration_in_s = 8;
+
+ // If this parameter is non-zero it will cause perfprofd to
+ // exit immediately if the build type is not userdebug or eng.
+ // Currently defaults to 1 (true).
+ optional bool only_debug_build = 9;
+
+ // If the "mpdecision" service is running at the point we are ready
+ // to kick off a profiling run, then temporarily disable the service
+ // and hard-wire all cores on prior to the collection run, provided
+ // that the duration of the recording is less than or equal to the value of
+ // 'hardwire_cpus_max_duration'.
+ optional bool hardwire_cpus = 10;
+ optional uint32 hardwire_cpus_max_duration_in_s = 11;
+
+ // Maximum number of unprocessed profiles we can accumulate in the
+ // destination directory. Once we reach this limit, we continue
+ // to collect, but we just overwrite the most recent profile.
+ optional uint32 max_unprocessed_profiles = 12;
+
+ // If set to 1, pass the -g option when invoking 'perf' (requests
+ // stack traces as opposed to flat profile).
+ optional bool stack_profile = 13;
+
+ // Control collection of various additional profile tags
+ optional bool collect_cpu_utilization = 14;
+ optional bool collect_charging_state = 15;
+ optional bool collect_booting = 16;
+ optional bool collect_camera_active = 17;
+
+ // The pid of the process to profile. May be negative, in which case
+ // the whole system will be profiled.
+ optional int32 process = 18;
+
+ // Whether to use a symbolizer on-device.
+ optional bool use_elf_symbolizer = 19;
+ // Whether to symbolize everything. If false, objects with build ID will be skipped.
+ optional bool symbolize_everything = 25;
+
+ // Whether to send the result to dropbox.
+ optional bool send_to_dropbox = 20;
+
+ // If true, use libz to compress the output proto.
+ optional bool compress = 21;
+
+ // Whether to fail or strip unsupported events.
+ optional bool fail_on_unsupported_events = 24;
+
+ repeated PerfConfigElement event_config = 23;
+};
diff --git a/perfprofd/perfprofd_io.cc b/perfprofd/perfprofd_io.cc
new file mode 100644
index 00000000..d88cae4a
--- /dev/null
+++ b/perfprofd/perfprofd_io.cc
@@ -0,0 +1,310 @@
+/*
+**
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#include "perfprofd_io.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <memory>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/macros.h>
+#include <android-base/stringprintf.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <zlib.h>
+
+#include "perfprofd_record.pb.h"
+
+namespace android {
+namespace perfprofd {
+
+using android::base::StringPrintf;
+using android::base::unique_fd;
+using android::base::WriteFully;
+
+namespace {
+
+// Protobuf's file implementation is not available in protobuf-lite. :-(
+class FileCopyingOutputStream : public ::google::protobuf::io::CopyingOutputStream {
+ public:
+ explicit FileCopyingOutputStream(android::base::unique_fd&& fd_in) : fd_(std::move(fd_in)) {
+ };
+ bool Write(const void * buffer, int size) override {
+ return WriteFully(fd_.get(), buffer, size);
+ }
+
+ private:
+ android::base::unique_fd fd_;
+};
+
+using google::protobuf::io::ZeroCopyOutputStream;
+
+// Protobuf's Gzip implementation is not available in protobuf-lite. :-(
+class GzipOutputStream : public ZeroCopyOutputStream {
+ public:
+ ~GzipOutputStream();
+
+ static std::unique_ptr<GzipOutputStream> Create(ZeroCopyOutputStream* next,
+ std::string* error_msg);
+
+ bool Next(void** data, int* size) override;
+
+ void BackUp(int count) override;
+
+ google::protobuf::int64 ByteCount() const override;
+
+ bool WriteAliasedRaw(const void* data, int size) override;
+ bool AllowsAliasing() const override;
+
+ bool Flush();
+ bool Close();
+
+ private:
+ GzipOutputStream(ZeroCopyOutputStream* next, z_stream* stream);
+
+ int Write(int flush_flags);
+ bool NextBuffer();
+
+ ZeroCopyOutputStream* next_;
+ void* next_data_;
+ int next_size_;
+
+ z_stream* stream_;
+ std::unique_ptr<uint8_t[]> stream_buffer_;
+ bool had_error_;
+};
+
+constexpr size_t kStreamBufferSize = 16u * 1024u;
+
+GzipOutputStream::GzipOutputStream(ZeroCopyOutputStream* next, z_stream* stream)
+ : next_(next),
+ next_data_(nullptr),
+ next_size_(0),
+ stream_(stream),
+ stream_buffer_(nullptr),
+ had_error_(false) {
+}
+
+GzipOutputStream::~GzipOutputStream() {
+ if (stream_ != nullptr) {
+ deflateEnd(stream_);
+ delete stream_;
+ stream_ = nullptr;
+ }
+}
+
+bool GzipOutputStream::WriteAliasedRaw(const void* data ATTRIBUTE_UNUSED,
+ int size ATTRIBUTE_UNUSED) {
+ LOG(FATAL) << "Not supported";
+ __builtin_unreachable();
+}
+bool GzipOutputStream::AllowsAliasing() const {
+ return false;
+}
+
+google::protobuf::int64 GzipOutputStream::ByteCount() const {
+ return stream_->total_in + stream_->avail_in;
+}
+
+std::unique_ptr<GzipOutputStream> GzipOutputStream::Create(ZeroCopyOutputStream* next,
+ std::string* error_msg) {
+ std::unique_ptr<z_stream> stream(new z_stream);
+
+ stream->zalloc = Z_NULL;
+ stream->zfree = Z_NULL;
+ stream->opaque = Z_NULL;
+ stream->msg = nullptr;
+ stream->avail_in = 0;
+ stream->total_in = 0;
+ stream->next_in = nullptr;
+ stream->total_out = 0;
+
+ {
+ constexpr int kWindowBits = 15;
+ constexpr int kGzipEncoding = 16;
+ constexpr int kMemLevel = 8; // Default.
+ int init_result = deflateInit2(stream.get(),
+ Z_DEFAULT_COMPRESSION,
+ Z_DEFLATED,
+ kWindowBits | kGzipEncoding,
+ kMemLevel,
+ Z_DEFAULT_STRATEGY);
+ if (init_result != Z_OK) {
+ *error_msg = StringPrintf("Could not initialize compression: %d (%s)",
+ init_result,
+ stream->msg != nullptr ? stream->msg : "no message");
+ return nullptr;
+ }
+ }
+
+ return std::unique_ptr<GzipOutputStream>(new GzipOutputStream(next, stream.release()));
+}
+
+bool GzipOutputStream::NextBuffer() {
+ for (;;) {
+ if (!next_->Next(&next_data_, &next_size_)) {
+ next_data_ = nullptr;
+ next_size_ = 0;
+ return false;
+ }
+ if (next_size_ == 0) {
+ continue;
+ }
+ stream_->next_out = static_cast<Bytef*>(next_data_);
+ stream_->avail_out = next_size_;
+ return true;
+ }
+}
+
+int GzipOutputStream::Write(int flush_flags) {
+ CHECK(flush_flags == Z_NO_FLUSH || flush_flags == Z_FULL_FLUSH || flush_flags == Z_FINISH);
+
+ int res;
+ do {
+ if ((next_data_ == nullptr || stream_->avail_out == 0) && !NextBuffer()) {
+ return Z_BUF_ERROR;
+ }
+ res = deflate(stream_, flush_flags);
+ } while (res == Z_OK && stream_->avail_out == 0);
+
+ if (flush_flags == Z_FULL_FLUSH || flush_flags == Z_FINISH) {
+ next_->BackUp(stream_->avail_out);
+ next_data_ = nullptr;
+ next_size_ = 0;
+ }
+
+ return res;
+}
+
+bool GzipOutputStream::Next(void** data, int* size) {
+ if (had_error_) {
+ return false;
+ }
+
+ // Write all pending data.
+ if (stream_->avail_in > 0) {
+ int write_error = Write(Z_NO_FLUSH);
+ if (write_error != Z_OK) {
+ had_error_ = true;
+ return false;
+ }
+ CHECK_EQ(stream_->avail_in, 0);
+ }
+
+ if (stream_buffer_ == nullptr) {
+ stream_buffer_.reset(new uint8_t[kStreamBufferSize]);
+ }
+
+ stream_->next_in = static_cast<Bytef*>(stream_buffer_.get());
+ stream_->avail_in = kStreamBufferSize;
+ *data = stream_buffer_.get();
+ *size = kStreamBufferSize;
+ return true;
+}
+
+void GzipOutputStream::BackUp(int count) {
+ CHECK_GE(stream_->avail_in, count);
+ stream_->avail_in -= count;
+}
+
+bool GzipOutputStream::Flush() {
+ if (had_error_) {
+ return false;
+ }
+
+ int res = Write(Z_FULL_FLUSH);
+ had_error_ |= (res != Z_OK)
+ && !(res == Z_BUF_ERROR && stream_->avail_in == 0 && stream_->avail_out > 0);
+ return !had_error_;
+}
+
+bool GzipOutputStream::Close() {
+ if (had_error_) {
+ return false;
+ }
+
+ {
+ int res;
+ do {
+ res = Write(Z_FINISH);
+ } while (res == Z_OK);
+ }
+
+ int res = deflateEnd(stream_);
+ delete stream_;
+ stream_ = nullptr;
+
+ had_error_ = true; // Pretend an error so no other operations succeed.
+
+ return res == Z_OK;
+}
+
+} // namespace
+
+bool SerializeProtobuf(android::perfprofd::PerfprofdRecord* encodedProfile,
+ android::base::unique_fd&& fd,
+ bool compress) {
+ FileCopyingOutputStream fcos(std::move(fd));
+ google::protobuf::io::CopyingOutputStreamAdaptor cosa(&fcos);
+
+ ZeroCopyOutputStream* out;
+
+ std::unique_ptr<GzipOutputStream> gzip;
+ if (compress) {
+ std::string error_msg;
+ gzip = GzipOutputStream::Create(&cosa, &error_msg);
+ if (gzip == nullptr) {
+ LOG(ERROR) << error_msg;
+ return false;
+ }
+ out = gzip.get();
+ } else {
+ out = &cosa;
+ }
+
+ bool serialized = encodedProfile->SerializeToZeroCopyStream(out);
+ if (!serialized) {
+ LOG(WARNING) << "SerializeToZeroCopyStream failed";
+ return false;
+ }
+
+ bool zip_ok = true;
+ if (gzip != nullptr) {
+ zip_ok = gzip->Flush();
+ zip_ok = gzip->Close() && zip_ok;
+ }
+ cosa.Flush();
+ return zip_ok;
+}
+
+bool SerializeProtobuf(PerfprofdRecord* encodedProfile,
+ const char* encoded_file_path,
+ bool compress) {
+ unlink(encoded_file_path); // Attempt to unlink for a clean slate.
+ constexpr int kFlags = O_CREAT | O_WRONLY | O_TRUNC | O_NOFOLLOW | O_CLOEXEC;
+ unique_fd fd(open(encoded_file_path, kFlags, 0664));
+ if (fd.get() == -1) {
+ PLOG(WARNING) << "Could not open " << encoded_file_path << " for serialization";
+ return false;
+ }
+ return SerializeProtobuf(encodedProfile, std::move(fd), compress);
+}
+
+} // namespace perfprofd
+} // namespace android
diff --git a/perfprofd/perfprofd_io.h b/perfprofd/perfprofd_io.h
new file mode 100644
index 00000000..3e754b54
--- /dev/null
+++ b/perfprofd/perfprofd_io.h
@@ -0,0 +1,38 @@
+/*
+**
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#ifndef SYSTEM_EXTRAS_PERFPROFD_PERFPROFD_IO_H_
+#define SYSTEM_EXTRAS_PERFPROFD_PERFPROFD_IO_H_
+
+#include <android-base/unique_fd.h>
+
+#include "perfprofd_record-fwd.h"
+
+namespace android {
+namespace perfprofd {
+
+bool SerializeProtobuf(android::perfprofd::PerfprofdRecord* encodedProfile,
+ const char* encoded_file_path,
+ bool compress = true);
+bool SerializeProtobuf(android::perfprofd::PerfprofdRecord* encodedProfile,
+ android::base::unique_fd&& fd,
+ bool compress = true);
+
+} // namespace perfprofd
+} // namespace android
+
+#endif
diff --git a/perfprofd/perfprofd_perf.cc b/perfprofd/perfprofd_perf.cc
new file mode 100644
index 00000000..15dde6fb
--- /dev/null
+++ b/perfprofd/perfprofd_perf.cc
@@ -0,0 +1,332 @@
+/*
+**
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#include "perfprofd_perf.h"
+
+
+#include <inttypes.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <cerrno>
+#include <cstdio>
+#include <cstring>
+#include <memory>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+
+#include "config.h"
+
+namespace android {
+namespace perfprofd {
+
+namespace {
+
+std::unordered_set<std::string>& GetSupportedPerfCountersInternal() {
+ static std::unordered_set<std::string>& vec = *new std::unordered_set<std::string>();
+ return vec;
+}
+
+} // namespace
+
+//
+// Invoke "perf record". Return value is OK_PROFILE_COLLECTION for
+// success, or some other error code if something went wrong.
+//
+PerfResult InvokePerf(Config& config,
+ const std::string &perf_path,
+ const char *stack_profile_opt,
+ unsigned duration,
+ const std::string &data_file_path,
+ const std::string &perf_stderr_path)
+{
+ std::vector<std::string> argv_backing;
+ std::vector<const char*> argv_vector;
+ char paranoid_env[] = "PERFPROFD_DISABLE_PERF_EVENT_PARANOID_CHANGE=1";
+ char* envp[2] = {paranoid_env, nullptr};
+
+ {
+ auto add = [&argv_backing](auto arg) {
+ argv_backing.push_back(arg);
+ };
+
+ add(perf_path);
+ add("record");
+
+ // -o perf.data
+ add("-o");
+ add(data_file_path);
+
+ // -c/f N
+ std::string p_str;
+ if (config.sampling_frequency > 0) {
+ add("-f");
+ add(android::base::StringPrintf("%u", config.sampling_frequency));
+ } else if (config.sampling_period > 0) {
+ add("-c");
+ add(android::base::StringPrintf("%u", config.sampling_period));
+ }
+
+ if (!config.event_config.empty()) {
+ const std::unordered_set<std::string>& supported = GetSupportedPerfCountersInternal();
+ for (const auto& event_set : config.event_config) {
+ if (event_set.events.empty()) {
+ LOG(WARNING) << "Unexpected empty event set";
+ continue;
+ }
+
+ std::ostringstream event_str;
+ bool added = false;
+ for (const std::string& event : event_set.events) {
+ if (supported.find(event) == supported.end()) {
+ LOG(WARNING) << "Event " << event << " is unsupported.";
+ if (config.fail_on_unsupported_events) {
+ return PerfResult::kUnsupportedEvent;
+ }
+ continue;
+ }
+ if (added) {
+ event_str << ',';
+ }
+ event_str << event;
+ added = true;
+ }
+
+ if (!added) {
+ continue;
+ }
+
+ if (event_set.sampling_period > 0) {
+ add("-c");
+ add(std::to_string(event_set.sampling_period));
+ }
+ add(event_set.group ? "--group" : "-e");
+ add(event_str.str());
+ }
+ }
+
+ // -g if desired
+ if (stack_profile_opt != nullptr) {
+ add(stack_profile_opt);
+ add("-m");
+ add("8192");
+ }
+
+ if (config.process < 0) {
+ // system wide profiling
+ add("-a");
+ } else {
+ add("-p");
+ add(std::to_string(config.process));
+ }
+
+ // no need for kernel or other symbols
+ add("--no-dump-kernel-symbols");
+ add("--no-dump-symbols");
+
+ // sleep <duration>
+ add("--duration");
+ add(android::base::StringPrintf("%u", duration));
+
+
+ // Now create the char* buffer.
+ argv_vector.resize(argv_backing.size() + 1, nullptr);
+ std::transform(argv_backing.begin(),
+ argv_backing.end(),
+ argv_vector.begin(),
+ [](const std::string& in) { return in.c_str(); });
+ }
+
+ pid_t pid = fork();
+
+ if (pid == -1) {
+ PLOG(ERROR) << "Fork failed";
+ return PerfResult::kForkFailed;
+ }
+
+ if (pid == 0) {
+ // child
+
+ // Open file to receive stderr/stdout from perf
+ FILE *efp = fopen(perf_stderr_path.c_str(), "w");
+ if (efp) {
+ dup2(fileno(efp), STDERR_FILENO);
+ dup2(fileno(efp), STDOUT_FILENO);
+ } else {
+ PLOG(WARNING) << "unable to open " << perf_stderr_path << " for writing";
+ }
+
+ // record the final command line in the error output file for
+ // posterity/debugging purposes
+ fprintf(stderr, "perf invocation (pid=%d):\n", getpid());
+ for (unsigned i = 0; argv_vector[i] != nullptr; ++i) {
+ fprintf(stderr, "%s%s", i ? " " : "", argv_vector[i]);
+ }
+ fprintf(stderr, "\n");
+
+ // exec
+ execvpe(argv_vector[0], const_cast<char* const*>(argv_vector.data()), envp);
+ fprintf(stderr, "exec failed: %s\n", strerror(errno));
+ exit(1);
+
+ } else {
+ // parent
+
+ // Try to sleep.
+ config.Sleep(duration);
+
+ // We may have been woken up to stop profiling.
+ if (config.ShouldStopProfiling()) {
+ // Send SIGHUP to simpleperf to make it stop.
+ kill(pid, SIGHUP);
+ }
+
+ // Wait for the child, so it's reaped correctly.
+ int st = 0;
+ pid_t reaped = TEMP_FAILURE_RETRY(waitpid(pid, &st, 0));
+
+ auto print_perferr = [&perf_stderr_path]() {
+ std::string tmp;
+ if (android::base::ReadFileToString(perf_stderr_path, &tmp)) {
+ LOG(WARNING) << tmp;
+ } else {
+ PLOG(WARNING) << "Could not read " << perf_stderr_path;
+ }
+ };
+
+ if (reaped == -1) {
+ PLOG(WARNING) << "waitpid failed";
+ } else if (WIFSIGNALED(st)) {
+ if (WTERMSIG(st) == SIGHUP && config.ShouldStopProfiling()) {
+ // That was us...
+ return PerfResult::kOK;
+ }
+ LOG(WARNING) << "perf killed by signal " << WTERMSIG(st);
+ print_perferr();
+ } else if (WEXITSTATUS(st) != 0) {
+ LOG(WARNING) << "perf bad exit status " << WEXITSTATUS(st);
+ print_perferr();
+ } else {
+ return PerfResult::kOK;
+ }
+ }
+
+ return PerfResult::kRecordFailed;
+}
+
+bool FindSupportedPerfCounters(const std::string& perf_path) {
+ const char* argv[] = { perf_path.c_str(), "list", nullptr };
+ char paranoid_env[] = "PERFPROFD_DISABLE_PERF_EVENT_PARANOID_CHANGE=1";
+ char* envp[2] = {paranoid_env, nullptr};
+
+ base::unique_fd link[2];
+ {
+ int link_fd[2];
+
+ if (pipe(link_fd) == -1) {
+ PLOG(ERROR) << "Pipe failed";
+ return false;
+ }
+ link[0].reset(link_fd[0]);
+ link[1].reset(link_fd[1]);
+ }
+
+ pid_t pid = fork();
+
+ if (pid == -1) {
+ PLOG(ERROR) << "Fork failed";
+ return PerfResult::kForkFailed;
+ }
+
+ if (pid == 0) {
+ // Child
+
+ // Redirect stdout and stderr.
+ dup2(link[1].get(), STDOUT_FILENO);
+ dup2(link[1].get(), STDERR_FILENO);
+
+ link[0].reset();
+ link[1].reset();
+
+ // exec
+ execvpe(argv[0], const_cast<char* const*>(argv), envp);
+ PLOG(WARNING) << "exec failed";
+ exit(1);
+ __builtin_unreachable();
+ }
+
+ link[1].reset();
+
+ std::string result;
+ if (!android::base::ReadFdToString(link[0].get(), &result)) {
+ PLOG(WARNING) << perf_path << " list reading failed.";
+ }
+
+ link[0].reset();
+
+ int status_code;
+ if (waitpid(pid, &status_code, 0) == -1) {
+ LOG(WARNING) << "Failed to wait for " << perf_path << " list";
+ return false;
+ }
+
+ if (!WIFEXITED(status_code) || WEXITSTATUS(status_code) != 0) {
+ LOG(WARNING) << perf_path << " list did not exit normally.";
+ return false;
+ }
+
+ std::unordered_set<std::string>& supported = GetSupportedPerfCountersInternal();
+ supported.clear();
+
+ // Could implement something with less memory requirements. But for now this is good
+ // enough.
+ std::vector<std::string> lines = base::Split(result, "\n");
+ for (const std::string& line : lines) {
+ if (line.length() < 2 || line.compare(0, 2, " ") != 0) {
+ continue;
+ }
+ const size_t comment = line.find('#');
+ const size_t space = line.find(' ', 2);
+ size_t end = std::min(space, comment);
+ if (end != std::string::npos) {
+ // Scan backwards.
+ --end;
+ while (end > 2 && isspace(line[end])) {
+ end--;
+ }
+ }
+ if (end > 2) {
+ supported.insert(line.substr(2, end - 2));
+ }
+ }
+
+ return true;
+}
+
+const std::unordered_set<std::string>& GetSupportedPerfCounters() {
+ return GetSupportedPerfCountersInternal();
+}
+
+} // namespace perfprofd
+} // namespace android
diff --git a/perfprofd/perfprofd_perf.h b/perfprofd/perfprofd_perf.h
new file mode 100644
index 00000000..232ef829
--- /dev/null
+++ b/perfprofd/perfprofd_perf.h
@@ -0,0 +1,57 @@
+/*
+**
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#ifndef SYSTEM_EXTRAS_PERFPROFD_PERFPROFD_PERF_H_
+#define SYSTEM_EXTRAS_PERFPROFD_PERFPROFD_PERF_H_
+
+#include <string>
+#include <unordered_set>
+
+struct Config;
+
+namespace android {
+namespace perfprofd {
+
+
+
+enum PerfResult {
+ kOK,
+ kForkFailed,
+ kRecordFailed,
+ kUnsupportedEvent,
+};
+
+//
+// Invoke "perf record". Return value is PerfResult::kOK for
+// success, or some other error code if something went wrong.
+//
+PerfResult InvokePerf(Config& config,
+ const std::string &perf_path,
+ const char *stack_profile_opt,
+ unsigned duration,
+ const std::string &data_file_path,
+ const std::string &perf_stderr_path);
+
+// Prepare the internal list of supported perf counters.
+bool FindSupportedPerfCounters(const std::string& perf_path);
+// Get the list of supported perf counters.
+const std::unordered_set<std::string>& GetSupportedPerfCounters();
+
+} // namespace perfprofd
+} // namespace android
+
+#endif // SYSTEM_EXTRAS_PERFPROFD_PERFPROFD_PERF_H_
diff --git a/perfprofd/perfprofd_record-fwd.h b/perfprofd/perfprofd_record-fwd.h
new file mode 100644
index 00000000..69725826
--- /dev/null
+++ b/perfprofd/perfprofd_record-fwd.h
@@ -0,0 +1,31 @@
+/*
+ *
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SYSTEM_EXTRAS_PERFPROFD_PERFPROFD_RECORD_FWD_H_
+#define SYSTEM_EXTRAS_PERFPROFD_PERFPROFD_RECORD_FWD_H_
+
+namespace quipper {
+class PerfDataProto;
+} // namespace quipper
+
+namespace android {
+namespace perfprofd {
+using PerfprofdRecord = ::quipper::PerfDataProto;
+} // namespace perfprofd
+} // namespace android
+
+#endif // SYSTEM_EXTRAS_PERFPROFD_PERFPROFD_RECORD_FWD_H_
diff --git a/perfprofd/perfprofd_record.proto b/perfprofd/perfprofd_record.proto
new file mode 100644
index 00000000..1660d5f1
--- /dev/null
+++ b/perfprofd/perfprofd_record.proto
@@ -0,0 +1,58 @@
+
+syntax = "proto2";
+
+import "perf_data.proto";
+
+option java_package = "com.google.android.perfprofd";
+
+package quipper;
+
+// Symbol info for a shared library without build id.
+message SymbolInfo {
+ // A symbol, stretching the given range of the library.
+ message Symbol {
+ optional string name = 1;
+ optional uint64 name_md5_prefix = 2;
+
+ optional uint64 addr = 3;
+ optional uint64 size = 4;
+ };
+
+ optional string filename = 1;
+ optional uint64 filename_md5_prefix = 2;
+
+ optional uint64 min_vaddr = 3;
+
+ repeated Symbol symbols = 4;
+};
+
+extend PerfDataProto {
+ optional int64 id = 32;
+
+ // Extra symbol info.
+ repeated SymbolInfo symbol_info = 33;
+
+ // Stats inherited from old perf_profile.proto.
+
+ // is device screen on at point when profile is collected?
+ optional bool display_on = 34;
+
+ // system load at point when profile is collected; corresponds
+ // to first value from /proc/loadavg multiplied by 100 then
+ // converted to int32
+ optional int32 sys_load_average = 35;
+
+ // At the point when the profile was collected, was a camera active?
+ optional bool camera_active = 36;
+
+ // At the point when the profile was collected, was the device still booting?
+ optional bool booting = 37;
+
+ // At the point when the profile was collected, was the device plugged into
+ // a charger?
+ optional bool on_charger = 38;
+
+ // CPU utilization measured prior to profile collection (expressed as
+ // 100 minus the idle percentage).
+ optional int32 cpu_utilization = 39;
+};
diff --git a/perfprofd/perfprofd_threaded_handler.h b/perfprofd/perfprofd_threaded_handler.h
new file mode 100644
index 00000000..76cdd67a
--- /dev/null
+++ b/perfprofd/perfprofd_threaded_handler.h
@@ -0,0 +1,198 @@
+/*
+ *
+ * Copyright 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.
+ */
+
+#ifndef SYSTEM_EXTRAS_PERFPROFD_PERFPROFD_THREADED_HANDLER_H_
+#define SYSTEM_EXTRAS_PERFPROFD_PERFPROFD_THREADED_HANDLER_H_
+
+#include <chrono>
+#include <condition_variable>
+#include <cstdio>
+#include <cstdlib>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <functional>
+
+#include <inttypes.h>
+#include <unistd.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include "perfprofd_record.pb.h"
+
+#include "config.h"
+#include "dropbox.h"
+#include "perfprofdcore.h"
+#include "perfprofd_io.h"
+
+namespace android {
+namespace perfprofd {
+
+class ThreadedConfig : public Config {
+ public:
+ void Sleep(size_t seconds) override {
+ if (seconds == 0) {
+ return;
+ }
+ std::unique_lock<std::mutex> guard(mutex_);
+ using namespace std::chrono_literals;
+ cv_.wait_for(guard, seconds * 1s, [&]() { return interrupted_; });
+ }
+ bool ShouldStopProfiling() override {
+ std::unique_lock<std::mutex> guard(mutex_);
+ return interrupted_;
+ }
+
+ void ResetStopProfiling() {
+ std::unique_lock<std::mutex> guard(mutex_);
+ interrupted_ = false;
+ }
+ void StopProfiling() {
+ std::unique_lock<std::mutex> guard(mutex_);
+ interrupted_ = true;
+ cv_.notify_all();
+ }
+
+ bool IsProfilingEnabled() const override {
+ return true;
+ }
+
+ // Operator= to simplify setting the config values. This will retain the
+ // original mutex, condition-variable etc.
+ ThreadedConfig& operator=(const ThreadedConfig& rhs) {
+ // Copy base fields.
+ *static_cast<Config*>(this) = static_cast<const Config&>(rhs);
+
+ return *this;
+ }
+
+ private:
+ bool is_profiling = false;
+ std::mutex mutex_;
+ std::condition_variable cv_;
+ bool interrupted_ = false;
+
+ friend class ThreadedHandler;
+};
+
+class ThreadedHandler {
+ public:
+ ThreadedHandler() : cur_config_(new ThreadedConfig()) {}
+ explicit ThreadedHandler(ThreadedConfig* in) : cur_config_(in) {
+ CHECK(cur_config_ != nullptr);
+ }
+
+ virtual ~ThreadedHandler() {}
+
+ template <typename ConfigFn> bool StartProfiling(ConfigFn fn, std::string* error_msg) {
+ std::lock_guard<std::mutex> guard(lock_);
+
+ if (cur_config_->is_profiling) {
+ *error_msg = "Already profiling";
+ return false;
+ }
+ cur_config_->is_profiling = true;
+ cur_config_->ResetStopProfiling();
+
+ fn(*cur_config_);
+
+ HandlerFn handler = GetResultHandler();
+ auto profile_runner = [handler](ThreadedHandler* service) {
+ ProfilingLoop(*service->cur_config_, handler);
+
+ // This thread is done.
+ std::lock_guard<std::mutex> unset_guard(service->lock_);
+ service->cur_config_->is_profiling = false;
+ };
+ std::thread profiling_thread(profile_runner, this);
+ profiling_thread.detach(); // Let it go.
+
+ return true;
+ }
+
+ bool StopProfiling(std::string* error_msg) {
+ std::lock_guard<std::mutex> guard(lock_);
+ if (!cur_config_->is_profiling) {
+ *error_msg = "Not profiling";
+ return false;
+ }
+
+ cur_config_->StopProfiling();
+
+ return true;
+ }
+
+ protected:
+ // Handler for ProfilingLoop.
+ virtual bool ResultHandler(android::perfprofd::PerfprofdRecord* encodedProfile,
+ Config* config) {
+ CHECK(config != nullptr);
+ if (encodedProfile == nullptr) {
+ return false;
+ }
+
+ if (static_cast<ThreadedConfig*>(config)->send_to_dropbox) {
+ std::string error_msg;
+ if (!dropbox::SendToDropbox(encodedProfile, config->destination_directory, &error_msg)) {
+ LOG(WARNING) << "Failed dropbox submission: " << error_msg;
+ return false;
+ }
+ return true;
+ }
+
+ if (encodedProfile == nullptr) {
+ return false;
+ }
+ std::string data_file_path(config->destination_directory);
+ data_file_path += "/perf.data";
+ std::string path = android::base::StringPrintf("%s.encoded.%d", data_file_path.c_str(), seq_);
+ if (!SerializeProtobuf(encodedProfile, path.c_str(), config->compress)) {
+ return false;
+ }
+
+ seq_++;
+ return true;
+ }
+
+ template <typename Fn>
+ void RunOnConfig(Fn& fn) {
+ std::lock_guard<std::mutex> guard(lock_);
+ fn(cur_config_->is_profiling, cur_config_.get());
+ }
+
+ private:
+ // Helper for the handler.
+ HandlerFn GetResultHandler() {
+ return HandlerFn(std::bind(&ThreadedHandler::ResultHandler,
+ this,
+ std::placeholders::_1,
+ std::placeholders::_2));
+ }
+
+ std::mutex lock_;
+
+ std::unique_ptr<ThreadedConfig> cur_config_;
+
+ int seq_ = 0;
+};
+
+} // namespace perfprofd
+} // namespace android
+
+#endif // SYSTEM_EXTRAS_PERFPROFD_PERFPROFD_THREADED_HANDLER_H_
diff --git a/perfprofd/perfprofdcore.cc b/perfprofd/perfprofdcore.cc
new file mode 100644
index 00000000..00ec8a70
--- /dev/null
+++ b/perfprofd/perfprofdcore.cc
@@ -0,0 +1,732 @@
+/*
+**
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <memory>
+#include <sstream>
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/macros.h>
+#include <android-base/scopeguard.h>
+#include <android-base/stringprintf.h>
+
+#ifdef __BIONIC__
+#include <android-base/properties.h>
+#endif
+
+#ifdef __ANDROID__
+#include <healthhalutils/HealthHalUtils.h>
+#endif
+
+#include "perfprofd_record.pb.h"
+
+#include "config.h"
+#include "cpuconfig.h"
+#include "perf_data_converter.h"
+#include "perfprofdcore.h"
+#include "perfprofd_io.h"
+#include "perfprofd_perf.h"
+#include "symbolizer.h"
+
+//
+// Perf profiling daemon -- collects system-wide profiles using
+//
+// simpleperf record -a
+//
+// and encodes them so that they can be uploaded by a separate service.
+//
+
+//......................................................................
+
+using ProtoUniquePtr = std::unique_ptr<android::perfprofd::PerfprofdRecord>;
+
+//
+// Output file from 'perf record'.
+//
+#define PERF_OUTPUT "perf.data"
+
+//
+// This enum holds the results of the "should we profile" configuration check.
+//
+typedef enum {
+
+ // All systems go for profile collection.
+ DO_COLLECT_PROFILE,
+
+ // The selected configuration directory doesn't exist.
+ DONT_PROFILE_MISSING_CONFIG_DIR,
+
+ // Destination directory does not contain the semaphore file that
+ // the perf profile uploading service creates when it determines
+ // that the user has opted "in" for usage data collection. No
+ // semaphore -> no user approval -> no profiling.
+ DONT_PROFILE_MISSING_SEMAPHORE,
+
+ // No perf executable present
+ DONT_PROFILE_MISSING_PERF_EXECUTABLE,
+
+ // We're running in the emulator, perf won't be able to do much
+ DONT_PROFILE_RUNNING_IN_EMULATOR
+
+} CKPROFILE_RESULT;
+
+static bool common_initialized = false;
+
+//
+// Are we running in the emulator? If so, stub out profile collection
+// Starts as uninitialized (-1), then set to 1 or 0 at init time.
+//
+static int running_in_emulator = -1;
+
+//
+// Is this a debug build ('userdebug' or 'eng')?
+//
+static bool is_debug_build = false;
+
+//
+// Random number generator seed (set at startup time).
+//
+static unsigned short random_seed[3];
+
+//
+// Convert a CKPROFILE_RESULT to a string
+//
+static const char *ckprofile_result_to_string(CKPROFILE_RESULT result)
+{
+ switch (result) {
+ case DO_COLLECT_PROFILE:
+ return "DO_COLLECT_PROFILE";
+ case DONT_PROFILE_MISSING_CONFIG_DIR:
+ return "missing config directory";
+ case DONT_PROFILE_MISSING_SEMAPHORE:
+ return "missing semaphore file";
+ case DONT_PROFILE_MISSING_PERF_EXECUTABLE:
+ return "missing 'perf' executable";
+ case DONT_PROFILE_RUNNING_IN_EMULATOR:
+ return "running in emulator";
+ default:
+ return "unknown";
+ }
+}
+
+//
+// Check to see whether we should perform a profile collection
+//
+static CKPROFILE_RESULT check_profiling_enabled(const Config& config)
+{
+ //
+ // Profile collection in the emulator doesn't make sense
+ //
+ assert(running_in_emulator != -1);
+ if (running_in_emulator) {
+ return DONT_PROFILE_RUNNING_IN_EMULATOR;
+ }
+
+ if (!config.IsProfilingEnabled()) {
+ return DONT_PROFILE_MISSING_CONFIG_DIR;
+ }
+
+ // Check for existence of simpleperf/perf executable
+ std::string pp = config.perf_path;
+ if (access(pp.c_str(), R_OK|X_OK) == -1) {
+ LOG(WARNING) << "unable to access/execute " << pp;
+ return DONT_PROFILE_MISSING_PERF_EXECUTABLE;
+ }
+
+ //
+ // We are good to go
+ //
+ return DO_COLLECT_PROFILE;
+}
+
+bool get_booting()
+{
+#ifdef __BIONIC__
+ return android::base::GetBoolProperty("sys.boot_completed", false) != true;
+#else
+ return false;
+#endif
+}
+
+//
+// Constructor takes a timeout (in seconds) and a child pid; If an
+// alarm set for the specified number of seconds triggers, then a
+// SIGKILL is sent to the child. Destructor resets alarm. Example:
+//
+// pid_t child_pid = ...;
+// { AlarmHelper h(10, child_pid);
+// ... = read_from_child(child_pid, ...);
+// }
+//
+// NB: this helper is not re-entrant-- avoid nested use or
+// use by multiple threads
+//
+class AlarmHelper {
+ public:
+ AlarmHelper(unsigned num_seconds, pid_t child)
+ {
+ struct sigaction sigact;
+ assert(child);
+ assert(child_ == 0);
+ memset(&sigact, 0, sizeof(sigact));
+ sigact.sa_sigaction = handler;
+ sigaction(SIGALRM, &sigact, &oldsigact_);
+ child_ = child;
+ alarm(num_seconds);
+ }
+ ~AlarmHelper()
+ {
+ alarm(0);
+ child_ = 0;
+ sigaction(SIGALRM, &oldsigact_, NULL);
+ }
+ static void handler(int, siginfo_t *, void *);
+
+ private:
+ struct sigaction oldsigact_;
+ static pid_t child_;
+};
+
+pid_t AlarmHelper::child_;
+
+void AlarmHelper::handler(int, siginfo_t *, void *)
+{
+ LOG(WARNING) << "SIGALRM timeout";
+ kill(child_, SIGKILL);
+}
+
+//
+// This implementation invokes "dumpsys media.camera" and inspects the
+// output to determine if any camera clients are active. NB: this is
+// currently disable (via config option) until the selinux issues can
+// be sorted out. Another possible implementation (not yet attempted)
+// would be to use the binder to call into the native camera service
+// via "ICameraService".
+//
+bool get_camera_active()
+{
+ int pipefds[2];
+ if (pipe2(pipefds, O_CLOEXEC) != 0) {
+ PLOG(ERROR) << "pipe2() failed";
+ return false;
+ }
+ pid_t pid = fork();
+ if (pid == -1) {
+ PLOG(ERROR) << "fork() failed";
+ close(pipefds[0]);
+ close(pipefds[1]);
+ return false;
+ } else if (pid == 0) {
+ // child
+ close(pipefds[0]);
+ dup2(pipefds[1], fileno(stderr));
+ dup2(pipefds[1], fileno(stdout));
+ const char *argv[10];
+ unsigned slot = 0;
+ argv[slot++] = "/system/bin/dumpsys";
+ argv[slot++] = "media.camera";
+ argv[slot++] = nullptr;
+ execvp(argv[0], (char * const *)argv);
+ PLOG(ERROR) << "execvp() failed";
+ return false;
+ }
+ // parent
+ AlarmHelper helper(10, pid);
+ close(pipefds[1]);
+
+ // read output
+ bool have_cam = false;
+ bool have_clients = true;
+ std::string dump_output;
+ bool result = android::base::ReadFdToString(pipefds[0], &dump_output);
+ close(pipefds[0]);
+ if (result) {
+ std::stringstream ss(dump_output);
+ std::string line;
+ while (std::getline(ss,line,'\n')) {
+ if (line.find("Camera module API version:") !=
+ std::string::npos) {
+ have_cam = true;
+ }
+ if (line.find("No camera module available") !=
+ std::string::npos ||
+ line.find("No active camera clients yet") !=
+ std::string::npos) {
+ have_clients = false;
+ }
+ }
+ }
+
+ // reap child (no zombies please)
+ int st = 0;
+ TEMP_FAILURE_RETRY(waitpid(pid, &st, 0));
+ return have_cam && have_clients;
+}
+
+bool get_charging()
+{
+#ifdef __ANDROID__
+ using android::sp;
+ using android::hardware::Return;
+ using android::hardware::health::V2_0::get_health_service;
+ using android::hardware::health::V2_0::HealthInfo;
+ using android::hardware::health::V2_0::IHealth;
+ using android::hardware::health::V2_0::Result;
+
+ sp<IHealth> service = get_health_service();
+ if (service == nullptr) {
+ LOG(ERROR) << "Failed to get health HAL";
+ return false;
+ }
+ Result res = Result::UNKNOWN;
+ HealthInfo val;
+ Return<void> ret =
+ service->getHealthInfo([&](Result out_res, HealthInfo out_val) {
+ res = out_res;
+ val = out_val;
+ });
+ if (!ret.isOk()) {
+ LOG(ERROR) << "Failed to call getChargeStatus on health HAL: " << ret.description();
+ return false;
+ }
+ if (res != Result::SUCCESS) {
+ LOG(ERROR) << "Failed to retrieve charge status from health HAL: result = "
+ << toString(res);
+ return false;
+ }
+ return val.legacy.chargerAcOnline || val.legacy.chargerUsbOnline ||
+ val.legacy.chargerWirelessOnline;
+#else
+ return false;
+#endif
+}
+
+static bool postprocess_proc_stat_contents(const std::string &pscontents,
+ long unsigned *idleticks,
+ long unsigned *remainingticks)
+{
+ long unsigned usertime, nicetime, systime, idletime, iowaittime;
+ long unsigned irqtime, softirqtime;
+
+ int rc = sscanf(pscontents.c_str(), "cpu %lu %lu %lu %lu %lu %lu %lu",
+ &usertime, &nicetime, &systime, &idletime,
+ &iowaittime, &irqtime, &softirqtime);
+ if (rc != 7) {
+ return false;
+ }
+ *idleticks = idletime;
+ *remainingticks = usertime + nicetime + systime + iowaittime + irqtime + softirqtime;
+ return true;
+}
+
+unsigned collect_cpu_utilization()
+{
+ std::string contents;
+ long unsigned idle[2];
+ long unsigned busy[2];
+ for (unsigned iter = 0; iter < 2; ++iter) {
+ if (!android::base::ReadFileToString("/proc/stat", &contents)) {
+ return 0;
+ }
+ if (!postprocess_proc_stat_contents(contents, &idle[iter], &busy[iter])) {
+ return 0;
+ }
+ if (iter == 0) {
+ sleep(1);
+ }
+ }
+ long unsigned total_delta = (idle[1] + busy[1]) - (idle[0] + busy[0]);
+ long unsigned busy_delta = busy[1] - busy[0];
+ return busy_delta * 100 / total_delta;
+}
+
+static void annotate_encoded_perf_profile(android::perfprofd::PerfprofdRecord* profile,
+ const Config& config,
+ unsigned cpu_utilization)
+{
+ //
+ // Incorporate cpu utilization (collected prior to perf run)
+ //
+ if (config.collect_cpu_utilization) {
+ profile->SetExtension(quipper::cpu_utilization, cpu_utilization);
+ }
+
+ //
+ // Load average as reported by the kernel
+ //
+ std::string load;
+ double fload = 0.0;
+ if (android::base::ReadFileToString("/proc/loadavg", &load) &&
+ sscanf(load.c_str(), "%lf", &fload) == 1) {
+ int iload = static_cast<int>(fload * 100.0);
+ profile->SetExtension(quipper::sys_load_average, iload);
+ } else {
+ PLOG(ERROR) << "Failed to read or scan /proc/loadavg";
+ }
+
+ //
+ // Device still booting? Camera in use? Plugged into charger?
+ //
+ bool is_booting = get_booting();
+ if (config.collect_booting) {
+ profile->SetExtension(quipper::booting, is_booting);
+ }
+ if (config.collect_camera_active) {
+ profile->SetExtension(quipper::camera_active, is_booting ? false : get_camera_active());
+ }
+ if (config.collect_charging_state) {
+ profile->SetExtension(quipper::on_charger, get_charging());
+ }
+
+ //
+ // Examine the contents of wake_unlock to determine whether the
+ // device display is on or off. NB: is this really the only way to
+ // determine this info?
+ //
+ std::string disp;
+ if (android::base::ReadFileToString("/sys/power/wake_unlock", &disp)) {
+ bool ison = (strstr(disp.c_str(), "PowerManagerService.Display") == 0);
+ profile->SetExtension(quipper::display_on, ison);
+ } else {
+ PLOG(ERROR) << "Failed to read /sys/power/wake_unlock";
+ }
+}
+
+static ProtoUniquePtr encode_to_proto(const std::string &data_file_path,
+ const Config& config,
+ unsigned cpu_utilization,
+ perfprofd::Symbolizer* symbolizer) {
+ //
+ // Open and read perf.data file
+ //
+ ProtoUniquePtr encodedProfile(
+ android::perfprofd::RawPerfDataToAndroidPerfProfile(data_file_path,
+ symbolizer,
+ config.symbolize_everything));
+ if (encodedProfile == nullptr) {
+ return nullptr;
+ }
+
+ // All of the info in 'encodedProfile' is derived from the perf.data file;
+ // here we tack display status, cpu utilization, system load, etc.
+ annotate_encoded_perf_profile(encodedProfile.get(), config, cpu_utilization);
+
+ return encodedProfile;
+}
+
+PROFILE_RESULT encode_to_proto(const std::string &data_file_path,
+ const char *encoded_file_path,
+ const Config& config,
+ unsigned cpu_utilization,
+ perfprofd::Symbolizer* symbolizer)
+{
+ ProtoUniquePtr encodedProfile = encode_to_proto(data_file_path,
+ config,
+ cpu_utilization,
+ symbolizer);
+
+ //
+ // Issue error if no samples
+ //
+ if (encodedProfile == nullptr || encodedProfile->events_size() == 0) {
+ return ERR_PERF_ENCODE_FAILED;
+ }
+
+ return android::perfprofd::SerializeProtobuf(encodedProfile.get(),
+ encoded_file_path,
+ config.compress)
+ ? OK_PROFILE_COLLECTION
+ : ERR_WRITE_ENCODED_FILE_FAILED;
+}
+
+//
+// Remove all files in the destination directory during initialization
+//
+static void cleanup_destination_dir(const std::string& dest_dir)
+{
+ DIR* dir = opendir(dest_dir.c_str());
+ if (dir != NULL) {
+ struct dirent* e;
+ while ((e = readdir(dir)) != 0) {
+ if (e->d_name[0] != '.') {
+ std::string file_path = dest_dir + "/" + e->d_name;
+ remove(file_path.c_str());
+ }
+ }
+ closedir(dir);
+ } else {
+ PLOG(WARNING) << "unable to open destination dir " << dest_dir << " for cleanup";
+ }
+}
+
+//
+// Collect a perf profile. Steps for this operation are:
+// - kick off 'perf record'
+// - read perf.data, convert to protocol buf
+//
+static ProtoUniquePtr collect_profile(Config& config)
+{
+ //
+ // Collect cpu utilization if enabled
+ //
+ unsigned cpu_utilization = 0;
+ if (config.collect_cpu_utilization) {
+ cpu_utilization = collect_cpu_utilization();
+ }
+
+ //
+ // Form perf.data file name, perf error output file name
+ //
+ const std::string& destdir = config.destination_directory;
+ std::string data_file_path(destdir);
+ data_file_path += "/";
+ data_file_path += PERF_OUTPUT;
+ std::string perf_stderr_path(destdir);
+ perf_stderr_path += "/perferr.txt";
+
+ //
+ // Remove any existing perf.data file -- if we don't do this, perf
+ // will rename the old file and we'll have extra cruft lying around.
+ //
+ struct stat statb;
+ if (stat(data_file_path.c_str(), &statb) == 0) { // if file exists...
+ if (unlink(data_file_path.c_str())) { // then try to remove
+ PLOG(WARNING) << "unable to unlink previous perf.data file";
+ }
+ }
+
+ //
+ // The "mpdecision" daemon can cause problems for profile
+ // collection: if it decides to online a CPU partway through the
+ // 'perf record' run, the activity on that CPU will be invisible to
+ // perf, and if it offlines a CPU during the recording this can
+ // sometimes leave the PMU in an unusable state (dmesg errors of the
+ // form "perfevents: unable to request IRQXXX for ..."). To avoid
+ // these issues, if "mpdecision" is running the helper below will
+ // stop the service and then online all available CPUs. The object
+ // destructor (invoked when this routine terminates) will then
+ // restart the service again when needed.
+ //
+ uint32_t duration = config.sample_duration_in_s;
+ bool hardwire = config.hardwire_cpus;
+ uint32_t max_duration = config.hardwire_cpus_max_duration_in_s;
+ bool take_action = (hardwire && duration <= max_duration);
+ HardwireCpuHelper helper(take_action);
+
+ auto scope_guard = android::base::make_scope_guard(
+ [&data_file_path]() { unlink(data_file_path.c_str()); });
+
+ //
+ // Invoke perf
+ //
+ const char *stack_profile_opt =
+ (config.stack_profile ? "-g" : nullptr);
+ const std::string& perf_path = config.perf_path;
+
+ android::perfprofd::PerfResult invoke_res =
+ android::perfprofd::InvokePerf(config,
+ perf_path,
+ stack_profile_opt,
+ duration,
+ data_file_path,
+ perf_stderr_path);
+ if (invoke_res != android::perfprofd::PerfResult::kOK) {
+ return nullptr;
+ }
+
+ //
+ // Read the resulting perf.data file, encode into protocol buffer, then write
+ // the result to the file perf.data.encoded
+ //
+ std::unique_ptr<perfprofd::Symbolizer> symbolizer;
+ if (config.use_elf_symbolizer) {
+ symbolizer = perfprofd::CreateELFSymbolizer();
+ }
+ return encode_to_proto(data_file_path, config, cpu_utilization, symbolizer.get());
+}
+
+//
+// Assuming that we want to collect a profile every N seconds,
+// randomly partition N into two sub-intervals.
+//
+static void determine_before_after(unsigned &sleep_before_collect,
+ unsigned &sleep_after_collect,
+ unsigned collection_interval)
+{
+ double frac = erand48(random_seed);
+ sleep_before_collect = (unsigned) (((double)collection_interval) * frac);
+ assert(sleep_before_collect <= collection_interval);
+ sleep_after_collect = collection_interval - sleep_before_collect;
+}
+
+//
+// Set random number generator seed
+//
+static void set_seed(uint32_t use_fixed_seed)
+{
+ unsigned seed = 0;
+ if (use_fixed_seed) {
+ //
+ // Use fixed user-specified seed
+ //
+ seed = use_fixed_seed;
+ } else {
+ //
+ // Randomized seed
+ //
+#ifdef __BIONIC__
+ seed = arc4random();
+#else
+ seed = 12345678u;
+#endif
+ }
+ LOG(INFO) << "random seed set to " << seed;
+ // Distribute the 32-bit seed into the three 16-bit array
+ // elements. The specific values being written do not especially
+ // matter as long as we are setting them to something based on the seed.
+ random_seed[0] = seed & 0xffff;
+ random_seed[1] = (seed >> 16);
+ random_seed[2] = (random_seed[0] ^ random_seed[1]);
+}
+
+void CommonInit(uint32_t use_fixed_seed, const char* dest_dir) {
+ // Children of init inherit an artificially low OOM score -- this is not
+ // desirable for perfprofd (its OOM score should be on par with
+ // other user processes).
+ std::stringstream oomscore_path;
+ oomscore_path << "/proc/" << getpid() << "/oom_score_adj";
+ if (!android::base::WriteStringToFile("0", oomscore_path.str())) {
+ LOG(ERROR) << "unable to write to " << oomscore_path.str();
+ }
+
+ set_seed(use_fixed_seed);
+ if (dest_dir != nullptr) {
+ cleanup_destination_dir(dest_dir);
+ }
+
+#ifdef __BIONIC__
+ running_in_emulator = android::base::GetBoolProperty("ro.kernel.qemu", false);
+ is_debug_build = android::base::GetBoolProperty("ro.debuggable", false);
+#else
+ running_in_emulator = false;
+ is_debug_build = true;
+#endif
+
+ common_initialized = true;
+}
+
+void GlobalInit(const std::string& perf_path) {
+ if (!android::perfprofd::FindSupportedPerfCounters(perf_path)) {
+ LOG(WARNING) << "Could not read supported perf counters.";
+ }
+}
+
+bool IsDebugBuild() {
+ CHECK(common_initialized);
+ return is_debug_build;
+}
+
+template <typename ConfigFn, typename UpdateFn>
+static void ProfilingLoopImpl(ConfigFn config, UpdateFn update, HandlerFn handler) {
+ unsigned iterations = 0;
+ while(config()->main_loop_iterations == 0 ||
+ iterations < config()->main_loop_iterations) {
+ if (config()->ShouldStopProfiling()) {
+ return;
+ }
+
+ // Figure out where in the collection interval we're going to actually
+ // run perf
+ unsigned sleep_before_collect = 0;
+ unsigned sleep_after_collect = 0;
+ determine_before_after(sleep_before_collect,
+ sleep_after_collect,
+ config()->collection_interval_in_s);
+ if (sleep_before_collect > 0) {
+ config()->Sleep(sleep_before_collect);
+ }
+
+ if (config()->ShouldStopProfiling()) {
+ return;
+ }
+
+ // Run any necessary updates.
+ update();
+
+ // Check for profiling enabled...
+ CKPROFILE_RESULT ckresult = check_profiling_enabled(*config());
+ if (ckresult != DO_COLLECT_PROFILE) {
+ LOG(INFO) << "profile collection skipped (" << ckprofile_result_to_string(ckresult) << ")";
+ } else {
+ // Kick off the profiling run...
+ LOG(INFO) << "initiating profile collection";
+ ProtoUniquePtr proto = collect_profile(*config());
+ if (proto == nullptr) {
+ LOG(WARNING) << "profile collection failed";
+ }
+
+ // Always report, even a null result.
+ bool handle_result = handler(proto.get(), config());
+ if (handle_result) {
+ LOG(INFO) << "profile collection complete";
+ } else if (proto != nullptr) {
+ LOG(WARNING) << "profile handling failed";
+ }
+ }
+
+ if (config()->ShouldStopProfiling()) {
+ return;
+ }
+
+ if (sleep_after_collect > 0) {
+ config()->Sleep(sleep_after_collect);
+ }
+ iterations += 1;
+ }
+}
+
+void ProfilingLoop(Config& config, HandlerFn handler) {
+ CommonInit(config.use_fixed_seed, nullptr);
+
+ auto config_fn = [&config]() {
+ return &config;;
+ };
+ auto do_nothing = []() {
+ };
+ ProfilingLoopImpl(config_fn, do_nothing, handler);
+}
+
+void ProfilingLoop(std::function<Config*()> config_fn,
+ std::function<void()> update_fn,
+ HandlerFn handler) {
+ ProfilingLoopImpl(config_fn, update_fn, handler);
+}
diff --git a/perfprofd/perfprofdcore.h b/perfprofd/perfprofdcore.h
new file mode 100644
index 00000000..fdcc6f7a
--- /dev/null
+++ b/perfprofd/perfprofdcore.h
@@ -0,0 +1,91 @@
+/*
+**
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#ifndef SYSTEM_EXTRAS_PERFPROFD_PERFPROFDCORE_H_
+#define SYSTEM_EXTRAS_PERFPROFD_PERFPROFDCORE_H_
+
+#include <functional>
+#include <memory>
+#include <string>
+
+#include "perfprofd_record-fwd.h"
+
+struct Config;
+
+namespace perfprofd {
+struct Symbolizer;
+}
+
+void CommonInit(uint32_t use_fixed_seed, const char* dest_dir);
+void GlobalInit(const std::string& perf_path);
+
+//
+// This enumeration holds the results of what happened when on an
+// attempted perf profiling run.
+//
+typedef enum {
+
+ // Success
+ OK_PROFILE_COLLECTION,
+
+ // Fork system call failed (lo mem?)
+ ERR_FORK_FAILED,
+
+ // Perf ran but crashed or returned a bad exit status
+ ERR_PERF_RECORD_FAILED,
+
+ // The perf.data encoding process failed somehow
+ ERR_PERF_ENCODE_FAILED,
+
+ // We tried to open the output file perf.data.encoded but the open failed
+ ERR_OPEN_ENCODED_FILE_FAILED,
+
+ // Error while writing perf.data.encoded
+ ERR_WRITE_ENCODED_FILE_FAILED
+} PROFILE_RESULT;
+
+//
+// Given a full path to a perf.data file specified by "data_file_path",
+// read/summarize/encode the contents into a new file specified
+// by "encoded_file_path". Return status indicates whether the operation
+// was successful (either OK_PROFILE_COLLECTION or an error of some sort).
+//
+PROFILE_RESULT encode_to_proto(const std::string &data_file_path,
+ const char *encoded_file_path,
+ const Config& config,
+ unsigned cpu_utilization,
+ perfprofd::Symbolizer* symbolizer);
+
+using HandlerFn = std::function<bool(android::perfprofd::PerfprofdRecord* proto,
+ Config* config)>;
+
+void ProfilingLoop(Config& config, HandlerFn handler);
+void ProfilingLoop(std::function<Config*()> config_fn,
+ std::function<void()> update_fn,
+ HandlerFn handler);
+
+//
+// Exposed for unit testing
+//
+extern unsigned collect_cpu_utilization();
+extern bool get_booting();
+extern bool get_charging();
+extern bool get_camera_active();
+
+bool IsDebugBuild();
+
+#endif
diff --git a/perfprofd/perfprofdmain.cc b/perfprofd/perfprofdmain.cc
new file mode 100644
index 00000000..0f9f53e9
--- /dev/null
+++ b/perfprofd/perfprofdmain.cc
@@ -0,0 +1,60 @@
+/*
+**
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#include <string.h>
+
+#include <android-base/logging.h>
+
+#include "config.h"
+#include "perfprofd_binder.h"
+#include "perfprofd_cmdline.h"
+#include "perfprofdcore.h"
+
+extern int perfprofd_main(int argc, char** argv, Config* config);
+
+int main(int argc, char** argv)
+{
+ if (argc > 1 && strcmp(argv[1], "--binder") == 0) {
+ return android::perfprofd::binder::Main();
+ }
+
+ struct PosixSleepConfig : public Config {
+ void Sleep(size_t seconds) override {
+ sleep(seconds);
+ }
+ bool IsProfilingEnabled() const override {
+ //
+ // Check for existence of semaphore file in config directory
+ //
+ if (access(config_directory.c_str(), F_OK) == -1) {
+ PLOG(WARNING) << "unable to open config directory " << config_directory;
+ return false;
+ }
+
+ // Check for existence of semaphore file
+ std::string semaphore_filepath = config_directory
+ + "/" + SEMAPHORE_FILENAME;
+ if (access(semaphore_filepath.c_str(), F_OK) == -1) {
+ return false;
+ }
+
+ return true;
+ }
+ };
+ PosixSleepConfig config;
+ return perfprofd_main(argc, argv, &config);
+}
diff --git a/perfprofd/quipper_helper.h b/perfprofd/quipper_helper.h
new file mode 100644
index 00000000..d72bd405
--- /dev/null
+++ b/perfprofd/quipper_helper.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <iterator>
+
+#include "perf_data.pb.h"
+
+namespace android {
+namespace perfprofd {
+namespace quipper {
+
+template<typename Iterator, typename Predicate>
+class FilteredIterator {
+ public:
+ using value_type = typename std::iterator_traits<Iterator>::value_type;
+ using difference_type = typename std::iterator_traits<Iterator>::difference_type;
+ using reference = typename std::iterator_traits<Iterator>::reference;
+ using pointer = typename std::iterator_traits<Iterator>::pointer;
+
+ FilteredIterator(const Iterator& begin, const Iterator& end, const Predicate& pred)
+ : iter_(begin), end_(end), pred_(pred) {
+ filter();
+ }
+
+ reference operator*() const {
+ return *iter_;
+ }
+ pointer operator->() const {
+ return std::addressof(*iter_);
+ }
+
+ FilteredIterator& operator++() {
+ ++iter_;
+ filter();
+ return *this;
+ }
+
+ FilteredIterator end() {
+ return FilteredIterator(end_, end_, pred_);
+ }
+
+ bool operator==(const FilteredIterator& rhs) const {
+ return iter_ == rhs.iter_;
+ }
+ bool operator!=(const FilteredIterator& rhs) const {
+ return !(operator==(rhs));
+ }
+
+private:
+ void filter() {
+ while (iter_ != end_ && !pred_(*iter_)) {
+ ++iter_;
+ }
+ }
+
+ Iterator iter_;
+ Iterator end_;
+ Predicate pred_;
+};
+
+template <typename Predicate>
+using EventFilteredIterator = FilteredIterator<
+ decltype(static_cast<::quipper::PerfDataProto*>(nullptr)->events().begin()),
+ Predicate>;
+
+struct CommEventPredicate {
+ bool operator()(const ::quipper::PerfDataProto_PerfEvent& evt) {
+ return evt.has_comm_event();
+ }
+};
+struct CommEventIterator : public EventFilteredIterator<CommEventPredicate> {
+ explicit CommEventIterator(const ::quipper::PerfDataProto& proto)
+ : EventFilteredIterator<CommEventPredicate>(proto.events().begin(),
+ proto.events().end(),
+ CommEventPredicate()) {
+ }
+};
+
+struct MmapEventPredicate {
+ bool operator()(const ::quipper::PerfDataProto_PerfEvent& evt) {
+ return evt.has_mmap_event();
+ }
+};
+struct MmapEventIterator : public EventFilteredIterator<MmapEventPredicate> {
+ explicit MmapEventIterator(const ::quipper::PerfDataProto& proto)
+ : EventFilteredIterator<MmapEventPredicate>(proto.events().begin(),
+ proto.events().end(),
+ MmapEventPredicate()) {
+ }
+};
+
+struct SampleEventPredicate {
+ bool operator()(const ::quipper::PerfDataProto_PerfEvent& evt) {
+ return evt.has_sample_event();
+ }
+};
+struct SampleEventIterator : public EventFilteredIterator<SampleEventPredicate> {
+ explicit SampleEventIterator(const ::quipper::PerfDataProto& proto)
+ : EventFilteredIterator<SampleEventPredicate>(proto.events().begin(),
+ proto.events().end(),
+ SampleEventPredicate()) {
+ }
+};
+
+struct ForkEventPredicate {
+ bool operator()(const ::quipper::PerfDataProto_PerfEvent& evt) {
+ return evt.has_fork_event();
+ }
+};
+struct ForkEventIterator : public EventFilteredIterator<ForkEventPredicate> {
+ explicit ForkEventIterator(const ::quipper::PerfDataProto& proto)
+ : EventFilteredIterator<ForkEventPredicate>(proto.events().begin(),
+ proto.events().end(),
+ ForkEventPredicate()) {
+ }
+};
+
+struct ExitEventPredicate {
+ bool operator()(const ::quipper::PerfDataProto_PerfEvent& evt) {
+ return evt.has_exit_event();
+ }
+};
+struct ExitEventIterator : public EventFilteredIterator<ExitEventPredicate> {
+ explicit ExitEventIterator(const ::quipper::PerfDataProto& proto)
+ : EventFilteredIterator<ExitEventPredicate>(proto.events().begin(),
+ proto.events().end(),
+ ExitEventPredicate()) {
+ }
+};
+
+} // namespace quipper
+} // namespace perfprofd
+} // namespace android
diff --git a/perfprofd/scripts/Android.bp b/perfprofd/scripts/Android.bp
new file mode 100644
index 00000000..93aed1c8
--- /dev/null
+++ b/perfprofd/scripts/Android.bp
@@ -0,0 +1,89 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+python_defaults {
+ name: "perfprofd_python_default",
+ version: {
+ py2: {
+ enabled: true,
+ embedded_launcher: true,
+ },
+ py3: {
+ enabled: false,
+ embedded_launcher: false,
+ },
+ },
+}
+
+python_binary_host {
+ name: "perf_proto_stack",
+ defaults: ["perfprofd_python_default"],
+ main: "perf_proto_stack.py",
+ srcs: [
+ "perf_proto_stack.py",
+ "sorted_collection.py",
+ ":quipper_data_proto",
+ ":perfprofd_record_proto",
+ ],
+ libs: [
+ "python-symbol",
+ // Soong won't add "libprotobuf-python" to the dependencies if
+ // filegroup contains .proto files. So add it here explicitly.
+ "libprotobuf-python",
+ ],
+ proto: {
+ canonical_path_from_root: false,
+ include_dirs: ["external/perf_data_converter/src/quipper"],
+ },
+ required: [ "unwind_symbols" ],
+}
+
+python_binary_host {
+ name: "perf_proto_json2sqlite",
+ defaults: ["perfprofd_python_default"],
+ main: "perf_proto_json2sqlite.py",
+ srcs: [
+ "perf_proto_json2sqlite.py",
+ ],
+}
+
+python_binary_host {
+ name: "perf_proto_flames",
+ defaults: ["perfprofd_python_default"],
+ main: "perf_proto_stack_sqlite_flame.py",
+ srcs: [
+ "perf_proto_stack_sqlite_flame.py",
+ ],
+ libs: [
+ "simpleperf-inferno",
+ ],
+}
+
+python_binary_host {
+ name: "perf_config_proto",
+ defaults: ["perfprofd_python_default"],
+ main: "perf_config_proto.py",
+ srcs: [
+ "perf_config_proto.py",
+ ":perfprofd_config_proto",
+ ],
+ libs: [
+ // Soong won't add "libprotobuf-python" to the dependencies if
+ // filegroup contains .proto files. So add it here explicitly.
+ "libprotobuf-python",
+ ],
+ proto: {
+ canonical_path_from_root: false,
+ },
+}
diff --git a/perfprofd/scripts/perf_config_proto.py b/perfprofd/scripts/perf_config_proto.py
new file mode 100644
index 00000000..2b374a8c
--- /dev/null
+++ b/perfprofd/scripts/perf_config_proto.py
@@ -0,0 +1,193 @@
+#!/usr/bin/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 converter of a Config proto.
+
+# Generate with:
+# aprotoc -I=system/extras/perfprofd --python_out=system/extras/perfprofd/scripts \
+# system/extras/perfprofd/binder_interface/perfprofd_config.proto
+#
+# Note: it is necessary to do a '*' import to not have to jump through hoops
+# with reflective instantiation.
+from perfprofd_config_pb2 import *
+
+# Necessary for introspection.
+from google.protobuf.descriptor import FieldDescriptor
+
+import sys
+
+PROTO_FIELD_TYPE_NAMES = {
+ FieldDescriptor.TYPE_DOUBLE: "double",
+ FieldDescriptor.TYPE_FLOAT: "float",
+ FieldDescriptor.TYPE_INT64: "int64",
+ FieldDescriptor.TYPE_UINT64: "uint64",
+ FieldDescriptor.TYPE_INT32: "int32",
+ FieldDescriptor.TYPE_FIXED64: "fixed64",
+ FieldDescriptor.TYPE_FIXED32: "fixed32",
+ FieldDescriptor.TYPE_BOOL: "bool",
+ FieldDescriptor.TYPE_STRING: "string",
+ FieldDescriptor.TYPE_GROUP: "group",
+ FieldDescriptor.TYPE_MESSAGE: "message",
+ FieldDescriptor.TYPE_BYTES: "bytes",
+ FieldDescriptor.TYPE_UINT32: "uint32",
+ FieldDescriptor.TYPE_ENUM: "enum",
+ FieldDescriptor.TYPE_SFIXED32: "sfixed32",
+ FieldDescriptor.TYPE_SFIXED64: "sfixed64",
+ FieldDescriptor.TYPE_SINT32: "sint32",
+ FieldDescriptor.TYPE_SINT64: "sint64",
+}
+def get_type_string(proto_field_type):
+ if proto_field_type in PROTO_FIELD_TYPE_NAMES:
+ return PROTO_FIELD_TYPE_NAMES[proto_field_type]
+ return "unknown type"
+
+
+def read_message(msg_descriptor, indent):
+ istr = ' ' * indent
+ print('%s%s' % (istr, msg_descriptor.name))
+ # Create an instance
+ instance = globals()[msg_descriptor.name]()
+
+ # Fill fields.
+
+ primitive_fields = [None]
+ message_fields = [None]
+ for field in msg_descriptor.fields:
+ if field.type == FieldDescriptor.TYPE_MESSAGE:
+ message_fields.append(field)
+ else:
+ primitive_fields.append(field)
+
+ def table_loop(fields, field_fn):
+ while True:
+ # Print selection table
+ maxlen = len(str(len(fields) - 1))
+ def pad_index(key):
+ while len(key) < maxlen:
+ key = ' ' + key
+ return key
+
+ for i in xrange(1, len(fields)):
+ print('%s%s: %s' % (istr, pad_index(str(i)), fields[i].name))
+ print('%s%s: done' % (istr, pad_index('0')))
+ print('%s%s: end' % (istr, pad_index('!')))
+
+ sel = raw_input('%s ? ' % (istr))
+ if sel == '!':
+ # Special-case input, end argument collection.
+ return False
+
+ try:
+ sel_int = int(sel)
+ if sel_int == 0:
+ return True
+
+ if sel_int > 0 and sel_int < len(fields):
+ field = fields[sel_int]
+ if not field_fn(field):
+ return False
+ else:
+ print('Not a valid input (%d)!' % (sel_int))
+ continue
+ except:
+ print('Not a valid input! (%s, %s)' % (sel, str(sys.exc_info()[0])))
+ continue
+
+# # 1) Non-message-type fields.
+ if len(primitive_fields) > 1:
+ print('%s(Primitives)' % (istr))
+
+ def primitive_fn(field):
+ input = raw_input('%s -> %s (%s): ' % (istr, field.name, get_type_string(field.type)))
+ if input == '':
+ # Skip this field
+ return True
+ if input == '!':
+ # Special-case input, end argument collection.
+ return False
+
+ # Simplification: assume ints or bools or strings, but not floats
+ if field.type == FieldDescriptor.TYPE_BOOL:
+ input = input.lower()
+ set_val = True if input == 'y' or input == 'true' or input == '1' else False
+ elif field.type == FieldDescriptor.TYPE_STRING:
+ set_val = input
+ else:
+ try:
+ set_val = int(input)
+ except:
+ print('Could not parse input as integer!')
+ return True
+ if field.label == FieldDescriptor.LABEL_REPEATED:
+ getattr(instance, field.name).extend([set_val])
+ else:
+ setattr(instance, field.name, set_val)
+ return True
+
+ if not table_loop(primitive_fields, primitive_fn):
+ return (instance, False)
+
+ # 2) Message-type fields.
+ if len(message_fields) > 1:
+ print('%s(Nested messages)' % (istr))
+
+ def message_fn(field):
+ sub_msg, cont = read_message(field.message_type, indent + 4)
+ if sub_msg is not None:
+ if field.label == FieldDescriptor.LABEL_REPEATED:
+ # Repeated field, use extend.
+ getattr(instance, field.name).extend([sub_msg])
+ else:
+ # Singular field, copy into.
+ getattr(instance, field.name).CopyFrom(sub_msg)
+ return cont
+
+ if not table_loop(message_fields, message_fn):
+ return (instance, False)
+
+ return (instance, True)
+
+
+def collect_and_write(filename):
+ config, _ = read_message(ProfilingConfig.DESCRIPTOR, 0)
+
+ if config is not None:
+ with open(filename, "wb") as f:
+ f.write(config.SerializeToString())
+
+
+def read_and_print(filename):
+ config = ProfilingConfig()
+
+ with open(filename, "rb") as f:
+ config.ParseFromString(f.read())
+
+ print config
+
+
+def print_usage():
+ print('Usage: python perf_config_proto.py (read|write) filename')
+
+
+if __name__ == "__main__":
+ if len(sys.argv) < 3:
+ print_usage()
+ elif sys.argv[1] == 'read':
+ read_and_print(sys.argv[2])
+ elif sys.argv[1] == 'write':
+ collect_and_write(sys.argv[2])
+ else:
+ print_usage()
diff --git a/perfprofd/scripts/perf_proto_json2sqlite.py b/perfprofd/scripts/perf_proto_json2sqlite.py
new file mode 100644
index 00000000..b0d74c8b
--- /dev/null
+++ b/perfprofd/scripts/perf_proto_json2sqlite.py
@@ -0,0 +1,167 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import argparse
+import itertools
+import json
+import sqlite3
+
+
+class SqliteWriter(object):
+ def __init__(self):
+ self.sample_count = 0
+ self.dso_map = {}
+ self.pid_map = {}
+ self.tid_map = {}
+ self.symbol_map = {}
+
+ def open(self, out):
+ self._conn = sqlite3.connect(out)
+ self._c = self._conn.cursor()
+ # Ensure tables exist
+ # The sample replicates pid and tid.
+ try:
+ self._c.execute('''CREATE TABLE pids (id integer PRIMARY KEY AUTOINCREMENT,
+ name text)''')
+ self._c.execute('''CREATE TABLE tids (id integer PRIMARY KEY AUTOINCREMENT,
+ name text)''')
+ self._c.execute('''CREATE TABLE syms (id integer PRIMARY KEY AUTOINCREMENT,
+ name text)''')
+ self._c.execute('''CREATE TABLE dsos (id integer PRIMARY KEY AUTOINCREMENT,
+ name text)''')
+ self._c.execute('''CREATE TABLE samples (id integer PRIMARY KEY AUTOINCREMENT,
+ pid_id int not null,
+ tid_id int not null)
+ ''')
+ self._c.execute('''CREATE TABLE stacks (sample_id int not null,
+ depth int not null,
+ dso_id int not null,
+ sym_id int not null,
+ offset int not null,
+ primary key (sample_id, depth))
+ ''')
+ except sqlite3.OperationalError:
+ pass # ignore
+
+ def close(self):
+ self._conn.commit()
+ self._conn.close()
+
+ def insert_into_tmp_or_get(self, name, table_dict, table_dict_tmp):
+ if name in table_dict:
+ return table_dict[name]
+ if name in table_dict_tmp:
+ return table_dict_tmp[name]
+ index = len(table_dict) + len(table_dict_tmp)
+ table_dict_tmp[name] = index
+ return index
+
+ def prepare(self):
+ self.dso_tmp_map = {}
+ self.pid_tmp_map = {}
+ self.tid_tmp_map = {}
+ self.symbol_tmp_map = {}
+ self.samples_tmp_list = []
+ self.stacks_tmp_list = []
+
+ def write_sqlite_index_table(self, table_dict, table_name):
+ for key, value in table_dict.iteritems():
+ self._c.execute("insert into {tn} values (?,?)".format(tn=table_name), (value, key))
+
+ def flush(self):
+ self.write_sqlite_index_table(self.pid_tmp_map, 'pids')
+ self.write_sqlite_index_table(self.tid_tmp_map, 'tids')
+ self.write_sqlite_index_table(self.dso_tmp_map, 'dsos')
+ self.write_sqlite_index_table(self.symbol_tmp_map, 'syms')
+
+ for sample in self.samples_tmp_list:
+ self._c.execute("insert into samples values (?,?,?)", sample)
+ for stack in self.stacks_tmp_list:
+ self._c.execute("insert into stacks values (?,?,?,?,?)", stack)
+
+ self.pid_map.update(self.pid_tmp_map)
+ self.tid_map.update(self.tid_tmp_map)
+ self.dso_map.update(self.dso_tmp_map)
+ self.symbol_map.update(self.symbol_tmp_map)
+
+ self.dso_tmp_map = {}
+ self.pid_tmp_map = {}
+ self.tid_tmp_map = {}
+ self.symbol_tmp_map = {}
+ self.samples_tmp_list = []
+ self.stacks_tmp_list = []
+
+ def add_sample(self, sample, tid_name_map):
+ sample_id = self.sample_count
+ self.sample_count = self.sample_count + 1
+
+ def get_name(pid, name_map):
+ if pid in name_map:
+ return name_map[pid]
+ pid_str = str(pid)
+ if pid_str in name_map:
+ return name_map[pid_str]
+ if pid == 0:
+ return "[kernel]"
+ return "[unknown]"
+
+ pid_name = get_name(sample[0], tid_name_map)
+ pid_id = self.insert_into_tmp_or_get(pid_name, self.pid_map, self.pid_tmp_map)
+ tid_name = get_name(sample[1], tid_name_map)
+ tid_id = self.insert_into_tmp_or_get(tid_name, self.tid_map, self.tid_tmp_map)
+
+ self.samples_tmp_list.append((sample_id, pid_id, tid_id))
+
+ stack_depth = 0
+ for entry in sample[2]:
+ sym_id = self.insert_into_tmp_or_get(entry[0], self.symbol_map, self.symbol_tmp_map)
+ dso = entry[2]
+ if dso is None:
+ dso = "None"
+ dso_id = self.insert_into_tmp_or_get(dso, self.dso_map, self.dso_tmp_map)
+
+ self.stacks_tmp_list.append((sample_id, stack_depth, dso_id, sym_id, entry[1]))
+
+ stack_depth = stack_depth + 1
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description='''Process a set of perfprofd JSON files produced
+ by perf_proto_stack.py into SQLite database''')
+
+ parser.add_argument('file', help='JSON files to parse and combine', metavar='file', nargs='+')
+
+ parser.add_argument('--sqlite-out', help='SQLite database output', type=str,
+ default='sqlite.db')
+
+ args = parser.parse_args()
+ if args is not None:
+ sql_out = SqliteWriter()
+ sql_out.open(args.sqlite_out)
+ sql_out.prepare()
+
+ for f in args.file:
+ print 'Processing %s' % (f)
+ fp = open(f, 'r')
+ data = json.load(fp)
+ fp.close()
+
+ for sample in data['samples']:
+ sql_out.add_sample(sample, data['names'])
+
+ sql_out.flush()
+
+ sql_out.close()
diff --git a/perfprofd/scripts/perf_proto_stack.py b/perfprofd/scripts/perf_proto_stack.py
new file mode 100644
index 00000000..2b0c3107
--- /dev/null
+++ b/perfprofd/scripts/perf_proto_stack.py
@@ -0,0 +1,576 @@
+#!/usr/bin/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.
+
+# Super simplistic printer of a perfprofd output proto. Illustrates
+# how to parse and traverse a perfprofd output proto in Python.
+
+# This relies on libunwindstack's unwind_symbol. Build with
+# mmma system/core/libunwindstack
+
+import argparse
+from datetime import datetime
+import itertools
+import json
+import logging
+from multiprocessing.dummy import Pool as ThreadPool
+import os.path
+from sorted_collection import SortedCollection
+import subprocess
+from threading import Timer
+
+# Generate with:
+# aprotoc -I=external/perf_data_converter/src/quipper \
+# --python_out=system/extras/perfprofd/scripts \
+# external/perf_data_converter/src/quipper/perf_data.proto
+# aprotoc -I=external/perf_data_converter/src/quipper -I=system/extras/perfprofd \
+# --python_out=system/extras/perfprofd/scripts \
+# system/extras/perfprofd/perfprofd_record.proto
+import perfprofd_record_pb2
+import perf_data_pb2
+
+# Make sure that symbol is on the PYTHONPATH, e.g., run as
+# PYTHONPATH=$PYTHONPATH:$ANDROID_BUILD_TOP/development/scripts python ...
+import symbol
+from symbol import SymbolInformation
+
+logging.basicConfig(format='%(message)s')
+
+# This is wrong. But then the symbol module is a bad quagmire.
+# TODO: Check build IDs.
+symbol.SetAbi(["ABI: 'arm64'"])
+
+
+class MmapState(object):
+
+ def __init__(self):
+ self._list = SortedCollection((), lambda x: x[0])
+
+ def add_map(self, start, length, pgoff, name):
+ map_tuple = (start, length, pgoff, name)
+ self._list.insert(map_tuple)
+
+ def find(self, addr):
+ try:
+ map_tuple = self._list.find_le(addr)
+ if addr < map_tuple[0] + map_tuple[1]:
+ return map_tuple
+ return None
+ except ValueError:
+ return None
+
+ def copy(self):
+ ret = MmapState()
+ ret._list = self._list.copy()
+ return ret
+
+ def __str__(self):
+ return 'MmapState: ' + self._list.__str__()
+
+ def __repr__(self):
+ return self.__str__()
+
+
+class SymbolMap(object):
+ def __init__(self, min_v):
+ self._list = SortedCollection((), lambda x: x[0])
+ self._min_vaddr = min_v
+
+ def add_symbol(self, start, length, name):
+ tuple = (start, length, name)
+ self._list.insert(tuple)
+
+ def find(self, addr):
+ try:
+ tuple = self._list.find_le(addr)
+ if addr < tuple[0] + tuple[1]:
+ return tuple[2]
+ return None
+ except ValueError:
+ return None
+
+ def copy(self):
+ ret = SymbolMap()
+ ret._list = self._list.copy()
+ return ret
+
+ def __str__(self):
+ return "SymbolMap: " + self._list.__str__()
+
+ def __repr__(self):
+ return self.__str__()
+
+
+def intern_uni(u):
+ return intern(u.encode('ascii', 'replace'))
+
+
+def collect_tid_names(perf_data):
+ tid_name_map = {}
+ for event in perf_data.events:
+ if event.HasField('comm_event'):
+ tid_name_map[event.comm_event.tid] = intern_uni(event.comm_event.comm)
+ return tid_name_map
+
+
+def create_symbol_maps(profile):
+ symbol_maps = {}
+
+ for si in profile.Extensions[perfprofd_record_pb2.symbol_info]:
+ map = SymbolMap(si.min_vaddr)
+ symbol_maps[si.filename] = map
+ for sym in si.symbols:
+ map.add_symbol(sym.addr, sym.size, intern_uni(sym.name))
+ return symbol_maps
+
+
+def update_mmap_states(event, state_map):
+ if event.HasField('mmap_event'):
+ mmap_event = event.mmap_event
+ # Skip kernel stuff.
+ if mmap_event.tid == 0:
+ return
+ # Create new map, if necessary.
+ if mmap_event.pid not in state_map:
+ state_map[mmap_event.pid] = MmapState()
+ state_map[mmap_event.pid].add_map(mmap_event.start, mmap_event.len, mmap_event.pgoff,
+ intern_uni(mmap_event.filename))
+ elif event.HasField('fork_event'):
+ fork_event = event.fork_event
+ # Skip threads
+ if fork_event.pid == fork_event.ppid:
+ return
+
+ if fork_event.ppid not in state_map:
+ logging.warn("fork from %d without map", fork_event.ppid)
+ return
+ state_map[fork_event.pid] = state_map[fork_event.ppid].copy()
+
+
+skip_dso = set()
+vaddr = {}
+
+
+def find_vaddr(vaddr_map, filename):
+ if filename in vaddr_map:
+ return vaddr_map[filename]
+
+ path = "%s/%s" % (symbol.SYMBOLS_DIR, filename)
+ if not os.path.isfile(path):
+ logging.warn('Cannot find %s for min_vaddr', filename)
+ vaddr_map[filename] = 0
+ return 0
+
+ try:
+ # Use "-W" to have single-line format.
+ res = subprocess.check_output(['readelf', '-lW', path])
+ lines = res.split("\n")
+ reading_headers = False
+ min_vaddr = None
+
+ def min_fn(x, y):
+ return y if x is None else min(x, y)
+ # Using counting loop for access to next line.
+ for i in range(0, len(lines) - 1):
+ line = lines[i].strip()
+ if reading_headers:
+ if line == "":
+ # Block is done, won't find anything else.
+ break
+ if line.startswith("LOAD"):
+ # Look at the current line to distinguish 32-bit from 64-bit
+ line_split = line.split()
+ if len(line_split) >= 8:
+ if " R E " in line:
+ # Found something expected. So parse VirtAddr.
+ try:
+ min_vaddr = min_fn(min_vaddr, int(line_split[2], 0))
+ except ValueError:
+ pass
+ else:
+ logging.warn('Could not parse readelf line %s', line)
+ else:
+ if line.strip() == "Program Headers:":
+ reading_headers = True
+
+ if min_vaddr is None:
+ min_vaddr = 0
+ logging.debug("min_vaddr for %s is %d", filename, min_vaddr)
+ vaddr_map[filename] = min_vaddr
+ except subprocess.CalledProcessError:
+ logging.warn('Error finding min_vaddr for %s', filename)
+
+ vaddr_map[filename] = 0
+ return vaddr_map[filename]
+
+
+unwind_symbols_cache = {}
+unwind_symbols_warn_missing_cache = set()
+
+
+def run_unwind_symbols(filename, offset_hex):
+ path = "%s/%s" % (symbol.SYMBOLS_DIR, filename)
+ if not os.path.isfile(path):
+ if path not in unwind_symbols_warn_missing_cache:
+ logging.warn('Cannot find %s for unwind_symbols', filename)
+ unwind_symbols_warn_missing_cache.add(path)
+ return None
+
+ if (path, offset_hex) in unwind_symbols_cache:
+ pair = unwind_symbols_cache[(path, offset_hex)]
+ if pair is None:
+ return None
+ return [(pair[0], pair[1], filename)]
+
+ try:
+ res = subprocess.check_output(['unwind_symbols', path, offset_hex])
+ lines = res.split("\n")
+ for line in lines:
+ if line.startswith('<0x'):
+ parts = line.split(' ', 1)
+ if len(parts) == 2:
+ # Get offset, too.
+ offset = 0
+ plus_index = parts[0].find('>+')
+ if plus_index > 0:
+ offset_str = parts[0][plus_index + 2:-1]
+ try:
+ offset = int(offset_str)
+ except ValueError:
+ logging.warn('error parsing offset from %s', parts[0])
+
+ # TODO C++ demangling necessary.
+ logging.debug('unwind_symbols: %s %s -> %s +%d', filename, offset_hex, parts[1],
+ offset)
+ sym = intern(parts[1])
+ unwind_symbols_cache[(path, offset_hex)] = (sym, offset)
+ return [(sym, offset, filename)]
+ except subprocess.CalledProcessError:
+ logging.warn('Failed running unwind_symbols for %s', filename)
+ unwind_symbols_cache[(path, offset_hex)] = None
+ return None
+
+
+def decode_with_symbol_lib(name, addr_rel_hex):
+ info = SymbolInformation(name, addr_rel_hex)
+ # As-is, only info[0] (inner-most inlined function) is recognized.
+ (source_symbol, source_location, object_symbol_with_offset) = info[0]
+
+ def parse_symbol_lib_output(s):
+ i = s.rfind('+')
+ if i > 0:
+ try:
+ off = int(s[i + 1:])
+ return (s[0:i], off)
+ except ValueError:
+ pass
+ return (s, 0)
+
+ ret = []
+
+ if object_symbol_with_offset is not None:
+ pair = parse_symbol_lib_output(object_symbol_with_offset)
+ ret.append((intern(pair[0]), pair[1], name))
+
+ if source_symbol is not None:
+ iterinfo = iter(info)
+ next(iterinfo)
+ for (sym_inlined, loc_inlined, _) in iterinfo:
+ # TODO: Figure out what's going on here:
+ if sym_inlined is not None:
+ pair = parse_symbol_lib_output(sym_inlined)
+ ret.insert(0, (intern(pair[0]), pair[1], name))
+ if len(ret) > 0:
+ return ret
+ return None
+
+
+def decode_addr(addr, mmap_state, device_symbols):
+ """Try to decode the given address against the current mmap table and device symbols.
+
+ First, look up the address in the mmap state. If none is found, use a simple address
+ heuristic to guess kernel frames on 64-bit devices.
+
+ Next, check on-device symbolization for a hit.
+
+ Last, try to symbolize against host information. First try the symbol module. However,
+ as it is based on addr2line, it will not work for pure-gnu_debugdata DSOs (e.g., ART
+ preopt artifacts). For that case, use libunwindstack's unwind_symbols.
+ """
+
+ map = mmap_state.find(addr)
+ if map is None:
+ # If it looks large enough, assume it's from
+ # the kernel.
+ if addr > 18000000000000000000:
+ return [("[kernel]", 0, "[kernel]")]
+ return [("%d (no mapped segment)" % addr, 0, None)]
+ name = map[3]
+ logging.debug('%d is %s (%d +%d)', addr, name, map[0], map[1])
+
+ # Once relocation packer is off, it would be:
+ # offset = addr - map.start + map.pgoff
+ # Right now it is
+ # offset = addr - map.start (+ min_vaddr)
+ # Note that on-device symbolization doesn't include min_vaddr but
+ # does include pgoff.
+ offset = addr - map[0]
+
+ if name in device_symbols:
+ offset = offset + map[2]
+ symbol = device_symbols[name].find(offset)
+ if symbol is None:
+ return [("%s (missing on-device symbol)" % (name), offset, name)]
+ else:
+ # TODO: Should we change the format?
+ return [(symbol, 0, name)]
+ offset = offset + find_vaddr(vaddr, name)
+ if (name, offset) in skip_dso:
+ # We already failed, skip symbol finding.
+ return [(name, offset, name)]
+ else:
+ addr_rel_hex = intern("%x" % offset)
+ ret = decode_with_symbol_lib(name, addr_rel_hex)
+ if ret is not None and len(ret) != 0:
+ # Addr2line may report oatexec+xyz. Let unwind_symbols take care of that.
+ if len(ret) != 1 or ret[0][0] != 'oatexec':
+ logging.debug('Got result from symbol module: %s', str(ret))
+ return ret
+ # Try unwind_symbols
+ ret = run_unwind_symbols(name, addr_rel_hex)
+ if ret is not None and len(ret) != 0:
+ return ret
+ logging.warn("Failed to find symbol for %s +%d (%d)", name, offset, addr)
+ # Remember the fail.
+ skip_dso.add((name, offset))
+ return [(name, offset, name)]
+
+
+def print_sample(sample, tid_name_map):
+ if sample[0] in tid_name_map:
+ pid_name = "%s (%d)" % (tid_name_map[sample[0]], sample[0])
+ elif sample[0] == 0:
+ pid_name = "kernel (0)"
+ else:
+ pid_name = "unknown (%d)" % (sample[0])
+
+ if sample[1] in tid_name_map:
+ tid_name = "%s (%d)" % (tid_name_map[sample[1]], sample[1])
+ elif sample[1] == 0:
+ tid_name = "kernel (0)"
+ else:
+ tid_name = "unknown (%d)" % (sample[1])
+ print " %s - %s:" % (pid_name, tid_name)
+
+ for sym in sample[2]:
+ print " %s +%d (%s)" % (sym[0], sym[1], sym[2])
+
+
+def print_samples(samples, tid_name_map):
+ for sample in samples:
+ print_sample(sample, tid_name_map)
+
+
+def symbolize_events(perf_data, device_symbols, tid_name_map, printSamples=False,
+ removeKernelTop=False):
+ samples = []
+ mmap_states = {}
+ for event in perf_data.events:
+ update_mmap_states(event, mmap_states)
+ if event.HasField('sample_event'):
+ sample_ev = event.sample_event
+ # Handle sample.
+ new_sample = None
+ if sample_ev.pid in mmap_states:
+ mmap_state = mmap_states[sample_ev.pid]
+ ip_sym = decode_addr(sample_ev.ip, mmap_state, device_symbols)
+ stack = ip_sym
+ for cc_ip in sample_ev.callchain:
+ cc_sym = decode_addr(cc_ip, mmap_state, device_symbols)
+ stack.extend(cc_sym)
+ if removeKernelTop:
+ while len(stack) > 1 and stack[0][0] == "[kernel]":
+ stack.pop(0)
+ new_sample = (sample_ev.pid, sample_ev.tid, stack)
+ else:
+ # Handle kernel symbols specially.
+ if sample_ev.pid == 0:
+ samples.append((0, sample_ev.tid, [("[kernel]", 0, "[kernel]")]))
+ elif sample_ev.pid in tid_name_map:
+ samples.append((sample_ev.pid, sample_ev.tid, [(tid_name_map[sample_ev.pid], 0,
+ None)]))
+ else:
+ samples.append((sample_ev.pid, sample_ev.tid, [("[unknown]", 0, None)]))
+ if new_sample is not None:
+ samples.append(new_sample)
+ if printSamples:
+ print_sample(new_sample, tid_name_map)
+ return samples
+
+
+def count_key_reduce_function(x, y, key_fn):
+ key = key_fn(y)
+ if key not in x:
+ x[key] = 0
+ x[key] += 1
+ return x
+
+
+def print_histogram(samples, reduce_key_fn, label_key_fn, size):
+ # Create a sorted list of top samples.
+ sorted_count_list = sorted(
+ reduce(lambda x, y: count_key_reduce_function(x, y, reduce_key_fn), samples, {}).
+ iteritems(),
+ cmp=lambda x, y: cmp(x[1], y[1]),
+ reverse=True)
+ sorted_count_topX = list(itertools.islice(sorted_count_list, size))
+
+ # Print top-size samples.
+ print 'Histogram top-%d:' % (size)
+ for i in xrange(0, len(sorted_count_topX)):
+ print ' %d: %s (%s)' % (i + 1, label_key_fn(sorted_count_topX[i][0]),
+ sorted_count_topX[i][1])
+
+
+def get_name(pid, tid_name_map):
+ if pid in tid_name_map:
+ return tid_name_map[pid]
+ if pid == 0:
+ return "[kernel]"
+ return "[unknown]"
+
+
+def create_cmd(args, f):
+ ret = ['python', '-u', 'system/extras/perfprofd/scripts/perf_proto_stack.py']
+ if args.syms is not None:
+ ret.extend(['--syms', args.syms[0]])
+ if args.print_samples is not None:
+ ret.append('--print-samples')
+ if args.skip_kernel_syms is not None:
+ ret.append('--skip-kernel-syms')
+ if args.print_pid_histogram is not None:
+ ret.append('--print-pid-histogram')
+ if args.print_sym_histogram is not None:
+ ret.append('--print-sym-histogram')
+ if args.print_dso_histogram is not None:
+
+ ret.append('--print-dso-histogram')
+ ret.extend(['--json-out', '%s.json' % (f)])
+ ret.append(f)
+ return ret
+
+
+def run_cmd(x):
+
+ args = x[0]
+ f = x[1]
+ cmd = create_cmd(args, f)
+ logging.warn('Running on %s', f)
+ success = False
+ logging.debug('%r', cmd)
+ err_out = open('%s.err' % (f), 'w')
+
+ def kill(process):
+ process.kill()
+ start = datetime.now()
+
+ p = subprocess.Popen(cmd, stderr=err_out)
+ kill_timer = Timer(3600, kill, [p])
+ try:
+ kill_timer.start()
+ p.communicate()
+ success = True
+
+ finally:
+ kill_timer.cancel()
+ err_out.close()
+ end = datetime.now()
+ logging.warn('Ended %s (%s)', f, str(end - start))
+ return '%s: %r' % (f, success)
+
+
+def parallel_runner(args):
+ pool = ThreadPool(args.parallel)
+ map_args = map(lambda f: (args, f), args.file)
+
+ result = pool.map(run_cmd, map_args)
+ pool.close()
+ pool.join()
+ print result
+
+
+def run(args):
+ if args.syms is not None:
+ symbol.SYMBOLS_DIR = args.syms[0]
+ print_symbols = args.print_samples is not None
+ skip_kernel_syms = args.skip_kernel_syms is not None
+
+ # TODO: accept argument for parsing.
+ file = open(args.file[0], 'rb')
+ data = file.read()
+
+ file.close()
+
+ profile = perf_data_pb2.PerfDataProto()
+ # PerfprofdRecord()
+ profile.ParseFromString(data)
+
+ perf_data = profile
+
+ print "Stats: ", perf_data.stats
+
+ tid_name_map = collect_tid_names(perf_data)
+ symbol_maps = create_symbol_maps(profile)
+
+ samples = symbolize_events(perf_data, symbol_maps, tid_name_map, printSamples=print_symbols,
+ removeKernelTop=skip_kernel_syms)
+
+ if args.print_pid_histogram is not None:
+ print_histogram(samples, lambda x: x[0], lambda x: get_name(x, tid_name_map), 25)
+ if args.print_sym_histogram is not None:
+ print_histogram(samples, lambda x: x[2][0][0], lambda x: x, 100)
+ if args.print_dso_histogram is not None:
+ print_histogram(samples, lambda x: x[2][0][2], lambda x: x, 25)
+
+ if args.json_out is not None:
+ json_file = open(args.json_out[0], 'w')
+ json_data = {'samples': samples, 'names': tid_name_map}
+ json.dump(json_data, json_file)
+ json_file.close()
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description='Process a perfprofd record.')
+
+ parser.add_argument('file', help='proto file to parse', metavar='file', nargs='+')
+ parser.add_argument('--syms', help='directory for symbols', nargs=1)
+ parser.add_argument('--json-out', help='output file for JSON', nargs=1)
+ parser.add_argument('--print-samples', help='print samples', action='store_const', const=True)
+ parser.add_argument('--skip-kernel-syms', help='skip kernel symbols at the top of stack',
+ action='store_const', const=True)
+ parser.add_argument('--print-pid-histogram', help='print a top-25 histogram of processes',
+ action='store_const', const=True)
+ parser.add_argument('--print-sym-histogram', help='print a top-100 histogram of symbols',
+ action='store_const', const=True)
+ parser.add_argument('--print-dso-histogram', help='print a top-25 histogram of maps',
+ action='store_const', const=True)
+ parser.add_argument('--parallel', help='run parallel jobs', type=int)
+
+ args = parser.parse_args()
+ if args is not None:
+ if args.parallel is not None:
+ parallel_runner(args)
+ else:
+ run(args)
diff --git a/perfprofd/scripts/perf_proto_stack_sqlite_flame.py b/perfprofd/scripts/perf_proto_stack_sqlite_flame.py
new file mode 100644
index 00000000..3eb2379a
--- /dev/null
+++ b/perfprofd/scripts/perf_proto_stack_sqlite_flame.py
@@ -0,0 +1,272 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Make sure that simpleperf's inferno is on the PYTHONPATH, e.g., run as
+# PYTHONPATH=$PYTHONPATH:$ANDROID_BUILD_TOP/system/extras/simpleperf/scripts/inferno python ..
+
+import argparse
+import itertools
+import sqlite3
+
+
+class Callsite(object):
+ def __init__(self, dso_id, sym_id):
+ self.dso_id = dso_id
+ self.sym_id = sym_id
+ self.count = 0
+ self.child_map = {}
+ self.id = self._get_next_callsite_id()
+
+ def add(self, dso_id, sym_id):
+ if (dso_id, sym_id) in self.child_map:
+ return self.child_map[(dso_id, sym_id)]
+ new_callsite = Callsite(dso_id, sym_id)
+ self.child_map[(dso_id, sym_id)] = new_callsite
+ return new_callsite
+
+ def child_count_to_self(self):
+ self.count = reduce(lambda x, y: x + y[1].count, self.child_map.iteritems(), 0)
+
+ def trim(self, local_threshold_in_percent, global_threshold):
+ local_threshold = local_threshold_in_percent * 0.01 * self.count
+ threshold = max(local_threshold, global_threshold)
+ for k, v in self.child_map.items():
+ if v.count < threshold:
+ del self.child_map[k]
+ for _, v in self.child_map.iteritems():
+ v.trim(local_threshold_in_percent, global_threshold)
+
+ def _get_str(self, id, m):
+ if id in m:
+ return m[id]
+ return str(id)
+
+ def print_callsite_ascii(self, depth, indent, dsos, syms):
+
+ print ' ' * indent + "%s (%s) [%d]" % (self._get_str(self.sym_id, syms),
+ self._get_str(self.dso_id, dsos),
+ self.count)
+ if depth == 0:
+ return
+ for v in sorted(self.child_map.itervalues, key=lambda x: x.count, reverse=True):
+ v.print_callsite_ascii(depth - 1, indent + 1, dsos, syms)
+
+ # Functions for flamegraph compatibility.
+
+ callsite_counter = 0
+
+ @classmethod
+ def _get_next_callsite_id(cls):
+ cls.callsite_counter += 1
+ return cls.callsite_counter
+
+ def create_children_list(self):
+ self.children = sorted(self.child_map.itervalues(), key=lambda x: x.count, reverse=True)
+
+ def generate_offset(self, start_offset):
+ self.offset = start_offset
+ child_offset = start_offset
+ for child in self.children:
+ child_offset = child.generate_offset(child_offset)
+ return self.offset + self.count
+
+ def svgrenderer_compat(self, dsos, syms):
+ self.create_children_list()
+ self.method = self._get_str(self.sym_id, syms)
+ self.dso = self._get_str(self.dso_id, dsos)
+ self.offset = 0
+ for c in self.children:
+ c.svgrenderer_compat(dsos, syms)
+
+ def weight(self):
+ return float(self.count)
+
+ def get_max_depth(self):
+ if self.child_map:
+ return max([c.get_max_depth() for c in self.child_map.itervalues()]) + 1
+ return 1
+
+
+class SqliteReader(object):
+ def __init__(self):
+ self.root = Callsite("root", "root")
+ self.dsos = {}
+ self.syms = {}
+
+ def open(self, f):
+ self._conn = sqlite3.connect(f)
+ self._c = self._conn.cursor()
+
+ def close(self):
+ self._conn.close()
+
+ def read(self, local_threshold_in_percent, global_threshold_in_percent, limit,
+ skip_simpleperf):
+ # Read aux tables first, as we need to find the kernel symbols.
+ def read_table(name, dest_table):
+ self._c.execute('select id, name from %s' % (name))
+ while True:
+ rows = self._c.fetchmany(100)
+ if not rows:
+ break
+ for row in rows:
+ dest_table[row[0]] = row[1]
+
+ print 'Reading DSOs'
+ read_table('dsos', self.dsos)
+
+ print 'Reading symbol strings'
+ read_table('syms', self.syms)
+
+ kernel_sym_id = None
+ for i, v in self.syms.iteritems():
+ if v == '[kernel]':
+ kernel_sym_id = i
+ break
+
+ skip_query_str = ""
+ if skip_simpleperf:
+ self._c.execute('select id from pids where name = "simpleperf"')
+ pid_row = self._c.fetchone()
+ if pid_row:
+ skip_query_join = "as st join samples sa on st.sample_id = sa.id "
+ skip_query_str = skip_query_join + "where sa.pid_id != %d" % (pid_row[0])
+
+ query_prefix = 'select sample_id, depth, dso_id, sym_id from stacks '
+ query_suffix = ' order by sample_id asc, depth desc'
+
+ print 'Reading samples'
+ self._c.execute(query_prefix + skip_query_str + query_suffix)
+
+ last_sample_id = None
+ chain = None
+ count = 0
+ while True:
+ rows = self._c.fetchmany(100)
+
+ if not rows:
+ break
+ for row in rows:
+ if row[3] == kernel_sym_id and row[1] == 0:
+ # Skip kernel.
+ continue
+ if row[0] != last_sample_id:
+ last_sample_id = row[0]
+ chain = self.root
+ chain = chain.add(row[2], row[3])
+ chain.count = chain.count + 1
+ count = count + len(rows)
+ if limit is not None and count >= limit:
+ print 'Breaking as limit is reached'
+ break
+
+ self.root.child_count_to_self()
+ global_threshold = global_threshold_in_percent * 0.01 * self.root.count
+ self.root.trim(local_threshold_in_percent, global_threshold)
+
+ def print_data_ascii(self, depth):
+ self.root.print_callsite_ascii(depth, 0, self.dsos, self.syms)
+
+ def get_script_js(self):
+ # Try to get the data directly (if packaged with embedded_loader).
+ import os.path
+ import sys
+ if '__loader__' in globals():
+ try:
+ js = __loader__.get_data(os.path.join(os.path.dirname(__file__), "script.js"))
+ if js is not None and len(js) > 0:
+ return js
+ except:
+ pass
+ # See if we can find it another way.
+ rel_paths = [
+ # Maybe we're being run packaged.
+ "script.js",
+ # Maybe we're being run directly.
+ "../../simpleperf/scripts/inferno/script.js",
+ ]
+ for rel_path in rel_paths:
+ script_js = os.path.join(os.path.dirname(__file__), rel_path)
+ if os.path.exists(script_js):
+ with open(script_js, 'r') as script_f:
+ return script_f.read()
+ return None
+
+ def print_svg(self, filename, depth):
+ from svg_renderer import render_svg
+ self.root.svgrenderer_compat(self.dsos, self.syms)
+ self.root.generate_offset(0)
+ f = open(filename, 'w')
+ f.write('''
+<html>
+<body>
+<div id='flamegraph_id' style='font-family: Monospace;'>
+<style type="text/css"> .s { stroke:black; stroke-width:0.5; cursor:pointer;} </style>
+<style type="text/css"> .t:hover { cursor:pointer; } </style>
+''')
+
+ class FakeProcess:
+ def __init__(self):
+ self.props = {'trace_offcpu': False}
+ fake_process = FakeProcess()
+ render_svg(fake_process, self.root, f, 'hot')
+
+ f.write('''
+</div>
+''')
+
+ # Emit script.js, if we can find it.
+ script_data = self.get_script_js()
+ if script_data is not None:
+ f.write('<script>\n')
+ f.write(script_data)
+ f.write('''
+</script>
+<br/><br/>
+<div>Navigate with WASD, zoom in with SPACE, zoom out with BACKSPACE.</div>
+<script>document.addEventListener('DOMContentLoaded', flamegraphInit);</script>
+</body>
+</html>
+''')
+ f.close()
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description='''Translate a perfprofd database into a flame
+ representation''')
+
+ parser.add_argument('file', help='the sqlite database to use', metavar='file', type=str)
+
+ parser.add_argument('--html-out', help='output file for HTML flame graph', type=str)
+ parser.add_argument('--threshold', help='child threshold in percent', type=float, default=5)
+ parser.add_argument('--global-threshold', help='global threshold in percent', type=float,
+ default=.1)
+ parser.add_argument('--depth', help='depth to print to', type=int, default=10)
+ parser.add_argument('--limit', help='limit to given number of stack trace entries', type=int)
+ parser.add_argument('--skip-simpleperf', help='skip simpleperf samples', action='store_const',
+ const=True)
+
+ args = parser.parse_args()
+ if args is not None:
+ sql_out = SqliteReader()
+ sql_out.open(args.file)
+ sql_out.read(args.threshold, args.global_threshold, args.limit,
+ args.skip_simpleperf is not None)
+ if args.html_out is None:
+ sql_out.print_data_ascii(args.depth)
+ else:
+ sql_out.print_svg(args.html_out, args.depth)
+ sql_out.close()
diff --git a/perfprofd/scripts/sorted_collection.py b/perfprofd/scripts/sorted_collection.py
new file mode 100644
index 00000000..46622e21
--- /dev/null
+++ b/perfprofd/scripts/sorted_collection.py
@@ -0,0 +1,147 @@
+# Note: Taken from https://code.activestate.com/recipes/577197-sortedcollection/.
+#
+# Copyright 2010 Raymond Hettinger
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so, subject to the
+# following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all copies
+# or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+# FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+from bisect import bisect_left, bisect_right
+
+
+class SortedCollection(object):
+ def __init__(self, iterable=(), key=None):
+ self._given_key = key
+ key = (lambda x: x) if key is None else key
+ decorated = sorted((key(item), item) for item in iterable)
+ self._keys = [k for k, item in decorated]
+ self._items = [item for k, item in decorated]
+ self._key = key
+
+ def _getkey(self):
+ return self._key
+
+ def _setkey(self, key):
+ if key is not self._key:
+ self.__init__(self._items, key=key)
+
+ def _delkey(self):
+ self._setkey(None)
+
+ key = property(_getkey, _setkey, _delkey, 'key function')
+
+ def clear(self):
+ self.__init__([], self._key)
+
+ def copy(self):
+ return self.__class__(self, self._key)
+
+ def __len__(self):
+ return len(self._items)
+
+ def __getitem__(self, i):
+ return self._items[i]
+
+ def __iter__(self):
+ return iter(self._items)
+
+ def __reversed__(self):
+ return reversed(self._items)
+
+ def __repr__(self):
+ return '%s(%r, key=%s)' % (
+ self.__class__.__name__,
+ self._items,
+ getattr(self._given_key, '__name__', repr(self._given_key))
+ )
+
+ def __reduce__(self):
+ return self.__class__, (self._items, self._given_key)
+
+ def __contains__(self, item):
+ k = self._key(item)
+ i = bisect_left(self._keys, k)
+ j = bisect_right(self._keys, k)
+ return item in self._items[i:j]
+
+ def index(self, item):
+ 'Find the position of an item. Raise ValueError if not found.'
+ k = self._key(item)
+ i = bisect_left(self._keys, k)
+ j = bisect_right(self._keys, k)
+ return self._items[i:j].index(item) + i
+
+ def count(self, item):
+ 'Return number of occurrences of item'
+ k = self._key(item)
+ i = bisect_left(self._keys, k)
+ j = bisect_right(self._keys, k)
+ return self._items[i:j].count(item)
+
+ def insert(self, item):
+ 'Insert a new item. If equal keys are found, add to the left'
+ k = self._key(item)
+ i = bisect_left(self._keys, k)
+ self._keys.insert(i, k)
+ self._items.insert(i, item)
+
+ def insert_right(self, item):
+ 'Insert a new item. If equal keys are found, add to the right'
+ k = self._key(item)
+ i = bisect_right(self._keys, k)
+ self._keys.insert(i, k)
+ self._items.insert(i, item)
+
+ def remove(self, item):
+ 'Remove first occurence of item. Raise ValueError if not found'
+ i = self.index(item)
+ del self._keys[i]
+ del self._items[i]
+
+ def find(self, k):
+ 'Return first item with a key == k. Raise ValueError if not found.'
+ i = bisect_left(self._keys, k)
+ if i != len(self) and self._keys[i] == k:
+ return self._items[i]
+ raise ValueError('No item found with key equal to: %r' % (k,))
+
+ def find_le(self, k):
+ 'Return last item with a key <= k. Raise ValueError if not found.'
+ i = bisect_right(self._keys, k)
+ if i:
+ return self._items[i-1]
+ raise ValueError('No item found with key at or below: %r' % (k,))
+
+ def find_lt(self, k):
+ 'Return last item with a key < k. Raise ValueError if not found.'
+ i = bisect_left(self._keys, k)
+ if i:
+ return self._items[i-1]
+ raise ValueError('No item found with key below: %r' % (k,))
+
+ def find_ge(self, k):
+ 'Return first item with a key >= equal to k. Raise ValueError if not found'
+ i = bisect_left(self._keys, k)
+ if i != len(self):
+ return self._items[i]
+ raise ValueError('No item found with key at or above: %r' % (k,))
+
+ def find_gt(self, k):
+ 'Return first item with a key > k. Raise ValueError if not found'
+ i = bisect_right(self._keys, k)
+ if i != len(self):
+ return self._items[i]
+ raise ValueError('No item found with key above: %r' % (k,))
diff --git a/perfprofd/symbolizer.cc b/perfprofd/symbolizer.cc
new file mode 100644
index 00000000..3b39f6f5
--- /dev/null
+++ b/perfprofd/symbolizer.cc
@@ -0,0 +1,179 @@
+/*
+ *
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "symbolizer.h"
+
+#include <map>
+#include <memory>
+#include <unordered_map>
+
+#include <android-base/logging.h>
+#include <base/mem_map.h>
+#include <build_id.h>
+#include <read_dex_file.h>
+#include <read_elf.h>
+#include <vdex_file.h>
+
+namespace perfprofd {
+
+namespace {
+
+struct SimpleperfSymbolizer : public Symbolizer {
+ // For simplicity, we assume non-overlapping symbols.
+ struct Symbol {
+ Symbol(std::string_view n, uint64_t l) : name(n), length(l) {}
+
+ std::string name;
+ uint64_t length;
+ };
+ using SymbolMap = std::map<uint64_t, Symbol>;
+
+ std::string Decode(const std::string& dso, uint64_t address) override {
+ auto it = dsos.find(dso);
+ if (it == dsos.end()) {
+ LoadDso(dso);
+ it = dsos.find(dso);
+ DCHECK(it != dsos.end());
+ }
+
+ const SymbolMap& map = it->second;
+ if (map.empty()) {
+ return "";
+ }
+ auto upper_bound = map.upper_bound(address);
+ if (upper_bound == map.begin()) {
+ // Nope, not in the map.
+ return "";
+ }
+
+ upper_bound--;
+ if (upper_bound->first + upper_bound->second.length > address) {
+ // This element covers the given address, return its name.
+ return upper_bound->second.name;
+ }
+
+ return "";
+ }
+
+ void LoadDso(const std::string& dso) {
+ // See whether it's an ELF file.
+ {
+ SymbolMap elf_data;
+ auto callback = [&elf_data](const ElfFileSymbol& sym) {
+ if (sym.is_func) {
+ if (sym.len == 0) {
+ LOG(ERROR) << "Symbol size is zero for " << sym.name;
+ }
+ elf_data.emplace(sym.vaddr, Symbol(sym.name, sym.len));
+ }
+ };
+ ElfStatus status = ParseSymbolsFromElfFile(dso, BuildId(), callback);
+ if (status == ElfStatus::NO_ERROR) {
+ dsos.emplace(dso, std::move(elf_data));
+ return;
+ }
+ }
+
+ // See whether it's a vdex file.
+ {
+ ::art::MemMap::Init();
+
+ using VdexFile = ::art::VdexFile;
+ std::string error_msg;
+ std::unique_ptr<VdexFile> vdex = VdexFile::Open(dso,
+ /* writable= */ false,
+ /* low_4gb= */ false,
+ /* unquicken= */ false,
+ &error_msg);
+ if (vdex != nullptr) {
+ const uint8_t* cur = nullptr;
+ std::vector<uint64_t> dex_file_offsets;
+ const uint8_t* base = vdex->Begin();
+ for (;;) {
+ cur = vdex->GetNextDexFileData(cur);
+ if (cur == nullptr) {
+ break;
+ }
+ dex_file_offsets.push_back(cur - base);
+ }
+
+ if (!dex_file_offsets.empty()) {
+ std::vector<DexFileSymbol> symbols;
+ if (ReadSymbolsFromDexFile(dso, dex_file_offsets, &symbols)) {
+ SymbolMap vdex_data;
+ for (const DexFileSymbol& symbol : symbols) {
+ vdex_data.emplace(symbol.offset, Symbol(symbol.name, symbol.len));
+ }
+ dsos.emplace(dso, std::move(vdex_data));
+ LOG(INFO) << "Found " << symbols.size() << " dex symbols in " << dso;
+ return;
+ } else {
+ LOG(WARNING) << "Could not read symbols from dex files in " << dso;
+ }
+ } else {
+ LOG(WARNING) << "Could not find dex files for vdex " << dso;
+ dsos.emplace(dso, SymbolMap());
+ }
+ } else {
+ LOG(WARNING) << dso << " is not a vdex: " << error_msg;
+ }
+ }
+
+ // TODO: See whether it's a dex file.
+
+ // OK, give up.
+ LOG(WARNING) << "Could not symbolize " << dso;
+ dsos.emplace(dso, SymbolMap());
+ }
+
+ bool GetMinExecutableVAddr(const std::string& dso, uint64_t* addr) override {
+ uint64_t file_offset_of_min_vaddr;
+ ElfStatus status = ReadMinExecutableVirtualAddressFromElfFile(dso, BuildId(), addr,
+ &file_offset_of_min_vaddr);
+ if (status != ElfStatus::NO_ERROR) {
+ return true;
+ }
+
+ {
+ ::art::MemMap::Init();
+
+ using VdexFile = ::art::VdexFile;
+ std::string error_msg;
+ std::unique_ptr<VdexFile> vdex = VdexFile::Open(dso,
+ /* writable= */ false,
+ /* low_4gb= */ false,
+ /* unquicken= */ false,
+ &error_msg);
+ if (vdex != nullptr) {
+ *addr = 0u;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ std::unordered_map<std::string, SymbolMap> dsos;
+};
+
+} // namespace
+
+std::unique_ptr<Symbolizer> CreateELFSymbolizer() {
+ return std::unique_ptr<Symbolizer>(new SimpleperfSymbolizer());
+}
+
+} // namespace perfprofd
diff --git a/perfprofd/symbolizer.h b/perfprofd/symbolizer.h
new file mode 100644
index 00000000..10771595
--- /dev/null
+++ b/perfprofd/symbolizer.h
@@ -0,0 +1,35 @@
+/*
+ *
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SYSTEM_EXTRAS_PERFPROFD_SYMBOLIZER_H_
+#define SYSTEM_EXTRAS_PERFPROFD_SYMBOLIZER_H_
+
+#include <memory>
+
+namespace perfprofd {
+
+struct Symbolizer {
+ virtual ~Symbolizer() {}
+ virtual std::string Decode(const std::string& dso, uint64_t address) = 0;
+ virtual bool GetMinExecutableVAddr(const std::string& dso, uint64_t* addr) = 0;
+};
+
+std::unique_ptr<Symbolizer> CreateELFSymbolizer();
+
+} // namespace perfprofd
+
+#endif // SYSTEM_EXTRAS_PERFPROFD_SYMBOLIZER_H_
diff --git a/perfprofd/tests/Android.bp b/perfprofd/tests/Android.bp
new file mode 100644
index 00000000..21649033
--- /dev/null
+++ b/perfprofd/tests/Android.bp
@@ -0,0 +1,73 @@
+// Build the unit tests.
+
+cc_defaults {
+ name: "perfprofd_test_defaults",
+ defaults: [
+ "libartd_static_defaults",
+ "perfprofd_defaults",
+ "perfprofd_debug_defaults",
+ ],
+
+ strip: {
+ keep_symbols: true,
+ },
+}
+
+//
+// Unit test for perfprofd
+//
+cc_test {
+ name: "perfprofd_test",
+ defaults: [
+ "perfprofd_test_defaults",
+ "libsimpleperf_dex_read_static_reqs_defaults",
+ "libsimpleperf_elf_read_static_reqs_defaults",
+ ],
+ test_suites: ["device-tests"],
+ host_supported: true,
+
+ stl: "libc++",
+ static_libs: [
+ "libperfprofdcored",
+ "libperfprofd_proto_config",
+ "libsimpleperf_dex_read",
+ "libsimpleperf_elf_read",
+ "libbase",
+ "libutils",
+ "libz",
+ "libprotobuf-cpp-lite",
+ "liblog",
+ ],
+ target: {
+ host: {
+ host_ldlibs: [
+ "-lncurses",
+ ],
+ },
+
+ // The live tests require simpleperf.
+ android: {
+ required: [
+ "simpleperf",
+ ],
+ static_libs: [
+ "libhealthhalutils",
+ ],
+ shared_libs: [
+ "android.hardware.health@2.0",
+ "libbinder",
+ "libhidlbase",
+ "libservices",
+ "libutils",
+ ],
+ },
+
+ },
+ srcs: [
+ "perfprofd_test.cc",
+ ],
+ data: [
+ "canned.perf.data",
+ "callchain.canned.perf.data",
+ ],
+}
diff --git a/perfprofd/tests/AndroidTest.xml b/perfprofd/tests/AndroidTest.xml
new file mode 100644
index 00000000..20b13731
--- /dev/null
+++ b/perfprofd/tests/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?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 perfprofd_test">
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="perfprofd_test->/data/local/tmp/perfprofd_test" />
+ <option name="push" value="canned.perf.data->/data/local/tmp/canned.perf.data" />
+ <option name="push" value="callchain.canned.perf.data->/data/local/tmp/callchain.canned.perf.data" />
+
+ </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="perfprofd_test" />
+ </test>
+</configuration>
diff --git a/perfprofd/tests/README.txt b/perfprofd/tests/README.txt
new file mode 100644
index 00000000..949e73f0
--- /dev/null
+++ b/perfprofd/tests/README.txt
@@ -0,0 +1,64 @@
+Native tests for 'perfprofd'. Please run with
+
+ runtest --path=system/extras/perfprofd/tests
+
+(where runtest == $ANDROID_BUILD_TOP"/development/testrunner/runtest.py).
+
+Notes:
+
+1. Several of the testpoints in this unit tests perform a live 'simpleperf'
+run on the device (if you are using a userdebug build, simpleperf should
+already be available in /system/xbin/simpleperf).
+
+2. Part of the test is a system-wide profile, meaning that you will
+need to run 'adb root' prior to test execution.
+
+3. The daemon under test, perfprofd, is broken into a main function, a
+"core" library, and a "utils library. Picture:
+
+ +-----------+ perfprofdmain.o
+ | perfprofd |
+ | main() | 1-liner; calls perfprofd_main()
+ +-----------+
+ |
+ v
+ +-----------+ perfprofdcore.a
+ | perfprofd |
+ | core | most of the interesting code is here;
+ | | calls into utils library when for
+ +-----------+ operations such as sleep, log, etc
+ |
+ v
+ +-----------+ perfprofdutils.a
+ | perfprofd |
+ | utils | real implementations of perfprofd_sleep,
+ | | perfprofd_log_* etc
+ +-----------+
+
+Because the daemon tends to spend a lot of time sleeping/waiting,
+it is impractical to try to test it directly. Instead we insert a
+mock utilities layer and then have a test driver that invokes the
+daemon main function. Picture for perfprofd_test:
+
+ +----------------+ perfprofd_test.cc
+ | perfprofd_test |
+ | | makes calls into perfprofd_main(),
+ +----------------+ then verifies behavior
+ |
+ v
+ +-----------+ perfprofdcore.a
+ | perfprofd |
+ | core | same as above
+ +-----------+
+ |
+ v
+ +-----------+ perfprofdmockutils.a
+ | perfprofd |
+ | mockutils | mock implementations of perfprofd_sleep,
+ | | perfprofd_log_* etc
+ +-----------+
+
+The mockup versions of perfprofd_sleep() and perfprofd_log_* do
+simply log the fact that they are called; the test driver can
+then examine the log to make sure that the daemon is doing
+what it is supposed to be doing.
diff --git a/perfprofd/tests/callchain.canned.perf.data b/perfprofd/tests/callchain.canned.perf.data
new file mode 100644
index 00000000..8d843935
--- /dev/null
+++ b/perfprofd/tests/callchain.canned.perf.data
Binary files differ
diff --git a/perfprofd/tests/canned.perf.data b/perfprofd/tests/canned.perf.data
new file mode 100644
index 00000000..e6510d2a
--- /dev/null
+++ b/perfprofd/tests/canned.perf.data
Binary files differ
diff --git a/perfprofd/tests/perfprofd_test.cc b/perfprofd/tests/perfprofd_test.cc
new file mode 100644
index 00000000..311ab6a0
--- /dev/null
+++ b/perfprofd/tests/perfprofd_test.cc
@@ -0,0 +1,1743 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <cctype>
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <mutex>
+#include <regex>
+#include <string>
+#include <unordered_set>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <libgen.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/macros.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/thread_annotations.h>
+#include <gtest/gtest.h>
+#include <zlib.h>
+
+#include "config.h"
+#include "configreader.h"
+#include "map_utils.h"
+#include "perfprofdcore.h"
+#include "perfprofd_cmdline.h"
+#include "perfprofd_perf.h"
+#include "perfprofd_threaded_handler.h"
+#include "quipper_helper.h"
+#include "symbolizer.h"
+
+#include "perfprofd_record.pb.h"
+
+using namespace android::perfprofd::quipper;
+
+static_assert(android::base::kEnableDChecks, "Expected DCHECKs to be enabled");
+
+//
+// Set to argv[0] on startup
+//
+static std::string gExecutableRealpath;
+
+namespace {
+
+using android::base::LogId;
+using android::base::LogSeverity;
+
+class TestLogHelper {
+ public:
+ void Install() {
+ using namespace std::placeholders;
+ android::base::SetLogger(
+ std::bind(&TestLogHelper::TestLogFunction, this, _1, _2, _3, _4, _5, _6));
+ }
+
+ std::string JoinTestLog(const char* delimiter) {
+ std::unique_lock<std::mutex> ul(lock_);
+ return android::base::Join(test_log_messages_, delimiter);
+ }
+ template <typename Predicate>
+ std::string JoinTestLog(const char* delimiter, Predicate pred) {
+ std::unique_lock<std::mutex> ul(lock_);
+ std::vector<std::string> tmp;
+ std::copy_if(test_log_messages_.begin(),
+ test_log_messages_.end(),
+ std::back_inserter(tmp),
+ pred);
+ return android::base::Join(tmp, delimiter);
+ }
+
+ private:
+ void TestLogFunction(LogId log_id,
+ LogSeverity severity,
+ const char* tag,
+ const char* file,
+ unsigned int line,
+ const char* message) {
+ std::unique_lock<std::mutex> ul(lock_);
+ constexpr char log_characters[] = "VDIWEFF";
+ char severity_char = log_characters[severity];
+ test_log_messages_.push_back(android::base::StringPrintf("%c: %s", severity_char, message));
+
+ if (severity >= LogSeverity::FATAL_WITHOUT_ABORT) {
+ android::base::StderrLogger(log_id, severity, tag, file, line, message);
+ }
+ }
+
+ private:
+ std::mutex lock_;
+
+ std::vector<std::string> test_log_messages_;
+};
+
+} // namespace
+
+// Path to perf executable on device
+#define PERFPATH "/system/bin/perf"
+
+// Temporary config file that we will emit for the daemon to read
+#define CONFIGFILE "perfprofd.conf"
+
+static bool bothWhiteSpace(char lhs, char rhs)
+{
+ return (std::isspace(lhs) && std::isspace(rhs));
+}
+
+#ifdef __ANDROID__
+
+static bool IsPerfSupported() {
+ auto check_perf_supported = []() {
+#if defined(__i386__) || defined(__x86_64__)
+ // Cloud devices may suppress perf. Check for arch_perfmon.
+ std::string cpuinfo;
+ if (!android::base::ReadFileToString("/proc/cpuinfo", &cpuinfo)) {
+ // This is pretty unexpected. Return true to see if we can run tests anyways.
+ return true;
+ }
+ return cpuinfo.find("arch_perfmon") != std::string::npos;
+#else
+ // Expect other architectures to have perf support.
+ return true;
+#endif
+ };
+ static bool perf_supported = check_perf_supported();
+ return perf_supported;
+}
+
+#endif
+
+//
+// Squeeze out repeated whitespace from expected/actual logs.
+//
+static std::string squeezeWhite(const std::string &str,
+ const char *tag,
+ bool dump=false)
+{
+ if (dump) { fprintf(stderr, "raw %s is %s\n", tag, str.c_str()); }
+ std::string result(str);
+ std::replace(result.begin(), result.end(), '\n', ' ');
+ auto new_end = std::unique(result.begin(), result.end(), bothWhiteSpace);
+ result.erase(new_end, result.end());
+ while (result.begin() != result.end() && std::isspace(*result.rbegin())) {
+ result.pop_back();
+ }
+ if (dump) { fprintf(stderr, "squeezed %s is %s\n", tag, result.c_str()); }
+ return result;
+}
+
+//
+// Replace all occurrences of a string with another string.
+//
+static std::string replaceAll(const std::string &str,
+ const std::string &from,
+ const std::string &to)
+{
+ std::string ret = "";
+ size_t pos = 0;
+ while (pos < str.size()) {
+ size_t found = str.find(from, pos);
+ if (found == std::string::npos) {
+ ret += str.substr(pos);
+ break;
+ }
+ ret += str.substr(pos, found - pos) + to;
+ pos = found + from.size();
+ }
+ return ret;
+}
+
+//
+// Replace occurrences of special variables in the string.
+//
+#ifdef __ANDROID__
+static std::string expandVars(const std::string &str) {
+#ifdef __LP64__
+ return replaceAll(str, "$NATIVE_TESTS", "/data/nativetest64");
+#else
+ return replaceAll(str, "$NATIVE_TESTS", "/data/nativetest");
+#endif
+}
+#endif
+
+class PerfProfdTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ test_logger.Install();
+ create_dirs();
+ }
+
+ virtual void TearDown() {
+ android::base::SetLogger(android::base::StderrLogger);
+
+ // TODO: proper management of test files. For now, use old system() code.
+ for (const auto dir : { &dest_dir, &conf_dir }) {
+ std::string cmd("rm -rf ");
+ cmd += *dir;
+ int ret = system(cmd.c_str());
+ CHECK_EQ(0, ret);
+ }
+ }
+
+ protected:
+ //
+ // Check to see if the log messages emitted by the daemon
+ // match the expected result. By default we use a partial
+ // match, e.g. if we see the expected excerpt anywhere in the
+ // result, it's a match (for exact match, set exact to true)
+ //
+ ::testing::AssertionResult CompareLogMessages(const std::string& expected,
+ bool exactMatch = false) {
+ std::string sqexp = squeezeWhite(expected, "expected");
+
+ // Strip out JIT errors.
+ std::regex jit_regex("E: Failed to open ELF file: [^ ]*dalvik-jit-code-cache.*");
+ auto strip_jit = [&](const std::string& str) {
+ std::smatch jit_match;
+ return !std::regex_match(str, jit_match, jit_regex);
+ };
+ std::string sqact = squeezeWhite(test_logger.JoinTestLog(" ", strip_jit), "actual");
+
+ if (exactMatch) {
+ if (sqexp == sqact) {
+ return ::testing::AssertionSuccess() << sqexp << " is equal to " << sqact;
+ }
+ return ::testing::AssertionFailure() << "Expected:" << std::endl << sqexp << std::endl
+ << "Received:" << std::endl << sqact;
+ } else {
+ if (sqact.find(sqexp) == std::string::npos) {
+ return ::testing::AssertionFailure()
+ << "Expected to find:" << std::endl << sqexp << std::endl
+ << "in:" << std::endl << sqact;
+ }
+ return ::testing::AssertionSuccess() << sqexp << " was found in " << sqact;
+ }
+ }
+
+ // test_dir is the directory containing the test executable and
+ // any files associated with the test (will be created by the harness).
+ std::string test_dir;
+
+ // dest_dir is a temporary directory that we're using as the destination directory.
+ // It is backed by temp_dir1.
+ std::string dest_dir;
+
+ // conf_dir is a temporary directory that we're using as the configuration directory.
+ // It is backed by temp_dir2.
+ std::string conf_dir;
+
+ TestLogHelper test_logger;
+
+ private:
+ void create_dirs() {
+ temp_dir1.reset(new TemporaryDir());
+ temp_dir2.reset(new TemporaryDir());
+ dest_dir = temp_dir1->path;
+ conf_dir = temp_dir2->path;
+ test_dir = android::base::Dirname(gExecutableRealpath);
+ }
+
+ std::unique_ptr<TemporaryDir> temp_dir1;
+ std::unique_ptr<TemporaryDir> temp_dir2;
+};
+
+///
+/// Helper class to kick off a run of the perfprofd daemon with a specific
+/// config file.
+///
+class PerfProfdRunner {
+ public:
+ explicit PerfProfdRunner(const std::string& config_dir)
+ : config_dir_(config_dir)
+ {
+ config_path_ = config_dir + "/" CONFIGFILE;
+ }
+
+ ~PerfProfdRunner()
+ {
+ remove_processed_file();
+ }
+
+ void addToConfig(const std::string &line)
+ {
+ config_text_ += line;
+ config_text_ += "\n";
+ }
+
+ void remove_semaphore_file()
+ {
+ std::string semaphore(config_dir_);
+ semaphore += "/" SEMAPHORE_FILENAME;
+ unlink(semaphore.c_str());
+ }
+
+ void create_semaphore_file()
+ {
+ std::string semaphore(config_dir_);
+ semaphore += "/" SEMAPHORE_FILENAME;
+ close(open(semaphore.c_str(), O_WRONLY|O_CREAT, 0600));
+ }
+
+ void write_processed_file(int start_seq, int end_seq)
+ {
+ std::string processed = config_dir_ + "/" PROCESSED_FILENAME;
+ FILE *fp = fopen(processed.c_str(), "w");
+ for (int i = start_seq; i < end_seq; i++) {
+ fprintf(fp, "%d\n", i);
+ }
+ fclose(fp);
+ }
+
+ void remove_processed_file()
+ {
+ std::string processed = config_dir_ + "/" PROCESSED_FILENAME;
+ unlink(processed.c_str());
+ }
+
+ struct LoggingConfig : public Config {
+ void Sleep(size_t seconds) override {
+ // Log sleep calls but don't sleep.
+ LOG(INFO) << "sleep " << seconds << " seconds";
+ }
+
+ bool IsProfilingEnabled() const override {
+ //
+ // Check for existence of semaphore file in config directory
+ //
+ if (access(config_directory.c_str(), F_OK) == -1) {
+ PLOG(WARNING) << "unable to open config directory " << config_directory;
+ return false;
+ }
+
+ // Check for existence of semaphore file
+ std::string semaphore_filepath = config_directory
+ + "/" + SEMAPHORE_FILENAME;
+ if (access(semaphore_filepath.c_str(), F_OK) == -1) {
+ return false;
+ }
+
+ return true;
+ }
+ };
+
+ int invoke()
+ {
+ static const char *argv[3] = { "perfprofd", "-c", "" };
+ argv[2] = config_path_.c_str();
+
+ writeConfigFile(config_path_, config_text_);
+
+ // execute daemon main
+ LoggingConfig config;
+ return perfprofd_main(3, (char **) argv, &config);
+ }
+
+ private:
+ std::string config_dir_;
+ std::string config_path_;
+ std::string config_text_;
+
+ void writeConfigFile(const std::string &config_path,
+ const std::string &config_text)
+ {
+ FILE *fp = fopen(config_path.c_str(), "w");
+ ASSERT_TRUE(fp != nullptr);
+ fprintf(fp, "%s\n", config_text.c_str());
+ fclose(fp);
+ }
+};
+
+//......................................................................
+
+static std::string encoded_file_path(const std::string& dest_dir,
+ int seq) {
+ return android::base::StringPrintf("%s/perf.data.encoded.%d",
+ dest_dir.c_str(), seq);
+}
+
+static void readEncodedProfile(const std::string& dest_dir,
+ bool compressed,
+ android::perfprofd::PerfprofdRecord& encodedProfile)
+{
+ struct stat statb;
+ int perf_data_stat_result = stat(encoded_file_path(dest_dir, 0).c_str(), &statb);
+ ASSERT_NE(-1, perf_data_stat_result);
+
+ // read
+ std::string encoded;
+ encoded.resize(statb.st_size);
+ FILE *ifp = fopen(encoded_file_path(dest_dir, 0).c_str(), "r");
+ ASSERT_NE(nullptr, ifp);
+ size_t items_read = fread((void*) encoded.data(), statb.st_size, 1, ifp);
+ ASSERT_EQ(1, items_read);
+ fclose(ifp);
+
+ // uncompress
+ if (compressed && !encoded.empty()) {
+ z_stream stream;
+ stream.zalloc = Z_NULL;
+ stream.zfree = Z_NULL;
+ stream.opaque = Z_NULL;
+
+ {
+ constexpr int kWindowBits = 15;
+ constexpr int kGzipEncoding = 16;
+ int init_result = inflateInit2(&stream, kWindowBits | kGzipEncoding);
+ if (init_result != Z_OK) {
+ LOG(ERROR) << "Could not initialize libz stream " << init_result;
+ return;
+ }
+ }
+
+ std::string buf;
+ buf.reserve(2 * encoded.size());
+ stream.avail_in = encoded.size();
+ stream.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(encoded.data()));
+
+ int result;
+ do {
+ uint8_t chunk[1024];
+ stream.next_out = static_cast<Bytef*>(chunk);
+ stream.avail_out = arraysize(chunk);
+
+ result = inflate(&stream, 0);
+ const size_t amount = arraysize(chunk) - stream.avail_out;
+ if (amount > 0) {
+ if (buf.capacity() - buf.size() < amount) {
+ buf.reserve(buf.capacity() + 64u * 1024u);
+ CHECK_LE(amount, buf.capacity() - buf.size());
+ }
+ size_t index = buf.size();
+ buf.resize(buf.size() + amount);
+ memcpy(reinterpret_cast<uint8_t*>(const_cast<char*>(buf.data())) + index, chunk, amount);
+ }
+ } while (result == Z_OK);
+ inflateEnd(&stream);
+ if (result != Z_STREAM_END) {
+ LOG(ERROR) << "Finished with not-Z_STREAM_END " << result;
+ return;
+ }
+ encoded = buf;
+ }
+
+ // decode
+ encodedProfile.ParseFromString(encoded);
+}
+
+#define RAW_RESULT(x) #x
+
+TEST_F(PerfProfdTest, TestUtil)
+{
+ EXPECT_EQ("", replaceAll("", "", ""));
+ EXPECT_EQ("zzbc", replaceAll("abc", "a", "zz"));
+ EXPECT_EQ("azzc", replaceAll("abc", "b", "zz"));
+ EXPECT_EQ("abzz", replaceAll("abc", "c", "zz"));
+ EXPECT_EQ("xxyyzz", replaceAll("abc", "abc", "xxyyzz"));
+}
+
+TEST_F(PerfProfdTest, MissingGMS)
+{
+ //
+ // AWP requires cooperation between the daemon and the GMS core
+ // piece. If we're running on a device that has an old or damaged
+ // version of GMS core, then the config directory we're interested in
+ // may not be there. This test insures that the daemon does the
+ // right thing in this case.
+ //
+ PerfProfdRunner runner(conf_dir);
+ runner.addToConfig("only_debug_build=0");
+ runner.addToConfig("trace_config_read=0");
+ runner.addToConfig("config_directory=/does/not/exist");
+ runner.addToConfig("main_loop_iterations=1");
+ runner.addToConfig("use_fixed_seed=1");
+ runner.addToConfig("collection_interval=100");
+
+ // Kick off daemon
+ int daemon_main_return_code = runner.invoke();
+
+ // Check return code from daemon
+ EXPECT_EQ(0, daemon_main_return_code);
+
+ // Verify log contents
+ const std::string expected = RAW_RESULT(
+ I: sleep 90 seconds
+ W: unable to open config directory /does/not/exist: No such file or directory
+ I: profile collection skipped (missing config directory)
+ );
+
+ // check to make sure entire log matches
+ EXPECT_TRUE(CompareLogMessages(expected));
+}
+
+
+TEST_F(PerfProfdTest, MissingOptInSemaphoreFile)
+{
+ //
+ // Android device owners must opt in to "collect and report usage
+ // data" in order for us to be able to collect profiles. The opt-in
+ // check is performed in the GMS core component; if the check
+ // passes, then it creates a semaphore file for the daemon to pick
+ // up on.
+ //
+ PerfProfdRunner runner(conf_dir);
+ runner.addToConfig("only_debug_build=0");
+ std::string cfparam("config_directory="); cfparam += conf_dir;
+ runner.addToConfig(cfparam);
+ std::string ddparam("destination_directory="); ddparam += dest_dir;
+ runner.addToConfig(ddparam);
+ runner.addToConfig("main_loop_iterations=1");
+ runner.addToConfig("use_fixed_seed=1");
+ runner.addToConfig("collection_interval=100");
+
+ runner.remove_semaphore_file();
+
+ // Kick off daemon
+ int daemon_main_return_code = runner.invoke();
+
+ // Check return code from daemon
+ EXPECT_EQ(0, daemon_main_return_code);
+
+ // Verify log contents
+ const std::string expected = RAW_RESULT(
+ I: profile collection skipped (missing config directory)
+ );
+ // check to make sure log excerpt matches
+ EXPECT_TRUE(CompareLogMessages(expected));
+}
+
+TEST_F(PerfProfdTest, MissingPerfExecutable)
+{
+ //
+ // Perfprofd uses the 'simpleperf' tool to collect profiles
+ // (although this may conceivably change in the future). This test
+ // checks to make sure that if 'simpleperf' is not present we bail out
+ // from collecting profiles.
+ //
+ PerfProfdRunner runner(conf_dir);
+ runner.addToConfig("only_debug_build=0");
+ runner.addToConfig("trace_config_read=1");
+ std::string cfparam("config_directory="); cfparam += conf_dir;
+ runner.addToConfig(cfparam);
+ std::string ddparam("destination_directory="); ddparam += dest_dir;
+ runner.addToConfig(ddparam);
+ runner.addToConfig("main_loop_iterations=1");
+ runner.addToConfig("use_fixed_seed=1");
+ runner.addToConfig("collection_interval=100");
+ runner.addToConfig("perf_path=/does/not/exist");
+
+ // Create semaphore file
+ runner.create_semaphore_file();
+
+ // Kick off daemon
+ int daemon_main_return_code = runner.invoke();
+
+ // Check return code from daemon
+ EXPECT_EQ(0, daemon_main_return_code);
+
+ // expected log contents
+ const std::string expected = RAW_RESULT(
+ I: profile collection skipped (missing 'perf' executable)
+ );
+ // check to make sure log excerpt matches
+ EXPECT_TRUE(CompareLogMessages(expected));
+}
+
+TEST_F(PerfProfdTest, BadPerfRun)
+{
+ //
+ // Perf tools tend to be tightly coupled with a specific kernel
+ // version -- if things are out of sync perf could fail or
+ // crash. This test makes sure that we detect such a case and log
+ // the error.
+ //
+ PerfProfdRunner runner(conf_dir);
+ runner.addToConfig("only_debug_build=0");
+ std::string cfparam("config_directory="); cfparam += conf_dir;
+ runner.addToConfig(cfparam);
+ std::string ddparam("destination_directory="); ddparam += dest_dir;
+ runner.addToConfig(ddparam);
+ runner.addToConfig("main_loop_iterations=1");
+ runner.addToConfig("use_fixed_seed=1");
+ runner.addToConfig("collection_interval=100");
+#ifdef __ANDROID__
+ runner.addToConfig("perf_path=/system/bin/false");
+#else
+ runner.addToConfig("perf_path=/bin/false");
+#endif
+
+ // Create semaphore file
+ runner.create_semaphore_file();
+
+ // Kick off daemon
+ int daemon_main_return_code = runner.invoke();
+
+ // Check return code from daemon
+ EXPECT_EQ(0, daemon_main_return_code);
+
+ // Verify log contents. Because of perferr logging containing pids and test paths,
+ // it is easier to have three expected parts.
+ const std::string expected1 = "W: perf bad exit status 1";
+ const std::string expected2 = "bin/false record";
+ const std::string expected3 = "W: profile collection failed";
+
+ // check to make sure log excerpt matches
+ EXPECT_TRUE(CompareLogMessages(expected1));
+ EXPECT_TRUE(CompareLogMessages(expected2));
+ EXPECT_TRUE(CompareLogMessages(expected3));
+}
+
+TEST_F(PerfProfdTest, ConfigFileParsing)
+{
+ //
+ // Gracefully handly malformed items in the config file
+ //
+ PerfProfdRunner runner(conf_dir);
+ runner.addToConfig("only_debug_build=0");
+ runner.addToConfig("main_loop_iterations=1");
+ runner.addToConfig("collection_interval=100");
+ runner.addToConfig("use_fixed_seed=1");
+ runner.addToConfig("destination_directory=/does/not/exist");
+
+ // assorted bad syntax
+ runner.addToConfig("collection_interval=-1");
+ runner.addToConfig("collection_interval=18446744073709551615");
+ runner.addToConfig("nonexistent_key=something");
+ runner.addToConfig("no_equals_stmt");
+
+ // Kick off daemon
+ int daemon_main_return_code = runner.invoke();
+
+ // Check return code from daemon
+ EXPECT_EQ(0, daemon_main_return_code);
+
+ // Verify log contents
+ const std::string expected = RAW_RESULT(
+ W: line 6: value -1 cannot be parsed
+ W: line 7: specified value 18446744073709551615 for 'collection_interval' outside permitted range [0 4294967295]
+ W: line 8: unknown option 'nonexistent_key'
+ W: line 9: line malformed (no '=' found)
+ );
+
+ // check to make sure log excerpt matches
+ EXPECT_TRUE(CompareLogMessages(expected));
+}
+
+TEST_F(PerfProfdTest, ConfigFileParsing_Events) {
+ auto check_event_config = [](const Config& config,
+ size_t index,
+ const std::vector<std::string>& names,
+ bool group,
+ uint32_t period) {
+ if (config.event_config.size() <= index) {
+ return ::testing::AssertionFailure() << "Not enough entries " << config.event_config.size()
+ << " " << index;
+ }
+ const auto& elem = config.event_config[index];
+
+ if (elem.group != group) {
+ return ::testing::AssertionFailure() << "Type wrong " << elem.group << " " << group;
+ }
+
+ if (elem.sampling_period != period) {
+ return ::testing::AssertionFailure() << "Period wrong " << elem.sampling_period << " "
+ << period;
+ }
+
+ auto strvec = [](const std::vector<std::string>& v) {
+ return "[" + android::base::Join(v, ',') + "]";
+ };
+ if (elem.events.size() != names.size()) {
+ return ::testing::AssertionFailure() << "Names wrong " << strvec(elem.events) << " "
+ << strvec(names);
+ }
+ for (size_t i = 0; i != elem.events.size(); ++i) {
+ if (elem.events[i] != names[i]) {
+ return ::testing::AssertionFailure() << "Names wrong at " << i << ": "
+ << strvec(elem.events) << " "
+ << strvec(names);
+ }
+ }
+ return ::testing::AssertionSuccess();
+ };
+
+ {
+ std::string data = "-e_hello,world=1\n"
+ "-g_foo,bar=2\n"
+ "-e_abc,xyz=3\n"
+ "-g_ftrace:test,ftrace:test2=4";
+
+ ConfigReader reader;
+ std::string error_msg;
+ ASSERT_TRUE(reader.Read(data, true, &error_msg)) << error_msg;
+
+ PerfProfdRunner::LoggingConfig config;
+ reader.FillConfig(&config);
+
+ EXPECT_TRUE(check_event_config(config, 0, { "hello", "world" }, false, 1));
+ EXPECT_TRUE(check_event_config(config, 1, { "foo", "bar" }, true, 2));
+ EXPECT_TRUE(check_event_config(config, 2, { "abc", "xyz" }, false, 3));
+ EXPECT_TRUE(check_event_config(config, 3, { "ftrace:test", "ftrace:test2" }, true, 4));
+ }
+
+ {
+ std::string data = "-e_hello,world=dummy";
+
+ ConfigReader reader;
+ std::string error_msg;
+ EXPECT_FALSE(reader.Read(data, true, &error_msg));
+ }
+
+ {
+ std::string data = "-g_hello,world=dummy";
+
+ ConfigReader reader;
+ std::string error_msg;
+ EXPECT_FALSE(reader.Read(data, true, &error_msg));
+ }
+}
+
+
+TEST_F(PerfProfdTest, ConfigDump) {
+ constexpr const char* kConfigElems[] = {
+ "collection_interval=14400",
+ "use_fixed_seed=1",
+ "main_loop_iterations=2",
+ "destination_directory=/does/not/exist",
+ "config_directory=a",
+ "perf_path=/system/xbin/simpleperf2",
+ "sampling_period=3",
+ "sampling_frequency=4",
+ "sample_duration=5",
+ "only_debug_build=1",
+ "hardwire_cpus=1",
+ "hardwire_cpus_max_duration=6",
+ "max_unprocessed_profiles=7",
+ "stack_profile=1",
+ "trace_config_read=1",
+ "collect_cpu_utilization=1",
+ "collect_charging_state=1",
+ "collect_booting=1",
+ "collect_camera_active=1",
+ "process=8",
+ "use_elf_symbolizer=1",
+ "symbolize_everything=1",
+ "compress=1",
+ "dropbox=1",
+ "fail_on_unsupported_events=1",
+ "-e_hello,world=1",
+ "-g_foo,bar=2",
+ "-e_abc,xyz=3",
+ "-g_ftrace:test,ftrace:test2=4",
+ };
+
+ std::string input;
+ for (const char* elem : kConfigElems) {
+ input.append(elem);
+ input.append("\n");
+ }
+
+ ConfigReader reader;
+ std::string error_msg;
+ ASSERT_TRUE(reader.Read(input, false, &error_msg)) << error_msg;
+
+ PerfProfdRunner::LoggingConfig config;
+ reader.FillConfig(&config);
+
+ std::string output = ConfigReader::ConfigToString(config);
+ for (const char* elem : kConfigElems) {
+ EXPECT_TRUE(output.find(elem) != std::string::npos) << elem << " not in " << output;
+ }
+}
+
+TEST_F(PerfProfdTest, ProfileCollectionAnnotations)
+{
+ unsigned util1 = collect_cpu_utilization();
+ EXPECT_LE(util1, 100);
+ EXPECT_GE(util1, 0);
+
+ // NB: expectation is that when we run this test, the device will be
+ // completed booted, will be on charger, and will not have the camera
+ // active.
+ EXPECT_FALSE(get_booting());
+#ifdef __ANDROID__
+ EXPECT_TRUE(get_charging());
+#endif
+ EXPECT_FALSE(get_camera_active());
+}
+
+namespace {
+
+template <typename Iterator>
+size_t CountEvents(const quipper::PerfDataProto& proto) {
+ size_t count = 0;
+ for (Iterator it(proto); it != it.end(); ++it) {
+ count++;
+ }
+ return count;
+}
+
+size_t CountCommEvents(const quipper::PerfDataProto& proto) {
+ return CountEvents<CommEventIterator>(proto);
+}
+size_t CountMmapEvents(const quipper::PerfDataProto& proto) {
+ return CountEvents<MmapEventIterator>(proto);
+}
+size_t CountSampleEvents(const quipper::PerfDataProto& proto) {
+ return CountEvents<SampleEventIterator>(proto);
+}
+size_t CountForkEvents(const quipper::PerfDataProto& proto) {
+ return CountEvents<ForkEventIterator>(proto);
+}
+size_t CountExitEvents(const quipper::PerfDataProto& proto) {
+ return CountEvents<ExitEventIterator>(proto);
+}
+
+std::string CreateStats(const quipper::PerfDataProto& proto) {
+ std::ostringstream oss;
+ oss << "Mmap events: " << CountMmapEvents(proto) << std::endl;
+ oss << "Sample events: " << CountSampleEvents(proto) << std::endl;
+ oss << "Comm events: " << CountCommEvents(proto) << std::endl;
+ oss << "Fork events: " << CountForkEvents(proto) << std::endl;
+ oss << "Exit events: " << CountExitEvents(proto) << std::endl;
+ return oss.str();
+}
+
+std::string FormatSampleEvent(const quipper::PerfDataProto_SampleEvent& sample) {
+ std::ostringstream oss;
+ if (sample.has_pid()) {
+ oss << "pid=" << sample.pid();
+ }
+ if (sample.has_tid()) {
+ oss << " tid=" << sample.tid();
+ }
+ if (sample.has_ip()) {
+ oss << " ip=" << sample.ip();
+ }
+ if (sample.has_addr()) {
+ oss << " addr=" << sample.addr();
+ }
+ if (sample.callchain_size() > 0) {
+ oss << " callchain=";
+ for (uint64_t cc : sample.callchain()) {
+ oss << "->" << cc;
+ }
+ }
+ return oss.str();
+}
+
+}
+
+struct BasicRunWithCannedPerf : PerfProfdTest {
+ void VerifyBasicCannedProfile(const android::perfprofd::PerfprofdRecord& encodedProfile) {
+ const quipper::PerfDataProto& perf_data = encodedProfile;
+
+ // Expect 21108 events.
+ EXPECT_EQ(21108, perf_data.events_size()) << CreateStats(perf_data);
+
+ EXPECT_EQ(48, CountMmapEvents(perf_data)) << CreateStats(perf_data);
+ EXPECT_EQ(19986, CountSampleEvents(perf_data)) << CreateStats(perf_data);
+ EXPECT_EQ(1033, CountCommEvents(perf_data)) << CreateStats(perf_data);
+ EXPECT_EQ(15, CountForkEvents(perf_data)) << CreateStats(perf_data);
+ EXPECT_EQ(26, CountExitEvents(perf_data)) << CreateStats(perf_data);
+
+ if (HasNonfatalFailure()) {
+ FAIL();
+ }
+
+ {
+ MmapEventIterator mmap(perf_data);
+ constexpr std::pair<const char*, uint64_t> kMmapEvents[] = {
+ std::make_pair("[kernel.kallsyms]_text", 0),
+ std::make_pair("/system/lib/libc.so", 3067412480u),
+ std::make_pair("/system/vendor/lib/libdsutils.so", 3069911040u),
+ std::make_pair("/system/lib/libc.so", 3067191296u),
+ std::make_pair("/system/lib/libc++.so", 3069210624u),
+ std::make_pair("/data/dalvik-cache/arm/system@framework@boot.oat", 1900048384u),
+ std::make_pair("/system/lib/libjavacore.so", 2957135872u),
+ std::make_pair("/system/vendor/lib/libqmi_encdec.so", 3006644224u),
+ std::make_pair("/data/dalvik-cache/arm/system@framework@wifi-service.jar@classes.dex",
+ 3010351104u),
+ std::make_pair("/system/lib/libart.so", 3024150528u),
+ std::make_pair("/system/lib/libz.so", 3056410624u),
+ std::make_pair("/system/lib/libicui18n.so", 3057610752u),
+ };
+ for (auto& pair : kMmapEvents) {
+ EXPECT_STREQ(pair.first, mmap->mmap_event().filename().c_str());
+ EXPECT_EQ(pair.second, mmap->mmap_event().start()) << pair.first;
+ ++mmap;
+ }
+ }
+
+ {
+ CommEventIterator comm(perf_data);
+ constexpr const char* kCommEvents[] = {
+ "init", "kthreadd", "ksoftirqd/0", "kworker/u:0H", "migration/0", "khelper",
+ "netns", "modem_notifier", "smd_channel_clo", "smsm_cb_wq", "rpm-smd", "kworker/u:1H",
+ };
+ for (auto str : kCommEvents) {
+ EXPECT_STREQ(str, comm->comm_event().comm().c_str());
+ ++comm;
+ }
+ }
+
+ {
+ SampleEventIterator samples(perf_data);
+ constexpr const char* kSampleEvents[] = {
+ "pid=0 tid=0 ip=3222720196",
+ "pid=0 tid=0 ip=3222910876",
+ "pid=0 tid=0 ip=3222910876",
+ "pid=0 tid=0 ip=3222910876",
+ "pid=0 tid=0 ip=3222910876",
+ "pid=0 tid=0 ip=3222910876",
+ "pid=0 tid=0 ip=3222910876",
+ "pid=3 tid=3 ip=3231975108",
+ "pid=5926 tid=5926 ip=3231964952",
+ "pid=5926 tid=5926 ip=3225342428",
+ "pid=5926 tid=5926 ip=3223841448",
+ "pid=5926 tid=5926 ip=3069807920",
+ };
+ for (auto str : kSampleEvents) {
+ EXPECT_STREQ(str, FormatSampleEvent(samples->sample_event()).c_str());
+ ++samples;
+ }
+
+ // Skip some samples.
+ for (size_t i = 0; i != 5000; ++i) {
+ ++samples;
+ }
+ constexpr const char* kSampleEvents2[] = {
+ "pid=5938 tid=5938 ip=3069630992",
+ "pid=5938 tid=5938 ip=3069626616",
+ "pid=5938 tid=5938 ip=3069626636",
+ "pid=5938 tid=5938 ip=3069637212",
+ "pid=5938 tid=5938 ip=3069637208",
+ "pid=5938 tid=5938 ip=3069637252",
+ "pid=5938 tid=5938 ip=3069346040",
+ "pid=5938 tid=5938 ip=3069637128",
+ "pid=5938 tid=5938 ip=3069626616",
+ };
+ for (auto str : kSampleEvents2) {
+ EXPECT_STREQ(str, FormatSampleEvent(samples->sample_event()).c_str());
+ ++samples;
+ }
+
+ // Skip some samples.
+ for (size_t i = 0; i != 5000; ++i) {
+ ++samples;
+ }
+ constexpr const char* kSampleEvents3[] = {
+ "pid=5938 tid=5938 ip=3069912036",
+ "pid=5938 tid=5938 ip=3069637260",
+ "pid=5938 tid=5938 ip=3069631024",
+ "pid=5938 tid=5938 ip=3069346064",
+ "pid=5938 tid=5938 ip=3069637356",
+ "pid=5938 tid=5938 ip=3069637144",
+ "pid=5938 tid=5938 ip=3069912036",
+ "pid=5938 tid=5938 ip=3069912036",
+ "pid=5938 tid=5938 ip=3069631244",
+ };
+ for (auto str : kSampleEvents3) {
+ EXPECT_STREQ(str, FormatSampleEvent(samples->sample_event()).c_str());
+ ++samples;
+ }
+ }
+ }
+};
+
+TEST_F(BasicRunWithCannedPerf, Basic)
+{
+ //
+ // Verify the portion of the daemon that reads and encodes
+ // perf.data files. Here we run the encoder on a canned perf.data
+ // file and verify that the resulting protobuf contains what
+ // we think it should contain.
+ //
+ std::string input_perf_data(test_dir);
+ input_perf_data += "/canned.perf.data";
+
+ // Set up config to avoid these annotations (they are tested elsewhere)
+ ConfigReader config_reader;
+ config_reader.overrideUnsignedEntry("collect_cpu_utilization", 0);
+ config_reader.overrideUnsignedEntry("collect_charging_state", 0);
+ config_reader.overrideUnsignedEntry("collect_camera_active", 0);
+
+ // Disable compression.
+ config_reader.overrideUnsignedEntry("compress", 0);
+
+ PerfProfdRunner::LoggingConfig config;
+ config_reader.FillConfig(&config);
+
+ // Kick off encoder and check return code
+ PROFILE_RESULT result =
+ encode_to_proto(input_perf_data, encoded_file_path(dest_dir, 0).c_str(), config, 0, nullptr);
+ ASSERT_EQ(OK_PROFILE_COLLECTION, result) << test_logger.JoinTestLog(" ");
+
+ // Read and decode the resulting perf.data.encoded file
+ android::perfprofd::PerfprofdRecord encodedProfile;
+ readEncodedProfile(dest_dir, false, encodedProfile);
+
+ VerifyBasicCannedProfile(encodedProfile);
+}
+
+TEST_F(BasicRunWithCannedPerf, Compressed)
+{
+ //
+ // Verify the portion of the daemon that reads and encodes
+ // perf.data files. Here we run the encoder on a canned perf.data
+ // file and verify that the resulting protobuf contains what
+ // we think it should contain.
+ //
+ std::string input_perf_data(test_dir);
+ input_perf_data += "/canned.perf.data";
+
+ // Set up config to avoid these annotations (they are tested elsewhere)
+ ConfigReader config_reader;
+ config_reader.overrideUnsignedEntry("collect_cpu_utilization", 0);
+ config_reader.overrideUnsignedEntry("collect_charging_state", 0);
+ config_reader.overrideUnsignedEntry("collect_camera_active", 0);
+
+ // Enable compression.
+ config_reader.overrideUnsignedEntry("compress", 1);
+
+ PerfProfdRunner::LoggingConfig config;
+ config_reader.FillConfig(&config);
+
+ // Kick off encoder and check return code
+ PROFILE_RESULT result =
+ encode_to_proto(input_perf_data, encoded_file_path(dest_dir, 0).c_str(), config, 0, nullptr);
+ ASSERT_EQ(OK_PROFILE_COLLECTION, result) << test_logger.JoinTestLog(" ");
+
+ // Read and decode the resulting perf.data.encoded file
+ android::perfprofd::PerfprofdRecord encodedProfile;
+ readEncodedProfile(dest_dir, true, encodedProfile);
+
+ VerifyBasicCannedProfile(encodedProfile);
+}
+
+class BasicRunWithCannedPerfWithSymbolizer : public BasicRunWithCannedPerf {
+ protected:
+ std::vector<::testing::AssertionResult> Run(bool symbolize_everything, size_t expected_count) {
+ //
+ // Verify the portion of the daemon that reads and encodes
+ // perf.data files. Here we run the encoder on a canned perf.data
+ // file and verify that the resulting protobuf contains what
+ // we think it should contain.
+ //
+ std::string input_perf_data(test_dir);
+ input_perf_data += "/canned.perf.data";
+
+ // Set up config to avoid these annotations (they are tested elsewhere)
+ ConfigReader config_reader;
+ config_reader.overrideUnsignedEntry("collect_cpu_utilization", 0);
+ config_reader.overrideUnsignedEntry("collect_charging_state", 0);
+ config_reader.overrideUnsignedEntry("collect_camera_active", 0);
+
+ // Disable compression.
+ config_reader.overrideUnsignedEntry("compress", 0);
+
+ if (symbolize_everything) {
+ config_reader.overrideUnsignedEntry("symbolize_everything", 1);
+ }
+
+ PerfProfdRunner::LoggingConfig config;
+ config_reader.FillConfig(&config);
+
+ // Kick off encoder and check return code
+ struct TestSymbolizer : public perfprofd::Symbolizer {
+ std::string Decode(const std::string& dso, uint64_t address) override {
+ return dso + "@" + std::to_string(address);
+ }
+ bool GetMinExecutableVAddr(const std::string& dso, uint64_t* addr) override {
+ *addr = 4096;
+ return true;
+ }
+ };
+ TestSymbolizer test_symbolizer;
+ PROFILE_RESULT result =
+ encode_to_proto(input_perf_data,
+ encoded_file_path(dest_dir, 0).c_str(),
+ config,
+ 0,
+ &test_symbolizer);
+ if (result != OK_PROFILE_COLLECTION) {
+ return { ::testing::AssertionFailure() << "Profile collection failed: " << result };
+ }
+
+ std::vector<::testing::AssertionResult> ret;
+
+ // Read and decode the resulting perf.data.encoded file
+ android::perfprofd::PerfprofdRecord encodedProfile;
+ readEncodedProfile(dest_dir, false, encodedProfile);
+
+ VerifyBasicCannedProfile(encodedProfile);
+
+ auto find_symbol = [&](const std::string& filename) -> const quipper::SymbolInfo* {
+ const size_t size = encodedProfile.ExtensionSize(quipper::symbol_info);
+ for (size_t i = 0; i != size; ++i) {
+ auto& symbol_info = encodedProfile.GetExtension(quipper::symbol_info, i);
+ if (symbol_info.filename() == filename) {
+ return &symbol_info;
+ }
+ }
+ return nullptr;
+ };
+ auto all_filenames = [&]() {
+ std::ostringstream oss;
+ const size_t size = encodedProfile.ExtensionSize(quipper::symbol_info);
+ for (size_t i = 0; i != size; ++i) {
+ auto& symbol_info = encodedProfile.GetExtension(quipper::symbol_info, i);
+ oss << " " << symbol_info.filename();
+ }
+ return oss.str();
+ };
+
+ auto check_dsos = [&](const char* const* dsos, const size_t len) {
+ bool failed = false;
+ for (size_t i = 0; i != len; ++i) {
+ if (find_symbol(dsos[i]) == nullptr) {
+ failed = true;
+ ret.push_back(::testing::AssertionFailure() << "Did not find " << dsos[i]);
+ }
+ }
+ return failed;
+ };
+
+ bool failed = false;
+
+ constexpr const char* kDSOs[] = {
+ "/data/app/com.google.android.apps.plus-1/lib/arm/libcronet.so",
+ "/data/dalvik-cache/arm/system@framework@wifi-service.jar@classes.dex",
+ "/data/dalvik-cache/arm/data@app@com.google.android.gms-2@base.apk@classes.dex",
+ "/data/dalvik-cache/arm/system@framework@boot.oat",
+ };
+ failed |= check_dsos(kDSOs, arraysize(kDSOs));
+
+ if (symbolize_everything) {
+ constexpr const char* kDSOsWithBuildIDs[] = {
+ "/system/lib/libz.so", "/system/lib/libutils.so",
+ };
+ failed |= check_dsos(kDSOsWithBuildIDs, arraysize(kDSOsWithBuildIDs));
+ }
+
+ if (failed) {
+ ret.push_back(::testing::AssertionFailure() << "Found: " << all_filenames());
+ }
+
+ if (encodedProfile.ExtensionSize(quipper::symbol_info) != expected_count) {
+ ret.push_back(
+ ::testing::AssertionFailure() << "Expected " << expected_count
+ << " symbolized libraries, found "
+ << encodedProfile.ExtensionSize(quipper::symbol_info));
+ }
+
+ return ret;
+ }
+};
+
+TEST_F(BasicRunWithCannedPerfWithSymbolizer, Default) {
+ auto result = Run(false, 5);
+ for (const auto& result_component : result) {
+ EXPECT_TRUE(result_component);
+ }
+}
+
+TEST_F(BasicRunWithCannedPerfWithSymbolizer, Everything) {
+ auto result = Run(true, 26);
+ for (const auto& result_component : result) {
+ EXPECT_TRUE(result_component);
+ }
+}
+
+TEST_F(PerfProfdTest, CallchainRunWithCannedPerf)
+{
+ // This test makes sure that the perf.data converter
+ // can handle call chains.
+ //
+ std::string input_perf_data(test_dir);
+ input_perf_data += "/callchain.canned.perf.data";
+
+ // Set up config to avoid these annotations (they are tested elsewhere)
+ ConfigReader config_reader;
+ config_reader.overrideUnsignedEntry("collect_cpu_utilization", 0);
+ config_reader.overrideUnsignedEntry("collect_charging_state", 0);
+ config_reader.overrideUnsignedEntry("collect_camera_active", 0);
+
+ // Disable compression.
+ config_reader.overrideUnsignedEntry("compress", 0);
+
+ PerfProfdRunner::LoggingConfig config;
+ config_reader.FillConfig(&config);
+
+ // Kick off encoder and check return code
+ PROFILE_RESULT result =
+ encode_to_proto(input_perf_data, encoded_file_path(dest_dir, 0).c_str(), config, 0, nullptr);
+ ASSERT_EQ(OK_PROFILE_COLLECTION, result);
+
+ // Read and decode the resulting perf.data.encoded file
+ android::perfprofd::PerfprofdRecord encodedProfile;
+ readEncodedProfile(dest_dir, false, encodedProfile);
+
+ const quipper::PerfDataProto& perf_data = encodedProfile;
+
+ // Expect 21108 events.
+ EXPECT_EQ(2224, perf_data.events_size()) << CreateStats(perf_data);
+
+ {
+ SampleEventIterator samples(perf_data);
+ constexpr const char* kSampleEvents[] = {
+ "0: pid=6225 tid=6225 ip=18446743798834668032 callchain=->18446744073709551488->"
+ "18446743798834668032->18446743798834782596->18446743798834784624->"
+ "18446743798835055136->18446743798834788016->18446743798834789192->"
+ "18446743798834789512->18446743798834790216->18446743798833756776",
+ "1: pid=6225 tid=6225 ip=18446743798835685700 callchain=->18446744073709551488->"
+ "18446743798835685700->18446743798835688704->18446743798835650964->"
+ "18446743798834612104->18446743798834612276->18446743798835055528->"
+ "18446743798834788016->18446743798834789192->18446743798834789512->"
+ "18446743798834790216->18446743798833756776",
+ "2: pid=6225 tid=6225 ip=18446743798835055804 callchain=->18446744073709551488->"
+ "18446743798835055804->18446743798834788016->18446743798834789192->"
+ "18446743798834789512->18446743798834790216->18446743798833756776",
+ "3: pid=6225 tid=6225 ip=18446743798835991212 callchain=->18446744073709551488->"
+ "18446743798835991212->18446743798834491060->18446743798834675572->"
+ "18446743798834676516->18446743798834612172->18446743798834612276->"
+ "18446743798835056664->18446743798834788016->18446743798834789192->"
+ "18446743798834789512->18446743798834790216->18446743798833756776",
+ "4: pid=6225 tid=6225 ip=18446743798844881108 callchain=->18446744073709551488->"
+ "18446743798844881108->18446743798834836140->18446743798834846384->"
+ "18446743798834491100->18446743798834675572->18446743798834676516->"
+ "18446743798834612172->18446743798834612276->18446743798835056784->"
+ "18446743798834788016->18446743798834789192->18446743798834789512->"
+ "18446743798834790216->18446743798833756776",
+ };
+ size_t cmp_index = 0;
+ for (size_t index = 0; samples != samples.end(); ++samples, ++index) {
+ if (samples->sample_event().callchain_size() > 0) {
+ std::ostringstream oss;
+ oss << index << ": " << FormatSampleEvent(samples->sample_event());
+ EXPECT_STREQ(kSampleEvents[cmp_index], oss.str().c_str());
+ cmp_index++;
+ if (cmp_index == arraysize(kSampleEvents)) {
+ break;
+ }
+ }
+ }
+ }
+}
+
+#ifdef __ANDROID__
+
+TEST_F(PerfProfdTest, GetSupportedPerfCounters)
+{
+ if (!IsPerfSupported()) {
+ std::cerr << "Test not supported!" << std::endl;
+ return;
+ }
+ // Check basic perf counters.
+ {
+ struct DummyConfig : public Config {
+ void Sleep(size_t seconds) override {}
+ bool IsProfilingEnabled() const override { return false; }
+ };
+ DummyConfig config;
+ ASSERT_TRUE(android::perfprofd::FindSupportedPerfCounters(config.perf_path));
+ }
+ const std::unordered_set<std::string>& counters = android::perfprofd::GetSupportedPerfCounters();
+ EXPECT_TRUE(std::find(counters.begin(), counters.end(), std::string("cpu-cycles"))
+ != counters.end()) << android::base::Join(counters, ',');
+ EXPECT_TRUE(std::find(counters.begin(), counters.end(), std::string("page-faults"))
+ != counters.end()) << android::base::Join(counters, ',');
+}
+
+TEST_F(PerfProfdTest, BasicRunWithLivePerf)
+{
+ if (!IsPerfSupported()) {
+ std::cerr << "Test not supported!" << std::endl;
+ return;
+ }
+ //
+ // Basic test to exercise the main loop of the daemon. It includes
+ // a live 'perf' run
+ //
+ PerfProfdRunner runner(conf_dir);
+ runner.addToConfig("only_debug_build=0");
+ std::string ddparam("destination_directory="); ddparam += dest_dir;
+ runner.addToConfig(ddparam);
+ std::string cfparam("config_directory="); cfparam += conf_dir;
+ runner.addToConfig(cfparam);
+ runner.addToConfig("main_loop_iterations=1");
+ runner.addToConfig("use_fixed_seed=12345678");
+ runner.addToConfig("max_unprocessed_profiles=100");
+ runner.addToConfig("collection_interval=9999");
+ runner.addToConfig("sample_duration=2");
+ // Avoid the symbolizer for spurious messages.
+ runner.addToConfig("use_elf_symbolizer=0");
+
+ // Disable compression.
+ runner.addToConfig("compress=0");
+
+ // Create semaphore file
+ runner.create_semaphore_file();
+
+ // Kick off daemon
+ int daemon_main_return_code = runner.invoke();
+
+ // Check return code from daemon
+ ASSERT_EQ(0, daemon_main_return_code);
+
+ // Read and decode the resulting perf.data.encoded file
+ android::perfprofd::PerfprofdRecord encodedProfile;
+ readEncodedProfile(dest_dir, false, encodedProfile);
+
+ // Examine what we get back. Since it's a live profile, we can't
+ // really do much in terms of verifying the contents.
+ EXPECT_LT(0, encodedProfile.events_size());
+
+ // Verify log contents
+ const std::string expected = std::string(
+ "I: starting Android Wide Profiling daemon ") +
+ "I: config file path set to " + conf_dir + "/perfprofd.conf " +
+ RAW_RESULT(
+ I: random seed set to 12345678
+ I: sleep 674 seconds
+ I: initiating profile collection
+ I: sleep 2 seconds
+ I: profile collection complete
+ I: sleep 9325 seconds
+ I: finishing Android Wide Profiling daemon
+ );
+ // check to make sure log excerpt matches
+ EXPECT_TRUE(CompareLogMessages(expandVars(expected), true));
+}
+
+class PerfProfdLiveEventsTest : public PerfProfdTest {
+ protected:
+ ::testing::AssertionResult SetupAndInvoke(
+ const std::string& event_config,
+ const std::vector<std::string>& extra_config,
+ bool expect_success,
+ std::string expected_log,
+ bool log_match_exact) {
+ //
+ // Basic test to check that the event set functionality works.
+ //
+ // Note: this is brittle, as we do not really know which events the hardware
+ // supports. Use "cpu-cycles" and "page-faults" as something likely.
+ //
+ PerfProfdRunner runner(conf_dir);
+ runner.addToConfig("only_debug_build=0");
+ std::string ddparam("destination_directory="); ddparam += dest_dir;
+ runner.addToConfig(ddparam);
+ std::string cfparam("config_directory="); cfparam += conf_dir;
+ runner.addToConfig(cfparam);
+ runner.addToConfig("main_loop_iterations=1");
+ runner.addToConfig("use_fixed_seed=12345678");
+ runner.addToConfig("max_unprocessed_profiles=100");
+ runner.addToConfig("collection_interval=9999");
+ runner.addToConfig("sample_duration=2");
+ // Avoid the symbolizer for spurious messages.
+ runner.addToConfig("use_elf_symbolizer=0");
+
+ // Disable compression.
+ runner.addToConfig("compress=0");
+
+ // Set event set.
+ runner.addToConfig(event_config);
+
+ for (const std::string& str : extra_config) {
+ runner.addToConfig(str);
+ }
+
+ // Create semaphore file
+ runner.create_semaphore_file();
+
+ // Kick off daemon
+ int daemon_main_return_code = runner.invoke();
+
+ // Check return code from daemon
+ if (0 != daemon_main_return_code) {
+ return ::testing::AssertionFailure() << "Daemon exited with " << daemon_main_return_code;
+ }
+
+ if (expect_success) {
+ // Read and decode the resulting perf.data.encoded file
+ android::perfprofd::PerfprofdRecord encodedProfile;
+ readEncodedProfile(dest_dir, false, encodedProfile);
+
+ // Examine what we get back. Since it's a live profile, we can't
+ // really do much in terms of verifying the contents.
+ if (0 == encodedProfile.events_size()) {
+ return ::testing::AssertionFailure() << "Empty encoded profile.";
+ }
+ }
+
+ // Verify log contents
+ return CompareLogMessages(expandVars(expected_log), log_match_exact);
+ }
+};
+
+TEST_F(PerfProfdLiveEventsTest, BasicRunWithLivePerf_Events)
+{
+ if (!IsPerfSupported()) {
+ std::cerr << "Test not supported!" << std::endl;
+ return;
+ }
+ const std::string expected = std::string(
+ "I: starting Android Wide Profiling daemon ") +
+ "I: config file path set to " + conf_dir + "/perfprofd.conf " +
+ RAW_RESULT(
+ I: random seed set to 12345678
+ I: sleep 674 seconds
+ I: initiating profile collection
+ I: sleep 2 seconds
+ I: profile collection complete
+ I: sleep 9325 seconds
+ I: finishing Android Wide Profiling daemon
+ );
+ ASSERT_TRUE(SetupAndInvoke("-e_cpu-cycles,page-faults=100000", {}, true, expected, true));
+}
+
+TEST_F(PerfProfdLiveEventsTest, BasicRunWithLivePerf_Events_Strip)
+{
+ if (!IsPerfSupported()) {
+ std::cerr << "Test not supported!" << std::endl;
+ return;
+ }
+ const std::string expected = std::string(
+ "I: starting Android Wide Profiling daemon ") +
+ "I: config file path set to " + conf_dir + "/perfprofd.conf " +
+ RAW_RESULT(
+ I: random seed set to 12345678
+ I: sleep 674 seconds
+ I: initiating profile collection
+ W: Event does:not:exist is unsupported.
+ I: sleep 2 seconds
+ I: profile collection complete
+ I: sleep 9325 seconds
+ I: finishing Android Wide Profiling daemon
+ );
+ ASSERT_TRUE(SetupAndInvoke("-e_cpu-cycles,page-faults,does:not:exist=100000",
+ { "fail_on_unsupported_events=0" },
+ true,
+ expected,
+ true));
+}
+
+TEST_F(PerfProfdLiveEventsTest, BasicRunWithLivePerf_Events_NoStrip)
+{
+ if (!IsPerfSupported()) {
+ std::cerr << "Test not supported!" << std::endl;
+ return;
+ }
+ const std::string expected =
+ RAW_RESULT(
+ W: Event does:not:exist is unsupported.
+ W: profile collection failed
+ );
+ ASSERT_TRUE(SetupAndInvoke("-e_cpu-cycles,page-faults,does:not:exist=100000",
+ { "fail_on_unsupported_events=1" },
+ false,
+ expected,
+ false));
+}
+
+TEST_F(PerfProfdLiveEventsTest, BasicRunWithLivePerf_EventsGroup)
+{
+ if (!IsPerfSupported()) {
+ std::cerr << "Test not supported!" << std::endl;
+ return;
+ }
+ const std::string expected = std::string(
+ "I: starting Android Wide Profiling daemon ") +
+ "I: config file path set to " + conf_dir + "/perfprofd.conf " +
+ RAW_RESULT(
+ I: random seed set to 12345678
+ I: sleep 674 seconds
+ I: initiating profile collection
+ I: sleep 2 seconds
+ I: profile collection complete
+ I: sleep 9325 seconds
+ I: finishing Android Wide Profiling daemon
+ );
+ ASSERT_TRUE(SetupAndInvoke("-g_cpu-cycles,page-faults=100000", {}, true, expected, true));
+}
+
+TEST_F(PerfProfdTest, MultipleRunWithLivePerf)
+{
+ if (!IsPerfSupported()) {
+ std::cerr << "Test not supported!" << std::endl;
+ return;
+ }
+ //
+ // Basic test to exercise the main loop of the daemon. It includes
+ // a live 'perf' run
+ //
+ PerfProfdRunner runner(conf_dir);
+ runner.addToConfig("only_debug_build=0");
+ std::string ddparam("destination_directory="); ddparam += dest_dir;
+ runner.addToConfig(ddparam);
+ std::string cfparam("config_directory="); cfparam += conf_dir;
+ runner.addToConfig(cfparam);
+ runner.addToConfig("main_loop_iterations=3");
+ runner.addToConfig("use_fixed_seed=12345678");
+ runner.addToConfig("collection_interval=9999");
+ runner.addToConfig("sample_duration=2");
+ // Avoid the symbolizer for spurious messages.
+ runner.addToConfig("use_elf_symbolizer=0");
+
+ // Disable compression.
+ runner.addToConfig("compress=0");
+
+ runner.write_processed_file(1, 2);
+
+ // Create semaphore file
+ runner.create_semaphore_file();
+
+ // Kick off daemon
+ int daemon_main_return_code = runner.invoke();
+
+ // Check return code from daemon
+ ASSERT_EQ(0, daemon_main_return_code);
+
+ // Read and decode the resulting perf.data.encoded file
+ android::perfprofd::PerfprofdRecord encodedProfile;
+ readEncodedProfile(dest_dir, false, encodedProfile);
+
+ // Examine what we get back. Since it's a live profile, we can't
+ // really do much in terms of verifying the contents.
+ EXPECT_LT(0, encodedProfile.events_size());
+
+ // Examine that encoded.1 file is removed while encoded.{0|2} exists.
+ EXPECT_EQ(0, access(encoded_file_path(dest_dir, 0).c_str(), F_OK));
+ EXPECT_NE(0, access(encoded_file_path(dest_dir, 1).c_str(), F_OK));
+ EXPECT_EQ(0, access(encoded_file_path(dest_dir, 2).c_str(), F_OK));
+
+ // Verify log contents
+ const std::string expected = std::string(
+ "I: starting Android Wide Profiling daemon ") +
+ "I: config file path set to " + conf_dir + "/perfprofd.conf " +
+ RAW_RESULT(
+ I: random seed set to 12345678
+ I: sleep 674 seconds
+ I: initiating profile collection
+ I: sleep 2 seconds
+ I: profile collection complete
+ I: sleep 9325 seconds
+ I: sleep 4974 seconds
+ I: initiating profile collection
+ I: sleep 2 seconds
+ I: profile collection complete
+ I: sleep 5025 seconds
+ I: sleep 501 seconds
+ I: initiating profile collection
+ I: sleep 2 seconds
+ I: profile collection complete
+ I: sleep 9498 seconds
+ I: finishing Android Wide Profiling daemon
+ );
+ // check to make sure log excerpt matches
+ EXPECT_TRUE(CompareLogMessages(expandVars(expected), true));
+}
+
+TEST_F(PerfProfdTest, CallChainRunWithLivePerf)
+{
+ if (!IsPerfSupported()) {
+ std::cerr << "Test not supported!" << std::endl;
+ return;
+ }
+ //
+ // Collect a callchain profile, so as to exercise the code in
+ // perf_data post-processing that digests callchains.
+ //
+ PerfProfdRunner runner(conf_dir);
+ std::string ddparam("destination_directory="); ddparam += dest_dir;
+ runner.addToConfig(ddparam);
+ std::string cfparam("config_directory="); cfparam += conf_dir;
+ runner.addToConfig(cfparam);
+ runner.addToConfig("main_loop_iterations=1");
+ runner.addToConfig("use_fixed_seed=12345678");
+ runner.addToConfig("max_unprocessed_profiles=100");
+ runner.addToConfig("collection_interval=9999");
+ runner.addToConfig("stack_profile=1");
+ runner.addToConfig("sample_duration=2");
+ // Avoid the symbolizer for spurious messages.
+ runner.addToConfig("use_elf_symbolizer=0");
+
+ // Disable compression.
+ runner.addToConfig("compress=0");
+
+ // Create semaphore file
+ runner.create_semaphore_file();
+
+ // Kick off daemon
+ int daemon_main_return_code = runner.invoke();
+
+ // Check return code from daemon
+ ASSERT_EQ(0, daemon_main_return_code);
+
+ // Read and decode the resulting perf.data.encoded file
+ android::perfprofd::PerfprofdRecord encodedProfile;
+ readEncodedProfile(dest_dir, false, encodedProfile);
+
+ // Examine what we get back. Since it's a live profile, we can't
+ // really do much in terms of verifying the contents.
+ EXPECT_LT(0, encodedProfile.events_size());
+
+ // Verify log contents
+ const std::string expected = std::string(
+ "I: starting Android Wide Profiling daemon ") +
+ "I: config file path set to " + conf_dir + "/perfprofd.conf " +
+ RAW_RESULT(
+ I: random seed set to 12345678
+ I: sleep 674 seconds
+ I: initiating profile collection
+ I: sleep 2 seconds
+ I: profile collection complete
+ I: sleep 9325 seconds
+ I: finishing Android Wide Profiling daemon
+ );
+ // check to make sure log excerpt matches
+ EXPECT_TRUE(CompareLogMessages(expandVars(expected), true));
+
+ // Check that we have at least one SampleEvent with a callchain.
+ SampleEventIterator samples(encodedProfile);
+ bool found_callchain = false;
+ while (!found_callchain && samples != samples.end()) {
+ found_callchain = samples->sample_event().callchain_size() > 0;
+ }
+ EXPECT_TRUE(found_callchain) << CreateStats(encodedProfile);
+}
+
+#endif
+
+class RangeMapTest : public testing::Test {
+};
+
+TEST_F(RangeMapTest, TestRangeMap) {
+ using namespace android::perfprofd;
+
+ RangeMap<std::string, uint64_t> map;
+ auto print = [&]() {
+ std::ostringstream oss;
+ for (auto& aggr_sym : map) {
+ oss << aggr_sym.first << "#" << aggr_sym.second.symbol;
+ oss << "[";
+ for (auto& x : aggr_sym.second.offsets) {
+ oss << x << ",";
+ }
+ oss << "]";
+ }
+ return oss.str();
+ };
+
+ EXPECT_STREQ("", print().c_str());
+
+ map.Insert("a", 10);
+ EXPECT_STREQ("10#a[10,]", print().c_str());
+ map.Insert("a", 100);
+ EXPECT_STREQ("10#a[10,100,]", print().c_str());
+ map.Insert("a", 1);
+ EXPECT_STREQ("1#a[1,10,100,]", print().c_str());
+ map.Insert("a", 1);
+ EXPECT_STREQ("1#a[1,10,100,]", print().c_str());
+ map.Insert("a", 2);
+ EXPECT_STREQ("1#a[1,2,10,100,]", print().c_str());
+
+ map.Insert("b", 200);
+ EXPECT_STREQ("1#a[1,2,10,100,]200#b[200,]", print().c_str());
+ map.Insert("b", 199);
+ EXPECT_STREQ("1#a[1,2,10,100,]199#b[199,200,]", print().c_str());
+
+ map.Insert("c", 50);
+ EXPECT_STREQ("1#a[1,2,10,]50#c[50,]100#a[100,]199#b[199,200,]", print().c_str());
+}
+
+class ThreadedHandlerTest : public PerfProfdTest {
+ public:
+ void SetUp() override {
+ PerfProfdTest::SetUp();
+ threaded_handler_.reset(new android::perfprofd::ThreadedHandler());
+ }
+
+ void TearDown() override {
+ threaded_handler_.reset();
+ PerfProfdTest::TearDown();
+ }
+
+ protected:
+ std::unique_ptr<android::perfprofd::ThreadedHandler> threaded_handler_;
+};
+
+TEST_F(ThreadedHandlerTest, Basic) {
+ std::string error_msg;
+ EXPECT_FALSE(threaded_handler_->StopProfiling(&error_msg));
+}
+
+#ifdef __ANDROID__
+#define ThreadedHandlerTestName(x) x
+#else
+#define ThreadedHandlerTestName(x) DISABLED_ ## x
+#endif
+
+TEST_F(ThreadedHandlerTest, ThreadedHandlerTestName(Live)) {
+ auto config_fn = [](android::perfprofd::ThreadedConfig& config) {
+ // Use some values that make it likely that things don't fail quickly.
+ config.main_loop_iterations = 0;
+ config.collection_interval_in_s = 1000000;
+ };
+ std::string error_msg;
+ ASSERT_TRUE(threaded_handler_->StartProfiling(config_fn, &error_msg)) << error_msg;
+ EXPECT_TRUE(threaded_handler_->StopProfiling(&error_msg)) << error_msg;
+}
+
+int main(int argc, char **argv) {
+ // Always log to cerr, so that device failures are visible.
+ android::base::SetLogger(android::base::StderrLogger);
+
+ CHECK(android::base::Realpath(argv[0], &gExecutableRealpath));
+
+ // switch to / before starting testing (perfprofd
+ // should be location-independent)
+ chdir("/");
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/perfprofd/tests/perfprofdmockutils.cc b/perfprofd/tests/perfprofdmockutils.cc
new file mode 100644
index 00000000..f8858090
--- /dev/null
+++ b/perfprofd/tests/perfprofdmockutils.cc
@@ -0,0 +1,101 @@
+/*
+**
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#define LOG_TAG "perfprofd"
+
+#include <stdarg.h>
+#include <unistd.h>
+#include <vector>
+#include <string>
+#include <assert.h>
+
+#include <utils/Log.h>
+
+#include "perfprofdutils.h"
+
+static std::vector<std::string> *mock_log;
+
+static void append_to_log(const std::string &s)
+{
+ assert(mock_log);
+ mock_log->push_back(s);
+}
+
+void mock_perfprofdutils_init()
+{
+ assert(!mock_log);
+ mock_log = new std::vector<std::string>;
+}
+
+void mock_perfprofdutils_finish()
+{
+ assert(mock_log);
+ delete mock_log;
+}
+
+std::string mock_perfprofdutils_getlogged()
+{
+ std::string result;
+ assert(mock_log);
+ for (const std::string &s : (*mock_log)) {
+ result += s;
+ }
+ mock_log->clear();
+ return result;
+}
+
+extern "C" {
+
+#define LMAX 8192
+
+void perfprofd_mocklog(const char *tag, const char *fmt, va_list ap)
+{
+ char buffer[LMAX];
+ strcpy(buffer, tag);
+ vsnprintf(buffer+strlen(tag), LMAX, fmt, ap);
+ std::string b(buffer); b += "\012";
+ append_to_log(b);
+}
+
+void perfprofd_log_error(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap); fprintf(stderr, "\n");
+ perfprofd_mocklog("E: ", fmt, ap);
+ va_end(ap);
+}
+
+void perfprofd_log_warning(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap); fprintf(stderr, "\n");
+ perfprofd_mocklog("W: ", fmt, ap);
+ va_end(ap);
+}
+
+void perfprofd_log_info(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap); fprintf(stderr, "\n");
+ perfprofd_mocklog("I: ", fmt, ap);
+ va_end(ap);
+}
+
+}
diff --git a/perfprofd/tests/perfprofdmockutils.h b/perfprofd/tests/perfprofdmockutils.h
new file mode 100644
index 00000000..12caabb7
--- /dev/null
+++ b/perfprofd/tests/perfprofdmockutils.h
@@ -0,0 +1,31 @@
+/*
+**
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+///
+/// Set up mock utilities layer prior to unit test execution
+///
+extern void mock_perfprofdutils_init();
+
+///
+/// Set up mock utilities layer prior to unit test execution
+///
+extern void mock_perfprofdutils_finish();
+
+///
+/// Return string containing things logged to logd, plus sleep instances
+///
+extern std::string mock_perfprofdutils_getlogged();
diff --git a/puncture_fs/Android.bp b/puncture_fs/Android.bp
index 5c163372..194f78f6 100644
--- a/puncture_fs/Android.bp
+++ b/puncture_fs/Android.bp
@@ -15,7 +15,7 @@
cc_binary {
name: "puncture_fs",
- srcs: ["puncture_fs.cpp"],
+ srcs: ["puncture_fs.c"],
cflags: [
"-Wall",
"-Werror",
diff --git a/puncture_fs/puncture_fs.cpp b/puncture_fs/puncture_fs.c
index 9c01e3b0..dbb4efce 100644
--- a/puncture_fs/puncture_fs.cpp
+++ b/puncture_fs/puncture_fs.c
@@ -143,7 +143,7 @@ static bool puncture_fs (const char * const path, const u64 total_size,
u64 starting_max = 0;
u64 ending_max = increments;
char stay_dir[FILENAME_MAX], delete_dir[FILENAME_MAX];
- const char* rm_bin_argv[] = { "/system/bin/rm", "-rf", ""};
+ char *rm_bin_argv[] = { "/system/bin/rm", "-rf", ""};
u64 file_id = 1;
char *base_file_data;
u64 i = 0;
@@ -190,8 +190,8 @@ static bool puncture_fs (const char * const path, const u64 total_size,
fprintf(stderr, "\rSTAGE 2/2: 0%% Complete");
free(base_file_data);
rm_bin_argv[2] = delete_dir;
- if (logwrap_fork_execvp(ARRAY_SIZE(rm_bin_argv), rm_bin_argv, nullptr,
- false, LOG_KLOG, false, nullptr) < 0) {
+ if (android_fork_execvp_ext(ARRAY_SIZE(rm_bin_argv), rm_bin_argv,
+ NULL, 1, LOG_KLOG, 0, NULL, NULL, 0) < 0) {
fprintf(stderr, "\nFailed to delete %s\n", rm_bin_argv[2]);
return false;
}
diff --git a/simpleperf/Android.bp b/simpleperf/Android.bp
index 685ee6c8..be134209 100644
--- a/simpleperf/Android.bp
+++ b/simpleperf/Android.bp
@@ -96,6 +96,37 @@ cc_library_static {
}
cc_defaults {
+ name: "libsimpleperf_dex_read_static_reqs_defaults",
+ defaults: ["libdexfile_static_defaults"],
+ static_libs: [
+ "libdexfile_support_static",
+ ],
+ header_libs: ["libdexfile_external_headers"],
+ export_header_lib_headers: ["libdexfile_external_headers"],
+}
+
+cc_library_static {
+ name: "libsimpleperf_dex_read",
+ defaults: [
+ "simpleperf_defaults",
+ "libsimpleperf_dex_read_static_reqs_defaults",
+ ],
+ host_supported: true,
+
+ export_include_dirs: [
+ ".",
+ ],
+
+ static_libs: ["libbase"],
+
+ srcs: [
+ "read_dex_file.cpp",
+ ],
+
+ group_static_libs: true,
+}
+
+cc_defaults {
name: "simpleperf_cflags",
target: {
host: {
@@ -116,37 +147,6 @@ cc_defaults {
},
}
-// linked as a separate library because using OpenCSD headers needs to enable exception
-cc_library_static {
- name: "libsimpleperf_etm_decoder",
- defaults: [
- "simpleperf_cflags",
- "libsimpleperf_elf_read_static_reqs_defaults",
- ],
- host_supported: true,
- srcs: ["ETMDecoder.cpp"],
- cppflags: [
- // flags needed to include libopencsd_decoder headers
- "-Wno-ignored-qualifiers",
- "-Wno-unused-parameter",
- "-Wno-switch",
- "-Wno-unused-private-field",
- "-Wno-implicit-fallthrough",
- "-fexceptions",
- ],
- rtti: true,
- static_libs: [
- "libopencsd_decoder",
- "libbase",
- "liblog",
- ],
- target: {
- windows: {
- enabled: true,
- }
- }
-}
-
cc_defaults {
name: "simpleperf_static_libs",
defaults: [
@@ -155,12 +155,10 @@ cc_defaults {
],
host_supported: true,
static_libs: [
- "libsimpleperf_etm_decoder",
"libbase",
"liblog",
"libutils",
"libprotobuf-cpp-lite",
- "libopencsd_decoder",
],
target: {
linux: {
@@ -199,10 +197,6 @@ cc_defaults {
"libprotobuf-cpp-lite",
"libziparchive",
],
- static_libs: [
- "libsimpleperf_etm_decoder",
- "libopencsd_decoder",
- ],
target: {
linux: {
shared_libs: [
@@ -263,7 +257,6 @@ cc_defaults {
srcs: [
"cmd_dumprecord.cpp",
"cmd_help.cpp",
- "cmd_inject.cpp",
"cmd_kmem.cpp",
"cmd_report.cpp",
"cmd_report_sample.cpp",
@@ -292,7 +285,6 @@ cc_defaults {
"cmd_stat.cpp",
"cmd_trace_sched.cpp",
"environment.cpp",
- "ETMRecorder.cpp",
"event_fd.cpp",
"event_selection_set.cpp",
"InplaceSamplerClient.cpp",
@@ -358,7 +350,7 @@ cc_binary {
"simpleperf_static_libs",
],
dist: {
- targets: ["simpleperf"],
+ targets: ["sdk", "win_sdk", "simpleperf"],
},
srcs: [
"main.cpp",
@@ -462,7 +454,7 @@ cc_library_shared {
"simpleperf_static_libs",
],
dist: {
- targets: ["simpleperf"],
+ targets: ["sdk", "win_sdk", "simpleperf"],
},
srcs: [
"report_lib_interface.cpp",
@@ -535,7 +527,6 @@ cc_library_shared {
cc_defaults {
name: "simpleperf_test_srcs",
srcs: [
- "cmd_inject_test.cpp",
"cmd_kmem_test.cpp",
"cmd_report_test.cpp",
"cmd_report_sample_test.cpp",
diff --git a/simpleperf/Android.mk b/simpleperf/Android.mk
index a2202159..2e3a3c84 100644
--- a/simpleperf/Android.mk
+++ b/simpleperf/Android.mk
@@ -34,8 +34,7 @@ SIMPLEPERF_SCRIPT_LIST := \
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)
+ testdata/perf_with_interpreter_frames.data
SIMPLEPERF_SCRIPT_LIST := $(addprefix -f $(LOCAL_PATH)/,$(SIMPLEPERF_SCRIPT_LIST))
@@ -45,4 +44,4 @@ SIMPLEPERF_SCRIPT_PATH := \
$(SIMPLEPERF_SCRIPT_PATH) : $(SOONG_ZIP)
$(hide) $(SOONG_ZIP) -d -o $@ -C system/extras/simpleperf $(SIMPLEPERF_SCRIPT_LIST)
-$(call dist-for-goals,simpleperf,$(SIMPLEPERF_SCRIPT_PATH):simpleperf/simpleperf_script.zip)
+$(call dist-for-goals,sdk win_sdk,$(SIMPLEPERF_SCRIPT_PATH):simpleperf/simpleperf_script.zip)
diff --git a/simpleperf/ETMDecoder.cpp b/simpleperf/ETMDecoder.cpp
deleted file mode 100644
index 6e65dd59..00000000
--- a/simpleperf/ETMDecoder.cpp
+++ /dev/null
@@ -1,582 +0,0 @@
-/*
- * Copyright (C) 2019 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 "ETMDecoder.h"
-
-#include <android-base/logging.h>
-#include <android-base/strings.h>
-#include <llvm/Support/MemoryBuffer.h>
-#include <opencsd.h>
-
-using namespace simpleperf;
-
-namespace {
-
-class DecoderLogStr : public ocsdMsgLogStrOutI {
- public:
- void printOutStr(const std::string& out_str) override { LOG(INFO) << out_str; }
-};
-
-class DecodeErrorLogger : public ocsdDefaultErrorLogger {
- public:
- DecodeErrorLogger(const std::function<void(const ocsdError&)>& error_callback)
- : error_callback_(error_callback) {
- initErrorLogger(OCSD_ERR_SEV_INFO, false);
- msg_logger_.setLogOpts(ocsdMsgLogger::OUT_STR_CB);
- msg_logger_.setStrOutFn(&log_str_);
- setOutputLogger(&msg_logger_);
- }
-
- void LogError(const ocsd_hndl_err_log_t handle, const ocsdError* error) override {
- ocsdDefaultErrorLogger::LogError(handle, error);
- if (error != nullptr) {
- error_callback_(*error);
- }
- }
-
- private:
- std::function<void(const ocsdError&)> error_callback_;
- DecoderLogStr log_str_;
- ocsdMsgLogger msg_logger_;
-};
-
-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.
-class ETMV4IDecodeTree {
- public:
- ETMV4IDecodeTree()
- : error_logger_(std::bind(&ETMV4IDecodeTree::ProcessError, this, std::placeholders::_1)) {
- frame_decoder_.Configure(OCSD_DFRMTR_FRAME_MEM_ALIGN);
- frame_decoder_.getErrLogAttachPt()->attach(&error_logger_);
- }
-
- bool CreateDecoder(const EtmV4Config& config) {
- uint8_t trace_id = config.getTraceID();
- auto packet_decoder = std::make_unique<TrcPktProcEtmV4I>(trace_id);
- packet_decoder->setProtocolConfig(&config);
- packet_decoder->getErrorLogAttachPt()->replace_first(&error_logger_);
- frame_decoder_.getIDStreamAttachPt(trace_id)->attach(packet_decoder.get());
- auto result = packet_decoders_.emplace(trace_id, packet_decoder.release());
- if (!result.second) {
- LOG(ERROR) << "trace id " << trace_id << " has been used";
- }
- return result.second;
- }
-
- void AttachPacketSink(uint8_t trace_id, IPktDataIn<EtmV4ITrcPacket>& packet_sink) {
- auto& packet_decoder = packet_decoders_[trace_id];
- CHECK(packet_decoder);
- packet_decoder->getPacketOutAttachPt()->replace_first(&packet_sink);
- }
-
- void AttachPacketMonitor(uint8_t trace_id, IPktRawDataMon<EtmV4ITrcPacket>& packet_monitor) {
- auto& packet_decoder = packet_decoders_[trace_id];
- CHECK(packet_decoder);
- packet_decoder->getRawPacketMonAttachPt()->replace_first(&packet_monitor);
- }
-
- void AttachRawFramePrinter(RawFramePrinter& frame_printer) {
- frame_decoder_.Configure(frame_decoder_.getConfigFlags() | OCSD_DFRMTR_PACKED_RAW_OUT);
- frame_decoder_.getTrcRawFrameAttachPt()->replace_first(&frame_printer);
- }
-
- ITrcDataIn& GetDataIn() { return frame_decoder_; }
-
- void ProcessError(const ocsdError& error) {
- if (error.getErrorCode() == OCSD_ERR_INVALID_PCKT_HDR) {
- // Found an invalid packet header, following packets for this trace id may also be invalid.
- // So reset the decoder to find I_ASYNC packet in the data stream.
- if (auto it = packet_decoders_.find(error.getErrorChanID()); it != packet_decoders_.end()) {
- auto& packet_decoder = it->second;
- CHECK(packet_decoder);
- packet_decoder->TraceDataIn(OCSD_OP_RESET, error.getErrorIndex(), 0, nullptr, nullptr);
- }
- }
- }
-
- DecodeErrorLogger& ErrorLogger() { return error_logger_; }
-
- private:
- DecodeErrorLogger error_logger_;
- TraceFormatterFrameDecoder frame_decoder_;
- std::unordered_map<uint8_t, std::unique_ptr<TrcPktProcEtmV4I>> packet_decoders_;
-};
-
-// Similar to IPktDataIn<EtmV4ITrcPacket>, but add trace id.
-struct PacketCallback {
- virtual ~PacketCallback() {}
- virtual ocsd_datapath_resp_t ProcessPacket(uint8_t trace_id, ocsd_datapath_op_t op,
- ocsd_trc_index_t index_sop,
- const EtmV4ITrcPacket* pkt) = 0;
-};
-
-// Receives packets from a packet decoder in OpenCSD library.
-class PacketSink : public IPktDataIn<EtmV4ITrcPacket> {
- public:
- PacketSink(uint8_t trace_id) : trace_id_(trace_id) {}
-
- void AddCallback(PacketCallback* callback) { callbacks_.push_back(callback); }
-
- ocsd_datapath_resp_t PacketDataIn(ocsd_datapath_op_t op, ocsd_trc_index_t index_sop,
- const EtmV4ITrcPacket* pkt) override {
- for (auto& callback : callbacks_) {
- auto resp = callback->ProcessPacket(trace_id_, op, index_sop, pkt);
- if (IsRespError(resp)) {
- return resp;
- }
- }
- return OCSD_RESP_CONT;
- }
-
- private:
- uint8_t trace_id_;
- std::vector<PacketCallback*> callbacks_;
-};
-
-// Map (trace_id, ip address) to (binary_path, binary_offset), and read binary files.
-class MemAccess : public ITargetMemAccess {
- public:
- MemAccess(ThreadTree& thread_tree) : thread_tree_(thread_tree) {}
-
- void ProcessPacket(uint8_t trace_id, const EtmV4ITrcPacket* packet) {
- if (packet->getContext().updated_c) {
- tid_map_[trace_id] = packet->getContext().ctxtID;
- if (trace_id == trace_id_) {
- // Invalidate the cached buffer when the last trace stream changes thread.
- buffer_end_ = 0;
- }
- }
- }
-
- ocsd_err_t ReadTargetMemory(const ocsd_vaddr_t address, uint8_t cs_trace_id, ocsd_mem_space_acc_t,
- uint32_t* num_bytes, uint8_t* p_buffer) override {
- if (cs_trace_id == trace_id_ && address >= buffer_start_ &&
- address + *num_bytes <= buffer_end_) {
- if (buffer_ == nullptr) {
- *num_bytes = 0;
- } else {
- memcpy(p_buffer, buffer_ + (address - buffer_start_), *num_bytes);
- }
- return OCSD_OK;
- }
-
- size_t copy_size = 0;
- if (const MapEntry* map = FindMap(cs_trace_id, address); 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);
- }
- }
- // Update the last buffer cache.
- trace_id_ = cs_trace_id;
- buffer_ = memory == nullptr ? nullptr : (memory->getBufferStart() + map->pgoff);
- buffer_start_ = map->start_addr;
- buffer_end_ = map->get_end_addr();
- }
- *num_bytes = copy_size;
- return OCSD_OK;
- }
-
- private:
- const MapEntry* FindMap(uint8_t trace_id, uint64_t address) {
- if (auto it = tid_map_.find(trace_id); it != tid_map_.end()) {
- if (ThreadEntry* thread = thread_tree_.FindThread(it->second); thread != nullptr) {
- if (const MapEntry* map = thread_tree_.FindMap(thread, address);
- !thread_tree_.IsUnknownDso(map->dso)) {
- return map;
- }
- }
- }
- return nullptr;
- }
-
- llvm::MemoryBuffer* GetMemoryBuffer(Dso* dso) {
- if (auto it = memory_buffers_.find(dso); it != memory_buffers_.end()) {
- return it->second.get();
- }
- auto buffer_or_err = llvm::MemoryBuffer::getFile(dso->GetDebugFilePath());
- llvm::MemoryBuffer* buffer = buffer_or_err ? buffer_or_err.get().release() : nullptr;
- memory_buffers_.emplace(dso, buffer);
- return buffer;
- }
-
- // map from trace id to thread id
- std::unordered_map<uint8_t, pid_t> tid_map_;
- ThreadTree& thread_tree_;
- std::unordered_map<Dso*, std::unique_ptr<llvm::MemoryBuffer>> memory_buffers_;
- // cache of the last buffer
- uint8_t trace_id_ = 0;
- const char* buffer_ = nullptr;
- uint64_t buffer_start_ = 0;
- uint64_t buffer_end_ = 0;
-};
-
-class InstructionDecoder : public TrcIDecode {
- public:
- ocsd_err_t DecodeInstruction(ocsd_instr_info* instr_info) {
- this->instr_info = instr_info;
- return TrcIDecode::DecodeInstruction(instr_info);
- }
-
- ocsd_instr_info* instr_info;
-};
-
-// Similar to ITrcGenElemIn, but add next instruction info, which is needed to get branch to addr
-// for an InstructionRange element.
-struct ElementCallback {
- public:
- virtual ~ElementCallback(){};
- virtual ocsd_datapath_resp_t ProcessElement(ocsd_trc_index_t index_sop, uint8_t trace_id,
- const OcsdTraceElement& elem,
- const ocsd_instr_info* next_instr) = 0;
-};
-
-// Decode packets into elements.
-class PacketToElement : public PacketCallback, public ITrcGenElemIn {
- public:
- PacketToElement(ThreadTree& thread_tree, const std::unordered_map<uint8_t, EtmV4Config>& configs,
- DecodeErrorLogger& error_logger)
- : mem_access_(thread_tree) {
- for (auto& p : configs) {
- uint8_t trace_id = p.first;
- const EtmV4Config& config = p.second;
- element_decoders_.emplace(trace_id, trace_id);
- auto& decoder = element_decoders_[trace_id];
- decoder.setProtocolConfig(&config);
- decoder.getErrorLogAttachPt()->replace_first(&error_logger);
- decoder.getInstrDecodeAttachPt()->replace_first(&instruction_decoder_);
- decoder.getMemoryAccessAttachPt()->replace_first(&mem_access_);
- decoder.getTraceElemOutAttachPt()->replace_first(this);
- }
- }
-
- void AddCallback(ElementCallback* callback) { callbacks_.push_back(callback); }
-
- ocsd_datapath_resp_t ProcessPacket(uint8_t trace_id, ocsd_datapath_op_t op,
- ocsd_trc_index_t index_sop,
- const EtmV4ITrcPacket* pkt) override {
- if (pkt != nullptr) {
- mem_access_.ProcessPacket(trace_id, pkt);
- }
- return element_decoders_[trace_id].PacketDataIn(op, index_sop, pkt);
- }
-
- ocsd_datapath_resp_t TraceElemIn(const ocsd_trc_index_t index_sop, uint8_t trc_chan_id,
- const OcsdTraceElement& elem) override {
- for (auto& callback : callbacks_) {
- auto resp =
- callback->ProcessElement(index_sop, trc_chan_id, elem, instruction_decoder_.instr_info);
- if (IsRespError(resp)) {
- return resp;
- }
- }
- return OCSD_RESP_CONT;
- }
-
- private:
- // map from trace id of an etm device to its element decoder
- std::unordered_map<uint8_t, TrcPktDecodeEtmV4I> element_decoders_;
- MemAccess mem_access_;
- InstructionDecoder instruction_decoder_;
- std::vector<ElementCallback*> callbacks_;
-};
-
-// Dump etm data generated at different stages.
-class DataDumper : public ElementCallback {
- public:
- DataDumper(ETMV4IDecodeTree& decode_tree) : decode_tree_(decode_tree) {}
-
- void DumpRawData() {
- decode_tree_.AttachRawFramePrinter(frame_printer_);
- frame_printer_.setMessageLogger(&stdout_logger_);
- }
-
- void DumpPackets(const std::unordered_map<uint8_t, EtmV4Config>& configs) {
- for (auto& p : configs) {
- uint8_t trace_id = p.first;
- auto result = packet_printers_.emplace(trace_id, trace_id);
- CHECK(result.second);
- auto& packet_printer = result.first->second;
- decode_tree_.AttachPacketMonitor(trace_id, packet_printer);
- packet_printer.setMessageLogger(&stdout_logger_);
- }
- }
-
- void DumpElements() { element_printer_.setMessageLogger(&stdout_logger_); }
-
- ocsd_datapath_resp_t ProcessElement(ocsd_trc_index_t index_sop, uint8_t trc_chan_id,
- const OcsdTraceElement& elem, const ocsd_instr_info*) {
- return element_printer_.TraceElemIn(index_sop, trc_chan_id, elem);
- }
-
- private:
- ETMV4IDecodeTree& decode_tree_;
- RawFramePrinter frame_printer_;
- std::unordered_map<uint8_t, PacketPrinter<EtmV4ITrcPacket>> packet_printers_;
- TrcGenericElementPrinter element_printer_;
- ocsdMsgLogger stdout_logger_;
-};
-
-// Base class for parsing executed instruction ranges from etm data.
-class InstrRangeParser {
- public:
- InstrRangeParser(ThreadTree& thread_tree, const ETMDecoder::CallbackFn& callback)
- : thread_tree_(thread_tree), callback_(callback) {}
-
- virtual ~InstrRangeParser() {}
-
- protected:
- ThreadTree& thread_tree_;
- ETMDecoder::CallbackFn callback_;
-};
-
-// It decodes each ETMV4IPacket into TraceElements, and generates ETMInstrRanges from TraceElements.
-// Decoding each packet is slow, but ensures correctness.
-class BasicInstrRangeParser : public InstrRangeParser, public ElementCallback {
- public:
- BasicInstrRangeParser(ThreadTree& thread_tree, const ETMDecoder::CallbackFn& callback)
- : InstrRangeParser(thread_tree, callback) {}
-
- ocsd_datapath_resp_t ProcessElement(const ocsd_trc_index_t, uint8_t trace_id,
- const OcsdTraceElement& elem,
- const ocsd_instr_info* next_instr) override {
- if (elem.getType() == OCSD_GEN_TRC_ELEM_PE_CONTEXT) {
- if (elem.getContext().ctxt_id_valid) {
- // trace_id is associated with a new thread.
- pid_t new_tid = elem.getContext().context_id;
- auto& tid = tid_map_[trace_id];
- if (tid != new_tid) {
- tid = new_tid;
- if (trace_id == current_map_.trace_id) {
- current_map_.Invalidate();
- }
- }
- }
- } else if (elem.getType() == OCSD_GEN_TRC_ELEM_INSTR_RANGE) {
- if (!FindMap(trace_id, elem.st_addr)) {
- return OCSD_RESP_CONT;
- }
- instr_range_.dso = current_map_.map->dso;
- instr_range_.start_addr = current_map_.ToVaddrInFile(elem.st_addr);
- instr_range_.end_addr = current_map_.ToVaddrInFile(elem.en_addr - elem.last_instr_sz);
- bool end_with_branch =
- elem.last_i_type == OCSD_INSTR_BR || elem.last_i_type == OCSD_INSTR_BR_INDIRECT;
- bool branch_taken = end_with_branch && elem.last_instr_exec;
- if (elem.last_i_type == OCSD_INSTR_BR && branch_taken) {
- instr_range_.branch_to_addr = current_map_.ToVaddrInFile(next_instr->branch_addr);
- } else {
- instr_range_.branch_to_addr = 0;
- }
- instr_range_.branch_taken_count = branch_taken ? 1 : 0;
- instr_range_.branch_not_taken_count = branch_taken ? 0 : 1;
- callback_(instr_range_);
- }
- return OCSD_RESP_CONT;
- }
-
- private:
- struct CurrentMap {
- int trace_id = -1;
- const MapEntry* map = nullptr;
- uint64_t addr_in_file = 0;
-
- void Invalidate() { trace_id = -1; }
-
- bool IsAddrInMap(uint8_t trace_id, uint64_t addr) {
- return trace_id == this->trace_id && map != nullptr && addr >= map->start_addr &&
- addr < map->get_end_addr();
- }
-
- uint64_t ToVaddrInFile(uint64_t addr) {
- if (addr >= map->start_addr && addr < map->get_end_addr()) {
- return addr - map->start_addr + addr_in_file;
- }
- return 0;
- }
- };
-
- bool FindMap(uint8_t trace_id, uint64_t addr) {
- if (current_map_.IsAddrInMap(trace_id, addr)) {
- return true;
- }
- ThreadEntry* thread = thread_tree_.FindThread(tid_map_[trace_id]);
- if (thread != nullptr) {
- const MapEntry* map = thread_tree_.FindMap(thread, addr, false);
- if (map != nullptr && !thread_tree_.IsUnknownDso(map->dso)) {
- current_map_.trace_id = trace_id;
- current_map_.map = map;
- current_map_.addr_in_file =
- map->dso->IpToVaddrInFile(map->start_addr, map->start_addr, map->pgoff);
- return true;
- }
- }
- return false;
- }
-
- std::unordered_map<uint8_t, pid_t> tid_map_;
- CurrentMap current_map_;
- ETMInstrRange instr_range_;
-};
-
-// Etm data decoding in OpenCSD library has two steps:
-// 1. From byte stream to etm packets. Each packet shows an event happened. For example,
-// an Address packet shows the cpu is running the instruction at that address, an Atom
-// packet shows whether the cpu decides to branch or not.
-// 2. From etm packets to trace elements. To generates elements, the decoder needs both etm
-// packets and executed binaries. For example, an InstructionRange element needs the decoder
-// to find the next branch instruction starting from an address.
-//
-// ETMDecoderImpl uses OpenCSD library to decode etm data. It has the following properties:
-// 1. Supports flexible decoding strategy. It allows installing packet callbacks and element
-// callbacks, and decodes to either packets or elements based on requirements.
-// 2. Supports dumping data at different stages.
-class ETMDecoderImpl : public ETMDecoder {
- public:
- ETMDecoderImpl(ThreadTree& thread_tree) : thread_tree_(thread_tree) {}
-
- void CreateDecodeTree(const AuxTraceInfoRecord& auxtrace_info) {
- for (int i = 0; i < auxtrace_info.data->nr_cpu; i++) {
- auto& etm4 = auxtrace_info.data->etm4_info[i];
- ocsd_etmv4_cfg cfg;
- memset(&cfg, 0, sizeof(cfg));
- cfg.reg_idr0 = etm4.trcidr0;
- cfg.reg_idr1 = etm4.trcidr1;
- cfg.reg_idr2 = etm4.trcidr2;
- cfg.reg_idr8 = etm4.trcidr8;
- cfg.reg_configr = etm4.trcconfigr;
- cfg.reg_traceidr = etm4.trctraceidr;
- cfg.arch_ver = ARCH_V8;
- cfg.core_prof = profile_CortexA;
- uint8_t trace_id = cfg.reg_traceidr & 0x7f;
- configs_.emplace(trace_id, &cfg);
- decode_tree_.CreateDecoder(configs_[trace_id]);
- auto result = packet_sinks_.emplace(trace_id, trace_id);
- CHECK(result.second);
- decode_tree_.AttachPacketSink(trace_id, result.first->second);
- }
- }
-
- void EnableDump(const ETMDumpOption& option) override {
- dumper_.reset(new DataDumper(decode_tree_));
- if (option.dump_raw_data) {
- dumper_->DumpRawData();
- }
- if (option.dump_packets) {
- dumper_->DumpPackets(configs_);
- }
- if (option.dump_elements) {
- dumper_->DumpElements();
- InstallElementCallback(dumper_.get());
- }
- }
-
- void RegisterCallback(const CallbackFn& callback) {
- auto parser = std::make_unique<BasicInstrRangeParser>(thread_tree_, callback);
- InstallElementCallback(parser.get());
- instr_range_parser_.reset(parser.release());
- }
-
- 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
- // processing a new block.
- // 2. The beginning part of a data block may be truncated if kernel buffer is temporarily full.
- // So we may see garbage data, which can cause decoding errors if we don't reset decoders.
- auto resp =
- decode_tree_.GetDataIn().TraceDataIn(OCSD_OP_RESET, data_index_, 0, nullptr, nullptr);
- if (IsRespError(resp)) {
- LOG(ERROR) << "failed to reset decoder, resp " << resp;
- return false;
- }
- size_t left_size = size;
- while (left_size > 0) {
- uint32_t processed;
- auto resp = decode_tree_.GetDataIn().TraceDataIn(OCSD_OP_DATA, data_index_, left_size, data,
- &processed);
- if (IsRespError(resp)) {
- // A decoding error shouldn't ruin all data. Reset decoders to recover from it.
- LOG(INFO) << "reset etm decoders for seeing a decode failure, resp " << resp;
- decode_tree_.GetDataIn().TraceDataIn(OCSD_OP_RESET, data_index_ + processed, 0, nullptr,
- nullptr);
- }
- data += processed;
- left_size -= processed;
- data_index_ += processed;
- }
- return true;
- }
-
- private:
- void InstallElementCallback(ElementCallback* callback) {
- if (!packet_to_element_) {
- packet_to_element_.reset(
- new PacketToElement(thread_tree_, configs_, decode_tree_.ErrorLogger()));
- for (auto& p : packet_sinks_) {
- p.second.AddCallback(packet_to_element_.get());
- }
- }
- packet_to_element_->AddCallback(callback);
- }
-
- // map ip address to binary path and binary offset
- ThreadTree& thread_tree_;
- // handle to build OpenCSD decoder
- ETMV4IDecodeTree decode_tree_;
- // map from the trace id of an etm device to its config
- std::unordered_map<uint8_t, EtmV4Config> configs_;
- // map from the trace id of an etm device to its PacketSink
- std::unordered_map<uint8_t, PacketSink> packet_sinks_;
- std::unique_ptr<PacketToElement> packet_to_element_;
- std::unique_ptr<DataDumper> dumper_;
- // an index keeping processed etm data size
- size_t data_index_ = 0;
- std::unique_ptr<InstrRangeParser> instr_range_parser_;
-};
-
-} // namespace
-
-namespace simpleperf {
-
-bool ParseEtmDumpOption(const std::string& s, ETMDumpOption* option) {
- for (auto& value : android::base::Split(s, ",")) {
- if (value == "raw") {
- option->dump_raw_data = true;
- } else if (value == "packet") {
- option->dump_packets = true;
- } else if (value == "element") {
- option->dump_elements = true;
- } else {
- LOG(ERROR) << "unknown etm dump option: " << value;
- return false;
- }
- }
- return true;
-}
-
-std::unique_ptr<ETMDecoder> ETMDecoder::Create(const AuxTraceInfoRecord& auxtrace_info,
- ThreadTree& thread_tree) {
- auto decoder = std::make_unique<ETMDecoderImpl>(thread_tree);
- decoder->CreateDecodeTree(auxtrace_info);
- return std::unique_ptr<ETMDecoder>(decoder.release());
-}
-
-} // namespace simpleperf \ No newline at end of file
diff --git a/simpleperf/ETMDecoder.h b/simpleperf/ETMDecoder.h
deleted file mode 100644
index 3ebbd7dd..00000000
--- a/simpleperf/ETMDecoder.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2019 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 <functional>
-#include <memory>
-#include <string>
-
-#include "record.h"
-#include "thread_tree.h"
-
-namespace simpleperf {
-
-struct ETMDumpOption {
- bool dump_raw_data = false;
- bool dump_packets = false;
- bool dump_elements = false;
-};
-
-bool ParseEtmDumpOption(const std::string& s, ETMDumpOption* option);
-
-struct ETMInstrRange {
- // the binary containing the instruction range
- Dso* dso = nullptr;
- // the address of the first instruction in the binary
- uint64_t start_addr = 0;
- // the address of the last instruction in the binary
- uint64_t end_addr = 0;
- // If the last instruction is a branch instruction, and it branches
- // to a fixed location in the same binary, then branch_to_addr points
- // to the branched to instruction.
- uint64_t branch_to_addr = 0;
- // times the branch is taken
- uint64_t branch_taken_count = 0;
- // times the branch isn't taken
- uint64_t branch_not_taken_count = 0;
-};
-
-class ETMDecoder {
- public:
- static std::unique_ptr<ETMDecoder> Create(const AuxTraceInfoRecord& auxtrace_info,
- ThreadTree& thread_tree);
- virtual ~ETMDecoder() {}
- virtual void EnableDump(const ETMDumpOption& option) = 0;
-
- using CallbackFn = std::function<void(const ETMInstrRange&)>;
- virtual void RegisterCallback(const CallbackFn& callback) = 0;
-
- virtual bool ProcessData(const uint8_t* data, size_t size) = 0;
-};
-
-} // namespace simpleperf \ No newline at end of file
diff --git a/simpleperf/ETMRecorder.cpp b/simpleperf/ETMRecorder.cpp
deleted file mode 100644
index b561c483..00000000
--- a/simpleperf/ETMRecorder.cpp
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright (C) 2019 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 "ETMRecorder.h"
-
-#include <stdio.h>
-#include <sys/sysinfo.h>
-
-#include <memory>
-#include <limits>
-#include <string>
-
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/parseint.h>
-#include <android-base/strings.h>
-
-#include "utils.h"
-
-namespace simpleperf {
-
-static constexpr bool ETM_RECORD_TIMESTAMP = false;
-
-// Config bits from include/linux/coresight-pmu.h in the kernel
-// For etm_event_config:
-static constexpr int ETM_OPT_CTXTID = 14;
-static constexpr int ETM_OPT_TS = 28;
-// For etm_config_reg:
-static constexpr int ETM4_CFG_BIT_CTXTID = 6;
-static constexpr int ETM4_CFG_BIT_TS = 11;
-
-static const std::string ETM_DIR = "/sys/bus/event_source/devices/cs_etm/";
-
-// from coresight_get_trace_id(int cpu) in include/linux/coresight-pmu.h
-static int GetTraceId(int cpu) {
- return 0x10 + cpu * 2;
-}
-
-template <typename T>
-static bool ReadValueInEtmDir(const std::string& file, T* value, bool report_error = true) {
- std::string s;
- uint64_t v;
- if (!android::base::ReadFileToString(ETM_DIR + file, &s) ||
- !android::base::ParseUint(android::base::Trim(s), &v)) {
- if (report_error) {
- LOG(ERROR) << "failed to read " << ETM_DIR << file;
- }
- return false;
- }
- *value = static_cast<T>(v);
- return true;
-}
-
-static uint32_t GetBits(uint32_t value, int start, int end) {
- return (value >> start) & ((1U << (end - start + 1)) - 1);
-}
-
-int ETMPerCpu::GetMajorVersion() const {
- return GetBits(trcidr1, 8, 11);
-}
-
-bool ETMPerCpu::IsContextIDSupported() const {
- return GetBits(trcidr2, 5, 9) >= 4;
-}
-
-bool ETMPerCpu::IsTimestampSupported() const {
- return GetBits(trcidr0, 24, 28) > 0;
-}
-
-ETMRecorder& ETMRecorder::GetInstance() {
- static ETMRecorder etm;
- return etm;
-}
-
-int ETMRecorder::GetEtmEventType() {
- if (event_type_ == 0) {
- if (!IsDir(ETM_DIR) || !ReadValueInEtmDir("type", &event_type_, false)) {
- event_type_ = -1;
- }
- }
- return event_type_;
-}
-
-std::unique_ptr<EventType> ETMRecorder::BuildEventType() {
- int etm_event_type = GetEtmEventType();
- if (etm_event_type == -1) {
- return nullptr;
- }
- return std::make_unique<EventType>(
- "cs-etm", etm_event_type, 0, "CoreSight ETM instruction tracing", "arm");
-}
-
-bool ETMRecorder::CheckEtmSupport() {
- if (GetEtmEventType() == -1) {
- LOG(ERROR) << "etm event type isn't supported on device";
- return false;
- }
- if (!ReadEtmInfo()) {
- LOG(ERROR) << "etm devices are not available";
- return false;
- }
- for (const auto& p : etm_info_) {
- if (p.second.GetMajorVersion() < 4) {
- LOG(ERROR) << "etm device version is less than 4.0";
- return false;
- }
- if (!p.second.IsContextIDSupported()) {
- LOG(ERROR) << "etm device doesn't support contextID";
- return false;
- }
- }
- if (!FindSinkConfig()) {
- LOG(ERROR) << "can't find etr device, which moves etm data to memory";
- return false;
- }
- etm_supported_ = true;
- return true;
-}
-
-bool ETMRecorder::ReadEtmInfo() {
- int cpu_count = get_nprocs_conf();
- 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);
- if (!success) {
- return false;
- }
- }
- }
- return (etm_info_.size() == cpu_count);
-}
-
-bool ETMRecorder::FindSinkConfig() {
- for (const auto &name : GetEntriesInDir(ETM_DIR + "sinks")) {
- if (name.find("etr") != -1) {
- if (ReadValueInEtmDir("sinks/" + name, &sink_config_)) {
- return true;
- }
- }
- }
- return false;
-}
-
-void ETMRecorder::SetEtmPerfEventAttr(perf_event_attr* attr) {
- CHECK(etm_supported_);
- BuildEtmConfig();
- attr->config = etm_event_config_;
- attr->config2 = sink_config_;
-}
-
-void ETMRecorder::BuildEtmConfig() {
- if (etm_event_config_ == 0) {
- etm_event_config_ |= 1ULL << ETM_OPT_CTXTID;
- etm_config_reg_ |= 1U << ETM4_CFG_BIT_CTXTID;
-
- if (ETM_RECORD_TIMESTAMP) {
- bool ts_supported = true;
- for (auto& p : etm_info_) {
- ts_supported &= p.second.IsTimestampSupported();
- }
- if (ts_supported) {
- etm_event_config_ |= 1ULL << ETM_OPT_TS;
- etm_config_reg_ |= 1U << ETM4_CFG_BIT_TS;
- }
- }
- }
-}
-
-AuxTraceInfoRecord ETMRecorder::CreateAuxTraceInfoRecord() {
- AuxTraceInfoRecord::DataType data;
- memset(&data, 0, sizeof(data));
- data.aux_type = AuxTraceInfoRecord::AUX_TYPE_ETM;
- data.nr_cpu = etm_info_.size();
- data.pmu_type = GetEtmEventType();
- std::vector<AuxTraceInfoRecord::ETM4Info> etm4_v(etm_info_.size());
- size_t pos = 0;
- for (auto& p : etm_info_) {
- auto& e = etm4_v[pos++];
- e.magic = AuxTraceInfoRecord::MAGIC_ETM4;
- e.cpu = p.first;
- e.trcconfigr = etm_config_reg_;
- e.trctraceidr = GetTraceId(p.first);
- e.trcidr0 = p.second.trcidr0;
- e.trcidr1 = p.second.trcidr1;
- e.trcidr2 = p.second.trcidr2;
- e.trcidr8 = p.second.trcidr8;
- e.trcauthstatus = p.second.trcauthstatus;
- }
- return AuxTraceInfoRecord(data, etm4_v);
-}
-
-size_t ETMRecorder::GetAddrFilterPairs() {
- CHECK(etm_supported_);
- size_t min_pairs = std::numeric_limits<size_t>::max();
- for (auto& p : etm_info_) {
- min_pairs = std::min<size_t>(min_pairs, GetBits(p.second.trcidr4, 0, 3));
- }
- if (min_pairs > 0) {
- --min_pairs; // One pair is used by the kernel to set default addr filter.
- }
- return min_pairs;
-}
-
-} // namespace simpleperf \ No newline at end of file
diff --git a/simpleperf/ETMRecorder.h b/simpleperf/ETMRecorder.h
deleted file mode 100644
index 219741fb..00000000
--- a/simpleperf/ETMRecorder.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2019 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 <map>
-#include <memory>
-
-#include "event_type.h"
-#include "record.h"
-#include "perf_event.h"
-
-namespace simpleperf {
-
-struct ETMPerCpu {
- uint32_t trcidr0;
- uint32_t trcidr1;
- uint32_t trcidr2;
- uint32_t trcidr4;
- uint32_t trcidr8;
- uint32_t trcauthstatus;
-
- int GetMajorVersion() const;
- bool IsContextIDSupported() const;
- bool IsTimestampSupported() const;
-};
-
-// Help recording Coresight ETM data on ARM devices.
-// 1. Get etm event type on device.
-// 2. Get sink config, which selects the ETR device moving etm data to memory.
-// 3. Get etm info on each cpu.
-// The etm event type and sink config are used to build perf_event_attr for etm data tracing.
-// The etm info is kept in perf.data to help etm decoding.
-class ETMRecorder {
- public:
- static ETMRecorder& GetInstance();
-
- // If not found, return -1.
- int GetEtmEventType();
- std::unique_ptr<EventType> BuildEventType();
- bool CheckEtmSupport();
- void SetEtmPerfEventAttr(perf_event_attr* attr);
- AuxTraceInfoRecord CreateAuxTraceInfoRecord();
- size_t GetAddrFilterPairs();
-
- private:
- bool ReadEtmInfo();
- bool FindSinkConfig();
- void BuildEtmConfig();
-
- int event_type_ = 0;
- bool etm_supported_ = false;
- // select ETR device, setting in perf_event_attr->config2
- uint32_t sink_config_ = 0;
- // select etm options (timestamp, context_id, ...), setting in perf_event_attr->config
- uint64_t etm_event_config_ = 0;
- // record etm options in AuxTraceInfoRecord
- uint32_t etm_config_reg_ = 0;
- std::map<int, ETMPerCpu> etm_info_;
-};
-
-} // namespace simpleperf \ No newline at end of file
diff --git a/simpleperf/JITDebugReader.cpp b/simpleperf/JITDebugReader.cpp
index 7ef3f25f..4e688eea 100644
--- a/simpleperf/JITDebugReader.cpp
+++ b/simpleperf/JITDebugReader.cpp
@@ -49,7 +49,7 @@ 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;
-// Match the format of JITDescriptor in art/runtime/jit/debugger_interface.cc.
+// Match the format of JITDescriptor in art/runtime/jit/debugger_itnerface.cc.
template <typename ADDRT>
struct JITDescriptor {
uint32_t version;
@@ -63,15 +63,12 @@ struct JITDescriptor {
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';
+ bool Valid() const {
+ return version == 1 && strncmp(reinterpret_cast<const char*>(magic), "Android1", 8) == 0;
}
};
-// Match the format of JITCodeEntry in art/runtime/jit/debugger_interface.cc
-// with JITDescriptor.magic == "Android1".
+// Match the format of JITCodeEntry in art/runtime/jit/debugger_itnerface.cc.
template <typename ADDRT>
struct JITCodeEntry {
ADDRT next_addr;
@@ -85,8 +82,7 @@ struct JITCodeEntry {
}
};
-// Match the format of JITCodeEntry in art/runtime/jit/debugger_interface.cc
-// with JITDescriptor.magic == "Android1".
+// Match the format of JITCodeEntry in art/runtime/jit/debugger_interface.cc.
template <typename ADDRT>
struct __attribute__((packed)) PackedJITCodeEntry {
ADDRT next_addr;
@@ -100,110 +96,28 @@ struct __attribute__((packed)) PackedJITCodeEntry {
}
};
-// Match the format of JITCodeEntry in art/runtime/jit/debugger_interface.cc
-// with JITDescriptor.magic == "Android2".
-template <typename ADDRT>
-struct JITCodeEntryV2 {
- ADDRT next_addr;
- ADDRT prev_addr;
- ADDRT symfile_addr;
- uint64_t symfile_size;
- uint64_t register_timestamp; // CLOCK_MONOTONIC time of entry registration
- uint32_t seqlock; // even value if valid
-
- bool Valid() const {
- return (seqlock & 1) == 0;
- }
-};
-
-// Match the format of JITCodeEntry in art/runtime/jit/debugger_interface.cc
-// with JITDescriptor.magic == "Android2".
-template <typename ADDRT>
-struct __attribute__((packed)) PackedJITCodeEntryV2 {
- ADDRT next_addr;
- ADDRT prev_addr;
- ADDRT symfile_addr;
- uint64_t symfile_size;
- uint64_t register_timestamp;
- uint32_t seqlock;
-
- bool Valid() const {
- return (seqlock & 1) == 0;
- }
-};
-
-// Match the format of JITCodeEntry in art/runtime/jit/debugger_interface.cc
-// with JITDescriptor.magic == "Android2".
-template <typename ADDRT>
-struct __attribute__((packed)) PaddedJITCodeEntryV2 {
- ADDRT next_addr;
- ADDRT prev_addr;
- ADDRT symfile_addr;
- uint64_t symfile_size;
- uint64_t register_timestamp;
- uint32_t seqlock;
- uint32_t pad;
-
- bool Valid() const {
- return (seqlock & 1) == 0;
- }
-};
-
using JITDescriptor32 = JITDescriptor<uint32_t>;
using JITDescriptor64 = JITDescriptor<uint64_t>;
#if defined(__x86_64__)
// Make sure simpleperf built for i386 and x86_64 see the correct JITCodeEntry layout of i386.
using JITCodeEntry32 = PackedJITCodeEntry<uint32_t>;
-using JITCodeEntry32V2 = PackedJITCodeEntryV2<uint32_t>;
#else
using JITCodeEntry32 = JITCodeEntry<uint32_t>;
-using JITCodeEntry32V2 = JITCodeEntryV2<uint32_t>;
#endif
-
using JITCodeEntry64 = JITCodeEntry<uint64_t>;
-#if defined(__i386__)
-// Make sure simpleperf built for i386 and x86_64 see the correct JITCodeEntry layout of x86_64.
-using JITCodeEntry64V2 = PaddedJITCodeEntryV2<uint64_t>;
-#else
-using JITCodeEntry64V2 = JITCodeEntryV2<uint64_t>;
-#endif
-
-template <typename ADDRT>
-bool JITDescriptor<ADDRT>::Valid() const {
- const char* magic_str = reinterpret_cast<const char*>(magic);
- if (version != 1 ||
- !(strncmp(magic_str, "Android1", 8) == 0 || strncmp(magic_str, "Android2", 8) == 0)) {
- return false;
- }
- if (sizeof(*this) != sizeof_descriptor) {
- return false;
- }
- if (sizeof(ADDRT) == 4) {
- return sizeof_entry == (AndroidVersion() == 1) ? sizeof(JITCodeEntry32)
- : sizeof(JITCodeEntry32V2);
- }
- return sizeof_entry == (AndroidVersion() == 1) ? sizeof(JITCodeEntry64)
- : sizeof(JITCodeEntry64V2);
-}
// We want to support both 64-bit and 32-bit simpleperf when profiling either 64-bit or 32-bit
// apps. So using static_asserts to make sure that simpleperf on arm and aarch64 having the same
// view of structures, and simpleperf on i386 and x86_64 having the same view of structures.
static_assert(sizeof(JITDescriptor32) == 48, "");
static_assert(sizeof(JITDescriptor64) == 56, "");
-
#if defined(__i386__) or defined(__x86_64__)
static_assert(sizeof(JITCodeEntry32) == 28, "");
-static_assert(sizeof(JITCodeEntry32V2) == 32, "");
-static_assert(sizeof(JITCodeEntry64) == 40, "");
-static_assert(sizeof(JITCodeEntry64V2) == 48, "");
#else
static_assert(sizeof(JITCodeEntry32) == 32, "");
-static_assert(sizeof(JITCodeEntry32V2) == 40, "");
-static_assert(sizeof(JITCodeEntry64) == 40, "");
-static_assert(sizeof(JITCodeEntry64V2) == 48, "");
#endif
+static_assert(sizeof(JITCodeEntry64) == 40, "");
bool JITDebugReader::RegisterDebugInfoCallback(IOEventLoop* loop,
const debug_info_callback_t& callback) {
@@ -496,22 +410,22 @@ bool JITDebugReader::ReadDescriptors(Process& process, Descriptor* jit_descripto
bool JITDebugReader::LoadDescriptor(bool is_64bit, const char* data, Descriptor* descriptor) {
if (is_64bit) {
- return LoadDescriptorImpl<JITDescriptor64>(data, descriptor);
+ return LoadDescriptorImpl<JITDescriptor64, JITCodeEntry64>(data, descriptor);
}
- return LoadDescriptorImpl<JITDescriptor32>(data, descriptor);
+ return LoadDescriptorImpl<JITDescriptor32, JITCodeEntry32>(data, descriptor);
}
-template <typename DescriptorT>
+template <typename DescriptorT, typename CodeEntryT>
bool JITDebugReader::LoadDescriptorImpl(const char* data, Descriptor* descriptor) {
DescriptorT raw_descriptor;
MoveFromBinaryFormat(raw_descriptor, data);
- if (!raw_descriptor.Valid()) {
+ if (!raw_descriptor.Valid() || sizeof(raw_descriptor) != raw_descriptor.sizeof_descriptor ||
+ sizeof(CodeEntryT) != raw_descriptor.sizeof_entry) {
return false;
}
descriptor->action_seqlock = raw_descriptor.action_seqlock;
descriptor->action_timestamp = raw_descriptor.action_timestamp;
descriptor->first_entry_addr = raw_descriptor.first_entry_addr;
- descriptor->version = raw_descriptor.AndroidVersion();
return true;
}
@@ -521,26 +435,15 @@ bool JITDebugReader::LoadDescriptorImpl(const char* data, Descriptor* descriptor
bool JITDebugReader::ReadNewCodeEntries(Process& process, const Descriptor& descriptor,
uint64_t last_action_timestamp, uint32_t read_entry_limit,
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<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 ReadNewCodeEntriesImplV2<JITCodeEntry32V2>(
+ if (process.is_64bit) {
+ return ReadNewCodeEntriesImpl<JITDescriptor64, JITCodeEntry64>(
process, descriptor, last_action_timestamp, read_entry_limit, new_code_entries);
}
- return false;
+ return ReadNewCodeEntriesImpl<JITDescriptor32, JITCodeEntry32>(
+ process, descriptor, last_action_timestamp, read_entry_limit, new_code_entries);
}
-template <typename CodeEntryT>
+template <typename DescriptorT, typename CodeEntryT>
bool JITDebugReader::ReadNewCodeEntriesImpl(Process& process, const Descriptor& descriptor,
uint64_t last_action_timestamp,
uint32_t read_entry_limit,
@@ -548,7 +451,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.
@@ -567,54 +469,12 @@ bool JITDebugReader::ReadNewCodeEntriesImpl(Process& process, const Descriptor&
// once we hit an entry with timestamp <= last_action_timestmap.
break;
}
- if (entry.symfile_size > 0) {
- 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;
-}
-
-// 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);
- }
+ 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;
@@ -639,6 +499,18 @@ void JITDebugReader::ReadJITCodeDebugInfo(Process& process,
if (!IsValidElfFileMagic(data.data(), jit_entry.symfile_size)) {
continue;
}
+ uint64_t min_addr = UINT64_MAX;
+ uint64_t max_addr = 0;
+ auto callback = [&](const ElfFileSymbol& symbol) {
+ min_addr = std::min(min_addr, symbol.vaddr);
+ max_addr = std::max(max_addr, symbol.vaddr + symbol.len);
+ LOG(VERBOSE) << "JITSymbol " << symbol.name << " at [" << std::hex << symbol.vaddr
+ << " - " << (symbol.vaddr + symbol.len) << " with size " << symbol.len;
+ };
+ if (ParseSymbolsFromElfFileInMemory(data.data(), jit_entry.symfile_size, callback) !=
+ ElfStatus::NO_ERROR || min_addr >= max_addr) {
+ 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)) {
@@ -647,16 +519,8 @@ void JITDebugReader::ReadJITCodeDebugInfo(Process& process,
if (keep_symfiles_) {
tmp_file->DoNotRemove();
}
- 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;
- debug_info->emplace_back(process.pid, jit_entry.timestamp, symbol.vaddr, symbol.len,
- tmp_file->path);
- };
- ParseSymbolsFromElfFileInMemory(data.data(), jit_entry.symfile_size, callback);
+ debug_info->emplace_back(process.pid, jit_entry.timestamp, min_addr, max_addr - min_addr,
+ tmp_file->path);
}
}
@@ -684,10 +548,8 @@ void JITDebugReader::ReadDexFileDebugInfo(Process& process,
std::string file_path;
std::string zip_path;
std::string entry_path;
- std::shared_ptr<ThreadMmap> extracted_dex_file_map;
if (ParseExtractedInMemoryPath(it->name, &zip_path, &entry_path)) {
file_path = GetUrlInApk(zip_path, entry_path);
- extracted_dex_file_map = std::make_shared<ThreadMmap>(*it);
} else {
if (!IsRegularFile(it->name)) {
// TODO: read dex file only exist in memory?
@@ -697,8 +559,7 @@ void JITDebugReader::ReadDexFileDebugInfo(Process& process,
}
// Offset of dex file in .vdex file or .apk file.
uint64_t dex_file_offset = dex_entry.symfile_addr - it->start_addr + it->pgoff;
- debug_info->emplace_back(process.pid, dex_entry.timestamp, dex_file_offset, file_path,
- extracted_dex_file_map);
+ debug_info->emplace_back(process.pid, dex_entry.timestamp, dex_file_offset, file_path);
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;
diff --git a/simpleperf/JITDebugReader.h b/simpleperf/JITDebugReader.h
index 1a3b5e69..15d03a02 100644
--- a/simpleperf/JITDebugReader.h
+++ b/simpleperf/JITDebugReader.h
@@ -30,7 +30,6 @@
#include <android-base/file.h>
#include <android-base/logging.h>
-#include "environment.h"
#include "IOEventLoop.h"
#include "record.h"
@@ -55,21 +54,15 @@ struct JITDebugInfo {
// For dex file, it is the path of the file containing the dex file.
std::string file_path;
- // The map for dex file extracted in memory. On Android Q, ART extracts dex files in apk files
- // directly into memory, and names it using prctl(). The kernel doesn't generate a new mmap
- // record for it. So we need to dump it manually.
- std::shared_ptr<ThreadMmap> extracted_dex_file_map;
-
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) {}
JITDebugInfo(pid_t pid, uint64_t timestamp, uint64_t dex_file_offset,
- const std::string& file_path,
- const std::shared_ptr<ThreadMmap>& extracted_dex_file_map)
+ const std::string& file_path)
: 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) {}
+ file_path(file_path) {}
bool operator>(const JITDebugInfo& other) const {
return timestamp > other.timestamp;
@@ -111,7 +104,6 @@ class JITDebugReader {
// An arch-independent representation of JIT/dex debug descriptor.
struct Descriptor {
- int version = 0;
uint32_t action_seqlock = 0; // incremented before and after any modification
uint64_t action_timestamp = 0; // CLOCK_MONOTONIC time of last action
uint64_t first_entry_addr = 0;
@@ -159,26 +151,22 @@ class JITDebugReader {
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);
- template <typename DescriptorT>
+ template <typename DescriptorT, typename CodeEntryT>
bool LoadDescriptorImpl(const char* data, Descriptor* descriptor);
bool ReadNewCodeEntries(Process& process, const Descriptor& descriptor,
uint64_t last_action_timestamp, uint32_t read_entry_limit,
std::vector<CodeEntry>* new_code_entries);
- template <typename CodeEntryT>
+ template <typename DescriptorT, typename CodeEntryT>
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);
void ReadDexFileDebugInfo(Process& process, const std::vector<CodeEntry>& dex_entries,
std::vector<JITDebugInfo>* debug_info);
- bool AddDebugInfo(const std::vector<JITDebugInfo>& debug_info, bool sync_kernel_records);
+ bool AddDebugInfo(const std::vector<JITDebugInfo>& jit_symfiles, bool sync_kernel_records);
bool keep_symfiles_ = false;
bool sync_with_records_ = false;
diff --git a/simpleperf/OfflineUnwinder.cpp b/simpleperf/OfflineUnwinder.cpp
index 07eda11f..b6b7d91f 100644
--- a/simpleperf/OfflineUnwinder.cpp
+++ b/simpleperf/OfflineUnwinder.cpp
@@ -18,8 +18,6 @@
#include <sys/mman.h>
-#include <unordered_map>
-
#include <android-base/logging.h>
#include <unwindstack/MachineArm.h>
#include <unwindstack/MachineArm64.h>
@@ -121,29 +119,22 @@ static unwindstack::Regs* GetBacktraceRegs(const RegSet& regs) {
static unwindstack::MapInfo* CreateMapInfo(const MapEntry* entry) {
const char* name = entry->dso->GetDebugFilePath().c_str();
uint64_t pgoff = entry->pgoff;
- auto tuple = SplitUrlInApk(entry->dso->GetDebugFilePath());
- if (std::get<0>(tuple)) {
- // The unwinder does not understand the ! format, so change back to
- // 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();
- pgoff += elf->entry_offset();
+ if (entry->pgoff == 0) {
+ auto tuple = SplitUrlInApk(entry->dso->GetDebugFilePath());
+ if (std::get<0>(tuple)) {
+ // The unwinder does not understand the ! format, so change back to
+ // 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();
+ pgoff = elf->entry_offset();
+ }
}
}
return new unwindstack::MapInfo(nullptr, entry->start_addr, entry->get_end_addr(), pgoff,
- PROT_READ | entry->flags, name);
+ PROT_READ | PROT_EXEC | entry->flags, name);
}
-class UnwindMaps : public unwindstack::Maps {
- public:
- void UpdateMaps(const MapSet& map_set);
-
- private:
- uint64_t version_ = 0u;
- std::vector<const MapEntry*> entries_;
-};
-
void UnwindMaps::UpdateMaps(const MapSet& map_set) {
if (version_ == map_set.version) {
return;
@@ -186,31 +177,15 @@ void UnwindMaps::UpdateMaps(const MapSet& map_set) {
});
entries_.resize(map_set.maps.size());
maps_.resize(map_set.maps.size());
- // prev_map is needed by libunwindstack to find the start of an embedded lib in an apk.
- // See http://b/120981155.
- for (size_t i = 1; i < maps_.size(); ++i) {
- maps_[i]->prev_map = maps_[i-1].get();
- }
}
-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;
-
- private:
- bool collect_stat_;
- std::unordered_map<pid_t, UnwindMaps> cached_maps_;
-};
+OfflineUnwinder::OfflineUnwinder(bool collect_stat) : collect_stat_(collect_stat) {
+ unwindstack::Elf::SetCachingEnabled(true);
+}
-bool OfflineUnwinderImpl::UnwindCallChain(const ThreadEntry& thread, const RegSet& regs,
- const char* stack, size_t stack_size,
- std::vector<uint64_t>* ips, std::vector<uint64_t>* sps) {
+bool OfflineUnwinder::UnwindCallChain(const ThreadEntry& thread, const RegSet& regs,
+ const char* stack, size_t stack_size,
+ std::vector<uint64_t>* ips, std::vector<uint64_t>* sps) {
uint64_t start_time;
if (collect_stat_) {
start_time = GetSystemClock();
@@ -228,14 +203,15 @@ bool OfflineUnwinderImpl::UnwindCallChain(const ThreadEntry& thread, const RegSe
UnwindMaps& cached_map = cached_maps_[thread.pid];
cached_map.UpdateMaps(*thread.maps);
+ std::shared_ptr<unwindstack::MemoryOfflineBuffer> stack_memory(
+ new unwindstack::MemoryOfflineBuffer(reinterpret_cast<const uint8_t*>(stack),
+ stack_addr, stack_addr + stack_size));
std::unique_ptr<unwindstack::Regs> unwind_regs(GetBacktraceRegs(regs));
if (!unwind_regs) {
return false;
}
- unwindstack::Unwinder unwinder(
- MAX_UNWINDING_FRAMES, &cached_map, unwind_regs.get(),
- unwindstack::Memory::CreateOfflineMemory(reinterpret_cast<const uint8_t*>(stack), stack_addr,
- stack_addr + stack_size));
+ unwindstack::Unwinder unwinder(MAX_UNWINDING_FRAMES, &cached_map, unwind_regs.get(),
+ stack_memory);
unwinder.SetResolveNames(false);
unwinder.Unwind();
size_t last_jit_method_frame = UINT_MAX;
@@ -307,8 +283,4 @@ bool OfflineUnwinderImpl::UnwindCallChain(const ThreadEntry& thread, const RegSe
return true;
}
-std::unique_ptr<OfflineUnwinder> OfflineUnwinder::Create(bool collect_stat) {
- return std::unique_ptr<OfflineUnwinder>(new OfflineUnwinderImpl(collect_stat));
-}
-
} // namespace simpleperf
diff --git a/simpleperf/OfflineUnwinder.h b/simpleperf/OfflineUnwinder.h
index b0ff7ac5..e5091167 100644
--- a/simpleperf/OfflineUnwinder.h
+++ b/simpleperf/OfflineUnwinder.h
@@ -19,10 +19,15 @@
#include <memory>
#include <vector>
+#include <unordered_map>
#include "perf_regs.h"
#include "thread_tree.h"
+#if defined(__linux__)
+#include <unwindstack/Maps.h>
+#endif
+
namespace simpleperf {
struct ThreadEntry;
@@ -50,14 +55,26 @@ struct UnwindingResult {
uint64_t stack_end;
};
+#if defined(__linux__)
+class UnwindMaps : public unwindstack::Maps {
+ public:
+ void UpdateMaps(const MapSet& map_set);
+
+ private:
+ uint64_t version_ = 0u;
+ std::vector<const MapEntry*> entries_;
+};
+
class OfflineUnwinder {
public:
- static std::unique_ptr<OfflineUnwinder> Create(bool collect_stat);
- virtual ~OfflineUnwinder() {}
+ OfflineUnwinder(bool collect_stat);
+
+ 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);
- virtual 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) = 0;
+ bool HasStat() const {
+ return collect_stat_;
+ }
const UnwindingResult& GetUnwindingResult() const {
return unwinding_result_;
@@ -67,13 +84,27 @@ class OfflineUnwinder {
return is_callchain_broken_for_incomplete_jit_debug_info_;
}
- protected:
- OfflineUnwinder() {}
-
+ private:
+ bool collect_stat_;
UnwindingResult unwinding_result_;
- bool is_callchain_broken_for_incomplete_jit_debug_info_ = false;
+ bool is_callchain_broken_for_incomplete_jit_debug_info_;
+
+ std::unordered_map<pid_t, UnwindMaps> cached_maps_;
+};
+
+#else // defined(__linux__)
+
+class OfflineUnwinder {
+ public:
+ OfflineUnwinder(bool) {}
+ bool UnwindCallChain(const ThreadEntry&, const RegSet&, const char*, size_t,
+ std::vector<uint64_t>*, std::vector<uint64_t>*) {
+ return false;
+ }
};
+#endif // !defined(__linux__)
+
} // namespace simpleperf
#endif // SIMPLE_PERF_OFFLINE_UNWINDER_H_
diff --git a/simpleperf/README.md b/simpleperf/README.md
deleted file mode 100644
index 7ca61c54..00000000
--- a/simpleperf/README.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Simpleperf
-
-This file is documentation for simpleperf maintainers.
-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
-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
-"Submit build requests". You'll get emails keeping you up to date with the
-progress of the snap and the build.
-
-## Updating the prebuilts
-
-Once you have the build id (a 7-digit number) and the build is complete, run the
-update script from within the `system/extras/simpleperf` directory:
-```
-./scripts/update.py --bid 1234567 -vv
-```
-
-This will create a new change that you can `repo upload`, then approve and
-submit as normal.
diff --git a/simpleperf/RecordReadThread.cpp b/simpleperf/RecordReadThread.cpp
index b89066b9..876f4041 100644
--- a/simpleperf/RecordReadThread.cpp
+++ b/simpleperf/RecordReadThread.cpp
@@ -23,9 +23,7 @@
#include <unordered_map>
#include "environment.h"
-#include "event_type.h"
#include "record.h"
-#include "utils.h"
namespace simpleperf {
@@ -206,22 +204,14 @@ bool KernelRecordReader::MoveToNextRecord(const RecordParser& parser) {
}
RecordReadThread::RecordReadThread(size_t record_buffer_size, const perf_event_attr& attr,
- size_t min_mmap_pages, size_t max_mmap_pages,
- size_t aux_buffer_size, bool allow_cutting_samples)
- : record_buffer_(record_buffer_size),
- record_parser_(attr),
- attr_(attr),
- min_mmap_pages_(min_mmap_pages),
- max_mmap_pages_(max_mmap_pages),
- aux_buffer_size_(aux_buffer_size) {
+ size_t min_mmap_pages, size_t max_mmap_pages)
+ : record_buffer_(record_buffer_size), record_parser_(attr), attr_(attr),
+ min_mmap_pages_(min_mmap_pages), max_mmap_pages_(max_mmap_pages) {
if (attr.sample_type & PERF_SAMPLE_STACK_USER) {
stack_size_in_sample_record_ = attr.sample_stack_user;
}
record_buffer_low_level_ = std::min(record_buffer_size / 4, kDefaultLowBufferLevel);
record_buffer_critical_level_ = std::min(record_buffer_size / 6, kDefaultCriticalBufferLevel);
- if (!allow_cutting_samples) {
- record_buffer_low_level_ = record_buffer_critical_level_;
- }
}
RecordReadThread::~RecordReadThread() {
@@ -293,13 +283,7 @@ std::unique_ptr<Record> RecordReadThread::GetRecord() {
record_buffer_.MoveToNextRecord();
char* p = record_buffer_.GetCurrentRecord();
if (p != nullptr) {
- std::unique_ptr<Record> r = ReadRecordFromBuffer(attr_, p);
- if (r->type() == PERF_RECORD_AUXTRACE) {
- auto auxtrace = static_cast<AuxTraceRecord*>(r.get());
- record_buffer_.AddCurrentRecordSize(auxtrace->data->aux_size);
- auxtrace->location.addr = r->Binary() + r->size();
- }
- return r;
+ return ReadRecordFromBuffer(attr_, p);
}
if (has_data_notification_) {
char dummy;
@@ -367,21 +351,13 @@ bool RecordReadThread::HandleAddEventFds(IOEventLoop& loop,
std::unordered_map<int, EventFd*> cpu_map;
for (size_t pages = max_mmap_pages_; pages >= min_mmap_pages_; pages >>= 1) {
bool success = true;
- bool report_error = pages == min_mmap_pages_;
for (EventFd* fd : event_fds) {
auto it = cpu_map.find(fd->Cpu());
if (it == cpu_map.end()) {
- if (!fd->CreateMappedBuffer(pages, report_error)) {
+ if (!fd->CreateMappedBuffer(pages, pages == min_mmap_pages_)) {
success = false;
break;
}
- if (IsEtmEventType(fd->attr().type)) {
- if (!fd->CreateAuxBuffer(aux_buffer_size_, report_error)) {
- fd->DestroyMappedBuffer();
- success = false;
- break;
- }
- }
cpu_map[fd->Cpu()] = fd;
} else {
if (!fd->ShareMappedBuffer(*(it->second), pages == min_mmap_pages_)) {
@@ -396,7 +372,6 @@ bool RecordReadThread::HandleAddEventFds(IOEventLoop& loop,
}
for (auto& pair : cpu_map) {
pair.second->DestroyMappedBuffer();
- pair.second->DestroyAuxBuffer();
}
cpu_map.clear();
}
@@ -423,7 +398,6 @@ bool RecordReadThread::HandleRemoveEventFds(const std::vector<EventFd*>& event_f
kernel_record_readers_.erase(it);
event_fd->StopPolling();
event_fd->DestroyMappedBuffer();
- event_fd->DestroyAuxBuffer();
}
}
}
@@ -445,39 +419,34 @@ bool RecordReadThread::ReadRecordsFromKernelBuffer() {
readers.push_back(&reader);
}
}
- bool has_data = false;
- if (!readers.empty()) {
- has_data = true;
- if (readers.size() == 1u) {
- // Only one buffer has data, process it directly.
- while (readers[0]->MoveToNextRecord(record_parser_)) {
- PushRecordToRecordBuffer(readers[0]);
- }
- } else {
- // Use a binary heap to merge records from different buffers. As records from the same
- // buffer are already ordered by time, we only need to merge the first record from all
- // buffers. And each time a record is popped from the heap, we put the next record from its
- // buffer into the heap.
- for (auto& reader : readers) {
- reader->MoveToNextRecord(record_parser_);
- }
- std::make_heap(readers.begin(), readers.end(), CompareRecordTime);
- size_t size = readers.size();
- while (size > 0) {
- std::pop_heap(readers.begin(), readers.begin() + size, CompareRecordTime);
- PushRecordToRecordBuffer(readers[size - 1]);
- if (readers[size - 1]->MoveToNextRecord(record_parser_)) {
- std::push_heap(readers.begin(), readers.begin() + size, CompareRecordTime);
- } else {
- size--;
- }
+ if (readers.empty()) {
+ break;
+ }
+ if (readers.size() == 1u) {
+ // Only one buffer has data, process it directly.
+ while (readers[0]->MoveToNextRecord(record_parser_)) {
+ PushRecordToRecordBuffer(readers[0]);
+ }
+ } else {
+ // Use a binary heap to merge records from different buffers. As records from the same buffer
+ // are already ordered by time, we only need to merge the first record from all buffers. And
+ // each time a record is popped from the heap, we put the next record from its buffer into
+ // the heap.
+ for (auto& reader : readers) {
+ reader->MoveToNextRecord(record_parser_);
+ }
+ std::make_heap(readers.begin(), readers.end(), CompareRecordTime);
+ size_t size = readers.size();
+ while (size > 0) {
+ std::pop_heap(readers.begin(), readers.begin() + size, CompareRecordTime);
+ PushRecordToRecordBuffer(readers[size - 1]);
+ if (readers[size - 1]->MoveToNextRecord(record_parser_)) {
+ std::push_heap(readers.begin(), readers.begin() + size, CompareRecordTime);
+ } else {
+ size--;
}
}
}
- ReadAuxDataFromKernelBuffer(&has_data);
- if (!has_data) {
- break;
- }
if (!SendDataNotificationToMainThread()) {
return false;
}
@@ -493,7 +462,7 @@ void RecordReadThread::PushRecordToRecordBuffer(KernelRecordReader* kernel_recor
if (free_size < record_buffer_critical_level_) {
// When the free size in record buffer is below critical level, drop sample records to save
// space for more important records (like mmap or fork records).
- stat_.lost_samples++;
+ lost_samples_++;
return;
}
size_t stack_size_limit = stack_size_in_sample_record_;
@@ -540,10 +509,10 @@ void RecordReadThread::PushRecordToRecordBuffer(KernelRecordReader* kernel_recor
memcpy(p + pos + new_stack_size, &new_stack_size, sizeof(uint64_t));
record_buffer_.FinishWrite();
if (new_stack_size < dyn_stack_size) {
- stat_.cut_stack_samples++;
+ cut_stack_samples_++;
}
} else {
- stat_.lost_samples++;
+ lost_samples_++;
}
return;
}
@@ -555,47 +524,9 @@ void RecordReadThread::PushRecordToRecordBuffer(KernelRecordReader* kernel_recor
record_buffer_.FinishWrite();
} else {
if (header.type == PERF_RECORD_SAMPLE) {
- stat_.lost_samples++;
+ lost_samples_++;
} else {
- stat_.lost_non_samples++;
- }
- }
-}
-
-void RecordReadThread::ReadAuxDataFromKernelBuffer(bool* has_data) {
- for (auto& reader : kernel_record_readers_) {
- EventFd* event_fd = reader.GetEventFd();
- if (event_fd->HasAuxBuffer()) {
- char* buf[2];
- size_t size[2];
- uint64_t offset = event_fd->GetAvailableAuxData(&buf[0], &size[0], &buf[1], &size[1]);
- size_t aux_size = size[0] + size[1];
- if (aux_size == 0) {
- continue;
- }
- *has_data = true;
- AuxTraceRecord auxtrace(Align(aux_size, 8), offset, event_fd->Cpu(), 0, event_fd->Cpu());
- size_t alloc_size = auxtrace.size() + auxtrace.data->aux_size;
- if (record_buffer_.GetFreeSize() < alloc_size + record_buffer_critical_level_) {
- stat_.lost_aux_data_size += aux_size;
- } else {
- char* p = record_buffer_.AllocWriteSpace(alloc_size);
- CHECK(p != nullptr);
- MoveToBinaryFormat(auxtrace.Binary(), auxtrace.size(), p);
- MoveToBinaryFormat(buf[0], size[0], p);
- if (size[1] != 0) {
- MoveToBinaryFormat(buf[1], size[1], p);
- }
- size_t pad_size = auxtrace.data->aux_size - aux_size;
- if (pad_size != 0) {
- uint64_t pad = 0;
- memcpy(p, &pad, pad_size);
- }
- record_buffer_.FinishWrite();
- stat_.aux_data_size += aux_size;
- LOG(DEBUG) << "record aux data " << aux_size << " bytes";
- }
- event_fd->DiscardAuxData(aux_size);
+ lost_non_samples_++;
}
}
}
diff --git a/simpleperf/RecordReadThread.h b/simpleperf/RecordReadThread.h
index 4e93f97a..c4a6830a 100644
--- a/simpleperf/RecordReadThread.h
+++ b/simpleperf/RecordReadThread.h
@@ -50,7 +50,6 @@ class RecordBuffer {
// Get data of the current record. Return nullptr if there is no records in the buffer.
char* GetCurrentRecord();
- void AddCurrentRecordSize(size_t size) { cur_read_record_size_ += size; }
// Called after reading a record, the space of the record will be writable.
void MoveToNextRecord();
@@ -83,14 +82,6 @@ class RecordParser {
size_t callchain_pos_in_sample_records_ = 0;
};
-struct RecordStat {
- size_t lost_samples = 0;
- size_t lost_non_samples = 0;
- size_t cut_stack_samples = 0;
- uint64_t aux_data_size = 0;
- uint64_t lost_aux_data_size = 0;
-};
-
// Read records from the kernel buffer belong to an event_fd.
class KernelRecordReader {
public:
@@ -124,8 +115,7 @@ 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);
+ size_t max_mmap_pages);
~RecordReadThread();
void SetBufferLevels(size_t record_buffer_low_level, size_t record_buffer_critical_level) {
record_buffer_low_level_ = record_buffer_low_level;
@@ -147,8 +137,11 @@ class RecordReadThread {
// If available, return the next record in the RecordBuffer, otherwise return nullptr.
std::unique_ptr<Record> GetRecord();
-
- const RecordStat& GetStat() const { return stat_; }
+ void GetLostRecords(size_t* lost_samples, size_t* lost_non_samples, size_t* cut_stack_samples) {
+ *lost_samples = lost_samples_;
+ *lost_non_samples = lost_non_samples_;
+ *cut_stack_samples = cut_stack_samples_;
+ }
private:
enum Cmd {
@@ -171,7 +164,6 @@ class RecordReadThread {
bool HandleRemoveEventFds(const std::vector<EventFd*>& event_fds);
bool ReadRecordsFromKernelBuffer();
void PushRecordToRecordBuffer(KernelRecordReader* kernel_record_reader);
- void ReadAuxDataFromKernelBuffer(bool* has_data);
bool SendDataNotificationToMainThread();
RecordBuffer record_buffer_;
@@ -185,7 +177,6 @@ class RecordReadThread {
size_t stack_size_in_sample_record_ = 0;
size_t min_mmap_pages_;
size_t max_mmap_pages_;
- size_t aux_buffer_size_;
// Used to pass command notification from the main thread to the read thread.
android::base::unique_fd write_cmd_fd_;
@@ -204,7 +195,9 @@ class RecordReadThread {
std::unique_ptr<std::thread> read_thread_;
std::vector<KernelRecordReader> kernel_record_readers_;
- RecordStat stat_;
+ size_t lost_samples_ = 0;
+ size_t lost_non_samples_ = 0;
+ size_t cut_stack_samples_ = 0;
};
} // namespace simpleperf
diff --git a/simpleperf/RecordReadThread_test.cpp b/simpleperf/RecordReadThread_test.cpp
index eae58345..a39b30ac 100644
--- a/simpleperf/RecordReadThread_test.cpp
+++ b/simpleperf/RecordReadThread_test.cpp
@@ -106,14 +106,10 @@ TEST(RecordParser, smoke) {
}
struct MockEventFd : public EventFd {
- MockEventFd(const perf_event_attr& attr, int cpu, char* buffer, size_t buffer_size,
- bool mock_aux_buffer)
+ MockEventFd(const perf_event_attr& attr, int cpu, char* buffer, size_t buffer_size)
: EventFd(attr, -1, "", 0, cpu) {
mmap_data_buffer_ = buffer;
mmap_data_buffer_size_ = buffer_size;
- if (mock_aux_buffer) {
- aux_buffer_size_ = 1; // Make HasAuxBuffer() return true.
- }
}
MOCK_METHOD2(CreateMappedBuffer, bool(size_t, bool));
@@ -122,11 +118,6 @@ struct MockEventFd : public EventFd {
MOCK_METHOD0(StopPolling, bool());
MOCK_METHOD1(GetAvailableMmapDataSize, size_t(size_t&));
MOCK_METHOD1(DiscardMmapData, void(size_t));
-
- MOCK_METHOD2(CreateAuxBuffer, bool(size_t, bool));
- MOCK_METHOD0(DestroyAuxBuffer, void());
- MOCK_METHOD4(GetAvailableAuxData, uint64_t(char**, size_t*, char**, size_t*));
- MOCK_METHOD1(DiscardAuxData, void(size_t));
};
static perf_event_attr CreateFakeEventAttr() {
@@ -179,7 +170,7 @@ TEST(KernelRecordReader, smoke) {
pos += records[i]->size();
}
// Read records using KernelRecordReader.
- MockEventFd event_fd(attr, 0, buffer.data(), buffer.size(), false);
+ MockEventFd event_fd(attr, 0, buffer.data(), buffer.size());
EXPECT_CALL(event_fd, GetAvailableMmapDataSize(Truly(SetArg(data_pos))))
.Times(1).WillOnce(Return(data_size));
@@ -217,7 +208,7 @@ class RecordReadThreadTest : public ::testing::Test {
}
event_fds_.resize(event_fd_count);
for (size_t i = 0; i < event_fd_count; ++i) {
- event_fds_[i].reset(new MockEventFd(attr, i, buffers_[i].data(), buffer_size, false));
+ event_fds_[i].reset(new MockEventFd(attr, i, buffers_[i].data(), buffer_size));
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)
@@ -225,7 +216,6 @@ class RecordReadThreadTest : public ::testing::Test {
EXPECT_CALL(*event_fds_[i], DiscardMmapData(Eq(data_size))).Times(1);
EXPECT_CALL(*event_fds_[i], StopPolling()).Times(1).WillOnce(Return(true));
EXPECT_CALL(*event_fds_[i], DestroyMappedBuffer()).Times(1);
- EXPECT_CALL(*event_fds_[i], DestroyAuxBuffer()).Times(1);
}
std::vector<EventFd*> result;
for (auto& fd : event_fds_) {
@@ -243,7 +233,7 @@ TEST_F(RecordReadThreadTest, handle_cmds) {
perf_event_attr attr = CreateFakeEventAttr();
records_ = CreateFakeRecords(attr, 2, 0, 0);
std::vector<EventFd*> event_fds = CreateFakeEventFds(attr, 2);
- RecordReadThread thread(128 * 1024, event_fds[0]->attr(), 1, 1, 0);
+ RecordReadThread thread(128 * 1024, event_fds[0]->attr(), 1, 1);
IOEventLoop loop;
bool has_notify = false;
auto callback = [&]() {
@@ -262,7 +252,7 @@ TEST_F(RecordReadThreadTest, handle_cmds) {
TEST_F(RecordReadThreadTest, read_records) {
perf_event_attr attr = CreateFakeEventAttr();
- RecordReadThread thread(128 * 1024, attr, 1, 1, 0);
+ RecordReadThread thread(128 * 1024, attr, 1, 1);
IOEventLoop loop;
size_t record_index;
auto callback = [&]() {
@@ -297,7 +287,7 @@ TEST_F(RecordReadThreadTest, process_sample_record) {
attr.sample_type |= PERF_SAMPLE_STACK_USER;
attr.sample_stack_user = 64 * 1024;
size_t record_buffer_size = 128 * 1024;
- RecordReadThread thread(record_buffer_size, attr, 1, 1, 0);
+ RecordReadThread thread(record_buffer_size, attr, 1, 1);
IOEventLoop loop;
ASSERT_TRUE(thread.RegisterDataCallback(loop, []() { return true; }));
@@ -339,16 +329,20 @@ TEST_F(RecordReadThreadTest, process_sample_record) {
thread.SetBufferLevels(record_buffer_size, record_buffer_size);
read_record(r);
ASSERT_FALSE(r);
- ASSERT_EQ(thread.GetStat().lost_samples, 1u);
- ASSERT_EQ(thread.GetStat().lost_non_samples, 0u);
- ASSERT_EQ(thread.GetStat().cut_stack_samples, 1u);
+ size_t lost_samples;
+ size_t lost_non_samples;
+ size_t cut_stack_samples;
+ thread.GetLostRecords(&lost_samples, &lost_non_samples, &cut_stack_samples);
+ ASSERT_EQ(lost_samples, 1u);
+ ASSERT_EQ(lost_non_samples, 0u);
+ ASSERT_EQ(cut_stack_samples, 1u);
}
// Test that the data notification exists until the RecordBuffer is empty. So we can read all
// records even if reading one record at a time.
TEST_F(RecordReadThreadTest, has_data_notification_until_buffer_empty) {
perf_event_attr attr = CreateFakeEventAttr();
- RecordReadThread thread(128 * 1024, attr, 1, 1, 0);
+ RecordReadThread thread(128 * 1024, attr, 1, 1);
IOEventLoop loop;
size_t record_index = 0;
auto read_one_record = [&]() {
@@ -371,131 +365,3 @@ TEST_F(RecordReadThreadTest, has_data_notification_until_buffer_empty) {
ASSERT_EQ(record_index, records_.size());
ASSERT_TRUE(thread.RemoveEventFds(event_fds));
}
-
-TEST_F(RecordReadThreadTest, no_cut_samples) {
- perf_event_attr attr = CreateFakeEventAttr();
- attr.sample_type |= PERF_SAMPLE_STACK_USER;
- attr.sample_stack_user = 64 * 1024;
- RecordReadThread thread(128 * 1024, attr, 1, 1, 0, false);
- IOEventLoop loop;
- ASSERT_TRUE(thread.RegisterDataCallback(loop, []() { return true; }));
- const size_t total_samples = 100;
- records_ = CreateFakeRecords(attr, total_samples, 8 * 1024, 8 * 1024);
- std::vector<EventFd*> event_fds = CreateFakeEventFds(attr, 1);
- ASSERT_TRUE(thread.AddEventFds(event_fds));
- ASSERT_TRUE(thread.SyncKernelBuffer());
- ASSERT_TRUE(thread.RemoveEventFds(event_fds));
- size_t received_samples = 0;
- while (thread.GetRecord()) {
- received_samples++;
- }
- ASSERT_GT(received_samples, 0u);
- ASSERT_GT(thread.GetStat().lost_samples, 0u);
- ASSERT_EQ(thread.GetStat().lost_samples, total_samples - received_samples);
- ASSERT_EQ(thread.GetStat().cut_stack_samples, 0u);
-}
-
-struct FakeAuxData {
- std::vector<char> buf1;
- std::vector<char> buf2;
- std::vector<char> pad;
- bool lost;
-
- FakeAuxData(size_t buf1_size, size_t buf2_size, char c, size_t pad_size, bool lost)
- : buf1(buf1_size, c), buf2(buf2_size, c), pad(pad_size, 0), lost(lost) {}
-};
-
-TEST_F(RecordReadThreadTest, read_aux_data) {
- const EventType* type = FindEventTypeByName("cs-etm");
- if (type == nullptr) {
- GTEST_LOG_(INFO) << "Omit this test as cs-etm event type isn't available";
- return;
- }
- std::vector<FakeAuxData> aux_data;
- aux_data.emplace_back(40, 0, '0', 0, false); // one buffer
- aux_data.emplace_back(40, 40, '1', 0, false); // two buffers
- aux_data.emplace_back(36, 0, '2', 4, false); // one buffer needs padding to 8 bytes alignment
- aux_data.emplace_back(1024, 0, '3', 0, true); // one buffer too big to fit into RecordReadThread
- size_t test_index = 0;
-
- auto SetBuf1 = [&](char** buf1) {
- *buf1 = aux_data[test_index].buf1.data();
- return true;
- };
- auto SetSize1 = [&](size_t* size1) {
- *size1 = aux_data[test_index].buf1.size();
- return true;
- };
- auto SetBuf2 = [&](char** buf2) {
- *buf2 = aux_data[test_index].buf2.data();
- return true;
- };
- auto SetSize2 = [&](size_t* size2) {
- *size2 = aux_data[test_index].buf2.size();
- return true;
- };
- auto CheckDiscardSize = [&](size_t size) {
- return size == aux_data[test_index].buf1.size() + aux_data[test_index].buf2.size();
- };
-
- const size_t AUX_BUFFER_SIZE = 4096;
-
- perf_event_attr attr = CreateDefaultPerfEventAttr(*type);
- MockEventFd fd(attr, 0, nullptr, 1, true);
- EXPECT_CALL(fd, CreateMappedBuffer(_, _)).Times(1).WillOnce(Return(true));
- EXPECT_CALL(fd, CreateAuxBuffer(Eq(AUX_BUFFER_SIZE), _)).Times(1).WillOnce(Return(true));
- EXPECT_CALL(fd, StartPolling(_, _)).Times(1).WillOnce(Return(true));
- EXPECT_CALL(fd, GetAvailableMmapDataSize(_)).Times(aux_data.size()).WillRepeatedly(Return(0));
- EXPECT_CALL(fd,
- GetAvailableAuxData(Truly(SetBuf1), Truly(SetSize1), Truly(SetBuf2), Truly(SetSize2)))
- .Times(aux_data.size());
- EXPECT_CALL(fd, DiscardAuxData(Truly(CheckDiscardSize))).Times(aux_data.size());
- EXPECT_CALL(fd, StopPolling()).Times(1).WillOnce(Return(true));
- EXPECT_CALL(fd, DestroyMappedBuffer()).Times(1);
- EXPECT_CALL(fd, DestroyAuxBuffer()).Times(1);
-
- RecordReadThread thread(1024, attr, 1, 1, AUX_BUFFER_SIZE);
- IOEventLoop loop;
- ASSERT_TRUE(thread.RegisterDataCallback(loop, []() { return true; }));
- ASSERT_TRUE(thread.AddEventFds({&fd}));
- for (; test_index < aux_data.size(); ++test_index) {
- ASSERT_TRUE(thread.SyncKernelBuffer());
- std::unique_ptr<Record> r = thread.GetRecord();
- if (aux_data[test_index].lost) {
- ASSERT_TRUE(r == nullptr);
- continue;
- }
- ASSERT_TRUE(r);
- ASSERT_EQ(r->type(), PERF_RECORD_AUXTRACE);
- auto auxtrace = static_cast<AuxTraceRecord*>(r.get());
- auto& expected = aux_data[test_index];
- ASSERT_EQ(auxtrace->data->aux_size,
- expected.buf1.size() + expected.buf2.size() + expected.pad.size());
- const char* p = auxtrace->location.addr;
- ASSERT_TRUE(p != nullptr);
- if (!expected.buf1.empty()) {
- ASSERT_EQ(memcmp(p, expected.buf1.data(), expected.buf1.size()), 0);
- p += expected.buf1.size();
- }
- if (!expected.buf2.empty()) {
- ASSERT_EQ(memcmp(p, expected.buf2.data(), expected.buf2.size()), 0);
- p += expected.buf2.size();
- }
- if (!expected.pad.empty()) {
- ASSERT_EQ(memcmp(p, expected.pad.data(), expected.pad.size()), 0);
- }
- }
- ASSERT_TRUE(thread.GetRecord() == nullptr);
- ASSERT_TRUE(thread.RemoveEventFds({&fd}));
- size_t aux_data_size = 0;
- size_t lost_aux_data_size = 0;
- for (auto& aux : aux_data) {
- if (aux.lost) {
- lost_aux_data_size += aux.buf1.size() + aux.buf2.size();
- } else {
- aux_data_size += aux.buf1.size() + aux.buf2.size();
- }
- }
- ASSERT_EQ(aux_data_size, thread.GetStat().aux_data_size);
- ASSERT_EQ(lost_aux_data_size, thread.GetStat().lost_aux_data_size);
-} \ No newline at end of file
diff --git a/simpleperf/SampleComparator.h b/simpleperf/SampleComparator.h
index 465370a1..77781811 100644
--- a/simpleperf/SampleComparator.h
+++ b/simpleperf/SampleComparator.h
@@ -50,8 +50,8 @@ int Compare(const T& a, const T& b) {
return strcmp(sample1->compare_part, sample2->compare_part); \
}
-BUILD_COMPARE_VALUE_FUNCTION(ComparePid, pid);
-BUILD_COMPARE_VALUE_FUNCTION(CompareTid, tid);
+BUILD_COMPARE_VALUE_FUNCTION(ComparePid, thread->pid);
+BUILD_COMPARE_VALUE_FUNCTION(CompareTid, thread->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());
diff --git a/simpleperf/SampleDisplayer.h b/simpleperf/SampleDisplayer.h
index 3a59abe5..2dde02eb 100644
--- a/simpleperf/SampleDisplayer.h
+++ b/simpleperf/SampleDisplayer.h
@@ -66,12 +66,12 @@ BUILD_DISPLAY_UINT64_FUNCTION(DisplaySampleCount, sample_count);
template <typename EntryT>
std::string DisplayPid(const EntryT* sample) {
- return android::base::StringPrintf("%d", static_cast<int>(sample->pid));
+ return android::base::StringPrintf("%d", sample->thread->pid);
}
template <typename EntryT>
std::string DisplayTid(const EntryT* sample) {
- return android::base::StringPrintf("%d", static_cast<int>(sample->tid));
+ return android::base::StringPrintf("%d", sample->thread->tid);
}
template <typename EntryT>
diff --git a/simpleperf/app_api/cpp/simpleperf.cpp b/simpleperf/app_api/cpp/simpleperf.cpp
index 54b331a3..23bb4fb5 100644
--- a/simpleperf/app_api/cpp/simpleperf.cpp
+++ b/simpleperf/app_api/cpp/simpleperf.cpp
@@ -188,7 +188,6 @@ class ProfileSessionImpl {
pid_t simpleperf_pid_ = -1;
int control_fd_ = -1;
int reply_fd_ = -1;
- bool trace_offcpu_ = false;
};
ProfileSessionImpl::~ProfileSessionImpl() {
@@ -205,11 +204,6 @@ void ProfileSessionImpl::StartRecording(const std::vector<std::string> &args) {
if (state_ != NOT_YET_STARTED) {
Abort("startRecording: session in wrong state %d", state_);
}
- for (const auto& arg : args) {
- if (arg == "--trace-offcpu") {
- trace_offcpu_ = true;
- }
- }
std::string simpleperf_path = FindSimpleperf();
CheckIfPerfEnabled();
CreateSimpleperfDataDir();
@@ -222,9 +216,6 @@ void ProfileSessionImpl::PauseRecording() {
if (state_ != STARTED) {
Abort("pauseRecording: session in wrong state %d", state_);
}
- if (trace_offcpu_) {
- Abort("--trace-offcpu doesn't work well with pause/resume recording");
- }
SendCmd("pause");
state_ = PAUSED;
}
@@ -279,61 +270,6 @@ static bool IsExecutableFile(const std::string& path) {
return false;
}
-static std::string ReadFile(FILE* fp) {
- std::string s;
- if (fp == nullptr) {
- return s;
- }
- char buf[200];
- while (true) {
- ssize_t n = fread(buf, 1, sizeof(buf), fp);
- if (n <= 0) {
- break;
- }
- s.insert(s.end(), buf, buf + n);
- }
- fclose(fp);
- return s;
-}
-
-static bool RunCmd(std::vector<const char*> args, std::string* stdout) {
- int stdout_fd[2];
- if (pipe(stdout_fd) != 0) {
- return false;
- }
- args.push_back(nullptr);
- // Fork handlers (like gsl_library_close) may hang in a multi-thread environment.
- // So we use vfork instead of fork to avoid calling them.
- int pid = vfork();
- if (pid == -1) {
- return false;
- }
- if (pid == 0) {
- // child process
- close(stdout_fd[0]);
- dup2(stdout_fd[1], 1);
- close(stdout_fd[1]);
- execvp(const_cast<char*>(args[0]), const_cast<char**>(args.data()));
- _exit(1);
- }
- // parent process
- close(stdout_fd[1]);
- int status;
- pid_t result = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
- if (result == -1) {
- Abort("failed to call waitpid: %s", strerror(errno));
- }
- if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
- return false;
- }
- if (stdout == nullptr) {
- close(stdout_fd[0]);
- } else {
- *stdout = ReadFile(fdopen(stdout_fd[0], "r"));
- }
- return true;
-}
-
std::string ProfileSessionImpl::FindSimpleperf() {
// 1. Try /data/local/tmp/simpleperf first. Probably it's newer than /system/bin/simpleperf.
std::string simpleperf_path = FindSimpleperfInTempDir();
@@ -356,21 +292,38 @@ std::string ProfileSessionImpl::FindSimpleperfInTempDir() {
}
// Copy it to app_dir to execute it.
const std::string to_path = app_data_dir_ + "/simpleperf";
- if (!RunCmd({"/system/bin/cp", path.c_str(), to_path.c_str()}, nullptr)) {
+ const std::string copy_cmd = "cp " + path + " " + to_path;
+ if (system(copy_cmd.c_str()) != 0) {
return "";
}
+ const std::string test_cmd = to_path;
// For apps with target sdk >= 29, executing app data file isn't allowed. So test executing it.
- if (!RunCmd({to_path.c_str()}, nullptr)) {
+ if (system(test_cmd.c_str()) != 0) {
return "";
}
return to_path;
}
-void ProfileSessionImpl::CheckIfPerfEnabled() {
+static std::string ReadFile(FILE* fp) {
std::string s;
- if (!RunCmd({"/system/bin/getprop", "security.perf_harden"}, &s)) {
+ char buf[200];
+ while (true) {
+ ssize_t n = fread(buf, 1, sizeof(buf), fp);
+ if (n <= 0) {
+ break;
+ }
+ s.insert(s.end(), buf, buf + n);
+ }
+ return s;
+}
+
+void ProfileSessionImpl::CheckIfPerfEnabled() {
+ FILE* fp = popen("/system/bin/getprop security.perf_harden", "re");
+ if (fp == nullptr) {
return; // Omit check if getprop doesn't exist.
}
+ std::string s = ReadFile(fp);
+ pclose(fp);
if (!s.empty() && s[0] == '1') {
Abort("linux perf events aren't enabled on the device. Please run api_profiler.py.");
}
@@ -413,9 +366,7 @@ void ProfileSessionImpl::CreateSimpleperfProcess(const std::string &simpleperf_p
argv[args.size()] = nullptr;
// 3. Start simpleperf process.
- // Fork handlers (like gsl_library_close) may hang in a multi-thread environment.
- // So we use vfork instead of fork to avoid calling them.
- int pid = vfork();
+ int pid = fork();
if (pid == -1) {
Abort("failed to fork: %s", strerror(errno));
}
@@ -464,6 +415,7 @@ ProfileSession::ProfileSession() {
Abort("failed to open /proc/self/cmdline: %s", strerror(errno));
}
std::string s = ReadFile(fp);
+ fclose(fp);
for (int i = 0; i < s.size(); i++) {
if (s[i] == '\0') {
s = s.substr(0, i);
diff --git a/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java b/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java
index cb0eac3d..f1e2b202 100644
--- a/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java
+++ b/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java
@@ -16,9 +16,6 @@
package com.android.simpleperf;
-import android.os.Build;
-import android.system.OsConstants;
-
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
@@ -56,7 +53,6 @@ import java.util.stream.Collectors;
* </p>
*/
public class ProfileSession {
- private static final String SIMPLEPERF_PATH_IN_IMAGE = "/system/bin/simpleperf";
enum State {
NOT_YET_STARTED,
@@ -67,10 +63,8 @@ public class ProfileSession {
private State state = State.NOT_YET_STARTED;
private String appDataDir;
- private String simpleperfPath;
private String simpleperfDataDir;
private Process simpleperfProcess;
- private boolean traceOffcpu = false;
/**
* @param appDataDir the same as android.content.Context.getDataDir().
@@ -121,12 +115,7 @@ public class ProfileSession {
if (state != State.NOT_YET_STARTED) {
throw new AssertionError("startRecording: session in wrong state " + state);
}
- for (String arg : args) {
- if (arg.equals("--trace-offcpu")) {
- traceOffcpu = true;
- }
- }
- simpleperfPath = findSimpleperf();
+ String simpleperfPath = findSimpleperf();
checkIfPerfEnabled();
createSimpleperfDataDir();
createSimpleperfProcess(simpleperfPath, args);
@@ -140,10 +129,6 @@ public class ProfileSession {
if (state != State.STARTED) {
throw new AssertionError("pauseRecording: session in wrong state " + state);
}
- if (traceOffcpu) {
- throw new AssertionError(
- "--trace-offcpu option doesn't work well with pause/resume recording");
- }
sendCmd("pause");
state = State.PAUSED;
}
@@ -166,14 +151,7 @@ public class ProfileSession {
if (state != State.STARTED && state != State.PAUSED) {
throw new AssertionError("stopRecording: session in wrong state " + state);
}
- if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P + 1 &&
- simpleperfPath.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();
- }
+ simpleperfProcess.destroy();
try {
int exitCode = simpleperfProcess.waitFor();
if (exitCode != 0) {
@@ -185,22 +163,6 @@ public class ProfileSession {
state = State.STOPPED;
}
- private void destroySimpleperfProcessWithoutClosingStdin() {
- // In format "Process[pid=? ..."
- String s = simpleperfProcess.toString();
- final String prefix = "Process[pid=";
- if (s.startsWith(prefix)) {
- int startIndex = prefix.length();
- int endIndex = s.indexOf(',');
- if (endIndex > startIndex) {
- int pid = Integer.parseInt(s.substring(startIndex, endIndex).trim());
- android.os.Process.sendSignal(pid, OsConstants.SIGTERM);
- return;
- }
- }
- simpleperfProcess.destroy();
- }
-
private String readInputStream(InputStream in) {
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String result = reader.lines().collect(Collectors.joining("\n"));
@@ -218,7 +180,7 @@ public class ProfileSession {
return simpleperfPath;
}
// 2. Try /system/bin/simpleperf, which is available on Android >= Q.
- simpleperfPath = SIMPLEPERF_PATH_IN_IMAGE;
+ simpleperfPath = "/system/bin/simpleperf";
if (isExecutableFile(simpleperfPath)) {
return simpleperfPath;
}
diff --git a/simpleperf/cmd_debug_unwind.cpp b/simpleperf/cmd_debug_unwind.cpp
index b5df2da3..c2019bd3 100644
--- a/simpleperf/cmd_debug_unwind.cpp
+++ b/simpleperf/cmd_debug_unwind.cpp
@@ -94,7 +94,7 @@ class DebugUnwindCommand : public Command {
),
input_filename_("perf.data"),
output_filename_("perf.data.debug"),
- offline_unwinder_(OfflineUnwinder::Create(true)),
+ offline_unwinder_(true),
callchain_joiner_(DEFAULT_CALL_CHAIN_JOINER_CACHE_SIZE, 1, true),
selected_time_(0) {
}
@@ -126,7 +126,7 @@ class DebugUnwindCommand : public Command {
std::unique_ptr<RecordFileReader> reader_;
std::unique_ptr<RecordFileWriter> writer_;
ThreadTree thread_tree_;
- std::unique_ptr<OfflineUnwinder> offline_unwinder_;
+ OfflineUnwinder offline_unwinder_;
CallChainJoiner callchain_joiner_;
Stat stat_;
uint64_t selected_time_;
@@ -236,12 +236,12 @@ bool DebugUnwindCommand::ProcessRecord(Record* record) {
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,
+ if (!offline_unwinder_.UnwindCallChain(*thread, regs, r.stack_user_data.data,
r.GetValidStackSize(), &ips, &sps)) {
return false;
}
- const UnwindingResult& unwinding_result = offline_unwinder_->GetUnwindingResult();
+ 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,
@@ -371,7 +371,9 @@ bool DebugUnwindCommand::WriteFeatureSections() {
// 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();
+ if (!reader_->ReadMetaInfoFeature(&info_map)) {
+ return false;
+ }
++it;
}
info_map["debug_unwind"] = "true";
diff --git a/simpleperf/cmd_debug_unwind_test.cpp b/simpleperf/cmd_debug_unwind_test.cpp
index 20a441ec..e6b68670 100644
--- a/simpleperf/cmd_debug_unwind_test.cpp
+++ b/simpleperf/cmd_debug_unwind_test.cpp
@@ -60,8 +60,9 @@ TEST(cmd_debug_unwind, symfs_option) {
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");
+ std::unordered_map<std::string, std::string> info_map;
+ ASSERT_TRUE(reader->ReadMetaInfoFeature(&info_map));
+ ASSERT_EQ(info_map["debug_unwind"], "true");
}
TEST(cmd_debug_unwind, unwind_with_ip_zero_in_callchain) {
@@ -72,20 +73,3 @@ TEST(cmd_debug_unwind, unwind_with_ip_zero_in_callchain) {
"-o", tmp_file.path}));
ASSERT_NE(capture.Finish().find("Unwinding sample count: 1"), std::string::npos);
}
-
-TEST(cmd_debug_unwind, unwind_embedded_lib_in_apk) {
- // Check if we can unwind through a native library embedded in an apk. In the profiling data
- // file, there is a sample with ip address pointing to
- // /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;
- ASSERT_TRUE(DebugUnwindCmd()->Run({"-i", GetTestData("perf_unwind_embedded_lib_in_apk.data"),
- "--symfs", GetTestDataDir(), "-o", tmp_file.path}));
- CaptureStdout capture;
- ASSERT_TRUE(capture.Start());
- ASSERT_TRUE(CreateCommandInstance("report-sample")->Run(
- {"--show-callchain", "-i", tmp_file.path}));
- std::string output = capture.Finish();
- ASSERT_NE(output.find("libnative-lib.so"), std::string::npos);
- ASSERT_NE(output.find("libc.so"), std::string::npos);
-}
diff --git a/simpleperf/cmd_dumprecord.cpp b/simpleperf/cmd_dumprecord.cpp
index 22a242e3..6f8762f8 100644
--- a/simpleperf/cmd_dumprecord.cpp
+++ b/simpleperf/cmd_dumprecord.cpp
@@ -25,8 +25,6 @@
#include <android-base/strings.h>
#include "command.h"
-#include "dso.h"
-#include "ETMDecoder.h"
#include "event_attr.h"
#include "event_type.h"
#include "perf_regs.h"
@@ -35,19 +33,15 @@
#include "utils.h"
using namespace PerfFileFormat;
-using namespace simpleperf;
class DumpRecordCommand : public Command {
public:
DumpRecordCommand()
: Command("dump", "dump perf record file",
- // clang-format off
-"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"
-"--symdir <dir> Look for binaries in a directory recursively.\n"
- // clang-format on
- ) {}
+ "Usage: simpleperf dumprecord [options] [perf_record_file]\n"
+ " Dump different parts of a perf record file. Default file is perf.data.\n"),
+ record_filename_("perf.data"), record_file_arch_(GetBuildArch()) {
+ }
bool Run(const std::vector<std::string>& args);
@@ -56,12 +50,11 @@ class DumpRecordCommand : public Command {
void DumpFileHeader();
void DumpAttrSection();
bool DumpDataSection();
- bool DumpAuxData(const AuxRecord& aux, ETMDecoder& etm_decoder);
bool DumpFeatureSection();
- std::string record_filename_ = "perf.data";
+ std::string record_filename_;
std::unique_ptr<RecordFileReader> record_file_reader_;
- ETMDumpOption etm_dump_option_;
+ ArchType record_file_arch_;
};
bool DumpRecordCommand::Run(const std::vector<std::string>& args) {
@@ -72,6 +65,25 @@ bool DumpRecordCommand::Run(const std::vector<std::string>& args) {
if (record_file_reader_ == nullptr) {
return false;
}
+ std::string arch = record_file_reader_->ReadFeatureString(FEAT_ARCH);
+ if (!arch.empty()) {
+ record_file_arch_ = GetArchType(arch);
+ if (record_file_arch_ == ARCH_UNSUPPORTED) {
+ return false;
+ }
+ }
+ ScopedCurrentArch scoped_arch(record_file_arch_);
+ std::unique_ptr<ScopedEventTypes> scoped_event_types;
+ if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_META_INFO)) {
+ std::unordered_map<std::string, std::string> meta_info;
+ if (!record_file_reader_->ReadMetaInfoFeature(&meta_info)) {
+ return false;
+ }
+ auto it = meta_info.find("event_type_info");
+ if (it != meta_info.end()) {
+ scoped_event_types.reset(new ScopedEventTypes(it->second));
+ }
+ }
DumpFileHeader();
DumpAttrSection();
if (!DumpDataSection()) {
@@ -81,31 +93,12 @@ 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);
- return false;
- }
- }
- if (i + 1 < args.size()) {
- LOG(ERROR) << "too many record files";
+ if (args.size() == 1) {
+ record_filename_ = args[0];
+ } else if (args.size() > 1) {
+ ReportUnknownOption(args, 1);
return false;
}
- if (i + 1 == args.size()) {
- record_filename_ = args[i];
- }
return true;
}
@@ -168,7 +161,6 @@ void DumpRecordCommand::DumpAttrSection() {
}
bool DumpRecordCommand::DumpDataSection() {
- std::unique_ptr<ETMDecoder> etm_decoder;
ThreadTree thread_tree;
thread_tree.ShowIpForUnknownSymbol();
record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree);
@@ -220,33 +212,12 @@ bool DumpRecordCommand::DumpDataSection() {
PrintIndented(2, "%s (%s[+%" PRIx64 "])\n", symbol_name.c_str(), dso_name.c_str(),
vaddr_in_file);
}
- } else if (r->type() == PERF_RECORD_AUXTRACE_INFO) {
- etm_decoder = ETMDecoder::Create(*static_cast<AuxTraceInfoRecord*>(r.get()), thread_tree);
- if (!etm_decoder) {
- return false;
- }
- 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);
}
-bool DumpRecordCommand::DumpAuxData(const AuxRecord& aux, ETMDecoder& etm_decoder) {
- 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 true;
-}
-
bool DumpRecordCommand::DumpFeatureSection() {
std::map<int, SectionDesc> section_map = record_file_reader_->FeatureSectionDescriptors();
for (const auto& pair : section_map) {
@@ -297,15 +268,14 @@ bool DumpRecordCommand::DumpFeatureSection() {
}
}
} else if (feature == FEAT_META_INFO) {
+ std::unordered_map<std::string, std::string> info_map;
+ if (!record_file_reader_->ReadMetaInfoFeature(&info_map)) {
+ return false;
+ }
PrintIndented(1, "meta_info:\n");
- for (auto& pair : record_file_reader_->GetMetaInfoFeature()) {
+ for (auto& pair : info_map) {
PrintIndented(2, "%s = %s\n", pair.first.c_str(), pair.second.c_str());
}
- } else if (feature == FEAT_AUXTRACE) {
- PrintIndented(1, "file_offsets_of_auxtrace_records:\n");
- for (auto offset : record_file_reader_->ReadAuxTraceFeature()) {
- PrintIndented(2, "%" PRIu64 "\n", offset);
- }
}
}
return true;
diff --git a/simpleperf/cmd_dumprecord_test.cpp b/simpleperf/cmd_dumprecord_test.cpp
index 76afc1ca..a2a50cee 100644
--- a/simpleperf/cmd_dumprecord_test.cpp
+++ b/simpleperf/cmd_dumprecord_test.cpp
@@ -44,15 +44,3 @@ TEST(cmd_dump, dump_callchain_of_sample_records) {
ASSERT_NE(data.find("[kernel.kallsyms][+ffffffc000086b4a]"), std::string::npos);
ASSERT_NE(data.find("__ioctl (/system/lib64/libc.so[+70b6c])"), std::string::npos);
}
-
-TEST(cmd_dump, etm_data) {
- CaptureStdout capture;
- ASSERT_TRUE(capture.Start());
- ASSERT_TRUE(DumpCmd()->Run({"--dump-etm", "raw,packet,element", "--symdir",
- GetTestDataDir() + "etm", GetTestData(PERF_DATA_ETM_TEST_LOOP)}));
- std::string data = capture.Finish();
- ASSERT_NE(data.find("record aux:"), std::string::npos);
- ASSERT_NE(data.find("feature section for auxtrace:"), std::string::npos);
- // Check if we can decode etm data into instruction range elements.
- ASSERT_NE(data.find("OCSD_GEN_TRC_ELEM_INSTR_RANGE"), std::string::npos);
-}
diff --git a/simpleperf/cmd_inject.cpp b/simpleperf/cmd_inject.cpp
deleted file mode 100644
index 9cf40b93..00000000
--- a/simpleperf/cmd_inject.cpp
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2019 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 <string>
-
-#include "ETMDecoder.h"
-#include "command.h"
-#include "record_file.h"
-#include "thread_tree.h"
-#include "utils.h"
-
-using namespace simpleperf;
-
-namespace {
-
-using AddrPair = std::pair<uint64_t, uint64_t>;
-
-struct AddrPairHash {
- size_t operator()(const AddrPair& ap) const noexcept {
- size_t seed = 0;
- HashCombine(seed, ap.first);
- HashCombine(seed, ap.second);
- return seed;
- }
-};
-
-struct BinaryInfo {
- std::unordered_map<AddrPair, uint64_t, AddrPairHash> range_count_map;
- std::unordered_map<AddrPair, uint64_t, AddrPairHash> branch_count_map;
-};
-
-class InjectCommand : public Command {
- public:
- InjectCommand()
- : Command("inject", "convert etm instruction tracing data into instr ranges",
- // clang-format off
-"Usage: simpleperf inject [options]\n"
-"--binary binary_name Generate data only for binaries containing binary_name.\n"
-"-i <file> input perf.data, generated by recording cs-etm event type.\n"
-" Default is perf.data.\n"
-"-o <file> output file. Default is perf_inject.data.\n"
-" The output is in text format accepted by AutoFDO.\n"
-"--dump-etm type1,type2,... Dump etm data. A type is one of raw, packet and element.\n"
-"--symdir <dir> Look for binaries in a directory recursively.\n"
- // clang-format on
- ),
- output_fp_(nullptr, fclose) {}
-
- bool Run(const std::vector<std::string>& args) override {
- 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"));
- 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()); })) {
- 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_filter_ = 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;
- }
- } else {
- ReportUnknownOption(args, i);
- return false;
- }
- }
- return true;
- }
-
- 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);
- } else if (r->type() == PERF_RECORD_AUX) {
- AuxRecord* aux = static_cast<AuxRecord*>(r);
- uint64_t aux_size = aux->data->aux_size;
- if (aux_size > 0) {
- if (aux_data_buffer_.size() < aux_size) {
- aux_data_buffer_.resize(aux_size);
- }
- if (!record_file_reader_->ReadAuxData(aux->Cpu(), aux->data->aux_offset,
- aux_data_buffer_.data(), aux_size)) {
- LOG(ERROR) << "failed to read aux data";
- return false;
- }
- return etm_decoder_->ProcessData(aux_data_buffer_.data(), aux_size);
- }
- }
- return true;
- }
-
- void ProcessInstrRange(const ETMInstrRange& instr_range) {
- if (instr_range.dso->GetDebugFilePath().find(binary_name_filter_) == std::string::npos) {
- return;
- }
- auto& binary = binary_map_[instr_range.dso->GetDebugFilePath()];
- 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) {
- binary.branch_count_map[AddrPair(instr_range.end_addr, instr_range.branch_to_addr)] +=
- instr_range.branch_taken_count;
- }
- }
-
- void PostProcess() {
- for (const auto& pair : binary_map_) {
- const std::string& binary_path = pair.first;
- const BinaryInfo& binary = pair.second;
-
- // Write range_count_map.
- fprintf(output_fp_.get(), "%zu\n", binary.range_count_map.size());
- for (const auto& pair2 : binary.range_count_map) {
- 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);
- }
-
- // Write addr_count_map.
- fprintf(output_fp_.get(), "0\n");
-
- // Write branch_count_map.
- fprintf(output_fp_.get(), "%zu\n", binary.branch_count_map.size());
- for (const auto& pair2 : binary.branch_count_map) {
- const AddrPair& branch = pair2.first;
- uint64_t count = pair2.second;
-
- fprintf(output_fp_.get(), "%" PRIx64 "->%" PRIx64 ":%" PRIu64 "\n", branch.first,
- branch.second, count);
- }
-
- // Write the binary path in comment.
- fprintf(output_fp_.get(), "// %s\n\n", binary_path.c_str());
- }
- }
-
- std::string binary_name_filter_;
- std::string input_filename_ = "perf.data";
- std::string output_filename_ = "perf_inject.data";
- ThreadTree thread_tree_;
- std::unique_ptr<RecordFileReader> record_file_reader_;
- ETMDumpOption etm_dump_option_;
- std::unique_ptr<ETMDecoder> etm_decoder_;
- std::vector<uint8_t> aux_data_buffer_;
- std::unique_ptr<FILE, decltype(&fclose)> output_fp_;
-
- // Store results for AutoFDO.
- std::unordered_map<std::string, BinaryInfo> binary_map_;
-};
-
-} // namespace
-
-void RegisterInjectCommand() {
- return RegisterCommand("inject", [] { return std::unique_ptr<Command>(new InjectCommand); });
-}
diff --git a/simpleperf/cmd_inject_test.cpp b/simpleperf/cmd_inject_test.cpp
deleted file mode 100644
index 3401360d..00000000
--- a/simpleperf/cmd_inject_test.cpp
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2019 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 <android-base/file.h>
-#include <gtest/gtest.h>
-
-#include "command.h"
-#include "get_test_data.h"
-#include "test_util.h"
-
-static std::unique_ptr<Command> InjectCmd() { return CreateCommandInstance("inject"); }
-
-TEST(cmd_inject, smoke) {
- 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);
-}
-
-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_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_EQ(data.find("etm_test_loop"), std::string::npos);
-}
diff --git a/simpleperf/cmd_kmem.cpp b/simpleperf/cmd_kmem.cpp
index 453a2768..5d58c394 100644
--- a/simpleperf/cmd_kmem.cpp
+++ b/simpleperf/cmd_kmem.cpp
@@ -211,7 +211,7 @@ class SlabSampleTreeBuilder
return nullptr;
}
- SlabSample* CreateCallChainSample(const ThreadEntry*,
+ SlabSample* CreateCallChainSample(
const SlabSample* sample, uint64_t ip, bool in_kernel,
const std::vector<SlabSample*>& callchain,
const SlabAccumulateInfo& acc_info) override {
diff --git a/simpleperf/cmd_list.cpp b/simpleperf/cmd_list.cpp
index 46bee30d..c87c05f5 100644
--- a/simpleperf/cmd_list.cpp
+++ b/simpleperf/cmd_list.cpp
@@ -24,14 +24,11 @@
#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;
-
static bool IsEventTypeSupported(const EventType& event_type) {
if (event_type.type != PERF_TYPE_RAW) {
perf_event_attr attr = CreateDefaultPerfEventAttr(event_type);
@@ -48,7 +45,7 @@ static bool IsEventTypeSupported(const EventType& event_type) {
// We can't decide whether the raw event is supported by calling perf_event_open().
// Instead, we can check if it can collect some real number.
perf_event_attr attr = CreateDefaultPerfEventAttr(event_type);
- std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(attr, gettid(), -1, nullptr, false);
+ std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(attr, gettid(), -1, nullptr);
if (event_fd == nullptr) {
return false;
}
@@ -74,36 +71,19 @@ static bool IsEventTypeSupported(const EventType& event_type) {
static void PrintEventTypesOfType(uint32_t type, const std::string& type_name,
const std::set<EventType>& event_types) {
printf("List of %s:\n", type_name.c_str());
- if (GetBuildArch() == ARCH_ARM || GetBuildArch() == ARCH_ARM64) {
- if (type == PERF_TYPE_RAW) {
- printf(
- // clang-format off
-" # Please refer to \"PMU common architectural and microarchitectural event numbers\"\n"
-" # and \"ARM recommendations for IMPLEMENTATION DEFINED event numbers\" listed in\n"
-" # ARMv8 manual for details.\n"
-" # A possible link is https://developer.arm.com/docs/ddi0487/latest/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile.\n"
- // clang-format on
- );
- } else if (type == PERF_TYPE_HW_CACHE) {
- printf(" # More cache events are available in `simpleperf list raw`.\n");
- }
+ if (type == PERF_TYPE_RAW && (GetBuildArch() == ARCH_ARM || GetBuildArch() == ARCH_ARM64)) {
+ printf(" # Please refer to PMU event numbers listed in ARMv8 manual for details.\n");
+ printf(" # A possible link is https://developer.arm.com/docs/ddi0487/latest/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile.\n");
}
for (auto& event_type : event_types) {
if (event_type.type == type) {
- bool supported = IsEventTypeSupported(event_type);
- // For raw events, we may not be able to detect whether it is supported on device.
- // So always print them.
- if (!supported && type != PERF_TYPE_RAW) {
- continue;
- }
- printf(" %s", event_type.name.c_str());
- if (!supported) {
- printf(" (may not supported)");
+ if (IsEventTypeSupported(event_type)) {
+ printf(" %s", event_type.name.c_str());
+ if (!event_type.description.empty()) {
+ printf("\t\t# %s", event_type.description.c_str());
+ }
+ printf("\n");
}
- if (!event_type.description.empty()) {
- printf("\t\t# %s", event_type.description.c_str());
- }
- printf("\n");
}
}
printf("\n");
@@ -120,9 +100,8 @@ class ListCommand : public Command {
" hw hardware events\n"
" sw software events\n"
" cache hardware cache events\n"
-" raw raw cpu pmu events\n"
+" raw raw pmu events\n"
" tracepoint tracepoint events\n"
-" cs-etm coresight etm instruction tracing events\n"
"Options:\n"
"--show-features Show features supported on the device, including:\n"
" dwarf-based-call-graph\n"
@@ -149,7 +128,6 @@ bool ListCommand::Run(const std::vector<std::string>& args) {
{"raw", {PERF_TYPE_RAW, "raw events provided by cpu pmu"}},
{"tracepoint", {PERF_TYPE_TRACEPOINT, "tracepoint events"}},
{"user-space-sampler", {SIMPLEPERF_TYPE_USER_SPACE_SAMPLERS, "user-space samplers"}},
- {"cs-etm", {-1, "coresight etm events"}},
};
std::vector<std::string> names;
@@ -175,9 +153,6 @@ bool ListCommand::Run(const std::vector<std::string>& args) {
for (auto& name : names) {
auto it = type_map.find(name);
- if (name == "cs-etm") {
- it->second.first = ETMRecorder::GetInstance().GetEtmEventType();
- }
PrintEventTypesOfType(it->second.first, it->second.second, event_types);
}
return true;
diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp
index 2208503e..ff01d808 100644
--- a/simpleperf/cmd_record.cpp
+++ b/simpleperf/cmd_record.cpp
@@ -40,7 +40,6 @@
#include "CallChainJoiner.h"
#include "command.h"
#include "environment.h"
-#include "ETMRecorder.h"
#include "event_selection_set.h"
#include "event_type.h"
#include "IOEventLoop.h"
@@ -93,12 +92,6 @@ constexpr size_t DEFAULT_CALL_CHAIN_JOINER_CACHE_SIZE = 8 * 1024 * 1024;
static constexpr size_t kRecordBufferSize = 64 * 1024 * 1024;
static constexpr size_t kSystemWideRecordBufferSize = 256 * 1024 * 1024;
-static constexpr size_t kDefaultAuxBufferSize = 4 * 1024 * 1024;
-
-// On Pixel 3, it takes about 1ms to enable ETM, and 16-40ms to disable ETM and copy 4M ETM data.
-// So make default period to 100ms.
-static constexpr double kDefaultEtmDataFlushPeriodInSec = 0.1;
-
struct TimeStat {
uint64_t prepare_recording_time = 0;
uint64_t start_recording_time = 0;
@@ -186,16 +179,9 @@ class RecordCommand : public Command {
"-m mmap_pages Set the size of the buffer used to receiving sample data from\n"
" the kernel. It should be a power of 2. If not set, the max\n"
" possible value <= 1024 will be used.\n"
-"--aux-buffer-size <buffer_size> Set aux buffer size, only used in cs-etm event type.\n"
-" Need to be power of 2 and page size aligned.\n"
-" Used memory size is (buffer_size * (cpu_count + 1).\n"
-" Default is 4M.\n"
"--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"
"\n"
"Dwarf unwinding options:\n"
"--post-unwind=(yes|no) If `--call-graph dwarf` option is used, then the user's\n"
@@ -212,11 +198,6 @@ class RecordCommand : public Command {
"--callchain-joiner-min-matching-nodes count\n"
" When callchain joiner is used, set the matched nodes needed to join\n"
" callchains. The count should be >= 1. By default it is 1.\n"
-"--no-cut-samples Simpleperf uses a record buffer to cache records received from the kernel.\n"
-" When the available space in the buffer reaches low level, it cuts part of\n"
-" the stack data in samples. When the available space reaches critical level,\n"
-" it drops all samples. This option makes simpleperf not cut samples when the\n"
-" available space reaches low level.\n"
"\n"
"Recording file options:\n"
"--no-dump-kernel-symbols Don't dump kernel symbols in perf.data. By default\n"
@@ -296,7 +277,6 @@ class RecordCommand : public Command {
bool DumpKernelMaps();
bool DumpUserSpaceMaps();
bool DumpProcessMaps(pid_t pid, const std::unordered_set<pid_t>& tids);
- bool DumpAuxTraceInfo();
bool ProcessRecord(Record* record);
bool ShouldOmitRecord(Record* record);
bool DumpMapsForRecord(Record* record);
@@ -334,7 +314,6 @@ class RecordCommand : public Command {
EventSelectionSet event_selection_set_;
std::pair<size_t, size_t> mmap_page_range_;
- size_t aux_buffer_size_ = kDefaultAuxBufferSize;
ThreadTree thread_tree_;
std::string record_filename_;
@@ -359,7 +338,6 @@ class RecordCommand : public Command {
bool allow_callchain_joiner_;
size_t callchain_joiner_min_matching_nodes_;
std::unique_ptr<CallChainJoiner> callchain_joiner_;
- bool allow_cutting_samples_ = true;
std::unique_ptr<JITDebugReader> jit_debug_reader_;
uint64_t last_record_timestamp_; // used to insert Mmap2Records for JIT debug info
@@ -435,7 +413,7 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
return false;
}
if (unwind_dwarf_callchain_) {
- offline_unwinder_ = OfflineUnwinder::Create(false);
+ offline_unwinder_.reset(new OfflineUnwinder(false));
}
if (unwind_dwarf_callchain_ && allow_callchain_joiner_) {
callchain_joiner_.reset(new CallChainJoiner(DEFAULT_CALL_CHAIN_JOINER_CACHE_SIZE,
@@ -473,8 +451,7 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
need_to_check_targets = true;
}
// Profiling JITed/interpreted Java code is supported starting from Android P.
- // Also support profiling art interpreter on host.
- if (GetAndroidVersion() >= kAndroidVersionP || GetAndroidVersion() == 0) {
+ if (GetAndroidVersion() >= kAndroidVersionP) {
// 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.
@@ -492,8 +469,7 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
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_)) {
+ record_buffer_size)) {
return false;
}
auto callback =
@@ -538,7 +514,7 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
}
}
if (stdio_controls_profiling_) {
- if (!loop->AddReadEvent(0, [this, loop]() { return ProcessControlCmd(loop); })) {
+ if (!loop->AddReadEvent(0, [&]() { return ProcessControlCmd(loop); })) {
return false;
}
}
@@ -549,7 +525,7 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
if (!jit_debug_reader_->RegisterDebugInfoCallback(loop, callback)) {
return false;
}
- if (!system_wide_collection_) {
+ if (!app_package_name_.empty()) {
std::set<pid_t> pids = event_selection_set_.GetMonitoredProcesses();
for (pid_t tid : event_selection_set_.GetMonitoredThreads()) {
pid_t pid;
@@ -567,21 +543,6 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
}
}
}
- if (event_selection_set_.HasAuxTrace()) {
- // ETM data is dumped to kernel buffer only when there is no thread traced by ETM. It happens
- // either when all monitored threads are scheduled off cpu, or when all etm perf events are
- // disabled.
- // If ETM data isn't dumped to kernel buffer in time, overflow parts will be dropped. This
- // makes less than expected data, especially in system wide recording. So add a periodic event
- // to flush etm data by temporarily disable all perf events.
- auto etm_flush = [this]() {
- return event_selection_set_.SetEnableEvents(false) &&
- event_selection_set_.SetEnableEvents(true);
- };
- if (!loop->AddPeriodicEvent(SecondToTimeval(kDefaultEtmDataFlushPeriodInSec), etm_flush)) {
- return false;
- }
- }
return true;
}
@@ -662,38 +623,33 @@ bool RecordCommand::PostProcessRecording(const std::vector<std::string>& args) {
time_stat_.post_process_time = GetSystemClock();
// 4. Show brief record result.
- auto record_stat = event_selection_set_.GetRecordStat();
- if (event_selection_set_.HasAuxTrace()) {
- LOG(INFO) << "Aux data traced: " << record_stat.aux_data_size;
- if (record_stat.lost_aux_data_size != 0) {
- LOG(INFO) << "Aux data lost in user space: " << record_stat.lost_aux_data_size;
- }
- } else {
- std::string cut_samples;
- if (record_stat.cut_stack_samples > 0) {
- cut_samples = android::base::StringPrintf(" (cut %zu)", record_stat.cut_stack_samples);
- }
- lost_record_count_ += record_stat.lost_samples + record_stat.lost_non_samples;
- LOG(INFO) << "Samples recorded: " << sample_record_count_ << cut_samples
- << ". Samples lost: " << lost_record_count_ << ".";
- LOG(DEBUG) << "In user space, dropped " << record_stat.lost_samples << " samples, "
- << record_stat.lost_non_samples << " non samples, cut stack of "
- << record_stat.cut_stack_samples << " samples.";
- if (sample_record_count_ + lost_record_count_ != 0) {
- double lost_percent =
- static_cast<double>(lost_record_count_) / (lost_record_count_ + sample_record_count_);
- constexpr double LOST_PERCENT_WARNING_BAR = 0.1;
- if (lost_percent >= LOST_PERCENT_WARNING_BAR) {
- LOG(WARNING) << "Lost " << (lost_percent * 100) << "% of samples, "
- << "consider increasing mmap_pages(-m), "
- << "or decreasing sample frequency(-f), "
- << "or increasing sample period(-c).";
- }
- }
- if (callchain_joiner_) {
- callchain_joiner_->DumpStat();
+ size_t lost_samples;
+ size_t lost_non_samples;
+ size_t cut_stack_samples;
+ event_selection_set_.GetLostRecords(&lost_samples, &lost_non_samples, &cut_stack_samples);
+ std::string cut_samples;
+ if (cut_stack_samples > 0) {
+ cut_samples = android::base::StringPrintf(" (cut %zu)", cut_stack_samples);
+ }
+ lost_record_count_ += lost_samples + lost_non_samples;
+ LOG(INFO) << "Samples recorded: " << sample_record_count_ << cut_samples
+ << ". Samples lost: " << lost_record_count_ << ".";
+ LOG(DEBUG) << "In user space, dropped " << lost_samples << " samples, " << lost_non_samples
+ << " non samples, cut stack of " << cut_stack_samples << " samples.";
+ if (sample_record_count_ + lost_record_count_ != 0) {
+ double lost_percent = static_cast<double>(lost_record_count_) /
+ (lost_record_count_ + sample_record_count_);
+ constexpr double LOST_PERCENT_WARNING_BAR = 0.1;
+ if (lost_percent >= LOST_PERCENT_WARNING_BAR) {
+ LOG(WARNING) << "Lost " << (lost_percent * 100) << "% of samples, "
+ << "consider increasing mmap_pages(-m), "
+ << "or decreasing sample frequency(-f), "
+ << "or increasing sample period(-c).";
}
}
+ if (callchain_joiner_) {
+ callchain_joiner_->DumpStat();
+ }
LOG(DEBUG) << "Prepare recording time "
<< (time_stat_.start_recording_time - time_stat_.prepare_recording_time) / 1e6
<< " ms, recording time "
@@ -717,15 +673,6 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
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)) {
- return false;
- }
- if (!IsPowerOfTwo(aux_buffer_size_) || aux_buffer_size_ % sysconf(_SC_PAGE_SIZE)) {
- LOG(ERROR) << "invalid aux buffer size: " << args[i];
- return false;
- }
} else if (args[i] == "-b") {
branch_sampling_ = branch_sampling_type_map["any"];
} else if (args[i] == "-c" || args[i] == "-f") {
@@ -848,11 +795,6 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
}
} 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;
@@ -891,8 +833,6 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
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;
@@ -1037,11 +977,7 @@ bool RecordCommand::AdjustPerfEventLimit() {
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;
- if (event_selection_set_.HasAuxTrace()) {
- mlock_kb += cpus * aux_buffer_size_ / 1024;
- }
+ uint64_t mlock_kb = sysconf(_SC_NPROCESSORS_CONF) * (mmap_page_range_.second + 1) * 4;
uint64_t cur_mlock_kb;
if (GetPerfEventMlockKb(&cur_mlock_kb) && cur_mlock_kb < mlock_kb &&
!SetPerfEventMlockKb(mlock_kb)) {
@@ -1100,8 +1036,7 @@ bool RecordCommand::CreateAndInitRecordFile() {
}
// 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();
+ return DumpKernelSymbol() && DumpTracingData() && DumpKernelMaps() && DumpUserSpaceMaps();
}
std::unique_ptr<RecordFileWriter> RecordCommand::CreateRecordFile(
@@ -1267,14 +1202,6 @@ bool RecordCommand::ProcessRecord(Record* record) {
return SaveRecordWithoutUnwinding(record);
}
-bool RecordCommand::DumpAuxTraceInfo() {
- if (event_selection_set_.HasAuxTrace()) {
- AuxTraceInfoRecord auxtrace_info = ETMRecorder::GetInstance().CreateAuxTraceInfoRecord();
- return ProcessRecord(&auxtrace_info);
- }
- return true;
-}
-
template <typename MmapRecordType>
bool MapOnlyExistInMemory(MmapRecordType* record) {
return !record->InKernel() && MappedFileOnlyExistInMemory(record->filename);
@@ -1377,16 +1304,6 @@ bool RecordCommand::ProcessJITDebugInfo(const std::vector<JITDebugInfo>& debug_i
return false;
}
} 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_;
- 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)) {
- return false;
- }
- }
thread_tree_.AddDexFileOffset(info.file_path, info.dex_file_offset);
}
}
@@ -1607,14 +1524,10 @@ bool RecordCommand::DumpAdditionalFeatures(
Dso::ReadKernelSymbolsFromProc();
kernel_symbols_available = true;
}
- std::vector<uint64_t> auxtrace_offset;
auto callback = [&](const Record* r) {
thread_tree_.Update(*r);
if (r->type() == PERF_RECORD_SAMPLE) {
CollectHitFileInfo(*reinterpret_cast<const SampleRecord*>(r));
- } else if (r->type() == PERF_RECORD_AUXTRACE) {
- auto auxtrace = static_cast<const AuxTraceRecord*>(r);
- auxtrace_offset.emplace_back(auxtrace->location.file_offset - auxtrace->size());
}
};
if (!record_file_writer_->ReadDataSection(callback)) {
@@ -1625,9 +1538,6 @@ bool RecordCommand::DumpAdditionalFeatures(
if (branch_sampling_) {
feature_count++;
}
- if (!auxtrace_offset.empty()) {
- feature_count++;
- }
if (!record_file_writer_->BeginWriteFeatures(feature_count)) {
return false;
}
@@ -1667,9 +1577,6 @@ bool RecordCommand::DumpAdditionalFeatures(
if (!DumpMetaInfoFeature(kernel_symbols_available)) {
return false;
}
- if (!auxtrace_offset.empty() && !record_file_writer_->WriteAuxTraceFeature(auxtrace_offset)) {
- return false;
- }
if (!record_file_writer_->EndWriteFeatures()) {
return false;
@@ -1682,9 +1589,7 @@ bool RecordCommand::DumpBuildIdFeature() {
BuildId build_id;
std::vector<Dso*> dso_v = thread_tree_.GetAllDsos();
for (Dso* dso : dso_v) {
- // For aux tracing, we don't know which binaries are traced.
- // So dump build ids for all binaries.
- if (!dso->HasDumpId() && !event_selection_set_.HasAuxTrace()) {
+ if (!dso->HasDumpId()) {
continue;
}
if (dso->type() == DSO_KERNEL) {
diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp
index 184f9359..fdc26e60 100644
--- a/simpleperf/cmd_record_test.cpp
+++ b/simpleperf/cmd_record_test.cpp
@@ -23,7 +23,6 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
-#include <android-base/strings.h>
#include <map>
#include <memory>
@@ -32,7 +31,6 @@
#include "command.h"
#include "environment.h"
-#include "ETMRecorder.h"
#include "event_selection_set.h"
#include "get_test_data.h"
#include "record.h"
@@ -40,7 +38,6 @@
#include "test_util.h"
#include "thread_tree.h"
-using namespace simpleperf;
using namespace PerfFileFormat;
static std::unique_ptr<Command> RecordCmd() {
@@ -389,11 +386,12 @@ TEST(record_cmd, kernel_symbol) {
ASSERT_TRUE(success);
}
-static void ProcessSymbolsInPerfDataFile(
- const std::string& perf_data_file,
- const std::function<bool(const Symbol&, uint32_t)>& callback) {
- auto reader = RecordFileReader::CreateInstance(perf_data_file);
- ASSERT_TRUE(reader);
+// Check if dumped symbols in perf.data matches our expectation.
+static bool CheckDumpedSymbols(const std::string& path, bool allow_dumped_symbols) {
+ std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(path);
+ if (!reader) {
+ return false;
+ }
std::string file_path;
uint32_t file_type;
uint64_t min_vaddr;
@@ -401,24 +399,13 @@ static void ProcessSymbolsInPerfDataFile(
std::vector<Symbol> symbols;
std::vector<uint64_t> dex_file_offsets;
size_t read_pos = 0;
+ bool has_dumped_symbols = false;
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)) {
- return;
- }
+ if (!symbols.empty()) {
+ has_dumped_symbols = true;
}
}
-}
-
-// Check if dumped symbols in perf.data matches our expectation.
-static bool CheckDumpedSymbols(const std::string& path, bool allow_dumped_symbols) {
- bool has_dumped_symbols = false;
- auto callback = [&](const Symbol&, uint32_t) {
- has_dumped_symbols = true;
- return true;
- };
- ProcessSymbolsInPerfDataFile(path, callback);
// It is possible that there are no samples hitting functions having symbols.
// So "allow_dumped_symbols = true" doesn't guarantee "has_dumped_symbols = true".
if (!allow_dumped_symbols && has_dumped_symbols) {
@@ -454,14 +441,24 @@ TEST(record_cmd, dump_kernel_symbols) {
}
TemporaryFile tmpfile;
ASSERT_TRUE(RunRecordCmd({"-a", "-o", tmpfile.path, "sleep", "1"}));
+ std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
+ ASSERT_TRUE(reader != nullptr);
+ std::map<int, SectionDesc> section_map = reader->FeatureSectionDescriptors();
+ ASSERT_NE(section_map.find(FEAT_FILE), section_map.end());
+ 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;
+ size_t read_pos = 0;
bool has_kernel_symbols = false;
- auto callback = [&](const Symbol&, uint32_t file_type) {
- if (file_type == DSO_KERNEL) {
+ while (reader->ReadFileFeature(read_pos, &file_path, &file_type, &min_vaddr,
+ &file_offset_of_min_vaddr, &symbols, &dex_file_offsets)) {
+ if (file_type == DSO_KERNEL && !symbols.empty()) {
has_kernel_symbols = true;
}
- return has_kernel_symbols;
- };
- ProcessSymbolsInPerfDataFile(tmpfile.path, callback);
+ }
ASSERT_TRUE(has_kernel_symbols);
}
@@ -566,7 +563,8 @@ TEST(record_cmd, record_meta_info_feature) {
ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
ASSERT_TRUE(reader);
- auto& info_map = reader->GetMetaInfoFeature();
+ std::unordered_map<std::string, std::string> info_map;
+ ASSERT_TRUE(reader->ReadMetaInfoFeature(&info_map));
ASSERT_NE(info_map.find("simpleperf_version"), info_map.end());
ASSERT_NE(info_map.find("timestamp"), info_map.end());
#if defined(__ANDROID__)
@@ -604,7 +602,8 @@ TEST(record_cmd, trace_offcpu_option) {
ASSERT_TRUE(RunRecordCmd({"--trace-offcpu", "-f", "1000"}, tmpfile.path));
std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
ASSERT_TRUE(reader);
- auto info_map = reader->GetMetaInfoFeature();
+ std::unordered_map<std::string, std::string> info_map;
+ ASSERT_TRUE(reader->ReadMetaInfoFeature(&info_map));
ASSERT_EQ(info_map["trace_offcpu"], "true");
CheckEventType(tmpfile.path, "sched:sched_switch", 1u, 0u);
}
@@ -623,7 +622,8 @@ TEST(record_cmd, clockid_option) {
ASSERT_TRUE(RunRecordCmd({"--clockid", "monotonic"}, tmpfile.path));
std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
ASSERT_TRUE(reader);
- auto info_map = reader->GetMetaInfoFeature();
+ std::unordered_map<std::string, std::string> info_map;
+ ASSERT_TRUE(reader->ReadMetaInfoFeature(&info_map));
ASSERT_EQ(info_map["clockid"], "monotonic");
}
}
@@ -744,82 +744,47 @@ TEST(record_cmd, cpu_percent_option) {
ASSERT_FALSE(RunRecordCmd({"--cpu-percent", "101"}));
}
-class RecordingAppHelper {
- public:
- bool InstallApk(const std::string& apk_path, const std::string& package_name) {
- if (Workload::RunCmd({"pm", "install", "-t", "--abi", GetABI(), apk_path})) {
- installed_packages_.emplace_back(package_name);
- return true;
- }
- return false;
- }
-
- bool StartApp(const std::string& start_cmd) {
- app_start_proc_ = Workload::CreateWorkload(android::base::Split(start_cmd, " "));
- return app_start_proc_ && app_start_proc_->Start();
- }
-
- bool RecordData(const std::string& record_cmd) {
- std::vector<std::string> args = android::base::Split(record_cmd, " ");
- args.emplace_back("-o");
- args.emplace_back(perf_data_file_.path);
- return RecordCmd()->Run(args);
- }
-
- bool CheckData(const std::function<bool(const char*)>& process_symbol) {
- bool success = false;
- auto callback = [&](const Symbol& symbol, uint32_t) {
- if (process_symbol(symbol.DemangledName())) {
- success = true;
- }
- return success;
- };
- ProcessSymbolsInPerfDataFile(perf_data_file_.path, callback);
- return success;
- }
-
- ~RecordingAppHelper() {
- for (auto& package : installed_packages_) {
- Workload::RunCmd({"pm", "uninstall", package});
- }
- }
-
- private:
- const char* GetABI() {
-#if defined(__i386__)
- return "x86";
-#elif defined(__x86_64__)
- return "x86_64";
-#elif defined(__aarch64__)
- return "arm64-v8a";
-#elif defined(__arm__)
- return "armeabi-v7a";
-#else
- #error "unrecognized ABI"
-#endif
- }
-
- std::vector<std::string> installed_packages_;
- std::unique_ptr<Workload> app_start_proc_;
- TemporaryFile perf_data_file_;
-};
-
static void TestRecordingApps(const std::string& app_name) {
- 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"));
+ ASSERT_TRUE(Workload::RunCmd({"am", "start", app_name + "/.MainActivity"}));
+ TemporaryFile tmpfile;
+ ASSERT_TRUE(RecordCmd()->Run({"-o", tmpfile.path, "--app", app_name, "-g", "--duration", "3"}));
+ std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
+ ASSERT_TRUE(reader);
+ // Check if having samples.
+ bool has_sample = false;
+ ASSERT_TRUE(reader->ReadDataSection([&](std::unique_ptr<Record> r) {
+ if (r->type() == PERF_RECORD_SAMPLE) {
+ has_sample = true;
+ }
+ return true;
+ }));
+ ASSERT_TRUE(has_sample);
// Check if we can profile Java code by looking for a Java method name in dumped symbols, which
// is app_name + ".MainActivity$1.run".
const std::string expected_class_name = app_name + ".MainActivity";
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;
- };
- ASSERT_TRUE(helper.CheckData(process_symbol));
+ 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;
+ size_t read_pos = 0;
+ bool has_java_symbol = false;
+ ASSERT_TRUE(reader->HasFeature(FEAT_FILE));
+ 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) {
+ const char* name = symbol.DemangledName();
+ if (strstr(name, expected_class_name.c_str()) != nullptr &&
+ strstr(name, expected_method_name.c_str()) != nullptr) {
+ has_java_symbol = true;
+ }
+ }
+ }
+ ASSERT_TRUE(has_java_symbol);
}
TEST(record_cmd, app_option_for_debuggable_app) {
@@ -833,147 +798,3 @@ TEST(record_cmd, app_option_for_profileable_app) {
TEST_REQUIRE_APPS();
TestRecordingApps("com.android.simpleperf.profileable");
}
-
-TEST(record_cmd, record_java_app) {
- RecordingAppHelper helper;
- // 1. Install apk.
- ASSERT_TRUE(helper.InstallApk(GetTestData("DisplayBitmaps.apk"),
- "com.example.android.displayingbitmaps"));
- ASSERT_TRUE(helper.InstallApk(GetTestData("DisplayBitmapsTest.apk"),
- "com.example.android.displayingbitmaps.test"));
-
- // 2. Start the app.
- ASSERT_TRUE(
- helper.StartApp("am instrument -w -r -e debug false -e class "
- "com.example.android.displayingbitmaps.tests.GridViewTest "
- "com.example.android.displayingbitmaps.test/"
- "androidx.test.runner.AndroidJUnitRunner"));
-
- // 3. Record perf.data.
- ASSERT_TRUE(helper.RecordData(
- "-e cpu-clock --app com.example.android.displayingbitmaps -g --duration 10"));
-
- // 4. Check perf.data.
- auto process_symbol = [&](const char* name) {
-#if !defined(IN_CTS_TEST)
- const char* expected_name_with_keyguard = "androidx.test.runner"; // when screen is locked
- if (strstr(name, expected_name_with_keyguard) != nullptr) {
- return true;
- }
-#endif
- const char* expected_name = "androidx.test.espresso"; // when screen stays awake
- return strstr(name, expected_name) != nullptr;
- };
- ASSERT_TRUE(helper.CheckData(process_symbol));
-}
-
-TEST(record_cmd, record_native_app) {
- RecordingAppHelper helper;
- // 1. Install apk.
- ASSERT_TRUE(helper.InstallApk(GetTestData("EndlessTunnel.apk"), "com.google.sample.tunnel"));
-
- // 2. Start the app.
- ASSERT_TRUE(
- helper.StartApp("am start -n com.google.sample.tunnel/android.app.NativeActivity -a "
- "android.intent.action.MAIN -c android.intent.category.LAUNCHER"));
-
- // 3. Record perf.data.
- ASSERT_TRUE(helper.RecordData("-e cpu-clock --app com.google.sample.tunnel -g --duration 10"));
-
- // 4. Check perf.data.
- auto process_symbol = [&](const char* name) {
-#if !defined(IN_CTS_TEST)
- const char* expected_name_with_keyguard = "NativeActivity"; // when screen is locked
- if (strstr(name, expected_name_with_keyguard) != nullptr) {
- return true;
- }
-#endif
- const char* expected_name = "PlayScene::DoFrame"; // when screen is awake
- return strstr(name, expected_name) != nullptr;
- };
- ASSERT_TRUE(helper.CheckData(process_symbol));
-}
-
-TEST(record_cmd, no_cut_samples_option) {
- TEST_REQUIRE_HW_COUNTER();
- ASSERT_TRUE(RunRecordCmd({"--no-cut-samples"}));
-}
-
-TEST(record_cmd, cs_etm_event) {
- if (!ETMRecorder::GetInstance().CheckEtmSupport()) {
- GTEST_LOG_(INFO) << "Omit this test since etm isn't supported on this device";
- return;
- }
- TemporaryFile tmpfile;
- ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm"}, tmpfile.path));
- std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
- ASSERT_TRUE(reader);
-
- // cs-etm uses sample period instead of sample freq.
- ASSERT_EQ(reader->AttrSection().size(), 1u);
- const perf_event_attr* attr = reader->AttrSection()[0].attr;
- ASSERT_EQ(attr->freq, 0);
- ASSERT_EQ(attr->sample_period, 1);
-
- bool has_auxtrace_info = false;
- bool has_auxtrace = false;
- bool has_aux = false;
- ASSERT_TRUE(reader->ReadDataSection([&](std::unique_ptr<Record> r) {
- if (r->type() == PERF_RECORD_AUXTRACE_INFO) {
- has_auxtrace_info = true;
- } else if (r->type() == PERF_RECORD_AUXTRACE) {
- has_auxtrace = true;
- } else if (r->type() == PERF_RECORD_AUX) {
- has_aux = true;
- }
- return true;
- }));
- ASSERT_TRUE(has_auxtrace_info);
- ASSERT_TRUE(has_auxtrace);
- ASSERT_TRUE(has_aux);
-}
-
-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";
- return;
- }
- ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--aux-buffer-size", "1m"}));
- // not page size aligned
- ASSERT_FALSE(RunRecordCmd({"-e", "cs-etm", "--aux-buffer-size", "1024"}));
- // not power of two
- ASSERT_FALSE(RunRecordCmd({"-e", "cs-etm", "--aux-buffer-size", "12k"}));
-}
-
-TEST(record_cmd, include_filter_option) {
- TEST_REQUIRE_HW_COUNTER();
- if (!ETMRecorder::GetInstance().CheckEtmSupport()) {
- GTEST_LOG_(INFO) << "Omit this test since etm isn't supported on this device";
- return;
- }
- FILE* fp = popen("which sleep", "r");
- ASSERT_TRUE(fp != nullptr);
- std::string path;
- ASSERT_TRUE(android::base::ReadFdToString(fileno(fp), &path));
- 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}));
- TemporaryFile record_file;
- ASSERT_TRUE(
- RunRecordCmd({"-e", "cs-etm", "--include-filter", sleep_exec_path}, record_file.path));
- TemporaryFile inject_file;
- ASSERT_TRUE(
- CreateCommandInstance("inject")->Run({"-i", record_file.path, "-o", inject_file.path}));
- std::string data;
- ASSERT_TRUE(android::base::ReadFileToString(inject_file.path, &data));
- // Only instructions in sleep_exec_path are traced.
- for (auto& line : android::base::Split(data, "\n")) {
- if (android::base::StartsWith(line, "dso ")) {
- std::string dso = line.substr(strlen("dso "), sleep_exec_path.size());
- ASSERT_EQ(dso, sleep_exec_path);
- }
- }
-} \ No newline at end of file
diff --git a/simpleperf/cmd_report.cpp b/simpleperf/cmd_report.cpp
index 8e67f5e0..e6f5dabb 100644
--- a/simpleperf/cmd_report.cpp
+++ b/simpleperf/cmd_report.cpp
@@ -62,8 +62,7 @@ struct SampleEntry {
// accumuated when appearing in other sample's callchain
uint64_t accumulated_period;
uint64_t sample_count;
- pid_t pid;
- pid_t tid;
+ const ThreadEntry* thread;
const char* thread_comm;
const MapEntry* map;
const Symbol* symbol;
@@ -79,8 +78,7 @@ struct SampleEntry {
period(period),
accumulated_period(accumulated_period),
sample_count(sample_count),
- pid(thread->pid),
- tid(thread->tid),
+ thread(thread),
thread_comm(thread->comm),
map(map),
symbol(symbol),
@@ -185,10 +183,11 @@ class ReportCmdSampleTreeBuilder : public SampleTreeBuilder<SampleEntry, uint64_
return InsertSample(std::move(sample));
}
- SampleEntry* CreateCallChainSample(const ThreadEntry* thread, const SampleEntry* sample,
- uint64_t ip, bool in_kernel,
+ SampleEntry* CreateCallChainSample(const SampleEntry* sample, uint64_t ip,
+ bool in_kernel,
const std::vector<SampleEntry*>& callchain,
const uint64_t& acc_info) override {
+ const ThreadEntry* thread = sample->thread;
const MapEntry* map = thread_tree_->FindMap(thread, ip, in_kernel);
if (thread_tree_->IsUnknownDso(map->dso)) {
// The unwinders can give wrong ip addresses, which can't map to a valid dso. Skip them.
@@ -204,7 +203,7 @@ class ReportCmdSampleTreeBuilder : public SampleTreeBuilder<SampleEntry, uint64_
}
const ThreadEntry* GetThreadOfSample(SampleEntry* sample) override {
- return thread_tree_->FindThreadOrNew(sample->pid, sample->tid);
+ return sample->thread;
}
uint64_t GetPeriodForCallChain(const uint64_t& acc_info) override {
@@ -213,11 +212,11 @@ class ReportCmdSampleTreeBuilder : public SampleTreeBuilder<SampleEntry, uint64_
bool FilterSample(const SampleEntry* sample) override {
if (!pid_filter_.empty() &&
- pid_filter_.find(sample->pid) == pid_filter_.end()) {
+ pid_filter_.find(sample->thread->pid) == pid_filter_.end()) {
return false;
}
if (!tid_filter_.empty() &&
- tid_filter_.find(sample->tid) == tid_filter_.end()) {
+ tid_filter_.find(sample->thread->tid) == tid_filter_.end()) {
return false;
}
if (!comm_filter_.empty() &&
@@ -432,7 +431,7 @@ class ReportCommand : public Command {
private:
bool ParseOptions(const std::vector<std::string>& args);
- void ReadMetaInfoFromRecordFile();
+ bool ReadMetaInfoFromRecordFile();
bool ReadEventAttrFromRecordFile();
bool ReadFeaturesFromRecordFile();
bool ReadSampleTreeFromRecordFile();
@@ -468,6 +467,8 @@ class ReportCommand : public Command {
size_t sched_switch_attr_id_;
std::string report_filename_;
+ std::unordered_map<std::string, std::string> meta_info_;
+ std::unique_ptr<ScopedEventTypes> scoped_event_types_;
};
bool ReportCommand::Run(const std::vector<std::string>& args) {
@@ -481,7 +482,9 @@ bool ReportCommand::Run(const std::vector<std::string>& args) {
if (record_file_reader_ == nullptr) {
return false;
}
- ReadMetaInfoFromRecordFile();
+ if (!ReadMetaInfoFromRecordFile()) {
+ return false;
+ }
if (!ReadEventAttrFromRecordFile()) {
return false;
}
@@ -731,14 +734,25 @@ bool ReportCommand::ParseOptions(const std::vector<std::string>& args) {
return true;
}
-void ReportCommand::ReadMetaInfoFromRecordFile() {
- auto& meta_info = record_file_reader_->GetMetaInfoFeature();
- if (auto it = meta_info.find("system_wide_collection"); it != meta_info.end()) {
- system_wide_collection_ = it->second == "true";
- }
- if (auto it = meta_info.find("trace_offcpu"); it != meta_info.end()) {
- trace_offcpu_ = it->second == "true";
+bool ReportCommand::ReadMetaInfoFromRecordFile() {
+ if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_META_INFO)) {
+ if (!record_file_reader_->ReadMetaInfoFeature(&meta_info_)) {
+ return false;
+ }
+ auto it = meta_info_.find("system_wide_collection");
+ if (it != meta_info_.end()) {
+ system_wide_collection_ = it->second == "true";
+ }
+ it = meta_info_.find("trace_offcpu");
+ if (it != meta_info_.end()) {
+ trace_offcpu_ = it->second == "true";
+ }
+ it = meta_info_.find("event_type_info");
+ if (it != meta_info_.end()) {
+ scoped_event_types_.reset(new ScopedEventTypes(it->second));
+ }
}
+ return true;
}
bool ReportCommand::ReadEventAttrFromRecordFile() {
@@ -791,7 +805,7 @@ bool ReportCommand::ReadFeaturesFromRecordFile() {
std::vector<std::string> cmdline = record_file_reader_->ReadCmdlineFeature();
if (!cmdline.empty()) {
record_cmdline_ = android::base::Join(cmdline, ' ');
- if (record_file_reader_->GetMetaInfoFeature().count("system_wide_collection")) {
+ if (meta_info_.find("system_wide_collection") == meta_info_.end()) {
// TODO: the code to detect system wide collection option is fragile, remove
// it once we can do cross unwinding.
for (size_t i = 0; i < cmdline.size(); i++) {
diff --git a/simpleperf/cmd_report_sample.cpp b/simpleperf/cmd_report_sample.cpp
index e133fb28..d4b280cf 100644
--- a/simpleperf/cmd_report_sample.cpp
+++ b/simpleperf/cmd_report_sample.cpp
@@ -16,7 +16,6 @@
#include <inttypes.h>
-#include <limits>
#include <memory>
#include <android-base/strings.h>
@@ -113,7 +112,6 @@ class ReportSampleCommand : public Command {
bool OpenRecordFile();
bool PrintMetaInfo();
bool ProcessRecord(std::unique_ptr<Record> record);
- void UpdateThreadName(uint32_t pid, uint32_t tid);
bool ProcessSampleRecord(const SampleRecord& r);
bool PrintSampleRecordInProtobuf(const SampleRecord& record,
const std::vector<CallEntry>& entries);
@@ -138,12 +136,12 @@ class ReportSampleCommand : public Command {
size_t sample_count_;
size_t lost_count_;
bool trace_offcpu_;
+ std::unique_ptr<ScopedEventTypes> scoped_event_types_;
std::vector<std::string> event_types_;
+ std::unordered_map<std::string, std::string> meta_info_;
bool remove_unknown_kernel_symbols_;
bool kernel_symbols_available_;
bool show_art_frames_;
- // map from <pid, tid> to thread name
- std::map<uint64_t, const char*> thread_names_;
};
bool ReportSampleCommand::Run(const std::vector<std::string>& args) {
@@ -423,12 +421,22 @@ bool ReportSampleCommand::OpenRecordFile() {
return false;
}
record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
- auto& meta_info = record_file_reader_->GetMetaInfoFeature();
- if (auto it = meta_info.find("trace_offcpu"); it != meta_info.end()) {
- trace_offcpu_ = it->second == "true";
- }
- if (auto it = meta_info.find("kernel_symbols_available"); it != meta_info.end()) {
- kernel_symbols_available_ = it->second == "true";
+ if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_META_INFO)) {
+ if (!record_file_reader_->ReadMetaInfoFeature(&meta_info_)) {
+ return false;
+ }
+ auto it = meta_info_.find("event_type_info");
+ if (it != meta_info_.end()) {
+ scoped_event_types_.reset(new ScopedEventTypes(it->second));
+ }
+ it = meta_info_.find("trace_offcpu");
+ if (it != meta_info_.end()) {
+ trace_offcpu_ = it->second == "true";
+ }
+ it = meta_info_.find("kernel_symbols_available");
+ if (it != meta_info_.end()) {
+ kernel_symbols_available_ = it->second == "true";
+ }
}
for (EventAttrWithId& attr : record_file_reader_->AttrSection()) {
event_types_.push_back(GetEventNameByAttr(*attr.attr));
@@ -437,9 +445,8 @@ bool ReportSampleCommand::OpenRecordFile() {
}
bool ReportSampleCommand::PrintMetaInfo() {
- auto& meta_info = record_file_reader_->GetMetaInfoFeature();
- auto it = meta_info.find("app_package_name");
- std::string app_package_name = it != meta_info.end() ? it->second : "";
+ auto it = meta_info_.find("app_package_name");
+ std::string app_package_name = it != meta_info_.end() ? it->second : "";
if (use_protobuf_) {
proto::Record proto_record;
proto::MetaInfo* meta_info = proto_record.mutable_meta_info();
@@ -518,8 +525,6 @@ bool ReportSampleCommand::ProcessSampleRecord(const SampleRecord& r) {
entries.push_back(entry);
}
if (use_protobuf_) {
- uint64_t key = (static_cast<uint64_t>(r.tid_data.pid) << 32) | r.tid_data.tid;
- thread_names_[key] = thread->comm;
return PrintSampleRecordInProtobuf(r, entries);
}
return PrintSampleRecord(r, entries);
@@ -641,14 +646,17 @@ bool ReportSampleCommand::PrintFileInfoInProtobuf() {
}
bool ReportSampleCommand::PrintThreadInfoInProtobuf() {
- for (const auto& p : thread_names_) {
- uint32_t pid = p.first >> 32;
- uint32_t tid = p.first & std::numeric_limits<uint32_t>::max();
+ std::vector<const ThreadEntry*> threads = thread_tree_.GetAllThreads();
+ auto compare_thread_id = [](const ThreadEntry* t1, const ThreadEntry* t2) {
+ return t1->tid < t2->tid;
+ };
+ std::sort(threads.begin(), threads.end(), compare_thread_id);
+ for (auto& thread : threads) {
proto::Record proto_record;
proto::Thread* proto_thread = proto_record.mutable_thread();
- proto_thread->set_thread_id(tid);
- proto_thread->set_process_id(pid);
- proto_thread->set_thread_name(p.second);
+ proto_thread->set_thread_id(thread->tid);
+ proto_thread->set_process_id(thread->pid);
+ proto_thread->set_thread_name(thread->comm);
if (!WriteRecordInProtobuf(proto_record)) {
return false;
}
diff --git a/simpleperf/cmd_report_sample_test.cpp b/simpleperf/cmd_report_sample_test.cpp
index 9543634d..34ba3f72 100644
--- a/simpleperf/cmd_report_sample_test.cpp
+++ b/simpleperf/cmd_report_sample_test.cpp
@@ -80,7 +80,6 @@ TEST(cmd_report_sample, has_thread_record) {
std::string data;
GetProtobufReport(PERF_DATA_WITH_SYMBOLS, &data);
ASSERT_NE(data.find("thread:"), std::string::npos);
- ASSERT_NE(data.find("thread_name: t2"), std::string::npos);
}
TEST(cmd_report_sample, trace_offcpu) {
diff --git a/simpleperf/cmd_stat.cpp b/simpleperf/cmd_stat.cpp
index 06f45187..31ab2a39 100644
--- a/simpleperf/cmd_stat.cpp
+++ b/simpleperf/cmd_stat.cpp
@@ -24,7 +24,6 @@
#include <chrono>
#include <set>
#include <string>
-#include <string_view>
#include <vector>
#include <android-base/file.h>
@@ -94,16 +93,6 @@ struct CounterSummary {
return type_name + ":" + modifier;
}
- bool IsMonitoredAllTheTime() const {
- // If an event runs all the time it is enabled (by not sharing hardware
- // counters with other events), the scale of its summary is usually within
- // [1, 1 + 1e-5]. By setting SCALE_ERROR_LIMIT to 1e-5, We can identify
- // events monitored all the time in most cases while keeping the report
- // error rate <= 1e-5.
- constexpr double SCALE_ERROR_LIMIT = 1e-5;
- return (fabs(scale - 1.0) < SCALE_ERROR_LIMIT);
- }
-
private:
std::string ReadableCountValue(bool csv) {
if (type_name == "cpu-clock" || type_name == "task-clock") {
@@ -127,49 +116,24 @@ struct CounterSummary {
}
}
}
-};
-static const std::unordered_map<std::string_view, std::pair<std::string_view, std::string_view>>
- COMMON_EVENT_RATE_MAP = {
- {"cache-misses", {"cache-references", "miss rate"}},
- {"branch-misses", {"branch-instructions", "miss rate"}},
-};
-
-static const std::unordered_map<std::string_view, std::pair<std::string_view, std::string_view>>
- ARM_EVENT_RATE_MAP = {
- // Refer to "D6.10.5 Meaningful ratios between common microarchitectural events" in ARMv8
- // specification.
- {"raw-l1i-cache-refill", {"raw-l1i-cache", "level 1 instruction cache refill rate"}},
- {"raw-l1i-tlb-refill", {"raw-l1i-tlb", "level 1 instruction TLB refill rate"}},
- {"raw-l1d-cache-refill", {"raw-l1d-cache", "level 1 data or unified cache refill rate"}},
- {"raw-l1d-tlb-refill", {"raw-l1d-tlb", "level 1 data or unified TLB refill rate"}},
- {"raw-l2d-cache-refill", {"raw-l2d-cache", "level 2 data or unified cache refill rate"}},
- {"raw-l2i-cache-refill", {"raw-l2i-cache", "level 2 instruction cache refill rate"}},
- {"raw-l3d-cache-refill", {"raw-l3d-cache", "level 3 data or unified cache refill rate"}},
- {"raw-l2d-tlb-refill", {"raw-l2d-tlb", "level 2 data or unified TLB refill rate"}},
- {"raw-l2i-tlb-refill", {"raw-l2i-tlb", "level 2 instruction TLB refill rate"}},
- {"raw-bus-access", {"raw-bus-cycles", "bus accesses per cycle"}},
- {"raw-ll-cache-miss", {"raw-ll-cache", "last level data or unified cache refill rate"}},
- {"raw-dtlb-walk", {"raw-l1d-tlb", "data TLB miss rate"}},
- {"raw-itlb-walk", {"raw-l1i-tlb", "instruction TLB miss rate"}},
- {"raw-ll-cache-miss-rd", {"raw-ll-cache-rd", "memory read operation miss rate"}},
- {"raw-remote-access-rd",
- {"raw-remote-access", "read accesses to another socket in a multi-socket system"}},
- // Refer to "Table K3-2 Relationship between REFILL events and associated access events" in
- // ARMv8 specification.
- {"raw-l1d-cache-refill-rd", {"raw-l1d-cache-rd", "level 1 cache refill rate, read"}},
- {"raw-l1d-cache-refill-wr", {"raw-l1d-cache-wr", "level 1 cache refill rate, write"}},
- {"raw-l1d-tlb-refill-rd", {"raw-l1d-tlb-rd", "level 1 TLB refill rate, read"}},
- {"raw-l1d-tlb-refill-wr", {"raw-l1d-tlb-wr", "level 1 TLB refill rate, write"}},
- {"raw-l2d-cache-refill-rd", {"raw-l2d-cache-rd", "level 2 data cache refill rate, read"}},
- {"raw-l2d-cache-refill-wr", {"raw-l2d-cache-wr", "level 2 data cache refill rate, write"}},
- {"raw-l2d-tlb-refill-rd", {"raw-l2d-tlb-rd", "level 2 data TLB refill rate, read"}},
+ bool IsMonitoredAllTheTime() const {
+ // If an event runs all the time it is enabled (by not sharing hardware
+ // counters with other events), the scale of its summary is usually within
+ // [1, 1 + 1e-5]. By setting SCALE_ERROR_LIMIT to 1e-5, We can identify
+ // events monitored all the time in most cases while keeping the report
+ // error rate <= 1e-5.
+ constexpr double SCALE_ERROR_LIMIT = 1e-5;
+ return (fabs(scale - 1.0) < SCALE_ERROR_LIMIT);
+ }
};
class CounterSummaries {
public:
explicit CounterSummaries(bool csv) : csv_(csv) {}
- std::vector<CounterSummary>& Summaries() { return summaries_; }
+ void AddSummary(const CounterSummary& summary) {
+ summaries_.push_back(summary);
+ }
const CounterSummary* FindSummary(const std::string& type_name,
const std::string& modifier) {
@@ -193,8 +157,9 @@ class CounterSummaries {
const CounterSummary* other = FindSummary(s.type_name, "k");
if (other != nullptr && other->IsMonitoredAtTheSameTime(s)) {
if (FindSummary(s.type_name, "") == nullptr) {
- Summaries().emplace_back(s.type_name, "", s.group_id, s.count + other->count, s.scale,
- true, csv_);
+ AddSummary(CounterSummary(s.type_name, "", s.group_id,
+ s.count + other->count, s.scale, true,
+ csv_));
}
}
}
@@ -266,9 +231,31 @@ class CounterSummaries {
sap_mid);
}
}
- std::string rate_comment = GetRateComment(s, sap_mid);
- if (!rate_comment.empty()) {
- return rate_comment;
+ if (android::base::EndsWith(s.type_name, "-misses")) {
+ std::string other_name;
+ if (s.type_name == "cache-misses") {
+ other_name = "cache-references";
+ } else if (s.type_name == "branch-misses") {
+ other_name = "branch-instructions";
+ } else {
+ other_name =
+ s.type_name.substr(0, s.type_name.size() - strlen("-misses")) + "s";
+ }
+ const CounterSummary* other = FindSummary(other_name, s.modifier);
+ if (other != nullptr && other->IsMonitoredAtTheSameTime(s) &&
+ other->count != 0) {
+ double miss_rate = static_cast<double>(s.count) / other->count;
+ return android::base::StringPrintf("%lf%%%cmiss rate", miss_rate * 100,
+ sap_mid);
+ }
+ }
+ if (android::base::EndsWith(s.type_name, "-refill")) {
+ std::string other_name = s.type_name.substr(0, s.type_name.size() - strlen("-refill"));
+ const CounterSummary* other = FindSummary(other_name, s.modifier);
+ if (other != nullptr && other->IsMonitoredAtTheSameTime(s) && other->count != 0) {
+ double miss_rate = static_cast<double>(s.count) / other->count;
+ return android::base::StringPrintf("%f%%%cmiss rate", miss_rate * 100, sap_mid);
+ }
}
double running_time_in_sec;
if (!FindRunningTimeForSummary(s, &running_time_in_sec)) {
@@ -287,34 +274,6 @@ class CounterSummaries {
return android::base::StringPrintf("%.3lf%c/sec", rate, sap_mid);
}
- std::string GetRateComment(const CounterSummary& s, char sep) {
- std::string_view miss_event_name = s.type_name;
- std::string event_name;
- std::string rate_desc;
- if (auto it = COMMON_EVENT_RATE_MAP.find(miss_event_name); it != COMMON_EVENT_RATE_MAP.end()) {
- event_name = it->second.first;
- rate_desc = it->second.second;
- }
- if (event_name.empty() && (GetBuildArch() == ARCH_ARM || GetBuildArch() == ARCH_ARM64)) {
- if (auto it = ARM_EVENT_RATE_MAP.find(miss_event_name); it != ARM_EVENT_RATE_MAP.end()) {
- event_name = it->second.first;
- rate_desc = it->second.second;
- }
- }
- if (event_name.empty() && android::base::ConsumeSuffix(&miss_event_name, "-misses")) {
- event_name = std::string(miss_event_name) + "s";
- rate_desc = "miss rate";
- }
- if (!event_name.empty()) {
- const CounterSummary* other = FindSummary(event_name, s.modifier);
- if (other != nullptr && other->IsMonitoredAtTheSameTime(s) && other->count != 0) {
- double miss_rate = static_cast<double>(s.count) / other->count;
- return android::base::StringPrintf("%f%%%c%s", miss_rate * 100, sep, rate_desc.c_str());
- }
- }
- return "";
- }
-
bool FindRunningTimeForSummary(const CounterSummary& summary, double* running_time_in_sec) {
for (auto& s : summaries_) {
if ((s.type_name == "task-clock" || s.type_name == "cpu-clock") &&
@@ -331,48 +290,6 @@ class CounterSummaries {
bool csv_;
};
-// devfreq may use performance counters to calculate memory latency (as in
-// drivers/devfreq/arm-memlat-mon.c). Hopefully we can get more available counters by asking devfreq
-// to not use the memory latency governor temporarily.
-class DevfreqCounters {
- public:
- bool Use() {
- if (!IsRoot()) {
- LOG(ERROR) << "--use-devfreq-counters needs root permission to set devfreq governors";
- return false;
- }
- std::string devfreq_dir = "/sys/class/devfreq/";
- for (auto& name : GetSubDirs(devfreq_dir)) {
- std::string governor_path = devfreq_dir + name + "/governor";
- if (IsRegularFile(governor_path)) {
- std::string governor;
- if (!android::base::ReadFileToString(governor_path, &governor)) {
- LOG(ERROR) << "failed to read " << governor_path;
- return false;
- }
- governor = android::base::Trim(governor);
- if (governor == "mem_latency") {
- if (!android::base::WriteStringToFile("performance", governor_path)) {
- PLOG(ERROR) << "failed to write " << governor_path;
- return false;
- }
- mem_latency_governor_paths_.emplace_back(std::move(governor_path));
- }
- }
- }
- return true;
- }
-
- ~DevfreqCounters() {
- for (auto& path : mem_latency_governor_paths_) {
- android::base::WriteStringToFile("mem_latency", path);
- }
- }
-
- private:
- std::vector<std::string> mem_latency_governor_paths_;
-};
-
class StatCommand : public Command {
public:
StatCommand()
@@ -417,14 +334,6 @@ class StatCommand : public Command {
"-o output_filename Write report to output_filename instead of standard output.\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"
-#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"
-" making fewer counters available to users. This option asks devfreq\n"
-" to temporarily release counters by replacing memory-latency governor\n"
-" with performance governor. It affects memory latency during profiling,\n"
-" and may cause wedged power if simpleperf is killed in between.\n"
-#endif
"--verbose Show result in verbose mode.\n"
#if 0
// Below options are only used internally and shouldn't be visible to the public.
@@ -473,7 +382,6 @@ class StatCommand : public Command {
std::string app_package_name_;
bool in_app_context_;
android::base::unique_fd stop_signal_fd_;
- bool use_devfreq_counters_ = false;
};
bool StatCommand::Run(const std::vector<std::string>& args) {
@@ -492,12 +400,6 @@ bool StatCommand::Run(const std::vector<std::string>& args) {
output_filename_, !event_selection_set_.GetTracepointEvents().empty());
}
}
- DevfreqCounters devfreq_counters;
- if (use_devfreq_counters_) {
- if (!devfreq_counters.Use()) {
- return false;
- }
- }
if (event_selection_set_.empty()) {
if (!AddDefaultMeasuredEventTypes()) {
return false;
@@ -719,10 +621,6 @@ bool StatCommand::ParseOptions(const std::vector<std::string>& args,
if (!SetTracepointEventsFilePath(args[i])) {
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;
} else {
@@ -802,7 +700,6 @@ bool StatCommand::ShowCounters(const std::vector<CountersInfo>& counters,
}
}
- bool counters_always_available = true;
CounterSummaries summaries(csv_);
for (size_t i = 0; i < counters.size(); ++i) {
const CountersInfo& counters_info = counters[i];
@@ -827,9 +724,9 @@ bool StatCommand::ShowCounters(const std::vector<CountersInfo>& counters,
if (sum.time_running < sum.time_enabled && sum.time_running != 0) {
scale = static_cast<double>(sum.time_enabled) / sum.time_running;
}
- summaries.Summaries().emplace_back(counters_info.event_name, counters_info.event_modifier,
- counters_info.group_id, sum.value, scale, false, csv_);
- counters_always_available &= summaries.Summaries().back().IsMonitoredAllTheTime();
+ summaries.AddSummary(
+ CounterSummary(counters_info.event_name, counters_info.event_modifier,
+ counters_info.group_id, sum.value, scale, false, csv_));
}
summaries.AutoGenerateSummaries();
summaries.GenerateComments(duration_in_sec);
@@ -839,11 +736,6 @@ bool StatCommand::ShowCounters(const std::vector<CountersInfo>& counters,
fprintf(fp, "Total test time,%lf,seconds,\n", duration_in_sec);
else
fprintf(fp, "\nTotal test time: %lf seconds.\n", duration_in_sec);
-
- if (!counters_always_available) {
- LOG(WARNING) << "Some hardware counters are not always available (scale < 100%). "
- << "Try --use-devfreq-counters if on a rooted device.";
- }
return true;
}
diff --git a/simpleperf/cmd_stat_test.cpp b/simpleperf/cmd_stat_test.cpp
index 3668cb91..ecc7404e 100644
--- a/simpleperf/cmd_stat_test.cpp
+++ b/simpleperf/cmd_stat_test.cpp
@@ -303,11 +303,3 @@ TEST(stat_cmd, app_option_for_profileable_app) {
TEST_REQUIRE_APPS();
TestStatingApps("com.android.simpleperf.profileable");
}
-
-TEST(stat_cmd, use_devfreq_counters_option) {
-#if defined(__ANDROID__)
- TEST_IN_ROOT(StatCmd()->Run({"--use-devfreq-counters", "sleep", "0.1"}));
-#else
- GTEST_LOG_(INFO) << "This test tests an option only available on Android.";
-#endif
-}
diff --git a/simpleperf/cmd_trace_sched.cpp b/simpleperf/cmd_trace_sched.cpp
index a5961ec9..34c6318f 100644
--- a/simpleperf/cmd_trace_sched.cpp
+++ b/simpleperf/cmd_trace_sched.cpp
@@ -187,6 +187,17 @@ bool TraceSchedCommand::ParseSchedEvents(const std::string& record_file_path) {
if (!reader) {
return false;
}
+ std::unique_ptr<ScopedEventTypes> scoped_event_types;
+ if (reader->HasFeature(PerfFileFormat::FEAT_META_INFO)) {
+ std::unordered_map<std::string, std::string> meta_info;
+ if (!reader->ReadMetaInfoFeature(&meta_info)) {
+ return false;
+ }
+ auto it = meta_info.find("event_type_info");
+ if (it != meta_info.end()) {
+ scoped_event_types.reset(new ScopedEventTypes(it->second));
+ }
+ }
const EventType* event = FindEventTypeByName("sched:sched_stat_runtime");
std::vector<EventAttrWithId> attrs = reader->AttrSection();
if (attrs.size() != 1u || attrs[0].attr->type != event->type ||
diff --git a/simpleperf/command.cpp b/simpleperf/command.cpp
index e937e19b..4c83540c 100644
--- a/simpleperf/command.cpp
+++ b/simpleperf/command.cpp
@@ -24,6 +24,7 @@
#include <android-base/logging.h>
#include <android-base/parsedouble.h>
#include <android-base/parseint.h>
+#include <android-base/quick_exit.h>
#include "utils.h"
@@ -87,7 +88,6 @@ const std::vector<std::string> GetAllCommandNames() {
extern void RegisterDumpRecordCommand();
extern void RegisterHelpCommand();
-extern void RegisterInjectCommand();
extern void RegisterListCommand();
extern void RegisterKmemCommand();
extern void RegisterRecordCommand();
@@ -103,7 +103,6 @@ class CommandRegister {
CommandRegister() {
RegisterDumpRecordCommand();
RegisterHelpCommand();
- RegisterInjectCommand();
RegisterKmemCommand();
RegisterReportCommand();
RegisterReportSampleCommand();
@@ -176,9 +175,9 @@ bool RunSimpleperfCmd(int argc, char** argv) {
bool result = command->Run(args);
LOG(DEBUG) << "command '" << command_name << "' "
<< (result ? "finished successfully" : "failed");
- // Quick exit to avoid the cost of freeing memory and closing files.
+ // Quick exit to avoid cost freeing memory and closing files.
fflush(stdout);
fflush(stderr);
- _Exit(result ? 0 : 1);
+ android::base::quick_exit(result ? 0 : 1);
return result;
}
diff --git a/simpleperf/demo/CppApi/app/src/main/cpp/native-lib.cpp b/simpleperf/demo/CppApi/app/src/main/cpp/native-lib.cpp
index bdf5f468..a47e73b6 100644
--- a/simpleperf/demo/CppApi/app/src/main/cpp/native-lib.cpp
+++ b/simpleperf/demo/CppApi/app/src/main/cpp/native-lib.cpp
@@ -79,18 +79,14 @@ extern "C" JNIEXPORT void JNICALL
Java_simpleperf_demo_cpp_1api_MainActivity_runNativeCode(
JNIEnv *env,
jobject jobj) {
- static bool threadsStarted = false;
- if (!threadsStarted) {
- pthread_t profile_thread;
- if (pthread_create(&profile_thread, nullptr, ProfileThreadFunc, nullptr) != 0) {
- log("failed to create profile thread");
- return;
- }
- pthread_t busy_thread;
- if (pthread_create(&busy_thread, nullptr, BusyThreadFunc, nullptr) != 0) {
- log("failed to create busy thread");
- }
- threadsStarted = true;
+ pthread_t profile_thread;
+ if (pthread_create(&profile_thread, nullptr, ProfileThreadFunc, nullptr) != 0) {
+ log("failed to create profile thread");
+ return;
+ }
+ pthread_t busy_thread;
+ if (pthread_create(&busy_thread, nullptr, BusyThreadFunc, nullptr) != 0) {
+ log("failed to create busy thread");
}
}
diff --git a/simpleperf/demo/JavaApi/app/src/main/java/simpleperf/demo/java_api/MainActivity.java b/simpleperf/demo/JavaApi/app/src/main/java/simpleperf/demo/java_api/MainActivity.java
index 9cb0c4ad..2cb96f03 100644
--- a/simpleperf/demo/JavaApi/app/src/main/java/simpleperf/demo/java_api/MainActivity.java
+++ b/simpleperf/demo/JavaApi/app/src/main/java/simpleperf/demo/java_api/MainActivity.java
@@ -28,8 +28,6 @@ public class MainActivity extends AppCompatActivity {
TextView textView;
- static boolean threadsStarted = false;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -37,11 +35,8 @@ public class MainActivity extends AppCompatActivity {
textView = (TextView) findViewById(R.id.textView);
- if (!threadsStarted) {
- threadsStarted = true;
- Thread profileThread = createProfileThread();
- createBusyThread(profileThread);
- }
+ Thread profileThread = createProfileThread();
+ createBusyThread(profileThread);
}
Thread createProfileThread() {
diff --git a/simpleperf/doc/README.md b/simpleperf/doc/README.md
index eeaeead2..244d1045 100644
--- a/simpleperf/doc/README.md
+++ b/simpleperf/doc/README.md
@@ -1,12 +1,11 @@
# Simpleperf
-Simpleperf is a native CPU profiling tool for Android. It can be used to profile
+Simpleperf is a native profiling tool for Android. It can be used to profile
both Android applications and native processes running on Android. It can
-profile both Java and C++ code on Android. The simpleperf executable can run on Android >=L,
-and Python scripts can be used on Android >= N.
+profile both Java and C++ code on Android. It can be used on Android L
+and above.
-Simpleperf is part of the Android Open Source Project.
-The source code is [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/).
+Simpleperf is part of the Android Open Source Project. The source code is [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/).
The latest document is [here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/README.md).
## Table of Contents
@@ -14,16 +13,57 @@ The latest document is [here](https://android.googlesource.com/platform/system/e
- [Introduction](#introduction)
- [Tools in simpleperf](#tools-in-simpleperf)
- [Android application profiling](#android-application-profiling)
+ - [Prepare an Android application](#prepare-an-android-application)
+ - [Record and report profiling data](#record-and-report-profiling-data)
+ - [Record and report call graph](#record-and-report-call-graph)
+ - [Report in html interface](#report-in-html-interface)
+ - [Show flame graph](#show-flame-graph)
+ - [Record both on CPU time and off CPU time](#record-both-on-cpu-time-and-off-cpu-time)
+ - [Profile from launch](#profile-from-launch)
+ - [Parse profiling data manually](#parse-profiling-data-manually)
- [Android platform profiling](#android-platform-profiling)
- [Executable commands reference](#executable-commands-reference)
+ - [How simpleperf works](#how-simpleperf-works)
+ - [Commands](#commands)
+ - [The list command](#the-list-command)
+ - [The stat command](#the-stat-command)
+ - [Select events to stat](#select-events-to-stat)
+ - [Select target to stat](#select-target-to-stat)
+ - [Decide how long to stat](#decide-how-long-to-stat)
+ - [Decide the print interval](#decide-the-print-interval)
+ - [Display counters in systrace](#display-counters-in-systrace)
+ - [The record command](#the-record-command)
+ - [Select events to record](#select-events-to-record)
+ - [Select target to record](#select-target-to-record)
+ - [Set the frequency to record](#set-the-frequency-to-record)
+ - [Decide how long to record](#decide-how-long-to-record)
+ - [Set the path to store profiling data](#set-the-path-to-store-profiling-data)
+ - [Record call graphs](#record-call-graphs-in-record-cmd)
+ - [Record both on CPU time and off CPU time](#record-both-on-cpu-time-and-off-cpu-time-in-record-cmd)
+ - [The report command](#the-report-command)
+ - [Set the path to read profiling data](#set-the-path-to-read-profiling-data)
+ - [Set the path to find binaries](#set-the-path-to-find-binaries)
+ - [Filter samples](#filter-samples)
+ - [Group samples into sample entries](#group-samples-into-sample-entries)
+ - [Report call graphs](#report-call-graphs-in-report-cmd)
- [Scripts reference](#scripts-reference)
+ - [app_profiler.py](#app_profiler-py)
+ - [Profile from launch of an application](#profile-from-launch-of-an-application)
+ - [run_simpleperf_without_usb_connection.py](#run_simpleperf_without_usb_connection-py)
+ - [binary_cache_builder.py](#binary_cache_builder-py)
+ - [run_simpleperf_on_device.py](#run_simpleperf_on_device-py)
+ - [report.py](#report-py)
+ - [report_html.py](#report_html-py)
+ - [inferno](#inferno)
+ - [pprof_proto_generator.py](#pprof_proto_generator-py)
+ - [report_sample.py](#report_sample-py)
+ - [simpleperf_report_lib.py](#simpleperf_report_lib-py)
- [Answers to common issues](#answers-to-common-issues)
- [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)
- [How to solve missing symbols in report](#how-to-solve-missing-symbols-in-report)
- [Bugs and contribution](#bugs-and-contribution)
-
## Introduction
Simpleperf contains two parts: the simpleperf executable and Python scripts.
@@ -70,7 +110,6 @@ Python scripts are split into three parts according to their functions:
Detailed documentation for the Python scripts is [here](#scripts-reference).
-
## Tools in simpleperf
The simpleperf executables and Python scripts are located in simpleperf/ in ndk releases, and in
@@ -84,28 +123,1114 @@ 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.
+[app_profiler.py](#app_profiler-py): recording profiling data.
+
+[run_simpleperf_without_usb_connection.py](#run_simpleperf_without_usb_connection-py):
+ recording profiling data while the USB cable isn't connected.
+
+[binary_cache_builder.py](#binary_cache_builder-py): building binary cache for profiling data.
+
+[report.py](#report-py): reporting in stdio interface.
+
+[report_html.py](#report_html-py): reporting in html interface.
+
+[inferno.sh](#inferno) (or inferno.bat on Windows): generating flamegraph in html interface.
+
+inferno/: implementation of inferno. Used by inferno.sh.
+
+[pprof_proto_generator.py](#pprof_proto_generator-py): converting profiling data to the format
+ used by [pprof](https://github.com/google/pprof).
+
+[report_sample.py](#report_sample-py): converting profiling data to the format used by [FlameGraph](https://github.com/brendangregg/FlameGraph).
+
+[simpleperf_report_lib.py](#simpleperf_report_lib-py): library for parsing profiling data.
## Android application profiling
-See [android_application_profiling.md](./android_application_profiling.md).
+This section shows how to profile an Android application.
+Some examples are [Here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/demo/README.md).
+
+Profiling an Android application involves three steps:
+1. Prepare an Android application.
+2. Record profiling data.
+3. Report profiling data.
+
+### Prepare an Android application
+
+Based on the profiling situation, we may need to customize the build script to generate an apk file
+specifically for profiling. Below are some suggestions.
+
+1. If you want to profile a debug build of an application:
+
+For the debug build type, Android studio sets android::debuggable="true" in AndroidManifest.xml,
+enables JNI checks and may not optimize C/C++ code. It can be profiled by simpleperf without any
+change.
+
+2. If you want to profile a release build of an application:
+
+For the release build type, Android studio sets android::debuggable="false" in AndroidManifest.xml,
+disables JNI checks and optimizes C/C++ code. However, security restrictions mean that only apps
+with android::debuggable set to true can be profiled. So simpleperf can only profile a release
+build under these two circumstances:
+If you are on a rooted device, you can profile any app.
+
+If you are on Android >= O, we can use [wrap.sh](#https://developer.android.com/ndk/guides/wrap-script.html)
+to profile a release build:
+Step 1: Add android::debuggable="true" in AndroidManifest.xml to enable profiling.
+```
+<manifest ...>
+ <application android::debuggable="true" ...>
+```
+
+Step 2: Add wrap.sh in lib/`arch` directories. wrap.sh runs the app without passing any debug flags
+to ART, so the app runs as a release app. wrap.sh can be done by adding the script below in
+app/build.gradle.
+```
+android {
+ buildTypes {
+ release {
+ sourceSets {
+ release {
+ resources {
+ srcDir {
+ "wrap_sh_lib_dir"
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+task createWrapShLibDir
+ for (String abi : ["armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64"]) {
+ def dir = new File("app/wrap_sh_lib_dir/lib/" + abi)
+ dir.mkdirs()
+ def wrapFile = new File(dir, "wrap.sh")
+ wrapFile.withWriter { writer ->
+ writer.write('#!/system/bin/sh\n\$@\n')
+ }
+ }
+}
+```
+
+3. If you want to profile C/C++ code:
+
+Android studio strips symbol table and debug info of native libraries in the apk. So the profiling
+results may contain unknown symbols or broken callgraphs. To fix this, we can pass app_profiler.py
+a directory containing unstripped native libraries via the -lib option. Usually the directory can
+be the path of your Android Studio project.
+
+
+4. If you want to profile Java code:
+
+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.
+
+On Android O, simpleperf supports profiling Java code which is compiled into native instructions,
+and it also needs wrap.sh to use the compiled Java code. To compile Java code, we can pass
+app_profiler.py the --compile_java_code option.
+
+On Android N, simpleperf supports profiling Java code that is compiled into native instructions.
+To compile java code, we can pass app_profiler.py the --compile_java_code option.
+
+On Android <= M, simpleperf doesn't support profiling Java code.
+
+
+Below I use application [SimpleperfExampleWithNative](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/demo/SimpleperfExampleWithNative).
+It builds an app-profiling.apk for profiling.
+
+```sh
+$ git clone https://android.googlesource.com/platform/system/extras
+$ cd extras/simpleperf/demo
+# Open SimpleperfExamplesWithNative project with Android studio, and build this project
+# successfully, otherwise the `./gradlew` command below will fail.
+$ cd SimpleperfExampleWithNative
+
+# On windows, use "gradlew" instead.
+$ ./gradlew clean assemble
+$ adb install -r app/build/outputs/apk/profiling/app-profiling.apk
+```
+
+### Record and report profiling data
+
+We can use [app-profiler.py](#app_profiler-py) to profile Android applications.
+
+```sh
+# Cd to the directory of simpleperf scripts. Record perf.data.
+# -p option selects the profiled app using its package name.
+# --compile_java_code option compiles Java code into native instructions, which isn't needed on
+# Android >= P.
+# -a option selects the Activity to profile.
+# -lib option gives the directory to find debug native libraries.
+$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative --compile_java_code \
+ -a .MixActivity -lib path_of_SimpleperfExampleWithNative
+```
+
+This will collect profiling data in perf.data in the current directory, and related native
+binaries in binary_cache/.
+
+Normally we need to use the app when profiling, otherwise we may record no samples. But in this
+case, the MixActivity starts a busy thread. So we don't need to use the app while profiling.
+
+```sh
+# Report perf.data in stdio interface.
+$ python report.py
+Cmdline: /data/data/com.example.simpleperf.simpleperfexamplewithnative/simpleperf record ...
+Arch: arm64
+Event: task-clock:u (type 1, config 1)
+Samples: 10023
+Event count: 10023000000
+
+Overhead Command Pid Tid Shared Object Symbol
+27.04% BusyThread 5703 5729 /system/lib64/libart.so art::JniMethodStart(art::Thread*)
+25.87% BusyThread 5703 5729 /system/lib64/libc.so long StrToI<long, ...
+...
+```
+
+[report.py](#report-py) reports profiling data in stdio interface. If there are a lot of unknown
+symbols in the report, check [here](#how-to-solve-missing-symbols-in-report).
+
+```sh
+# Report perf.data in html interface.
+$ python report_html.py
+
+# Add source code and disassembly. Change the path of source_dirs if it not correct.
+$ python report_html.py --add_source_code --source_dirs path_of_SimpleperfExampleWithNative \
+ --add_disassembly
+```
+
+[report_html.py](#report_html-py) generates report in report.html, and pops up a browser tab to
+show it.
+
+### Record and report call graph
+
+We can record and report [call graphs](#record-call-graphs-in-record-cmd) as below.
+
+```sh
+# Record dwarf based call graphs: add "-g" in the -r option.
+$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative \
+ -r "-e task-clock:u -f 1000 --duration 10 -g" -lib path_of_SimpleperfExampleWithNative
+
+# Record stack frame based call graphs: add "--call-graph fp" in the -r option.
+$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative \
+ -r "-e task-clock:u -f 1000 --duration 10 --call-graph fp" \
+ -lib path_of_SimpleperfExampleWithNative
+
+# Report call graphs in stdio interface.
+$ python report.py -g
+
+# Report call graphs in python Tk interface.
+$ python report.py -g --gui
+
+# Report call graphs in html interface.
+$ python report_html.py
+
+# Report call graphs in flame graphs.
+# On Windows, use inferno.bat instead of ./inferno.sh.
+$ ./inferno.sh -sc
+```
+
+### Report in html interface
+
+We can use [report_html.py](#report_html-py) to show profiling results in a web browser.
+report_html.py integrates chart statistics, sample table, flame graphs, source code annotation
+and disassembly annotation. It is the recommended way to show reports.
+
+```sh
+$ python report_html.py
+```
+
+### Show flame graph
+
+To show flame graphs, we need to first record call graphs. Flame graphs are shown by
+report_html.py in the "Flamegraph" tab.
+We can also use [inferno](#inferno) to show flame graphs directly.
+
+```sh
+# On Windows, use inferno.bat instead of ./inferno.sh.
+$ ./inferno.sh -sc
+```
+
+We can also build flame graphs using https://github.com/brendangregg/FlameGraph.
+Please make sure you have perl installed.
+
+```sh
+$ git clone https://github.com/brendangregg/FlameGraph.git
+$ python report_sample.py --symfs binary_cache >out.perf
+$ FlameGraph/stackcollapse-perf.pl out.perf >out.folded
+$ FlameGraph/flamegraph.pl out.folded >a.svg
+```
+
+### Record both on CPU time and off CPU time
+
+We can [record both on CPU time and off CPU time](#record-both-on-cpu-time-and-off-cpu-time-in-record-cmd).
+
+First check if trace-offcpu feature is supported on the device.
+
+```sh
+$ python run_simpleperf_on_device.py list --show-features
+dwarf-based-call-graph
+trace-offcpu
+```
+
+If trace-offcpu is supported, it will be shown in the feature list. Then we can try it.
+
+```sh
+$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative -a .SleepActivity \
+ -r "-g -e task-clock:u -f 1000 --duration 10 --trace-offcpu" \
+ -lib path_of_SimpleperfExampleWithNative
+$ python report_html.py --add_disassembly --add_source_code \
+ --source_dirs path_of_SimpleperfExampleWithNative
+```
+
+### Profile from launch
+
+We can [profile from launch of an application](#profile-from-launch-of-an-application).
+
+```sh
+# Start simpleperf recording, then start the Activity to profile.
+$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative -a .MainActivity
+
+# We can also start the Activity on the device manually.
+# 1. Make sure the application isn't running or one of the recent apps.
+# 2. Start simpleperf recording.
+$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative
+# 3. Start the app manually on the device.
+```
+
+### Parse profiling data manually
+We can also write python scripts to parse profiling data manually, by using
+[simpleperf_report_lib.py](#simpleperf_report_lib-py). Examples are report_sample.py,
+report_html.py.
## Android platform profiling
-See [android_platform_profiling.md](./android_platform_profiling.md).
+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
+`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.
+
+```sh
+# Record surfaceflinger process for 10 seconds with dwarf based call graph. More examples are in
+# scripts reference in the doc.
+$ python app_profiler.py -np surfaceflinger -r "-g --duration 10"
+
+# Generate html report.
+$ python report_html.py
+```
+
+4. Since Android >= O has symbols for system libraries on device, we don't need to use unstripped
+binaries in `$ANDROID_PRODUCT_OUT/symbols` to report call graphs. However, they are needed to add
+source code and disassembly (with line numbers) in the report. Below is an example.
+
+```sh
+# Doing recording with app_profiler.py or simpleperf on device, and generates perf.data on host.
+$ python app_profiler.py -np surfaceflinger -r "--call-graph fp --duration 10"
+
+# Collect unstripped binaries from $ANDROID_PRODUCT_OUT/symbols to binary_cache/.
+$ python binary_cache_builder.py -lib $ANDROID_PRODUCT_OUT/symbols
+
+# Report source code and disassembly. Disassembling all binaries is slow, so it's better to add
+# --binary_filter option to only disassemble selected binaries.
+$ python report_html.py --add_source_code --source_dirs $ANDROID_BUILD_TOP --add_disassembly \
+ --binary_filter surfaceflinger.so
+```
## Executable commands reference
-See [executable_commands_reference.md](./executable_commands_reference.md).
+### How simpleperf works
+
+Modern CPUs have a hardware component called the performance monitoring unit (PMU). The PMU has
+several hardware counters, counting events like how many cpu cycles have happened, how many
+instructions have executed, or how many cache misses have happened.
+
+The Linux kernel wraps these hardware counters into hardware perf events. In addition, the Linux
+kernel also provides hardware independent software events and tracepoint events. The Linux kernel
+exposes all events to userspace via the perf_event_open system call, which is used by simpleperf.
+
+Simpleperf has three main commands: stat, record and report.
+
+The stat command gives a summary of how many events have happened in the profiled processes in a
+time period. Here’s how it works:
+1. Given user options, simpleperf enables profiling by making a system call to the kernel.
+2. The kernel enables counters while the profiled processes are running.
+3. After profiling, simpleperf reads counters from the kernel, and reports a counter summary.
+
+The record command records samples of the profiled processes in a time period. Here’s how it works:
+1. Given user options, simpleperf enables profiling by making a system call to the kernel.
+2. Simpleperf creates mapped buffers between simpleperf and the kernel.
+3. The kernel enables counters while the profiled processes are running.
+4. Each time a given number of events happen, the kernel dumps a sample to the mapped buffers.
+5. Simpleperf reads samples from the mapped buffers and stores profiling data in a file called
+ perf.data.
+
+The report command reads perf.data and any shared libraries used by the profiled processes,
+and outputs a report showing where the time was spent.
+
+### Commands
+
+Simpleperf supports several commands, listed below:
+
+```
+The debug-unwind command: debug/test dwarf based offline unwinding, used for debugging simpleperf.
+The dump command: dumps content in perf.data, used for debugging simpleperf.
+The help command: prints help information for other commands.
+The kmem command: collects kernel memory allocation information (will be replaced by Python scripts).
+The list command: lists all event types supported on the Android device.
+The record command: profiles processes and stores profiling data in perf.data.
+The report command: reports profiling data in perf.data.
+The report-sample command: reports each sample in perf.data, used for supporting integration of
+ simpleperf in Android Studio.
+The stat command: profiles processes and prints counter summary.
+
+```
+
+Each command supports different options, which can be seen through help message.
+
+```sh
+# List all commands.
+$ simpleperf --help
+
+# Print help message for record command.
+$ simpleperf record --help
+```
+
+Below describes the most frequently used commands, which are list, stat, record and report.
+
+### The list command
+
+The list command lists all events available on the device. Different devices may support different
+events because they have different hardware and kernels.
+
+```sh
+$ simpleperf list
+List of hw-cache events:
+ branch-loads
+ ...
+List of hardware events:
+ cpu-cycles
+ instructions
+ ...
+List of software events:
+ cpu-clock
+ task-clock
+ ...
+```
+
+On ARM/ARM64, the list command also shows a list of raw events, they are the events supported by
+the ARM PMU on the device. The kernel has wrapped part of them into hardware events and hw-cache
+events. For example, raw-cpu-cycles is wrapped into cpu-cycles, raw-instruction-retired is wrapped
+into instructions. The raw events are provided in case we want to use some events supported on the
+device, but unfortunately not wrapped by the kernel.
+
+### The stat command
+
+The stat command is used to get event counter values of the profiled processes. By passing options,
+we can select which events to use, which processes/threads to monitor, how long to monitor and the
+print interval.
+
+```sh
+# Stat using default events (cpu-cycles,instructions,...), and monitor process 7394 for 10 seconds.
+$ simpleperf stat -p 7394 --duration 10
+Performance counter statistics:
+
+ 1,320,496,145 cpu-cycles # 0.131736 GHz (100%)
+ 510,426,028 instructions # 2.587047 cycles per instruction (100%)
+ 4,692,338 branch-misses # 468.118 K/sec (100%)
+886.008130(ms) task-clock # 0.088390 cpus used (100%)
+ 753 context-switches # 75.121 /sec (100%)
+ 870 page-faults # 86.793 /sec (100%)
+
+Total test time: 10.023829 seconds.
+```
+
+#### Select events to stat
+
+We can select which events to use via -e.
+
+```sh
+# Stat event cpu-cycles.
+$ simpleperf stat -e cpu-cycles -p 11904 --duration 10
+
+# Stat event cache-references and cache-misses.
+$ simpleperf stat -e cache-references,cache-misses -p 11904 --duration 10
+```
+
+When running the stat command, if the number of hardware events is larger than the number of
+hardware counters available in the PMU, the kernel shares hardware counters between events, so each
+event is only monitored for part of the total time. In the example below, there is a percentage at
+the end of each row, showing the percentage of the total time that each event was actually
+monitored.
+
+```sh
+# Stat using event cache-references, cache-references:u,....
+$ simpleperf stat -p 7394 -e cache-references,cache-references:u,cache-references:k \
+ -e cache-misses,cache-misses:u,cache-misses:k,instructions --duration 1
+Performance counter statistics:
+
+4,331,018 cache-references # 4.861 M/sec (87%)
+3,064,089 cache-references:u # 3.439 M/sec (87%)
+1,364,959 cache-references:k # 1.532 M/sec (87%)
+ 91,721 cache-misses # 102.918 K/sec (87%)
+ 45,735 cache-misses:u # 51.327 K/sec (87%)
+ 38,447 cache-misses:k # 43.131 K/sec (87%)
+9,688,515 instructions # 10.561 M/sec (89%)
+
+Total test time: 1.026802 seconds.
+```
+
+In the example above, each event is monitored about 87% of the total time. But there is no
+guarantee that any pair of events are always monitored at the same time. If we want to have some
+events monitored at the same time, we can use --group.
+
+```sh
+# Stat using event cache-references, cache-references:u,....
+$ simpleperf stat -p 7964 --group cache-references,cache-misses \
+ --group cache-references:u,cache-misses:u --group cache-references:k,cache-misses:k \
+ -e instructions --duration 1
+Performance counter statistics:
+
+3,638,900 cache-references # 4.786 M/sec (74%)
+ 65,171 cache-misses # 1.790953% miss rate (74%)
+2,390,433 cache-references:u # 3.153 M/sec (74%)
+ 32,280 cache-misses:u # 1.350383% miss rate (74%)
+ 879,035 cache-references:k # 1.251 M/sec (68%)
+ 30,303 cache-misses:k # 3.447303% miss rate (68%)
+8,921,161 instructions # 10.070 M/sec (86%)
+
+Total test time: 1.029843 seconds.
+```
+
+#### Select target to stat
+
+We can select which processes or threads to monitor via -p or -t. Monitoring a
+process is the same as monitoring all threads in the process. Simpleperf can also fork a child
+process to run the new command and then monitor the child process.
+
+```sh
+# Stat process 11904 and 11905.
+$ simpleperf stat -p 11904,11905 --duration 10
+
+# Stat thread 11904 and 11905.
+$ simpleperf stat -t 11904,11905 --duration 10
+
+# Start a child process running `ls`, and stat it.
+$ simpleperf stat ls
+
+# Stat the process of an Android application. This only works for debuggable apps on non-rooted
+# devices.
+$ simpleperf stat --app com.example.simpleperf.simpleperfexamplewithnative
+
+# Stat system wide using -a.
+$ simpleperf stat -a --duration 10
+```
+
+#### Decide how long to stat
+
+When monitoring existing threads, we can use --duration to decide how long to monitor. When
+monitoring a child process running a new command, simpleperf monitors until the child process ends.
+In this case, we can use Ctrl-C to stop monitoring at any time.
+
+```sh
+# Stat process 11904 for 10 seconds.
+$ simpleperf stat -p 11904 --duration 10
+
+# Stat until the child process running `ls` finishes.
+$ simpleperf stat ls
+
+# Stop monitoring using Ctrl-C.
+$ simpleperf stat -p 11904 --duration 10
+^C
+```
+
+If you want to write a script to control how long to monitor, you can send one of SIGINT, SIGTERM,
+SIGHUP signals to simpleperf to stop monitoring.
+
+#### Decide the print interval
+
+When monitoring perf counters, we can also use --interval to decide the print interval.
+
+```sh
+# Print stat for process 11904 every 300ms.
+$ simpleperf stat -p 11904 --duration 10 --interval 300
+
+# Print system wide stat at interval of 300ms for 10 seconds. Note that system wide profiling needs
+# root privilege.
+$ su 0 simpleperf stat -a --duration 10 --interval 300
+```
+
+#### Display counters in systrace
+
+Simpleperf can also work with systrace to dump counters in the collected trace. Below is an example
+to do a system wide stat.
+
+```sh
+# Capture instructions (kernel only) and cache misses with interval of 300 milliseconds for 15
+# seconds.
+$ su 0 simpleperf stat -e instructions:k,cache-misses -a --interval 300 --duration 15
+# On host launch systrace to collect trace for 10 seconds.
+(HOST)$ external/chromium-trace/systrace.py --time=10 -o new.html sched gfx view
+# Open the collected new.html in browser and perf counters will be shown up.
+```
+
+### The record command
+
+The record command is used to dump samples of the profiled processes. Each sample can contain
+information like the time at which the sample was generated, the number of events since last
+sample, the program counter of a thread, the call chain of a thread.
+
+By passing options, we can select which events to use, which processes/threads to monitor,
+what frequency to dump samples, how long to monitor, and where to store samples.
+
+```sh
+# Record on process 7394 for 10 seconds, using default event (cpu-cycles), using default sample
+# frequency (4000 samples per second), writing records to perf.data.
+$ simpleperf record -p 7394 --duration 10
+simpleperf I cmd_record.cpp:316] Samples recorded: 21430. Samples lost: 0.
+```
+#### Select events to record
+
+By default, the cpu-cycles event is used to evaluate consumed cpu cycles. But we can also use other
+events via -e.
+
+```sh
+# Record using event instructions.
+$ simpleperf record -e instructions -p 11904 --duration 10
+
+# Record using task-clock, which shows the passed CPU time in nanoseconds.
+$ simpleperf record -e task-clock -p 11904 --duration 10
+```
+
+#### Select target to record
+
+The way to select target in record command is similar to that in the stat command.
+
+```sh
+# Record process 11904 and 11905.
+$ simpleperf record -p 11904,11905 --duration 10
+
+# Record thread 11904 and 11905.
+$ simpleperf record -t 11904,11905 --duration 10
+
+# Record a child process running `ls`.
+$ simpleperf record ls
+
+# Record the process of an Android application. This only works for debuggable apps on non-rooted
+# devices.
+$ simpleperf record --app com.example.simpleperf.simpleperfexamplewithnative
+
+# Record system wide.
+$ simpleperf record -a --duration 10
+```
+
+#### Set the frequency to record
+
+We can set the frequency to dump records via -f or -c. For example, -f 4000 means
+dumping approximately 4000 records every second when the monitored thread runs. If a monitored
+thread runs 0.2s in one second (it can be preempted or blocked in other times), simpleperf dumps
+about 4000 * 0.2 / 1.0 = 800 records every second. Another way is using -c. For example, -c 10000
+means dumping one record whenever 10000 events happen.
+
+```sh
+# Record with sample frequency 1000: sample 1000 times every second running.
+$ simpleperf record -f 1000 -p 11904,11905 --duration 10
+
+# Record with sample period 100000: sample 1 time every 100000 events.
+$ simpleperf record -c 100000 -t 11904,11905 --duration 10
+```
+
+To avoid taking too much time generating samples, kernel >= 3.10 sets the max percent of cpu time
+used for generating samples (default is 25%), and decreases the max allowed sample frequency when
+hitting that limit. Simpleperf uses --cpu-percent option to adjust it, but it needs either root
+privilege or to be on Android >= Q.
+
+```sh
+# Record with sample frequency 10000, with max allowed cpu percent to be 50%.
+$ simpleperf record -f 1000 -p 11904,11905 --duration 10 --cpu-percent 50
+```
+
+#### Decide how long to record
+
+The way to decide how long to monitor in record command is similar to that in the stat command.
+
+```sh
+# Record process 11904 for 10 seconds.
+$ simpleperf record -p 11904 --duration 10
+
+# Record until the child process running `ls` finishes.
+$ simpleperf record ls
+
+# Stop monitoring using Ctrl-C.
+$ simpleperf record -p 11904 --duration 10
+^C
+```
+
+If you want to write a script to control how long to monitor, you can send one of SIGINT, SIGTERM,
+SIGHUP signals to simpleperf to stop monitoring.
+
+#### Set the path to store profiling data
+
+By default, simpleperf stores profiling data in perf.data in the current directory. But the path
+can be changed using -o.
+
+```sh
+# Write records to data/perf2.data.
+$ simpleperf record -p 11904 -o data/perf2.data --duration 10
+```
+
+<a name="record-call-graphs-in-record-cmd"></a>
+#### Record call graphs
+
+A call graph is a tree showing function call relations. Below is an example.
+
+```
+main() {
+ FunctionOne();
+ FunctionTwo();
+}
+FunctionOne() {
+ FunctionTwo();
+ FunctionThree();
+}
+a call graph:
+ main-> FunctionOne
+ | |
+ | |-> FunctionTwo
+ | |-> FunctionThree
+ |
+ |-> FunctionTwo
+```
+
+A call graph shows how a function calls other functions, and a reversed call graph shows how
+a function is called by other functions. To show a call graph, we need to first record it, then
+report it.
+
+There are two ways to record a call graph, one is recording a dwarf based call graph, the other is
+recording a stack frame based call graph. Recording dwarf based call graphs needs support of debug
+information in native binaries. While recording stack frame based call graphs needs support of
+stack frame registers.
+
+```sh
+# Record a dwarf based call graph
+$ simpleperf record -p 11904 -g --duration 10
+
+# Record a stack frame based call graph
+$ simpleperf record -p 11904 --call-graph fp --duration 10
+```
+
+[Here](#suggestions-about-recording-call-graphs) are some suggestions about recording call graphs.
+
+<a name="record-both-on-cpu-time-and-off-cpu-time-in-record-cmd"></a>
+#### Record both on CPU time and off CPU time
+
+Simpleperf is a CPU profiler, it generates samples for a thread only when it is running on a CPU.
+However, sometimes we want to figure out where the time of a thread is spent, whether it is running
+on a CPU, or staying in the kernel's ready queue, or waiting for something like I/O events.
+
+To support this, the record command uses --trace-offcpu to trace both on CPU time and off CPU time.
+When --trace-offcpu is used, simpleperf generates a sample when a running thread is scheduled out,
+so we know the callstack of a thread when it is scheduled out. And when reporting a perf.data
+generated with --trace-offcpu, we use time to the next sample (instead of event counts from the
+previous sample) as the weight of the current sample. As a result, we can get a call graph based
+on timestamps, including both on CPU time and off CPU time.
+
+trace-offcpu is implemented using sched:sched_switch tracepoint event, which may not be supported
+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.
+
+```sh
+$ simpleperf list --show-features
+dwarf-based-call-graph
+trace-offcpu
+```
+
+If trace-offcpu is supported, it will be shown in the feature list. Then we can try it.
+
+```sh
+# Record with --trace-offcpu.
+$ simpleperf record -g -p 11904 --duration 10 --trace-offcpu
+
+# Record with --trace-offcpu using app_profiler.py.
+$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative -a .SleepActivity \
+ -r "-g -e task-clock:u -f 1000 --duration 10 --trace-offcpu"
+```
+
+Below is an example comparing the profiling result with / without --trace-offcpu.
+First we record without --trace-offcpu.
+
+```sh
+$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative -a .SleepActivity
+
+$ python report_html.py --add_disassembly --add_source_code --source_dirs ../demo
+```
+
+The result is [here](./without_trace_offcpu.html).
+In the result, all time is taken by RunFunction(), and sleep time is ignored.
+But if we add --trace-offcpu, the result changes.
+
+```sh
+$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative -a .SleepActivity \
+ -r "-g -e task-clock:u --trace-offcpu -f 1000 --duration 10"
+
+$ python report_html.py --add_disassembly --add_source_code --source_dirs ../demo
+```
+
+The result is [here](./trace_offcpu.html).
+In the result, half of the time is taken by RunFunction(), and the other half is taken by
+SleepFunction(). So it traces both on CPU time and off CPU time.
+
+### The report command
+
+The report command is used to report profiling data generated by the record command. The report
+contains a table of sample entries. Each sample entry is a row in the report. The report command
+groups samples belong to the same process, thread, library, function in the same sample entry. Then
+sort the sample entries based on the event count a sample entry has.
+
+By passing options, we can decide how to filter out uninteresting samples, how to group samples
+into sample entries, and where to find profiling data and binaries.
+
+Below is an example. Records are grouped into 4 sample entries, each entry is a row. There are
+several columns, each column shows piece of information belonging to a sample entry. The first
+column is Overhead, which shows the percentage of events inside the current sample entry in total
+events. As the perf event is cpu-cycles, the overhead is the percentage of CPU cycles used in each
+function.
+
+```sh
+# Reports perf.data, using only records sampled in libsudo-game-jni.so, grouping records using
+# thread name(comm), process id(pid), thread id(tid), function name(symbol), and showing sample
+# count for each row.
+$ simpleperf report --dsos /data/app/com.example.sudogame-2/lib/arm64/libsudo-game-jni.so \
+ --sort comm,pid,tid,symbol -n
+Cmdline: /data/data/com.example.sudogame/simpleperf record -p 7394 --duration 10
+Arch: arm64
+Event: cpu-cycles (type 0, config 0)
+Samples: 28235
+Event count: 546356211
+
+Overhead Sample Command Pid Tid Symbol
+59.25% 16680 sudogame 7394 7394 checkValid(Board const&, int, int)
+20.42% 5620 sudogame 7394 7394 canFindSolution_r(Board&, int, int)
+13.82% 4088 sudogame 7394 7394 randomBlock_r(Board&, int, int, int, int, int)
+6.24% 1756 sudogame 7394 7394 @plt
+```
+
+#### Set the path to read profiling data
+
+By default, the report command reads profiling data from perf.data in the current directory.
+But the path can be changed using -i.
+
+```sh
+$ simpleperf report -i data/perf2.data
+```
+
+#### Set the path to find binaries
+
+To report function symbols, simpleperf needs to read executable binaries used by the monitored
+processes to get symbol table and debug information. By default, the paths are the executable
+binaries used by monitored processes while recording. However, these binaries may not exist when
+reporting or not contain symbol table and debug information. So we can use --symfs to redirect
+the paths.
+
+```sh
+# In this case, when simpleperf wants to read executable binary /A/b, it reads file in /A/b.
+$ simpleperf report
+
+# In this case, when simpleperf wants to read executable binary /A/b, it prefers file in
+# /debug_dir/A/b to file in /A/b.
+$ simpleperf report --symfs /debug_dir
+
+# Read symbols for system libraries built locally. Note that this is not needed since Android O,
+# which ships symbols for system libraries on device.
+$ simpleperf report --symfs $ANDROID_PRODUCT_OUT/symbols
+```
+
+#### Filter samples
+
+When reporting, it happens that not all records are of interest. The report command supports four
+filters to select samples of interest.
+
+```sh
+# Report records in threads having name sudogame.
+$ simpleperf report --comms sudogame
+
+# Report records in process 7394 or 7395
+$ simpleperf report --pids 7394,7395
+
+# Report records in thread 7394 or 7395.
+$ simpleperf report --tids 7394,7395
+
+# Report records in libsudo-game-jni.so.
+$ simpleperf report --dsos /data/app/com.example.sudogame-2/lib/arm64/libsudo-game-jni.so
+```
+
+#### Group samples into sample entries
+
+The report command uses --sort to decide how to group sample entries.
+
+```sh
+# Group records based on their process id: records having the same process id are in the same
+# sample entry.
+$ simpleperf report --sort pid
+
+# Group records based on their thread id and thread comm: records having the same thread id and
+# thread name are in the same sample entry.
+$ simpleperf report --sort tid,comm
+
+# Group records based on their binary and function: records in the same binary and function are in
+# the same sample entry.
+$ simpleperf report --sort dso,symbol
+
+# Default option: --sort comm,pid,tid,dso,symbol. Group records in the same thread, and belong to
+# the same function in the same binary.
+$ simpleperf report
+```
+
+<a name="report-call-graphs-in-report-cmd"></a>
+#### Report call graphs
+
+To report a call graph, please make sure the profiling data is recorded with call graphs,
+as [here](#record-call-graphs-in-record-cmd).
+
+```
+$ simpleperf report -g
+```
## Scripts reference
-See [scripts_reference.md](./scripts_reference.md).
+<a name="app_profiler-py"></a>
+### app_profiler.py
+
+app_profiler.py is used to record profiling data for Android applications and native executables.
+
+```sh
+# Record an Android application.
+$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative
+
+# Record an Android application with Java code compiled into native instructions.
+$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative --compile_java_code
+
+# Record the launch of an Activity of an Android application.
+$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative -a .SleepActivity
+
+# Record a native process.
+$ python app_profiler.py -np surfaceflinger
+
+# Record a native process given its pid.
+$ python app_profiler.py --pid 11324
+
+# Record a command.
+$ python app_profiler.py -cmd \
+ "dex2oat --dex-file=/data/local/tmp/app-profiling.apk --oat-file=/data/local/tmp/a.oat"
+
+# Record an Android application, and use -r to send custom options to the record command.
+$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative \
+ -r "-e cpu-clock -g --duration 30"
+
+# Record both on CPU time and off CPU time.
+$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative \
+ -r "-e task-clock -g -f 1000 --duration 10 --trace-offcpu"
+
+# Save profiling data in a custom file (like perf_custom.data) instead of perf.data.
+$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative -o perf_custom.data
+```
+
+#### Profile from launch of an application
+
+Sometimes we want to profile the launch-time of an application. To support this, we added --app in
+the record command. The --app option sets the package name of the Android application to profile.
+If the app is not already running, the record command will poll for the app process in a loop with
+an interval of 1ms. So to profile from launch of an application, we can first start the record
+command with --app, then start the app. Below is an example.
+
+```sh
+$ python run_simpleperf_on_device.py record
+ --app com.example.simpleperf.simpleperfexamplewithnative \
+ -g --duration 1 -o /data/local/tmp/perf.data
+# Start the app manually or using the `am` command.
+```
+
+To make it convenient to use, app_profiler.py supports using the -a option to start an Activity
+after recording has started.
+
+```sh
+$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative -a .MainActivity
+```
+
+<a name="run_simpleperf_without_usb_connection-py"></a>
+### run_simpleperf_without_usb_connection.py
+
+run_simpleperf_without_usb_connection.py records profiling data while the USB cable isn't
+connected. Below is an example.
+
+```sh
+$ python run_simpleperf_without_usb_connection.py start \
+ -p com.example.simpleperf.simpleperfexamplewithnative
+# After the command finishes successfully, unplug the USB cable, run the
+# SimpleperfExampleWithNative app. After a few seconds, plug in the USB cable.
+$ python run_simpleperf_without_usb_connection.py stop
+# It may take a while to stop recording. After that, the profiling data is collected in perf.data
+# on host.
+```
+
+<a name="binary_cache_builder-py"></a>
+### binary_cache_builder.py
+
+The binary_cache directory is a directory holding binaries needed by a profiling data file. The
+binaries are expected to be unstripped, having debug information and symbol tables. The
+binary_cache directory is used by report scripts to read symbols of binaries. It is also used by
+report_html.py to generate annotated source code and disassembly.
+
+By default, app_profiler.py builds the binary_cache directory after recording. But we can also
+build binary_cache for existing profiling data files using binary_cache_builder.py. It is useful
+when you record profiling data using `simpleperf record` directly, to do system wide profiling or
+record without the USB cable connected.
+
+binary_cache_builder.py can either pull binaries from an Android device, or find binaries in
+directories on the host (via -lib).
+
+```sh
+# Generate binary_cache for perf.data, by pulling binaries from the device.
+$ python binary_cache_builder.py
+
+# Generate binary_cache, by pulling binaries from the device and finding binaries in
+# SimpleperfExampleWithNative.
+$ python binary_cache_builder.py -lib path_of_SimpleperfExampleWithNative
+```
+
+<a name="run_simpleperf_on_device-py"></a>
+### run_simpleperf_on_device.py
+
+This script pushes the simpleperf executable on the device, and run a simpleperf command on the
+device. It is more convenient than running adb commands manually.
+
+<a name="report-py"></a>
+### report.py
+
+report.py is a wrapper of the report command on the host. It accepts all options of the report
+command.
+
+```sh
+# Report call graph
+$ python report.py -g
+
+# Report call graph in a GUI window implemented by Python Tk.
+$ python report.py -g --gui
+```
+
+<a name="report_html-py"></a>
+### report_html.py
+
+report_html.py generates report.html based on the profiling data. Then the report.html can show
+the profiling result without depending on other files. So it can be shown in local browsers or
+passed to other machines. Depending on which command-line options are used, the content of the
+report.html can include: chart statistics, sample table, flame graphs, annotated source code for
+each function, annotated disassembly for each function.
+
+```sh
+# Generate chart statistics, sample table and flame graphs, based on perf.data.
+$ python report_html.py
+
+# Add source code.
+$ python report_html.py --add_source_code --source_dirs path_of_SimpleperfExampleWithNative
+
+# Add disassembly.
+$ python report_html.py --add_disassembly
+
+# Adding disassembly for all binaries can cost a lot of time. So we can choose to only add
+# disassembly for selected binaries.
+$ python report_html.py --add_disassembly --binary_filter libgame.so
+
+# report_html.py accepts more than one recording data file.
+$ python report_html.py -i perf1.data perf2.data
+```
+
+Below is an example of generating html profiling results for SimpleperfExampleWithNative.
+
+```sh
+$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative
+$ python report_html.py --add_source_code --source_dirs path_of_SimpleperfExampleWithNative \
+ --add_disassembly
+```
+
+After opening the generated [report.html](./report_html.html) in a browser, there are several tabs:
+
+The first tab is "Chart Statistics". You can click the pie chart to show the time consumed by each
+process, thread, library and function.
+
+The second tab is "Sample Table". It shows the time taken by each function. By clicking one row in
+the table, we can jump to a new tab called "Function".
+
+The third tab is "Flamegraph". It shows the flame graphs generated by [inferno](./inferno.md).
+
+The fourth tab is "Function". It only appears when users click a row in the "Sample Table" tab.
+It shows information of a function, including:
+
+1. A flame graph showing functions called by that function.
+2. A flame graph showing functions calling that function.
+3. Annotated source code of that function. It only appears when there are source code files for
+ that function.
+4. Annotated disassembly of that function. It only appears when there are binaries containing that
+ function.
+
+### inferno
+
+[inferno](./inferno.md) is a tool used to generate flame graph in a html file.
+
+```sh
+# Generate flame graph based on perf.data.
+# On Windows, use inferno.bat instead of ./inferno.sh.
+$ ./inferno.sh -sc --record_file perf.data
+
+# Record a native program and generate flame graph.
+$ ./inferno.sh -np surfaceflinger
+```
+
+<a name="pprof_proto_generator-py"></a>
+### pprof_proto_generator.py
+
+It converts a profiling data file into pprof.proto, a format used by [pprof](https://github.com/google/pprof).
+
+```sh
+# Convert perf.data in the current directory to pprof.proto format.
+$ python pprof_proto_generator.py
+$ pprof -pdf pprof.profile
+```
+
+<a name="report_sample-py"></a>
+### report_sample.py
+
+It converts a profiling data file into a format used by [FlameGraph](https://github.com/brendangregg/FlameGraph).
+
+```sh
+# Convert perf.data in the current directory to a format used by FlameGraph.
+$ python report_sample.py --symfs binary_cache >out.perf
+$ git clone https://github.com/brendangregg/FlameGraph.git
+$ FlameGraph/stackcollapse-perf.pl out.perf >out.folded
+$ FlameGraph/flamegraph.pl out.folded >a.svg
+```
+
+<a name="simpleperf_report_lib-py"></a>
+### simpleperf_report_lib.py
+
+simpleperf_report_lib.py is a Python library used to parse profiling data files generated by the
+record command. Internally, it uses libsimpleperf_report.so to do the work. Generally, for each
+profiling data file, we create an instance of ReportLib, pass it the file path (via SetRecordFile).
+Then we can read all samples through GetNextSample(). For each sample, we can read its event info
+(via GetEventOfCurrentSample), symbol info (via GetSymbolOfCurrentSample) and call chain info
+(via GetCallChainOfCurrentSample). We can also get some global information, like record options
+(via GetRecordCmd), the arch of the device (via GetArch) and meta strings (via MetaInfo).
+Examples of using simpleperf_report_lib.py are in report_sample.py, report_html.py,
+pprof_proto_generator.py and inferno/inferno.py.
## Answers to common issues
@@ -128,7 +1253,7 @@ Below is our experiences of dwarf based call graphs and stack frame based call g
dwarf based call graphs:
1. Need support of debug information in binaries.
2. Behave normally well on both ARM and ARM64, for both fully compiled Java code and C++ code.
-3. Can only unwind 64K stack for each sample. So usually can't show complete flamegraph. But
+3. Can only unwind 64K stack for each sample. So usually can't show complete flame-graph. But
probably is enough for users to identify hot places.
4. Take more CPU time than stack frame based call graphs. So the sample frequency is suggested
to be 1000 Hz. Thus at most 1000 samples per second.
@@ -140,7 +1265,7 @@ stack frame based call graphs:
3. Also don't work well on fully compiled Java code on ARM64. Because the ART compiler doesn't
reserve stack frame registers.
4. Work well when profiling native programs on ARM64. One example is profiling surfacelinger. And
- usually shows complete flamegraph when it works well.
+ usually shows complete flame-graph when it works well.
5. Take less CPU time than dwarf based call graphs. So the sample frequency can be 4000 Hz or
higher.
diff --git a/simpleperf/doc/android_application_profiling.md b/simpleperf/doc/android_application_profiling.md
deleted file mode 100644
index fae33df6..00000000
--- a/simpleperf/doc/android_application_profiling.md
+++ /dev/null
@@ -1,272 +0,0 @@
-# Android application profiling
-
-This section shows how to profile an Android application.
-Some examples are [Here](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/demo/README.md).
-
-Profiling an Android application involves three steps:
-1. Prepare an Android application.
-2. Record profiling data.
-3. Report profiling data.
-
-
-## Table of Contents
-
-- [Prepare an Android application](#prepare-an-android-application)
-- [Record and report profiling data](#record-and-report-profiling-data)
-- [Record and report call graph](#record-and-report-call-graph)
-- [Report in html interface](#report-in-html-interface)
-- [Show flamegraph](#show-flamegraph)
-- [Record both on CPU time and off CPU time](#record-both-on-cpu-time-and-off-cpu-time)
-- [Profile from launch](#profile-from-launch)
-- [Parse profiling data manually](#parse-profiling-data-manually)
-
-
-## Prepare an Android application
-
-Based on the profiling situation, we may need to customize the build script to generate an apk file
-specifically for profiling. Below are some suggestions.
-
-1. If you want to profile a debug build of an application:
-
-For the debug build type, Android studio sets android::debuggable="true" in AndroidManifest.xml,
-enables JNI checks and may not optimize C/C++ code. It can be profiled by simpleperf without any
-change.
-
-2. If you want to profile a release build of an application:
-
-For the release build type, Android studio sets android::debuggable="false" in AndroidManifest.xml,
-disables JNI checks and optimizes C/C++ code. However, security restrictions mean that only apps
-with android::debuggable set to true can be profiled. So simpleperf can only profile a release
-build under these two circumstances:
-If you are on a rooted device, you can profile any app.
-
-If you are on Android >= O, we can use [wrap.sh](https://developer.android.com/ndk/guides/wrap-script.html)
-to profile a release build:
-Step 1: Add android::debuggable="true" in AndroidManifest.xml to enable profiling.
-```
-<manifest ...>
- <application android::debuggable="true" ...>
-```
-
-Step 2: Add wrap.sh in lib/`arch` directories. wrap.sh runs the app without passing any debug flags
-to ART, so the app runs as a release app. wrap.sh can be done by adding the script below in
-app/build.gradle.
-```
-android {
- buildTypes {
- release {
- sourceSets {
- release {
- resources {
- srcDir {
- "wrap_sh_lib_dir"
- }
- }
- }
- }
- }
- }
-}
-
-task createWrapShLibDir
- for (String abi : ["armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64"]) {
- def dir = new File("app/wrap_sh_lib_dir/lib/" + abi)
- dir.mkdirs()
- def wrapFile = new File(dir, "wrap.sh")
- wrapFile.withWriter { writer ->
- writer.write('#!/system/bin/sh\n\$@\n')
- }
- }
-}
-```
-
-3. If you want to profile C/C++ code:
-
-Android studio strips symbol table and debug info of native libraries in the apk. So the profiling
-results may contain unknown symbols or broken callgraphs. To fix this, we can pass app_profiler.py
-a directory containing unstripped native libraries via the -lib option. Usually the directory can
-be the path of your Android Studio project.
-
-
-4. If you want to profile Java code:
-
-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.
-
-On Android O, simpleperf supports profiling Java code which is compiled into native instructions,
-and it also needs wrap.sh to use the compiled Java code. To compile Java code, we can pass
-app_profiler.py the --compile_java_code option.
-
-On Android N, simpleperf supports profiling Java code that is compiled into native instructions.
-To compile java code, we can pass app_profiler.py the --compile_java_code option.
-
-On Android <= M, simpleperf doesn't support profiling Java code.
-
-
-Below I use application [SimpleperfExampleWithNative](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/demo/SimpleperfExampleWithNative).
-It builds an app-profiling.apk for profiling.
-
-```sh
-$ git clone https://android.googlesource.com/platform/system/extras
-$ cd extras/simpleperf/demo
-# Open SimpleperfExamplesWithNative project with Android studio, and build this project
-# successfully, otherwise the `./gradlew` command below will fail.
-$ cd SimpleperfExampleWithNative
-
-# On windows, use "gradlew" instead.
-$ ./gradlew clean assemble
-$ adb install -r app/build/outputs/apk/profiling/app-profiling.apk
-```
-
-## Record and report profiling data
-
-We can use [app-profiler.py](scripts_reference.md#app_profilerpy) to profile Android applications.
-
-```sh
-# Cd to the directory of simpleperf scripts. Record perf.data.
-# -p option selects the profiled app using its package name.
-# --compile_java_code option compiles Java code into native instructions, which isn't needed on
-# Android >= P.
-# -a option selects the Activity to profile.
-# -lib option gives the directory to find debug native libraries.
-$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative --compile_java_code \
- -a .MixActivity -lib path_of_SimpleperfExampleWithNative
-```
-
-This will collect profiling data in perf.data in the current directory, and related native
-binaries in binary_cache/.
-
-Normally we need to use the app when profiling, otherwise we may record no samples. But in this
-case, the MixActivity starts a busy thread. So we don't need to use the app while profiling.
-
-```sh
-# Report perf.data in stdio interface.
-$ python report.py
-Cmdline: /data/data/com.example.simpleperf.simpleperfexamplewithnative/simpleperf record ...
-Arch: arm64
-Event: task-clock:u (type 1, config 1)
-Samples: 10023
-Event count: 10023000000
-
-Overhead Command Pid Tid Shared Object Symbol
-27.04% BusyThread 5703 5729 /system/lib64/libart.so art::JniMethodStart(art::Thread*)
-25.87% BusyThread 5703 5729 /system/lib64/libc.so long StrToI<long, ...
-...
-```
-
-[report.py](scripts_reference.md#reportpy) reports profiling data in stdio interface. If there
-are a lot of unknown symbols in the report, check [here](README.md#how-to-solve-missing-symbols-in-report).
-
-```sh
-# Report perf.data in html interface.
-$ python report_html.py
-
-# Add source code and disassembly. Change the path of source_dirs if it not correct.
-$ python report_html.py --add_source_code --source_dirs path_of_SimpleperfExampleWithNative \
- --add_disassembly
-```
-
-[report_html.py](scripts_reference.md#report_htmlpy) generates report in report.html, and pops up
-a browser tab to show it.
-
-## Record and report call graph
-
-We can record and report [call graphs](executable_commands_reference.md#record-call-graphs) as below.
-
-```sh
-# Record dwarf based call graphs: add "-g" in the -r option.
-$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative \
- -r "-e task-clock:u -f 1000 --duration 10 -g" -lib path_of_SimpleperfExampleWithNative
-
-# Record stack frame based call graphs: add "--call-graph fp" in the -r option.
-$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative \
- -r "-e task-clock:u -f 1000 --duration 10 --call-graph fp" \
- -lib path_of_SimpleperfExampleWithNative
-
-# Report call graphs in stdio interface.
-$ python report.py -g
-
-# Report call graphs in python Tk interface.
-$ python report.py -g --gui
-
-# Report call graphs in html interface.
-$ python report_html.py
-
-# Report call graphs in flamegraphs.
-# On Windows, use inferno.bat instead of ./inferno.sh.
-$ ./inferno.sh -sc
-```
-
-## Report in html interface
-
-We can use [report_html.py](scripts_reference.md#report_htmlpy) to show profiling results in a web browser.
-report_html.py integrates chart statistics, sample table, flamegraphs, source code annotation
-and disassembly annotation. It is the recommended way to show reports.
-
-```sh
-$ python report_html.py
-```
-
-## Show flamegraph
-
-To show flamegraphs, we need to first record call graphs. Flamegraphs are shown by
-report_html.py in the "Flamegraph" tab.
-We can also use [inferno](scripts_reference.md#inferno) to show flamegraphs directly.
-
-```sh
-# On Windows, use inferno.bat instead of ./inferno.sh.
-$ ./inferno.sh -sc
-```
-
-We can also build flamegraphs using https://github.com/brendangregg/FlameGraph.
-Please make sure you have perl installed.
-
-```sh
-$ git clone https://github.com/brendangregg/FlameGraph.git
-$ python report_sample.py --symfs binary_cache >out.perf
-$ FlameGraph/stackcollapse-perf.pl out.perf >out.folded
-$ FlameGraph/flamegraph.pl out.folded >a.svg
-```
-
-## 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).
-
-First check if trace-offcpu feature is supported on the device.
-
-```sh
-$ python run_simpleperf_on_device.py list --show-features
-dwarf-based-call-graph
-trace-offcpu
-```
-
-If trace-offcpu is supported, it will be shown in the feature list. Then we can try it.
-
-```sh
-$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative -a .SleepActivity \
- -r "-g -e task-clock:u -f 1000 --duration 10 --trace-offcpu" \
- -lib path_of_SimpleperfExampleWithNative
-$ python report_html.py --add_disassembly --add_source_code \
- --source_dirs path_of_SimpleperfExampleWithNative
-```
-
-## Profile from launch
-
-We can [profile from launch of an application](scripts_reference.md#profile-from-launch-of-an-application).
-
-```sh
-# Start simpleperf recording, then start the Activity to profile.
-$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative -a .MainActivity
-
-# We can also start the Activity on the device manually.
-# 1. Make sure the application isn't running or one of the recent apps.
-# 2. Start simpleperf recording.
-$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative
-# 3. Start the app manually on the device.
-```
-
-## Parse profiling data manually
-
-We can also write python scripts to parse profiling data manually, by using
-[simpleperf_report_lib.py](scripts_reference.md#simpleperf_report_libpy). Examples are report_sample.py,
-report_html.py.
diff --git a/simpleperf/doc/android_platform_profiling.md b/simpleperf/doc/android_platform_profiling.md
deleted file mode 100644
index afa1927b..00000000
--- a/simpleperf/doc/android_platform_profiling.md
+++ /dev/null
@@ -1,36 +0,0 @@
-# Android platform profiling
-
-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
-`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.
-
-```sh
-# Record surfaceflinger process for 10 seconds with dwarf based call graph. More examples are in
-# scripts reference in the doc.
-$ python app_profiler.py -np surfaceflinger -r "-g --duration 10"
-
-# Generate html report.
-$ python report_html.py
-```
-
-4. Since Android >= O has symbols for system libraries on device, we don't need to use unstripped
-binaries in `$ANDROID_PRODUCT_OUT/symbols` to report call graphs. However, they are needed to add
-source code and disassembly (with line numbers) in the report. Below is an example.
-
-```sh
-# Doing recording with app_profiler.py or simpleperf on device, and generates perf.data on host.
-$ python app_profiler.py -np surfaceflinger -r "--call-graph fp --duration 10"
-
-# Collect unstripped binaries from $ANDROID_PRODUCT_OUT/symbols to binary_cache/.
-$ python binary_cache_builder.py -lib $ANDROID_PRODUCT_OUT/symbols
-
-# Report source code and disassembly. Disassembling all binaries is slow, so it's better to add
-# --binary_filter option to only disassemble selected binaries.
-$ python report_html.py --add_source_code --source_dirs $ANDROID_BUILD_TOP --add_disassembly \
- --binary_filter surfaceflinger.so
-```
diff --git a/simpleperf/doc/executable_commands_reference.md b/simpleperf/doc/executable_commands_reference.md
deleted file mode 100644
index ef5c339a..00000000
--- a/simpleperf/doc/executable_commands_reference.md
+++ /dev/null
@@ -1,583 +0,0 @@
-# Executable commands reference
-
-## Table of Contents
-
-- [How simpleperf works](#how-simpleperf-works)
-- [Commands](#commands)
-- [The list command](#the-list-command)
-- [The stat command](#the-stat-command)
- - [Select events to stat](#select-events-to-stat)
- - [Select target to stat](#select-target-to-stat)
- - [Decide how long to stat](#decide-how-long-to-stat)
- - [Decide the print interval](#decide-the-print-interval)
- - [Display counters in systrace](#display-counters-in-systrace)
-- [The record command](#the-record-command)
- - [Select events to record](#select-events-to-record)
- - [Select target to record](#select-target-to-record)
- - [Set the frequency to record](#set-the-frequency-to-record)
- - [Decide how long to record](#decide-how-long-to-record)
- - [Set the path to store profiling data](#set-the-path-to-store-profiling-data)
- - [Record call graphs](#record-call-graphs)
- - [Record both on CPU time and off CPU time](#record-both-on-cpu-time-and-off-cpu-time)
-- [The report command](#the-report-command)
- - [Set the path to read profiling data](#set-the-path-to-read-profiling-data)
- - [Set the path to find binaries](#set-the-path-to-find-binaries)
- - [Filter samples](#filter-samples)
- - [Group samples into sample entries](#group-samples-into-sample-entries)
- - [Report call graphs](#report-call-graphs)
-
-
-## How simpleperf works
-
-Modern CPUs have a hardware component called the performance monitoring unit (PMU). The PMU has
-several hardware counters, counting events like how many cpu cycles have happened, how many
-instructions have executed, or how many cache misses have happened.
-
-The Linux kernel wraps these hardware counters into hardware perf events. In addition, the Linux
-kernel also provides hardware independent software events and tracepoint events. The Linux kernel
-exposes all events to userspace via the perf_event_open system call, which is used by simpleperf.
-
-Simpleperf has three main commands: stat, record and report.
-
-The stat command gives a summary of how many events have happened in the profiled processes in a
-time period. Here’s how it works:
-1. Given user options, simpleperf enables profiling by making a system call to the kernel.
-2. The kernel enables counters while the profiled processes are running.
-3. After profiling, simpleperf reads counters from the kernel, and reports a counter summary.
-
-The record command records samples of the profiled processes in a time period. Here’s how it works:
-1. Given user options, simpleperf enables profiling by making a system call to the kernel.
-2. Simpleperf creates mapped buffers between simpleperf and the kernel.
-3. The kernel enables counters while the profiled processes are running.
-4. Each time a given number of events happen, the kernel dumps a sample to the mapped buffers.
-5. Simpleperf reads samples from the mapped buffers and stores profiling data in a file called
- perf.data.
-
-The report command reads perf.data and any shared libraries used by the profiled processes,
-and outputs a report showing where the time was spent.
-
-## Commands
-
-Simpleperf supports several commands, listed below:
-
-```
-The debug-unwind command: debug/test dwarf based offline unwinding, used for debugging simpleperf.
-The dump command: dumps content in perf.data, used for debugging simpleperf.
-The help command: prints help information for other commands.
-The kmem command: collects kernel memory allocation information (will be replaced by Python scripts).
-The list command: lists all event types supported on the Android device.
-The record command: profiles processes and stores profiling data in perf.data.
-The report command: reports profiling data in perf.data.
-The report-sample command: reports each sample in perf.data, used for supporting integration of
- simpleperf in Android Studio.
-The stat command: profiles processes and prints counter summary.
-
-```
-
-Each command supports different options, which can be seen through help message.
-
-```sh
-# List all commands.
-$ simpleperf --help
-
-# Print help message for record command.
-$ simpleperf record --help
-```
-
-Below describes the most frequently used commands, which are list, stat, record and report.
-
-## The list command
-
-The list command lists all events available on the device. Different devices may support different
-events because they have different hardware and kernels.
-
-```sh
-$ simpleperf list
-List of hw-cache events:
- branch-loads
- ...
-List of hardware events:
- cpu-cycles
- instructions
- ...
-List of software events:
- cpu-clock
- task-clock
- ...
-```
-
-On ARM/ARM64, the list command also shows a list of raw events, they are the events supported by
-the ARM PMU on the device. The kernel has wrapped part of them into hardware events and hw-cache
-events. For example, raw-cpu-cycles is wrapped into cpu-cycles, raw-instruction-retired is wrapped
-into instructions. The raw events are provided in case we want to use some events supported on the
-device, but unfortunately not wrapped by the kernel.
-
-## The stat command
-
-The stat command is used to get event counter values of the profiled processes. By passing options,
-we can select which events to use, which processes/threads to monitor, how long to monitor and the
-print interval.
-
-```sh
-# Stat using default events (cpu-cycles,instructions,...), and monitor process 7394 for 10 seconds.
-$ simpleperf stat -p 7394 --duration 10
-Performance counter statistics:
-
- 1,320,496,145 cpu-cycles # 0.131736 GHz (100%)
- 510,426,028 instructions # 2.587047 cycles per instruction (100%)
- 4,692,338 branch-misses # 468.118 K/sec (100%)
-886.008130(ms) task-clock # 0.088390 cpus used (100%)
- 753 context-switches # 75.121 /sec (100%)
- 870 page-faults # 86.793 /sec (100%)
-
-Total test time: 10.023829 seconds.
-```
-
-### Select events to stat
-
-We can select which events to use via -e.
-
-```sh
-# Stat event cpu-cycles.
-$ simpleperf stat -e cpu-cycles -p 11904 --duration 10
-
-# Stat event cache-references and cache-misses.
-$ simpleperf stat -e cache-references,cache-misses -p 11904 --duration 10
-```
-
-When running the stat command, if the number of hardware events is larger than the number of
-hardware counters available in the PMU, the kernel shares hardware counters between events, so each
-event is only monitored for part of the total time. In the example below, there is a percentage at
-the end of each row, showing the percentage of the total time that each event was actually
-monitored.
-
-```sh
-# Stat using event cache-references, cache-references:u,....
-$ simpleperf stat -p 7394 -e cache-references,cache-references:u,cache-references:k \
- -e cache-misses,cache-misses:u,cache-misses:k,instructions --duration 1
-Performance counter statistics:
-
-4,331,018 cache-references # 4.861 M/sec (87%)
-3,064,089 cache-references:u # 3.439 M/sec (87%)
-1,364,959 cache-references:k # 1.532 M/sec (87%)
- 91,721 cache-misses # 102.918 K/sec (87%)
- 45,735 cache-misses:u # 51.327 K/sec (87%)
- 38,447 cache-misses:k # 43.131 K/sec (87%)
-9,688,515 instructions # 10.561 M/sec (89%)
-
-Total test time: 1.026802 seconds.
-```
-
-In the example above, each event is monitored about 87% of the total time. But there is no
-guarantee that any pair of events are always monitored at the same time. If we want to have some
-events monitored at the same time, we can use --group.
-
-```sh
-# Stat using event cache-references, cache-references:u,....
-$ simpleperf stat -p 7964 --group cache-references,cache-misses \
- --group cache-references:u,cache-misses:u --group cache-references:k,cache-misses:k \
- -e instructions --duration 1
-Performance counter statistics:
-
-3,638,900 cache-references # 4.786 M/sec (74%)
- 65,171 cache-misses # 1.790953% miss rate (74%)
-2,390,433 cache-references:u # 3.153 M/sec (74%)
- 32,280 cache-misses:u # 1.350383% miss rate (74%)
- 879,035 cache-references:k # 1.251 M/sec (68%)
- 30,303 cache-misses:k # 3.447303% miss rate (68%)
-8,921,161 instructions # 10.070 M/sec (86%)
-
-Total test time: 1.029843 seconds.
-```
-
-### Select target to stat
-
-We can select which processes or threads to monitor via -p or -t. Monitoring a
-process is the same as monitoring all threads in the process. Simpleperf can also fork a child
-process to run the new command and then monitor the child process.
-
-```sh
-# Stat process 11904 and 11905.
-$ simpleperf stat -p 11904,11905 --duration 10
-
-# Stat thread 11904 and 11905.
-$ simpleperf stat -t 11904,11905 --duration 10
-
-# Start a child process running `ls`, and stat it.
-$ simpleperf stat ls
-
-# Stat the process of an Android application. This only works for debuggable apps on non-rooted
-# devices.
-$ simpleperf stat --app com.example.simpleperf.simpleperfexamplewithnative
-
-# Stat system wide using -a.
-$ simpleperf stat -a --duration 10
-```
-
-### Decide how long to stat
-
-When monitoring existing threads, we can use --duration to decide how long to monitor. When
-monitoring a child process running a new command, simpleperf monitors until the child process ends.
-In this case, we can use Ctrl-C to stop monitoring at any time.
-
-```sh
-# Stat process 11904 for 10 seconds.
-$ simpleperf stat -p 11904 --duration 10
-
-# Stat until the child process running `ls` finishes.
-$ simpleperf stat ls
-
-# Stop monitoring using Ctrl-C.
-$ simpleperf stat -p 11904 --duration 10
-^C
-```
-
-If you want to write a script to control how long to monitor, you can send one of SIGINT, SIGTERM,
-SIGHUP signals to simpleperf to stop monitoring.
-
-### Decide the print interval
-
-When monitoring perf counters, we can also use --interval to decide the print interval.
-
-```sh
-# Print stat for process 11904 every 300ms.
-$ simpleperf stat -p 11904 --duration 10 --interval 300
-
-# Print system wide stat at interval of 300ms for 10 seconds. Note that system wide profiling needs
-# root privilege.
-$ su 0 simpleperf stat -a --duration 10 --interval 300
-```
-
-### Display counters in systrace
-
-Simpleperf can also work with systrace to dump counters in the collected trace. Below is an example
-to do a system wide stat.
-
-```sh
-# Capture instructions (kernel only) and cache misses with interval of 300 milliseconds for 15
-# seconds.
-$ su 0 simpleperf stat -e instructions:k,cache-misses -a --interval 300 --duration 15
-# On host launch systrace to collect trace for 10 seconds.
-(HOST)$ external/chromium-trace/systrace.py --time=10 -o new.html sched gfx view
-# Open the collected new.html in browser and perf counters will be shown up.
-```
-
-## The record command
-
-The record command is used to dump samples of the profiled processes. Each sample can contain
-information like the time at which the sample was generated, the number of events since last
-sample, the program counter of a thread, the call chain of a thread.
-
-By passing options, we can select which events to use, which processes/threads to monitor,
-what frequency to dump samples, how long to monitor, and where to store samples.
-
-```sh
-# Record on process 7394 for 10 seconds, using default event (cpu-cycles), using default sample
-# frequency (4000 samples per second), writing records to perf.data.
-$ simpleperf record -p 7394 --duration 10
-simpleperf I cmd_record.cpp:316] Samples recorded: 21430. Samples lost: 0.
-```
-
-### Select events to record
-
-By default, the cpu-cycles event is used to evaluate consumed cpu cycles. But we can also use other
-events via -e.
-
-```sh
-# Record using event instructions.
-$ simpleperf record -e instructions -p 11904 --duration 10
-
-# Record using task-clock, which shows the passed CPU time in nanoseconds.
-$ simpleperf record -e task-clock -p 11904 --duration 10
-```
-
-### Select target to record
-
-The way to select target in record command is similar to that in the stat command.
-
-```sh
-# Record process 11904 and 11905.
-$ simpleperf record -p 11904,11905 --duration 10
-
-# Record thread 11904 and 11905.
-$ simpleperf record -t 11904,11905 --duration 10
-
-# Record a child process running `ls`.
-$ simpleperf record ls
-
-# Record the process of an Android application. This only works for debuggable apps on non-rooted
-# devices.
-$ simpleperf record --app com.example.simpleperf.simpleperfexamplewithnative
-
-# Record system wide.
-$ simpleperf record -a --duration 10
-```
-
-### Set the frequency to record
-
-We can set the frequency to dump records via -f or -c. For example, -f 4000 means
-dumping approximately 4000 records every second when the monitored thread runs. If a monitored
-thread runs 0.2s in one second (it can be preempted or blocked in other times), simpleperf dumps
-about 4000 * 0.2 / 1.0 = 800 records every second. Another way is using -c. For example, -c 10000
-means dumping one record whenever 10000 events happen.
-
-```sh
-# Record with sample frequency 1000: sample 1000 times every second running.
-$ simpleperf record -f 1000 -p 11904,11905 --duration 10
-
-# Record with sample period 100000: sample 1 time every 100000 events.
-$ simpleperf record -c 100000 -t 11904,11905 --duration 10
-```
-
-To avoid taking too much time generating samples, kernel >= 3.10 sets the max percent of cpu time
-used for generating samples (default is 25%), and decreases the max allowed sample frequency when
-hitting that limit. Simpleperf uses --cpu-percent option to adjust it, but it needs either root
-privilege or to be on Android >= Q.
-
-```sh
-# Record with sample frequency 10000, with max allowed cpu percent to be 50%.
-$ simpleperf record -f 1000 -p 11904,11905 --duration 10 --cpu-percent 50
-```
-
-### Decide how long to record
-
-The way to decide how long to monitor in record command is similar to that in the stat command.
-
-```sh
-# Record process 11904 for 10 seconds.
-$ simpleperf record -p 11904 --duration 10
-
-# Record until the child process running `ls` finishes.
-$ simpleperf record ls
-
-# Stop monitoring using Ctrl-C.
-$ simpleperf record -p 11904 --duration 10
-^C
-```
-
-If you want to write a script to control how long to monitor, you can send one of SIGINT, SIGTERM,
-SIGHUP signals to simpleperf to stop monitoring.
-
-### Set the path to store profiling data
-
-By default, simpleperf stores profiling data in perf.data in the current directory. But the path
-can be changed using -o.
-
-```sh
-# Write records to data/perf2.data.
-$ simpleperf record -p 11904 -o data/perf2.data --duration 10
-```
-
-#### Record call graphs
-
-A call graph is a tree showing function call relations. Below is an example.
-
-```
-main() {
- FunctionOne();
- FunctionTwo();
-}
-FunctionOne() {
- FunctionTwo();
- FunctionThree();
-}
-a call graph:
- main-> FunctionOne
- | |
- | |-> FunctionTwo
- | |-> FunctionThree
- |
- |-> FunctionTwo
-```
-
-A call graph shows how a function calls other functions, and a reversed call graph shows how
-a function is called by other functions. To show a call graph, we need to first record it, then
-report it.
-
-There are two ways to record a call graph, one is recording a dwarf based call graph, the other is
-recording a stack frame based call graph. Recording dwarf based call graphs needs support of debug
-information in native binaries. While recording stack frame based call graphs needs support of
-stack frame registers.
-
-```sh
-# Record a dwarf based call graph
-$ simpleperf record -p 11904 -g --duration 10
-
-# Record a stack frame based call graph
-$ simpleperf record -p 11904 --call-graph fp --duration 10
-```
-
-[Here](README.md#suggestions-about-recording-call-graphs) are some suggestions about recording call graphs.
-
-### Record both on CPU time and off CPU time
-
-Simpleperf is a CPU profiler, it generates samples for a thread only when it is running on a CPU.
-However, sometimes we want to figure out where the time of a thread is spent, whether it is running
-on a CPU, or staying in the kernel's ready queue, or waiting for something like I/O events.
-
-To support this, the record command uses --trace-offcpu to trace both on CPU time and off CPU time.
-When --trace-offcpu is used, simpleperf generates a sample when a running thread is scheduled out,
-so we know the callstack of a thread when it is scheduled out. And when reporting a perf.data
-generated with --trace-offcpu, we use time to the next sample (instead of event counts from the
-previous sample) as the weight of the current sample. As a result, we can get a call graph based
-on timestamps, including both on CPU time and off CPU time.
-
-trace-offcpu is implemented using sched:sched_switch tracepoint event, which may not be supported
-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.
-
-```sh
-$ simpleperf list --show-features
-dwarf-based-call-graph
-trace-offcpu
-```
-
-If trace-offcpu is supported, it will be shown in the feature list. Then we can try it.
-
-```sh
-# Record with --trace-offcpu.
-$ simpleperf record -g -p 11904 --duration 10 --trace-offcpu
-
-# Record with --trace-offcpu using app_profiler.py.
-$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative -a .SleepActivity \
- -r "-g -e task-clock:u -f 1000 --duration 10 --trace-offcpu"
-```
-
-Below is an example comparing the profiling result with / without --trace-offcpu.
-First we record without --trace-offcpu.
-
-```sh
-$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative -a .SleepActivity
-
-$ python report_html.py --add_disassembly --add_source_code --source_dirs ../demo
-```
-
-The result is [here](./without_trace_offcpu.html).
-In the result, all time is taken by RunFunction(), and sleep time is ignored.
-But if we add --trace-offcpu, the result changes.
-
-```sh
-$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative -a .SleepActivity \
- -r "-g -e task-clock:u --trace-offcpu -f 1000 --duration 10"
-
-$ python report_html.py --add_disassembly --add_source_code --source_dirs ../demo
-```
-
-The result is [here](./trace_offcpu.html).
-In the result, half of the time is taken by RunFunction(), and the other half is taken by
-SleepFunction(). So it traces both on CPU time and off CPU time.
-
-## The report command
-
-The report command is used to report profiling data generated by the record command. The report
-contains a table of sample entries. Each sample entry is a row in the report. The report command
-groups samples belong to the same process, thread, library, function in the same sample entry. Then
-sort the sample entries based on the event count a sample entry has.
-
-By passing options, we can decide how to filter out uninteresting samples, how to group samples
-into sample entries, and where to find profiling data and binaries.
-
-Below is an example. Records are grouped into 4 sample entries, each entry is a row. There are
-several columns, each column shows piece of information belonging to a sample entry. The first
-column is Overhead, which shows the percentage of events inside the current sample entry in total
-events. As the perf event is cpu-cycles, the overhead is the percentage of CPU cycles used in each
-function.
-
-```sh
-# Reports perf.data, using only records sampled in libsudo-game-jni.so, grouping records using
-# thread name(comm), process id(pid), thread id(tid), function name(symbol), and showing sample
-# count for each row.
-$ simpleperf report --dsos /data/app/com.example.sudogame-2/lib/arm64/libsudo-game-jni.so \
- --sort comm,pid,tid,symbol -n
-Cmdline: /data/data/com.example.sudogame/simpleperf record -p 7394 --duration 10
-Arch: arm64
-Event: cpu-cycles (type 0, config 0)
-Samples: 28235
-Event count: 546356211
-
-Overhead Sample Command Pid Tid Symbol
-59.25% 16680 sudogame 7394 7394 checkValid(Board const&, int, int)
-20.42% 5620 sudogame 7394 7394 canFindSolution_r(Board&, int, int)
-13.82% 4088 sudogame 7394 7394 randomBlock_r(Board&, int, int, int, int, int)
-6.24% 1756 sudogame 7394 7394 @plt
-```
-
-### Set the path to read profiling data
-
-By default, the report command reads profiling data from perf.data in the current directory.
-But the path can be changed using -i.
-
-```sh
-$ simpleperf report -i data/perf2.data
-```
-
-### Set the path to find binaries
-
-To report function symbols, simpleperf needs to read executable binaries used by the monitored
-processes to get symbol table and debug information. By default, the paths are the executable
-binaries used by monitored processes while recording. However, these binaries may not exist when
-reporting or not contain symbol table and debug information. So we can use --symfs to redirect
-the paths.
-
-```sh
-# In this case, when simpleperf wants to read executable binary /A/b, it reads file in /A/b.
-$ simpleperf report
-
-# In this case, when simpleperf wants to read executable binary /A/b, it prefers file in
-# /debug_dir/A/b to file in /A/b.
-$ simpleperf report --symfs /debug_dir
-
-# Read symbols for system libraries built locally. Note that this is not needed since Android O,
-# which ships symbols for system libraries on device.
-$ simpleperf report --symfs $ANDROID_PRODUCT_OUT/symbols
-```
-
-### Filter samples
-
-When reporting, it happens that not all records are of interest. The report command supports four
-filters to select samples of interest.
-
-```sh
-# Report records in threads having name sudogame.
-$ simpleperf report --comms sudogame
-
-# Report records in process 7394 or 7395
-$ simpleperf report --pids 7394,7395
-
-# Report records in thread 7394 or 7395.
-$ simpleperf report --tids 7394,7395
-
-# Report records in libsudo-game-jni.so.
-$ simpleperf report --dsos /data/app/com.example.sudogame-2/lib/arm64/libsudo-game-jni.so
-```
-
-### Group samples into sample entries
-
-The report command uses --sort to decide how to group sample entries.
-
-```sh
-# Group records based on their process id: records having the same process id are in the same
-# sample entry.
-$ simpleperf report --sort pid
-
-# Group records based on their thread id and thread comm: records having the same thread id and
-# thread name are in the same sample entry.
-$ simpleperf report --sort tid,comm
-
-# Group records based on their binary and function: records in the same binary and function are in
-# the same sample entry.
-$ simpleperf report --sort dso,symbol
-
-# Default option: --sort comm,pid,tid,dso,symbol. Group records in the same thread, and belong to
-# the same function in the same binary.
-$ simpleperf report
-```
-
-#### Report call graphs
-
-To report a call graph, please make sure the profiling data is recorded with call graphs,
-as [here](#record-call-graphs).
-
-```
-$ simpleperf report -g
-```
diff --git a/simpleperf/doc/scripts_reference.md b/simpleperf/doc/scripts_reference.md
deleted file mode 100644
index 746da76a..00000000
--- a/simpleperf/doc/scripts_reference.md
+++ /dev/null
@@ -1,233 +0,0 @@
-# Scripts reference
-
-## Table of Contents
-
-- [app_profiler.py](#app_profilerpy)
- - [Profile from launch of an application](#profile-from-launch-of-an-application)
-- [run_simpleperf_without_usb_connection.py](#run_simpleperf_without_usb_connectionpy)
-- [binary_cache_builder.py](#binary_cache_builderpy)
-- [run_simpleperf_on_device.py](#run_simpleperf_on_devicepy)
-- [report.py](#reportpy)
-- [report_html.py](#report_htmlpy)
-- [inferno](#inferno)
-- [pprof_proto_generator.py](#pprof_proto_generatorpy)
-- [report_sample.py](#report_samplepy)
-- [simpleperf_report_lib.py](#simpleperf_report_libpy)
-
-
-## app_profiler.py
-
-app_profiler.py is used to record profiling data for Android applications and native executables.
-
-```sh
-# Record an Android application.
-$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative
-
-# Record an Android application with Java code compiled into native instructions.
-$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative --compile_java_code
-
-# Record the launch of an Activity of an Android application.
-$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative -a .SleepActivity
-
-# Record a native process.
-$ python app_profiler.py -np surfaceflinger
-
-# Record a native process given its pid.
-$ python app_profiler.py --pid 11324
-
-# Record a command.
-$ python app_profiler.py -cmd \
- "dex2oat --dex-file=/data/local/tmp/app-profiling.apk --oat-file=/data/local/tmp/a.oat"
-
-# Record an Android application, and use -r to send custom options to the record command.
-$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative \
- -r "-e cpu-clock -g --duration 30"
-
-# Record both on CPU time and off CPU time.
-$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative \
- -r "-e task-clock -g -f 1000 --duration 10 --trace-offcpu"
-
-# Save profiling data in a custom file (like perf_custom.data) instead of perf.data.
-$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative -o perf_custom.data
-```
-
-### Profile from launch of an application
-
-Sometimes we want to profile the launch-time of an application. To support this, we added --app in
-the record command. The --app option sets the package name of the Android application to profile.
-If the app is not already running, the record command will poll for the app process in a loop with
-an interval of 1ms. So to profile from launch of an application, we can first start the record
-command with --app, then start the app. Below is an example.
-
-```sh
-$ python run_simpleperf_on_device.py record
- --app com.example.simpleperf.simpleperfexamplewithnative \
- -g --duration 1 -o /data/local/tmp/perf.data
-# Start the app manually or using the `am` command.
-```
-
-To make it convenient to use, app_profiler.py supports using the -a option to start an Activity
-after recording has started.
-
-```sh
-$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative -a .MainActivity
-```
-
-### run_simpleperf_without_usb_connection.py
-
-run_simpleperf_without_usb_connection.py records profiling data while the USB cable isn't
-connected. Below is an example.
-
-```sh
-$ python run_simpleperf_without_usb_connection.py start \
- -p com.example.simpleperf.simpleperfexamplewithnative
-# After the command finishes successfully, unplug the USB cable, run the
-# SimpleperfExampleWithNative app. After a few seconds, plug in the USB cable.
-$ python run_simpleperf_without_usb_connection.py stop
-# It may take a while to stop recording. After that, the profiling data is collected in perf.data
-# on host.
-```
-
-## binary_cache_builder.py
-
-The binary_cache directory is a directory holding binaries needed by a profiling data file. The
-binaries are expected to be unstripped, having debug information and symbol tables. The
-binary_cache directory is used by report scripts to read symbols of binaries. It is also used by
-report_html.py to generate annotated source code and disassembly.
-
-By default, app_profiler.py builds the binary_cache directory after recording. But we can also
-build binary_cache for existing profiling data files using binary_cache_builder.py. It is useful
-when you record profiling data using `simpleperf record` directly, to do system wide profiling or
-record without the USB cable connected.
-
-binary_cache_builder.py can either pull binaries from an Android device, or find binaries in
-directories on the host (via -lib).
-
-```sh
-# Generate binary_cache for perf.data, by pulling binaries from the device.
-$ python binary_cache_builder.py
-
-# Generate binary_cache, by pulling binaries from the device and finding binaries in
-# SimpleperfExampleWithNative.
-$ python binary_cache_builder.py -lib path_of_SimpleperfExampleWithNative
-```
-
-## run_simpleperf_on_device.py
-
-This script pushes the simpleperf executable on the device, and run a simpleperf command on the
-device. It is more convenient than running adb commands manually.
-
-## report.py
-
-report.py is a wrapper of the report command on the host. It accepts all options of the report
-command.
-
-```sh
-# Report call graph
-$ python report.py -g
-
-# Report call graph in a GUI window implemented by Python Tk.
-$ python report.py -g --gui
-```
-
-## report_html.py
-
-report_html.py generates report.html based on the profiling data. Then the report.html can show
-the profiling result without depending on other files. So it can be shown in local browsers or
-passed to other machines. Depending on which command-line options are used, the content of the
-report.html can include: chart statistics, sample table, flamegraphs, annotated source code for
-each function, annotated disassembly for each function.
-
-```sh
-# Generate chart statistics, sample table and flamegraphs, based on perf.data.
-$ python report_html.py
-
-# Add source code.
-$ python report_html.py --add_source_code --source_dirs path_of_SimpleperfExampleWithNative
-
-# Add disassembly.
-$ python report_html.py --add_disassembly
-
-# Adding disassembly for all binaries can cost a lot of time. So we can choose to only add
-# disassembly for selected binaries.
-$ python report_html.py --add_disassembly --binary_filter libgame.so
-
-# report_html.py accepts more than one recording data file.
-$ python report_html.py -i perf1.data perf2.data
-```
-
-Below is an example of generating html profiling results for SimpleperfExampleWithNative.
-
-```sh
-$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative
-$ python report_html.py --add_source_code --source_dirs path_of_SimpleperfExampleWithNative \
- --add_disassembly
-```
-
-After opening the generated [report.html](./report_html.html) in a browser, there are several tabs:
-
-The first tab is "Chart Statistics". You can click the pie chart to show the time consumed by each
-process, thread, library and function.
-
-The second tab is "Sample Table". It shows the time taken by each function. By clicking one row in
-the table, we can jump to a new tab called "Function".
-
-The third tab is "Flamegraph". It shows the graphs generated by [inferno](./inferno.md).
-
-The fourth tab is "Function". It only appears when users click a row in the "Sample Table" tab.
-It shows information of a function, including:
-
-1. A flamegraph showing functions called by that function.
-2. A flamegraph showing functions calling that function.
-3. Annotated source code of that function. It only appears when there are source code files for
- that function.
-4. Annotated disassembly of that function. It only appears when there are binaries containing that
- function.
-
-## inferno
-
-[inferno](./inferno.md) is a tool used to generate flamegraph in a html file.
-
-```sh
-# Generate flamegraph based on perf.data.
-# On Windows, use inferno.bat instead of ./inferno.sh.
-$ ./inferno.sh -sc --record_file perf.data
-
-# Record a native program and generate flamegraph.
-$ ./inferno.sh -np surfaceflinger
-```
-
-## pprof_proto_generator.py
-
-It converts a profiling data file into pprof.proto, a format used by [pprof](https://github.com/google/pprof).
-
-```sh
-# Convert perf.data in the current directory to pprof.proto format.
-$ python pprof_proto_generator.py
-$ pprof -pdf pprof.profile
-```
-
-## report_sample.py
-
-It converts a profiling data file into a format used by [FlameGraph](https://github.com/brendangregg/FlameGraph).
-
-```sh
-# Convert perf.data in the current directory to a format used by FlameGraph.
-$ python report_sample.py --symfs binary_cache >out.perf
-$ git clone https://github.com/brendangregg/FlameGraph.git
-$ FlameGraph/stackcollapse-perf.pl out.perf >out.folded
-$ FlameGraph/flamegraph.pl out.folded >a.svg
-```
-
-## simpleperf_report_lib.py
-
-simpleperf_report_lib.py is a Python library used to parse profiling data files generated by the
-record command. Internally, it uses libsimpleperf_report.so to do the work. Generally, for each
-profiling data file, we create an instance of ReportLib, pass it the file path (via SetRecordFile).
-Then we can read all samples through GetNextSample(). For each sample, we can read its event info
-(via GetEventOfCurrentSample), symbol info (via GetSymbolOfCurrentSample) and call chain info
-(via GetCallChainOfCurrentSample). We can also get some global information, like record options
-(via GetRecordCmd), the arch of the device (via GetArch) and meta strings (via MetaInfo).
-
-Examples of using simpleperf_report_lib.py are in report_sample.py, report_html.py,
-pprof_proto_generator.py and inferno/inferno.py.
diff --git a/simpleperf/dso.cpp b/simpleperf/dso.cpp
index 813a66ec..07a10280 100644
--- a/simpleperf/dso.cpp
+++ b/simpleperf/dso.cpp
@@ -111,6 +111,15 @@ std::string DebugElfFileFinder::FindDebugFile(const std::string& dso_path, bool
return vdso_32bit_;
}
}
+ // 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()) {
+ return it->second;
+ }
+ }
+ }
auto check_path = [&](const std::string& path) {
BuildId debug_build_id;
if (GetBuildIdFromDsoPath(path, &debug_build_id)) {
@@ -123,15 +132,6 @@ std::string DebugElfFileFinder::FindDebugFile(const std::string& dso_path, bool
return false;
};
- // 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)) {
- return it->second;
- }
- }
- }
// 2. Try concatenating symfs_dir and dso_path.
if (!symfs_dir_.empty()) {
std::string path = GetPathInSymFsDir(dso_path);
diff --git a/simpleperf/dso_test.cpp b/simpleperf/dso_test.cpp
index 611984d9..04d039a2 100644
--- a/simpleperf/dso_test.cpp
+++ b/simpleperf/dso_test.cpp
@@ -46,14 +46,6 @@ TEST(DebugElfFileFinder, use_build_id_list) {
unlink(build_id_list_file.c_str());
}
-static std::string ConvertPathSeparator(const std::string& path) {
- std::string result = path;
- if (OS_PATH_SEPARATOR != '/') {
- std::replace(result.begin(), result.end(), '/', OS_PATH_SEPARATOR);
- }
- return result;
-}
-
TEST(DebugElfFileFinder, concatenating_symfs_dir) {
DebugElfFileFinder finder;
ASSERT_TRUE(finder.SetSymFsDir(GetTestDataDir()));
@@ -65,7 +57,8 @@ TEST(DebugElfFileFinder, concatenating_symfs_dir) {
BuildId build_id(ELF_FILE_BUILD_ID);
ASSERT_EQ(finder.FindDebugFile(ELF_FILE, false, build_id), GetTestDataDir() + ELF_FILE);
std::string native_lib_in_apk = APK_FILE + "!/" + NATIVELIB_IN_APK;
- std::string apk_path = ConvertPathSeparator(APK_FILE);
+ std::string apk_path = APK_FILE;
+ std::replace(apk_path.begin(), apk_path.end(), '/', OS_PATH_SEPARATOR);
ASSERT_EQ(finder.FindDebugFile(native_lib_in_apk, false, native_lib_build_id),
GetTestDataDir() + apk_path + "!/" + NATIVELIB_IN_APK);
}
@@ -85,27 +78,13 @@ TEST(DebugElfFileFinder, add_symbol_dir) {
DebugElfFileFinder finder;
ASSERT_FALSE(finder.AddSymbolDir(GetTestDataDir() + "dir_not_exist"));
ASSERT_EQ(finder.FindDebugFile("elf", false, CHECK_ELF_FILE_BUILD_ID), "elf");
- std::string symfs_dir = ConvertPathSeparator(GetTestDataDir() + CORRECT_SYMFS_FOR_BUILD_ID_CHECK);
+ std::string symfs_dir = GetTestDataDir() + CORRECT_SYMFS_FOR_BUILD_ID_CHECK;
+ std::replace(symfs_dir.begin(), symfs_dir.end(), '/', OS_PATH_SEPARATOR);
ASSERT_TRUE(finder.AddSymbolDir(symfs_dir));
ASSERT_EQ(finder.FindDebugFile("elf", false, CHECK_ELF_FILE_BUILD_ID),
symfs_dir + OS_PATH_SEPARATOR + "elf_for_build_id_check");
}
-TEST(DebugElfFileFinder, build_id_list) {
- DebugElfFileFinder finder;
- // Find file in symfs dir with correct build_id_list.
- std::string symfs_dir = ConvertPathSeparator(GetTestDataDir() + "data/symfs_with_build_id_list");
- ASSERT_TRUE(finder.SetSymFsDir(symfs_dir));
- ASSERT_EQ(finder.FindDebugFile("elf", false, CHECK_ELF_FILE_BUILD_ID),
- symfs_dir + OS_PATH_SEPARATOR + "elf_for_build_id_check");
-
- // Find file in symfs_dir with wrong build_id_list.
- symfs_dir = ConvertPathSeparator(GetTestDataDir() + "data/symfs_with_wrong_build_id_list");
- finder.Reset();
- ASSERT_TRUE(finder.SetSymFsDir(symfs_dir));
- ASSERT_EQ(finder.FindDebugFile("elf", false, CHECK_ELF_FILE_BUILD_ID), "elf");
-}
-
TEST(dso, dex_file_dso) {
#if defined(__linux__)
for (DsoType dso_type : {DSO_DEX_FILE, DSO_ELF_FILE}) {
diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp
index 86766921..3591626e 100644
--- a/simpleperf/environment.cpp
+++ b/simpleperf/environment.cpp
@@ -342,6 +342,10 @@ bool CheckPerfEventLimit() {
// enough permission to create inherited tracepoint events, write -1 to perf_event_paranoid.
// See http://b/62230699.
if (IsRoot()) {
+ char* env = getenv("PERFPROFD_DISABLE_PERF_EVENT_PARANOID_CHANGE");
+ if (env != nullptr && strcmp(env, "1") == 0) {
+ return true;
+ }
return android::base::WriteStringToFile("-1", "/proc/sys/kernel/perf_event_paranoid");
}
int limit_level;
diff --git a/simpleperf/event_attr.cpp b/simpleperf/event_attr.cpp
index ecc08439..09efa31b 100644
--- a/simpleperf/event_attr.cpp
+++ b/simpleperf/event_attr.cpp
@@ -133,7 +133,6 @@ void DumpPerfEventAttr(const perf_event_attr& attr, size_t indent) {
PrintIndented(indent + 1, "sample_id_all %u, exclude_host %u, exclude_guest %u\n",
attr.sample_id_all, attr.exclude_host, attr.exclude_guest);
- PrintIndented(indent + 1, "config2 0x%llx\n", attr.config2);
PrintIndented(indent + 1, "branch_sample_type 0x%" PRIx64 "\n", attr.branch_sample_type);
PrintIndented(indent + 1, "exclude_callchain_kernel %u, exclude_callchain_user %u\n",
attr.exclude_callchain_kernel, attr.exclude_callchain_user);
@@ -230,10 +229,7 @@ bool IsCpuSupported(const perf_event_attr& attr) {
std::string GetEventNameByAttr(const perf_event_attr& attr) {
for (const auto& event_type : GetAllEventTypes()) {
- // 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))) {
+ if (event_type.type == attr.type && event_type.config == attr.config) {
std::string name = event_type.name;
if (attr.exclude_user && !attr.exclude_kernel) {
name += ":k";
diff --git a/simpleperf/event_fd.cpp b/simpleperf/event_fd.cpp
index e4dfab54..0bfb8040 100644
--- a/simpleperf/event_fd.cpp
+++ b/simpleperf/event_fd.cpp
@@ -95,7 +95,6 @@ std::unique_ptr<EventFd> EventFd::OpenEventFile(const perf_event_attr& attr,
EventFd::~EventFd() {
DestroyMappedBuffer();
- DestroyAuxBuffer();
close(perf_event_fd_);
}
@@ -124,14 +123,6 @@ bool EventFd::SetEnableEvent(bool enable) {
return true;
}
-bool EventFd::SetFilter(const std::string& filter) {
- bool success = ioctl(perf_event_fd_, PERF_EVENT_IOC_SET_FILTER, filter.c_str()) >= 0;
- if (!success) {
- PLOG(ERROR) << "failed to set filter";
- }
- return success;
-}
-
bool EventFd::InnerReadCounter(PerfCounter* counter) const {
CHECK(counter != nullptr);
if (!android::base::ReadFully(perf_event_fd_, counter, sizeof(*counter))) {
@@ -244,9 +235,7 @@ size_t EventFd::GetAvailableMmapDataSize(size_t& data_pos) {
uint64_t write_head = mmap_metadata_page_->data_head;
uint64_t read_head = mmap_metadata_page_->data_tail;
- // The kernel may decrease data_head temporarily (http://b/132446871), making
- // write_head < read_head. So check it to avoid available data size underflow.
- if (write_head <= read_head) {
+ if (write_head == read_head) {
// No available data.
return 0;
}
@@ -262,66 +251,6 @@ void EventFd::DiscardMmapData(size_t discard_size) {
mmap_metadata_page_->data_tail += discard_size;
}
-bool EventFd::CreateAuxBuffer(size_t aux_buffer_size, bool report_error) {
- CHECK(HasMappedBuffer());
- CHECK(IsPowerOfTwo(aux_buffer_size));
- mmap_metadata_page_->aux_offset = mmap_len_;
- mmap_metadata_page_->aux_size = aux_buffer_size;
- mmap_metadata_page_->aux_head = 0;
- mmap_metadata_page_->aux_tail = 0;
- void* mmap_addr = mmap(nullptr, aux_buffer_size, PROT_READ | PROT_WRITE, MAP_SHARED,
- perf_event_fd_, mmap_metadata_page_->aux_offset);
- if (mmap_addr == MAP_FAILED) {
- if (report_error) {
- PLOG(ERROR) << "failed to mmap aux buffer of size " << aux_buffer_size << " for " << Name();
- } else {
- PLOG(DEBUG) << "failed to mmap aux buffer of size " << aux_buffer_size << " for " << Name();
- }
- return false;
- }
- aux_buffer_ = static_cast<char*>(mmap_addr);
- aux_buffer_size_ = aux_buffer_size;
- return true;
-}
-
-void EventFd::DestroyAuxBuffer() {
- if (HasAuxBuffer()) {
- munmap(aux_buffer_, aux_buffer_size_);
- aux_buffer_ = nullptr;
- aux_buffer_size_ = 0;
- }
-}
-
-uint64_t EventFd::GetAvailableAuxData(char** buf1, size_t* size1, char** buf2, size_t* size2) {
- // Aux buffer is similar to mapped_data_buffer. See comments in GetAvailableMmapData().
- uint64_t write_head = mmap_metadata_page_->aux_head;
- uint64_t read_head = mmap_metadata_page_->aux_tail;
- if (write_head <= read_head) {
- *size1 = *size2 = 0;
- return 0; // No available data.
- }
- // rmb() used to ensure reading data after reading aux_head.
- __sync_synchronize();
- size_t data_pos = read_head & (aux_buffer_size_ - 1);
- size_t data_size = write_head - read_head;
- *buf1 = aux_buffer_ + data_pos;
- if (data_size <= aux_buffer_size_ - data_pos) {
- *size1 = data_size;
- *size2 = 0;
- } else {
- *size1 = aux_buffer_size_ - data_pos;
- *buf2 = aux_buffer_;
- *size2 = data_size - *size1;
- }
- return read_head;
-}
-
-void EventFd::DiscardAuxData(size_t discard_size) {
- // mb() used to ensure finish reading data before writing aux_tail.
- __sync_synchronize();
- mmap_metadata_page_->aux_tail += discard_size;
-}
-
bool EventFd::StartPolling(IOEventLoop& loop,
const std::function<bool()>& callback) {
ioevent_ref_ = loop.AddReadEvent(perf_event_fd_, callback);
diff --git a/simpleperf/event_fd.h b/simpleperf/event_fd.h
index 0b3b0e69..e4e20ce4 100644
--- a/simpleperf/event_fd.h
+++ b/simpleperf/event_fd.h
@@ -59,7 +59,6 @@ class EventFd {
// It tells the kernel to start counting and recording events specified by
// this file.
bool SetEnableEvent(bool enable);
- bool SetFilter(const std::string& filter);
bool ReadCounter(PerfCounter* counter);
@@ -87,21 +86,6 @@ class EventFd {
// Discard the size of the data we have read, so the kernel can reuse the space for new data.
virtual void DiscardMmapData(size_t discard_size);
- // Manage the aux buffer, which receive auxiliary data sent by the kernel.
- // aux_buffer_size: should be power of two, and mod PAGE_SIZE is zero.
- virtual bool CreateAuxBuffer(size_t aux_buffer_size, bool report_error);
- bool HasAuxBuffer() const { return aux_buffer_size_ != 0; }
- virtual void DestroyAuxBuffer();
-
- // Get available aux data, which can appear in one or two continuous buffers.
- // buf1: return pointer to the first buffer
- // size1: return data size in the first buffer
- // buf2: return pointer to the second buffer
- // size2: return data size in the second buffer
- // Return value: return how many bytes of aux data has been read before.
- virtual uint64_t GetAvailableAuxData(char** buf1, size_t* size1, char** buf2, size_t* size2);
- virtual void DiscardAuxData(size_t discard_size);
-
// [callback] is called when there is data available in the mapped buffer.
virtual bool StartPolling(IOEventLoop& loop, const std::function<bool()>& callback);
virtual bool StopPolling();
@@ -134,14 +118,10 @@ class EventFd {
void* mmap_addr_;
size_t mmap_len_;
- // the first page of mapped area, whose content can be changed by the kernel at any time
- volatile perf_event_mmap_page* mmap_metadata_page_;
- // starting from the second page of mapped area, containing records written by the kernel
- char* mmap_data_buffer_;
+ perf_event_mmap_page* mmap_metadata_page_; // The first page of mmap_area.
+ char* mmap_data_buffer_; // Starting from the second page of mmap_area,
+ // containing records written by then kernel.
size_t mmap_data_buffer_size_;
- // receiving auxiliary data (like instruction tracing data generated by etm) from the kernel
- char* aux_buffer_ = nullptr;
- size_t aux_buffer_size_ = 0;
IOEventRef ioevent_ref_;
diff --git a/simpleperf/event_selection_set.cpp b/simpleperf/event_selection_set.cpp
index c7c7a38a..3fb53df9 100644
--- a/simpleperf/event_selection_set.cpp
+++ b/simpleperf/event_selection_set.cpp
@@ -23,7 +23,6 @@
#include <android-base/logging.h>
#include "environment.h"
-#include "ETMRecorder.h"
#include "event_attr.h"
#include "event_type.h"
#include "IOEventLoop.h"
@@ -31,8 +30,6 @@
#include "utils.h"
#include "RecordReadThread.h"
-using namespace simpleperf;
-
bool IsBranchSamplingSupported() {
const EventType* type = FindEventTypeByName("cpu-cycles");
if (type == nullptr) {
@@ -162,23 +159,11 @@ bool EventSelectionSet::BuildAndCheckEventSelection(const std::string& event_nam
selection->event_attr.exclude_host = event_type->exclude_host;
selection->event_attr.exclude_guest = event_type->exclude_guest;
selection->event_attr.precise_ip = event_type->precise_ip;
- if (IsEtmEventType(event_type->event_type.type)) {
- auto& etm_recorder = ETMRecorder::GetInstance();
- if (!etm_recorder.CheckEtmSupport()) {
- return false;
- }
- ETMRecorder::GetInstance().SetEtmPerfEventAttr(&selection->event_attr);
- }
bool set_default_sample_freq = false;
if (!for_stat_cmd_) {
if (event_type->event_type.type == PERF_TYPE_TRACEPOINT) {
selection->event_attr.freq = 0;
selection->event_attr.sample_period = DEFAULT_SAMPLE_PERIOD_FOR_TRACEPOINT_EVENT;
- } else if (IsEtmEventType(event_type->event_type.type)) {
- // ETM recording has no sample frequency to adjust. Using sample frequency only wastes time
- // enabling/disabling etm devices. So don't adjust frequency by default.
- selection->event_attr.freq = 0;
- selection->event_attr.sample_period = 1;
} else {
selection->event_attr.freq = 1;
// Set default sample freq here may print msg "Adjust sample freq to max allowed sample
@@ -233,9 +218,6 @@ bool EventSelectionSet::AddEventGroup(
if (!BuildAndCheckEventSelection(event_name, first_event, &selection)) {
return false;
}
- if (IsEtmEventType(selection.event_attr.type)) {
- has_aux_trace_ = true;
- }
first_event = false;
group.push_back(std::move(selection));
}
@@ -570,7 +552,7 @@ bool EventSelectionSet::OpenEventFiles(const std::vector<int>& on_cpus) {
}
}
}
- return ApplyFilters();
+ return true;
}
bool EventSelectionSet::IsUserSpaceSamplerGroup(EventSelectionGroup& group) {
@@ -596,47 +578,6 @@ bool EventSelectionSet::OpenUserSpaceSamplersOnGroup(EventSelectionGroup& group,
return true;
}
-bool EventSelectionSet::ApplyFilters() {
- if (include_filters_.empty()) {
- return true;
- }
- if (!has_aux_trace_) {
- LOG(ERROR) << "include 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;
- 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);
- if (!filter_str.empty()) {
- filter_str += ',';
- }
- android::base::StringAppendF(&filter_str, "filter 0/%" PRIu64 "@%s", file_size, path.c_str());
- }
- for (auto& group : groups_) {
- for (auto& selection : group) {
- if (IsEtmEventType(selection.event_type_modifier.event_type.type)) {
- for (auto& event_fd : selection.event_fds) {
- if (!event_fd->SetFilter(filter_str)) {
- return false;
- }
- }
- }
- }
- }
- return true;
-}
-
static bool ReadCounter(EventFd* event_fd, CounterInfo* counter) {
if (!event_fd->ReadCounter(&counter->counter)) {
return false;
@@ -669,11 +610,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) {
- 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));
+ size_t record_buffer_size) {
+ record_read_thread_.reset(new simpleperf::RecordReadThread(
+ record_buffer_size, groups_[0][0].event_attr, min_mmap_pages, max_mmap_pages));
return true;
}
@@ -765,6 +704,11 @@ bool EventSelectionSet::FinishReadMmapEventData() {
return loop_->RunLoop();
}
+void EventSelectionSet::GetLostRecords(size_t* lost_samples, size_t* lost_non_samples,
+ size_t* cut_stack_samples) {
+ record_read_thread_->GetLostRecords(lost_samples, lost_non_samples, cut_stack_samples);
+}
+
bool EventSelectionSet::HandleCpuHotplugEvents(const std::vector<int>& monitored_cpus,
double check_interval_in_sec) {
monitored_cpus_.insert(monitored_cpus.begin(), monitored_cpus.end());
diff --git a/simpleperf/event_selection_set.h b/simpleperf/event_selection_set.h
index 180213f5..d92fed50 100644
--- a/simpleperf/event_selection_set.h
+++ b/simpleperf/event_selection_set.h
@@ -32,7 +32,10 @@
#include "IOEventLoop.h"
#include "perf_event.h"
#include "record.h"
-#include "RecordReadThread.h"
+
+namespace simpleperf {
+ class RecordReadThread;
+}
constexpr double DEFAULT_PERIOD_TO_DETECT_CPU_HOTPLUG_EVENTS_IN_SEC = 0.5;
constexpr double DEFAULT_PERIOD_TO_CHECK_MONITORED_TARGETS_IN_SEC = 1;
@@ -93,7 +96,6 @@ class EventSelectionSet {
std::vector<const EventType*> GetEvents() const;
std::vector<const EventType*> GetTracepointEvents() const;
bool ExcludeKernel() const;
- bool HasAuxTrace() const { return has_aux_trace_; }
bool HasInplaceSampler() const;
std::vector<EventAttrWithId> GetEventAttrWithId() const;
@@ -109,9 +111,6 @@ 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 AddMonitoredProcesses(const std::set<pid_t>& processes) {
processes_.insert(processes.begin(), processes.end());
@@ -135,15 +134,11 @@ class EventSelectionSet {
bool OpenEventFiles(const std::vector<int>& on_cpus);
bool ReadCounters(std::vector<CountersInfo>* counters);
- bool MmapEventFiles(size_t min_mmap_pages, size_t max_mmap_pages, size_t aux_buffer_size,
- size_t record_buffer_size, bool allow_cutting_samples);
+ bool MmapEventFiles(size_t min_mmap_pages, size_t max_mmap_pages, size_t record_buffer_size);
bool PrepareToReadMmapEventData(const std::function<bool(Record*)>& callback);
bool SyncKernelBuffer();
bool FinishReadMmapEventData();
-
- const simpleperf::RecordStat& GetRecordStat() {
- return record_read_thread_->GetStat();
- }
+ void GetLostRecords(size_t* lost_samples, size_t* lost_non_samples, size_t* cut_stack_samples);
// If monitored_cpus is empty, monitor all cpus.
bool HandleCpuHotplugEvents(const std::vector<int>& monitored_cpus,
@@ -175,7 +170,6 @@ class EventSelectionSet {
const std::map<pid_t, std::set<pid_t>>& process_map);
bool OpenEventFilesOnGroup(EventSelectionGroup& group, pid_t tid, int cpu,
std::string* failed_event_type);
- bool ApplyFilters();
bool ReadMmapEventData(bool with_time_limit);
bool DetectCpuHotplugEvents();
@@ -199,9 +193,6 @@ class EventSelectionSet {
std::unique_ptr<simpleperf::RecordReadThread> record_read_thread_;
- bool has_aux_trace_ = false;
- std::vector<std::string> include_filters_;
-
DISALLOW_COPY_AND_ASSIGN(EventSelectionSet);
};
diff --git a/simpleperf/event_type.cpp b/simpleperf/event_type.cpp
index ec7ec8ef..ed323791 100644
--- a/simpleperf/event_type.cpp
+++ b/simpleperf/event_type.cpp
@@ -28,12 +28,9 @@
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
-#include "ETMRecorder.h"
#include "event_attr.h"
#include "utils.h"
-using namespace simpleperf;
-
#define EVENT_TYPE_TABLE_ENTRY(name, type, config, description, limited_arch) \
{name, type, config, description, limited_arch},
@@ -42,8 +39,6 @@ static const std::vector<EventType> static_event_type_array = {
};
static std::string tracepoint_events;
-static std::set<EventType> g_event_types;
-static uint32_t g_etm_event_type;
bool SetTracepointEventsFilePath(const std::string& filepath) {
if (!android::base::ReadFileToString(filepath, &tracepoint_events)) {
@@ -115,6 +110,8 @@ static std::vector<EventType> GetTracepointEventTypes() {
return result;
}
+static std::set<EventType> g_event_types;
+
std::string ScopedEventTypes::BuildString(const std::vector<const EventType*>& event_types) {
std::string result;
for (auto type : event_types) {
@@ -129,23 +126,18 @@ std::string ScopedEventTypes::BuildString(const std::vector<const EventType*>& e
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();
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, "", "");
}
}
ScopedEventTypes::~ScopedEventTypes() {
g_event_types = std::move(saved_event_types_);
- g_etm_event_type = saved_etm_event_type_;
}
const std::set<EventType>& GetAllEventTypes() {
@@ -153,13 +145,6 @@ const std::set<EventType>& GetAllEventTypes() {
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());
-#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;
}
@@ -259,7 +244,3 @@ std::unique_ptr<EventTypeAndModifier> ParseEventType(const std::string& event_ty
event_type_modifier->modifier = modifier;
return event_type_modifier;
}
-
-bool IsEtmEventType(uint32_t type) {
- return g_etm_event_type != 0 && type == g_etm_event_type;
-} \ No newline at end of file
diff --git a/simpleperf/event_type.h b/simpleperf/event_type.h
index 4421dfc8..d432cb32 100644
--- a/simpleperf/event_type.h
+++ b/simpleperf/event_type.h
@@ -71,7 +71,6 @@ class ScopedEventTypes {
private:
std::set<EventType> saved_event_types_;
- uint32_t saved_etm_event_type_;
};
const std::set<EventType>& GetAllEventTypes();
@@ -99,6 +98,5 @@ struct EventTypeAndModifier {
};
std::unique_ptr<EventTypeAndModifier> ParseEventType(const std::string& event_type_str);
-bool IsEtmEventType(uint32_t type);
#endif // SIMPLE_PERF_EVENT_H_
diff --git a/simpleperf/event_type_table.h b/simpleperf/event_type_table.h
index cd1f9d37..3ecf2c80 100644
--- a/simpleperf/event_type_table.h
+++ b/simpleperf/event_type_table.h
@@ -65,155 +65,52 @@ EVENT_TYPE_TABLE_ENTRY("node-prefetch-misses", PERF_TYPE_HW_CACHE, ((PERF_COUNT_
EVENT_TYPE_TABLE_ENTRY("inplace-sampler", SIMPLEPERF_TYPE_USER_SPACE_SAMPLERS, SIMPLEPERF_CONFIG_INPLACE_SAMPLER, "", "")
-EVENT_TYPE_TABLE_ENTRY("raw-sw-incr", PERF_TYPE_RAW, 0x0, "Instruction architecturally executed, Condition code check pass, software increment", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1i-cache-refill", PERF_TYPE_RAW, 0x1, "Level 1 instruction cache refill", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1i-tlb-refill", PERF_TYPE_RAW, 0x2, "Attributable Level 1 instruction TLB refill", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-refill", PERF_TYPE_RAW, 0x3, "Level 1 data cache refill", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache", PERF_TYPE_RAW, 0x4, "Level 1 data cache access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-tlb-refill", PERF_TYPE_RAW, 0x5, "Attributable Level 1 data TLB refill", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-ld-retired", PERF_TYPE_RAW, 0x6, "Instruction architecturally executed, Condition code check pass, load", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-st-retired", PERF_TYPE_RAW, 0x7, "Instruction architecturally executed, Condition code check pass, store", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-inst-retired", PERF_TYPE_RAW, 0x8, "Instruction architecturally executed", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-taken", PERF_TYPE_RAW, 0x9, "Exception taken", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-return", PERF_TYPE_RAW, 0xa, "Instruction architecturally executed, Condition code check pass, exception return", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-cid-write-retired", PERF_TYPE_RAW, 0xb, "Instruction architecturally executed, Condition code check pass, write to CONTEXTIDR", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-pc-write-retired", PERF_TYPE_RAW, 0xc, "Instruction architecturally executed, Condition code check pass, software change of the PC", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-br-immed-retired", PERF_TYPE_RAW, 0xd, "Instruction architecturally executed, immediate branch", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-br-return-retired", PERF_TYPE_RAW, 0xe, "Instruction architecturally executed, Condition code check pass, procedure return", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-unaligned-ldst-retired", PERF_TYPE_RAW, 0xf, "Instruction architecturally executed, Condition code check pass, unaligned load or store", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-br-mis-pred", PERF_TYPE_RAW, 0x10, "Mispredicted or not predicted branch Speculatively executed", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-cpu-cycles", PERF_TYPE_RAW, 0x11, "Cycle", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-br-pred", PERF_TYPE_RAW, 0x12, "Predictable branch Speculatively executed", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-mem-access", PERF_TYPE_RAW, 0x13, "Data memory access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1i-cache", PERF_TYPE_RAW, 0x14, "Attributable Level 1 instruction cache access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-wb", PERF_TYPE_RAW, 0x15, "Attributable Level 1 data cache write-back", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache", PERF_TYPE_RAW, 0x16, "Level 2 data cache access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-refill", PERF_TYPE_RAW, 0x17, "Level 2 data cache refill", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-wb", PERF_TYPE_RAW, 0x18, "Attributable Level 2 data cache write-back", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-bus-access", PERF_TYPE_RAW, 0x19, "Bus access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-memory-error", PERF_TYPE_RAW, 0x1a, "Local memory error", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-inst-spec", PERF_TYPE_RAW, 0x1b, "Operation Speculatively executed", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-ttbr-write-retired", PERF_TYPE_RAW, 0x1c, "Instruction architecturally executed, Condition code check pass, write to TTBR", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-bus-cycles", PERF_TYPE_RAW, 0x1d, "Bus cycle", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-chain", PERF_TYPE_RAW, 0x1e, "For odd-numbered counters, increments the count by one for each overflow of the preceding even-numbered counter. For even-numbered counters, there is no increment.", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-allocate", PERF_TYPE_RAW, 0x1f, "Attributable Level 1 data cache allocation without refill", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-allocate", PERF_TYPE_RAW, 0x20, "Attributable Level 2 data cache allocation without refill", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-br-retired", PERF_TYPE_RAW, 0x21, "Instruction architecturally executed, branch", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-br-mis-pred-retired", PERF_TYPE_RAW, 0x22, "Instruction architecturally executed, mispredicted branch", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-stall-frontend", PERF_TYPE_RAW, 0x23, "No operation issued due to the frontend", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-stall-backend", PERF_TYPE_RAW, 0x24, "No operation issued due to backend", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-tlb", PERF_TYPE_RAW, 0x25, "Attributable Level 1 data or unified TLB access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1i-tlb", PERF_TYPE_RAW, 0x26, "Attributable Level 1 instruction TLB access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2i-cache", PERF_TYPE_RAW, 0x27, "Attributable Level 2 instruction cache access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2i-cache-refill", PERF_TYPE_RAW, 0x28, "Attributable Level 2 instruction cache refill", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-allocate", PERF_TYPE_RAW, 0x29, "Attributable Level 3 data or unified cache allocation without refill", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-refill", PERF_TYPE_RAW, 0x2a, "Attributable Level 3 data cache refill", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache", PERF_TYPE_RAW, 0x2b, "Attributable Level 3 data cache access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-wb", PERF_TYPE_RAW, 0x2c, "Attributable Level 3 data or unified cache write-back", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-tlb-refill", PERF_TYPE_RAW, 0x2d, "Attributable Level 2 data or unified TLB refill", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2i-tlb-refill", PERF_TYPE_RAW, 0x2e, "Attributable Level 2 instruction TLB refill", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-tlb", PERF_TYPE_RAW, 0x2f, "Attributable Level 2 data or unified TLB access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2i-tlb", PERF_TYPE_RAW, 0x30, "Attributable Level 2 instruction TLB access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-remote-access", PERF_TYPE_RAW, 0x31, "Attributable access to another socket in a multi-socket system", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-ll-cache", PERF_TYPE_RAW, 0x32, "Attributable Last Level data cache access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-ll-cache-miss", PERF_TYPE_RAW, 0x33, "Attributable Last level data or unified cache miss", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-dtlb-walk", PERF_TYPE_RAW, 0x34, "Attributable data or unified TLB access with at least one translation table walk", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-itlb-walk", PERF_TYPE_RAW, 0x35, "Attributable instruction TLB access with at least one translation table walk", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-ll-cache-rd", PERF_TYPE_RAW, 0x36, "Attributable Last Level cache memory read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-ll-cache-miss-rd", PERF_TYPE_RAW, 0x37, "Attributable Last Level cache memory read miss", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-remote-access-rd", PERF_TYPE_RAW, 0x38, "Attributable memory read access to another socket in a multi-socket system", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-lmiss-rd", PERF_TYPE_RAW, 0x39, "Level 1 data cache long-latency read miss", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-op-retired", PERF_TYPE_RAW, 0x3a, "Micro-operation architecturally executed", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-op-spec", PERF_TYPE_RAW, 0x3b, "Micro-operation Speculatively executed", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-stall", PERF_TYPE_RAW, 0x3c, "No operation sent for execution", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-stall-slot-backend", PERF_TYPE_RAW, 0x3d, "No operation sent for execution on a Slot due to the backend", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-stall-slot-frontend", PERF_TYPE_RAW, 0x3e, "No operation send for execution on a Slot due to the frontend", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-stall-slot", PERF_TYPE_RAW, 0x3f, "No operation sent for execution on a Slot", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-rd", PERF_TYPE_RAW, 0x40, "Level 1 data cache read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-sample-pop", PERF_TYPE_RAW, 0x4000, "Sample Population", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-sample-feed", PERF_TYPE_RAW, 0x4001, "Sample Taken", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-sample-filtrate", PERF_TYPE_RAW, 0x4002, "Sample Taken and not removed by filtering", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-sample-collision", PERF_TYPE_RAW, 0x4003, "Sample collided with previous sample", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-cnt-cycles", PERF_TYPE_RAW, 0x4004, "Constant frequency cycles", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-stall-backend-mem", PERF_TYPE_RAW, 0x4005, "Memory stall cycles", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1i-cache-lmiss", PERF_TYPE_RAW, 0x4006, "Level 1 instruction cache long-latency miss", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-lmiss-rd", PERF_TYPE_RAW, 0x4009, "Level 2 data cache long-latency read miss", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2i-cache-lmiss", PERF_TYPE_RAW, 0x400a, "Level 2 instruction cache long-latency miss", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-lmiss-rd", PERF_TYPE_RAW, 0x400b, "Level 3 data cache long-latency read miss", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-sve-inst-retired", PERF_TYPE_RAW, 0x8002, "SVE Instructions architecturally executed", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-sve-inst-spec", PERF_TYPE_RAW, 0x8006, "SVE Instructions speculatively executed", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-wr", PERF_TYPE_RAW, 0x41, "Attributable Level 1 data cache access, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-refill-rd", PERF_TYPE_RAW, 0x42, "Attributable Level 1 data cache refill, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-refill-wr", PERF_TYPE_RAW, 0x43, "Attributable Level 1 data cache refill, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-refill-inner", PERF_TYPE_RAW, 0x44, "Attributable Level 1 data cache refill, inner", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-refill-outer", PERF_TYPE_RAW, 0x45, "Attributable Level 1 data cache refill, outer", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-wb-victim", PERF_TYPE_RAW, 0x46, "Attributable Level 1 data cache Write-Back, victim", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-wb-clean", PERF_TYPE_RAW, 0x47, "Level 1 data cache Write-Back, cleaning and coherency", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-cache-inval", PERF_TYPE_RAW, 0x48, "Attributable Level 1 data cache invalidate", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-tlb-refill-rd", PERF_TYPE_RAW, 0x4c, "Attributable Level 1 data TLB refill, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-tlb-refill-wr", PERF_TYPE_RAW, 0x4d, "Attributable Level 1 data TLB refill, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-tlb-rd", PERF_TYPE_RAW, 0x4e, "Attributable Level 1 data or unified TLB access, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l1d-tlb-wr", PERF_TYPE_RAW, 0x4f, "Attributable Level 1 data or unified TLB access, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-rd", PERF_TYPE_RAW, 0x50, "Attributable Level 2 data cache access, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-wr", PERF_TYPE_RAW, 0x51, "Attributable Level 2 data cache access, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-refill-rd", PERF_TYPE_RAW, 0x52, "Attributable Level 2 data cache refill, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-refill-wr", PERF_TYPE_RAW, 0x53, "Attributable Level 2 data cache refill, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-wb-victim", PERF_TYPE_RAW, 0x56, "Attributable Level 2 data cache Write-Back, victim", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-wb-clean", PERF_TYPE_RAW, 0x57, "Level 2 data cache Write-Back, cleaning and coherency", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-cache-inval", PERF_TYPE_RAW, 0x58, "Attributable Level 2 data cache invalidate", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-tlb-refill-rd", PERF_TYPE_RAW, 0x5c, "Attributable Level 2 data or unified TLB refill, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-tlb-refill-wr", PERF_TYPE_RAW, 0x5d, "Attributable Level 2 data or unified TLB refill, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-tlb-rd", PERF_TYPE_RAW, 0x5e, "Attributable Level 2 data or unified TLB access, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l2d-tlb-wr", PERF_TYPE_RAW, 0x5f, "Attributable Level 2 data or unified TLB access, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-bus-access-rd", PERF_TYPE_RAW, 0x60, "Bus access, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-bus-access-wr", PERF_TYPE_RAW, 0x61, "Bus access, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-bus-access-shared", PERF_TYPE_RAW, 0x62, "Bus access, Normal, Cacheable, Shareable", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-bus-access-not-shared", PERF_TYPE_RAW, 0x63, "Bus access, not Normal, Cacheable, Shareable", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-bus-access-normal", PERF_TYPE_RAW, 0x64, "Bus access, normal", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-bus-access-periph", PERF_TYPE_RAW, 0x65, "Bus access, peripheral", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-mem-access-rd", PERF_TYPE_RAW, 0x66, "Data memory access, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-mem-access-wr", PERF_TYPE_RAW, 0x67, "Data memory access, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-unaligned-ld-spec", PERF_TYPE_RAW, 0x68, "Unaligned access, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-unaligned-st-spec", PERF_TYPE_RAW, 0x69, "Unaligned access, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-unaligned-ldst-spec", PERF_TYPE_RAW, 0x6a, "Unaligned access", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-ldrex-spec", PERF_TYPE_RAW, 0x6c, "Exclusive operation speculatively executed, LDREX or LDX", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-strex-pass-spec", PERF_TYPE_RAW, 0x6d, "Exclusive operation speculatively executed, STREX or STX pass", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-strex-fail-spec", PERF_TYPE_RAW, 0x6e, "Exclusive operation speculatively executed, STREX or STX fail", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-strex-spec", PERF_TYPE_RAW, 0x6f, "Exclusive operation speculatively executed, STREX or STX", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-ld-spec", PERF_TYPE_RAW, 0x70, "Operation speculatively executed, load", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-st-spec", PERF_TYPE_RAW, 0x71, "Operation speculatively executed, store", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-ldst-spec", PERF_TYPE_RAW, 0x72, "Operation speculatively executed, load or store", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-dp-spec", PERF_TYPE_RAW, 0x73, "Operation speculatively executed, integer data processing", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-ase-spec", PERF_TYPE_RAW, 0x74, "Operation speculatively executed, Advanced SIMD instruction", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-vfp-spec", PERF_TYPE_RAW, 0x75, "Operation speculatively executed, floating-point instruction", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-pc-write-spec", PERF_TYPE_RAW, 0x76, "Operation speculatively executed, software change of the PC", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-crypto-spec", PERF_TYPE_RAW, 0x77, "Operation speculatively executed, Cryptographic instruction", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-br-immed-spec", PERF_TYPE_RAW, 0x78, "Branch speculatively executed, immediate branch", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-br-return-spec", PERF_TYPE_RAW, 0x79, "Branch speculatively executed, procedure return", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-br-indirect-spec", PERF_TYPE_RAW, 0x7a, "Branch speculatively executed, indirect branch", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-isb-spec", PERF_TYPE_RAW, 0x7c, "Barrier speculatively executed, ISB", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-dsb-spec", PERF_TYPE_RAW, 0x7d, "Barrier speculatively executed, DSB", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-dmb-spec", PERF_TYPE_RAW, 0x7e, "Barrier speculatively executed, DMB", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-undef", PERF_TYPE_RAW, 0x81, "Exception taken, Other synchronous", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-svc", PERF_TYPE_RAW, 0x82, "Exception taken, Supervisor Call", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-pabort", PERF_TYPE_RAW, 0x83, "Exception taken, Instruction Abort", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-dabort", PERF_TYPE_RAW, 0x84, "Exception taken, Data Abort and SError", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-irq", PERF_TYPE_RAW, 0x86, "Exception taken, IRQ", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-fiq", PERF_TYPE_RAW, 0x87, "Exception taken, FIQ", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-smc", PERF_TYPE_RAW, 0x88, "Exception taken, Secure Monitor Call", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-hvc", PERF_TYPE_RAW, 0x8a, "Exception taken, Hypervisor Call", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-trap-pabort", PERF_TYPE_RAW, 0x8b, "Exception taken, Instruction Abort not Taken locallyb", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-trap-dabort", PERF_TYPE_RAW, 0x8c, "Exception taken, Data Abort or SError not Taken locallyb", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-trap-other", PERF_TYPE_RAW, 0x8d, "Exception taken, Other traps not Taken locallyb", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-trap-irq", PERF_TYPE_RAW, 0x8e, "Exception taken, IRQ not Taken locallyb", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-exc-trap-fiq", PERF_TYPE_RAW, 0x8f, "Exception taken, FIQ not Taken locallyb", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-rc-ld-spec", PERF_TYPE_RAW, 0x90, "Release consistency operation speculatively executed, Load-Acquire", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-rc-st-spec", PERF_TYPE_RAW, 0x91, "Release consistency operation speculatively executed, Store-Release", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-rd", PERF_TYPE_RAW, 0xa0, "Attributable Level 3 data or unified cache access, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-wr", PERF_TYPE_RAW, 0xa1, "Attributable Level 3 data or unified cache access, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-refill-rd", PERF_TYPE_RAW, 0xa2, "Attributable Level 3 data or unified cache refill, read", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-refill-wr", PERF_TYPE_RAW, 0xa3, "Attributable Level 3 data or unified cache refill, write", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-wb-victim", PERF_TYPE_RAW, 0xa6, "Attributable Level 3 data or unified cache Write-Back, victim", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-wb-clean", PERF_TYPE_RAW, 0xa7, "Attributable Level 3 data or unified cache Write-Back, cache clean", "arm")
-EVENT_TYPE_TABLE_ENTRY("raw-l3d-cache-inval", PERF_TYPE_RAW, 0xa8, "Attributable Level 3 data or unified cache access, invalidate", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-sw-incr", PERF_TYPE_RAW, 0x0, "software increment", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l1-icache-refill", PERF_TYPE_RAW, 0x1, "level 1 instruction cache refill", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l1-itlb-refill", PERF_TYPE_RAW, 0x2, "level 1 instruction TLB refill", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l1-dcache-refill", PERF_TYPE_RAW, 0x3, "level 1 data cache refill", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l1-dcache", PERF_TYPE_RAW, 0x4, "level 1 data cache access", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l1-dtlb-refill", PERF_TYPE_RAW, 0x5, "level 1 data TLB refill", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-load-retired", PERF_TYPE_RAW, 0x6, "load (instruction architecturally executed)", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-store-retired", PERF_TYPE_RAW, 0x7, "store (instruction architecturally executed)", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-instruction-retired", PERF_TYPE_RAW, 0x8, "instructions (instruction architecturally executed)", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-exception-taken", PERF_TYPE_RAW, 0x9, "exception taken", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-exception-return", PERF_TYPE_RAW, 0xa, "exception return (instruction architecturally executed)", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-cid-write-retired", PERF_TYPE_RAW, 0xb, "write to CONTEXIDR (instruction architecturally executed)", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-pc-write-retired", PERF_TYPE_RAW, 0xc, "software change of the PC (instruction architecturally executed)", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-br-immed-retired", PERF_TYPE_RAW, 0xd, "immediate branch (instruction architecturally executed)", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-br-return-retired", PERF_TYPE_RAW, 0xe, "procedure return (instruction architecturally executed)", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-unaligned-ldst-retired", PERF_TYPE_RAW, 0xf, "unaligned load or store (instruction architecturally executed)", "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, "cpu cycles", "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-l1-icache", PERF_TYPE_RAW, 0x14, "level 1 instruction cache access", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l1-dcache-wb", PERF_TYPE_RAW, 0x15, "level 1 data cache write-back", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l2-dcache", PERF_TYPE_RAW, 0x16, "level 2 data cache access", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l2-dcache-refill", PERF_TYPE_RAW, 0x17, "level 2 data cache refill", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l2-dcache-wb", PERF_TYPE_RAW, 0x18, "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, "write to TTBR (instruction architecturally executed)", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-bus-cycles", PERF_TYPE_RAW, 0x1d, "bus cycle", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l1-dcache-allocate", PERF_TYPE_RAW, 0x1f, "level 1 data cache allocation without refill", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l2-dcache-allocate", PERF_TYPE_RAW, 0x20, "level 2 data cache allocation without refill", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-br-retired", PERF_TYPE_RAW, 0x21, "branch (instruction architecturally executed)", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-br-mis-pred-retired", PERF_TYPE_RAW, 0x22, "mispredicted branch (instruction architecturally executed)", "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 the backend", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l1-dtlb", PERF_TYPE_RAW, 0x25, "level 1 data or unified TLB access", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l1-itlb", PERF_TYPE_RAW, 0x26, "level 1 instruction TLB access", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l2-icache", PERF_TYPE_RAW, 0x27, "level 2 instruction cache access", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l2-icache-refill", PERF_TYPE_RAW, 0x28, "level 2 instruction cache refill", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l3-dcache-allocate", PERF_TYPE_RAW, 0x29, "level 3 data or unified cache allocation without refill", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l3-dcache-refill", PERF_TYPE_RAW, 0x2a, "level 3 data or unified cache refill", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l3-dcache", PERF_TYPE_RAW, 0x2b, "level 3 data or unified cache access", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l3-dcache-wb", PERF_TYPE_RAW, 0x2c, "level 3 data or unified cache write-back", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l2-dtlb-refill", PERF_TYPE_RAW, 0x2d, "level 2 data or unified TLB refill", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l2-itlb-refill", PERF_TYPE_RAW, 0x2e, "level 2 instruction TLB refill", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l2-dtlb", PERF_TYPE_RAW, 0x2f, "level 2 data or unified TLB access", "arm")
+EVENT_TYPE_TABLE_ENTRY("raw-l2-itlb", PERF_TYPE_RAW, 0x30, "level 2 instruction TLB access", "arm")
diff --git a/simpleperf/generate_event_type_table.py b/simpleperf/generate_event_type_table.py
index 9ed50c50..f410c13e 100755
--- a/simpleperf/generate_event_type_table.py
+++ b/simpleperf/generate_event_type_table.py
@@ -118,172 +118,57 @@ def gen_user_space_events():
return generated_str
def gen_arm_raw_events():
+ # Refer to "Table D5-7 PMU event numbers" in ARMv8 specification.
raw_types = [
- # Refer to "Table D6-7 PMU common architectural and microarchitectural event numbers" in ARMv8 specification.
- [0x0000, "sw-incr", "Instruction architecturally executed, Condition code check pass, software increment"],
- [0x0001, "l1i-cache-refill", "Level 1 instruction cache refill"],
- [0x0002, "l1i-tlb-refill", "Attributable Level 1 instruction TLB refill"],
- [0x0003, "l1d-cache-refill", "Level 1 data cache refill"],
- [0x0004, "l1d-cache", "Level 1 data cache access"],
- [0x0005, "l1d-tlb-refill", "Attributable Level 1 data TLB refill"],
- [0x0006, "ld-retired", "Instruction architecturally executed, Condition code check pass, load"],
- [0x0007, "st-retired", "Instruction architecturally executed, Condition code check pass, store"],
- [0x0008, "inst-retired", "Instruction architecturally executed"],
- [0x0009, "exc-taken", "Exception taken"],
- [0x000A, "exc-return", "Instruction architecturally executed, Condition code check pass, exception return"],
- [0x000B, "cid-write-retired", "Instruction architecturally executed, Condition code check pass, write to CONTEXTIDR"],
- [0x000C, "pc-write-retired", "Instruction architecturally executed, Condition code check pass, software change of the PC"],
- [0x000D, "br-immed-retired", "Instruction architecturally executed, immediate branch"],
- [0x000E, "br-return-retired", "Instruction architecturally executed, Condition code check pass, procedure return"],
- [0x000F, "unaligned-ldst-retired", "Instruction architecturally executed, Condition code check pass, unaligned load or store"],
- [0x0010, "br-mis-pred", "Mispredicted or not predicted branch Speculatively executed"],
- [0x0011, "cpu-cycles", "Cycle"],
- [0x0012, "br-pred", "Predictable branch Speculatively executed"],
- [0x0013, "mem-access", "Data memory access"],
- [0x0014, "l1i-cache", "Attributable Level 1 instruction cache access"],
- [0x0015, "l1d-cache-wb", "Attributable Level 1 data cache write-back"],
- [0x0016, "l2d-cache", "Level 2 data cache access"],
- [0x0017, "l2d-cache-refill", "Level 2 data cache refill"],
- [0x0018, "l2d-cache-wb", "Attributable Level 2 data cache write-back"],
- [0x0019, "bus-access", "Bus access"],
- [0x001A, "memory-error", "Local memory error"],
- [0x001B, "inst-spec", "Operation Speculatively executed"],
- [0x001C, "ttbr-write-retired", "Instruction architecturally executed, Condition code check pass, write to TTBR"],
- [0x001D, "bus-cycles", "Bus cycle"],
- [0x001E, "chain", "For odd-numbered counters, increments the count by one for each overflow of the preceding even-numbered counter. For even-numbered counters, there is no increment."],
- [0x001F, "l1d-cache-allocate", "Attributable Level 1 data cache allocation without refill"],
- [0x0020, "l2d-cache-allocate", "Attributable Level 2 data cache allocation without refill"],
- [0x0021, "br-retired", "Instruction architecturally executed, branch"],
- [0x0022, "br-mis-pred-retired", "Instruction architecturally executed, mispredicted branch"],
- [0x0023, "stall-frontend", "No operation issued due to the frontend"],
- [0x0024, "stall-backend", "No operation issued due to backend"],
- [0x0025, "l1d-tlb", "Attributable Level 1 data or unified TLB access"],
- [0x0026, "l1i-tlb", "Attributable Level 1 instruction TLB access"],
- [0x0027, "l2i-cache", "Attributable Level 2 instruction cache access"],
- [0x0028, "l2i-cache-refill", "Attributable Level 2 instruction cache refill"],
- [0x0029, "l3d-cache-allocate", "Attributable Level 3 data or unified cache allocation without refill"],
- [0x002A, "l3d-cache-refill", "Attributable Level 3 data cache refill"],
- [0x002B, "l3d-cache", "Attributable Level 3 data cache access"],
- [0x002C, "l3d-cache-wb", "Attributable Level 3 data or unified cache write-back"],
- [0x002D, "l2d-tlb-refill", "Attributable Level 2 data or unified TLB refill"],
- [0x002E, "l2i-tlb-refill", "Attributable Level 2 instruction TLB refill"],
- [0x002F, "l2d-tlb", "Attributable Level 2 data or unified TLB access"],
- [0x0030, "l2i-tlb", "Attributable Level 2 instruction TLB access"],
- [0x0031, "remote-access", "Attributable access to another socket in a multi-socket system"],
- [0x0032, "ll-cache", "Attributable Last Level data cache access"],
- [0x0033, "ll-cache-miss", "Attributable Last level data or unified cache miss"],
- [0x0034, "dtlb-walk", "Attributable data or unified TLB access with at least one translation table walk"],
- [0x0035, "itlb-walk", "Attributable instruction TLB access with at least one translation table walk"],
- [0x0036, "ll-cache-rd", "Attributable Last Level cache memory read"],
- [0x0037, "ll-cache-miss-rd", "Attributable Last Level cache memory read miss"],
- [0x0038, "remote-access-rd", "Attributable memory read access to another socket in a multi-socket system"],
- [0x0039, "l1d-cache-lmiss-rd", "Level 1 data cache long-latency read miss"],
- [0x003A, "op-retired", "Micro-operation architecturally executed"],
- [0x003B, "op-spec", "Micro-operation Speculatively executed"],
- [0x003C, "stall", "No operation sent for execution"],
- [0x003D, "stall-slot-backend", "No operation sent for execution on a Slot due to the backend"],
- [0x003E, "stall-slot-frontend", "No operation send for execution on a Slot due to the frontend"],
- [0x003F, "stall-slot", "No operation sent for execution on a Slot"],
- [0x0040, "l1d-cache-rd", "Level 1 data cache read"],
- [0x4000, "sample-pop", "Sample Population"],
- [0x4001, "sample-feed", "Sample Taken"],
- [0x4002, "sample-filtrate", "Sample Taken and not removed by filtering"],
- [0x4003, "sample-collision", "Sample collided with previous sample"],
- [0x4004, "cnt-cycles", "Constant frequency cycles"],
- [0x4005, "stall-backend-mem", "Memory stall cycles"],
- [0x4006, "l1i-cache-lmiss", "Level 1 instruction cache long-latency miss"],
- [0x4009, "l2d-cache-lmiss-rd", "Level 2 data cache long-latency read miss"],
- [0x400A, "l2i-cache-lmiss", "Level 2 instruction cache long-latency miss"],
- [0x400B, "l3d-cache-lmiss-rd", "Level 3 data cache long-latency read miss"],
- [0x8002, "sve-inst-retired", "SVE Instructions architecturally executed"],
- [0x8006, "sve-inst-spec", "SVE Instructions speculatively executed"],
-
- # Refer to "Table K3.1 ARM recommendations for IMPLEMENTATION DEFINED event numbers" in ARMv8 specification.
- #[0x0040, "l1d-cache-rd", "Attributable Level 1 data cache access, read"],
- [0x0041, "l1d-cache-wr", "Attributable Level 1 data cache access, write"],
- [0x0042, "l1d-cache-refill-rd", "Attributable Level 1 data cache refill, read"],
- [0x0043, "l1d-cache-refill-wr", "Attributable Level 1 data cache refill, write"],
- [0x0044, "l1d-cache-refill-inner", "Attributable Level 1 data cache refill, inner"],
- [0x0045, "l1d-cache-refill-outer", "Attributable Level 1 data cache refill, outer"],
- [0x0046, "l1d-cache-wb-victim", "Attributable Level 1 data cache Write-Back, victim"],
- [0x0047, "l1d-cache-wb-clean", "Level 1 data cache Write-Back, cleaning and coherency"],
- [0x0048, "l1d-cache-inval", "Attributable Level 1 data cache invalidate"],
- # 0x0049-0x004B - Reserved
- [0x004C, "l1d-tlb-refill-rd", "Attributable Level 1 data TLB refill, read"],
- [0x004D, "l1d-tlb-refill-wr", "Attributable Level 1 data TLB refill, write"],
- [0x004E, "l1d-tlb-rd", "Attributable Level 1 data or unified TLB access, read"],
- [0x004F, "l1d-tlb-wr", "Attributable Level 1 data or unified TLB access, write"],
- [0x0050, "l2d-cache-rd", "Attributable Level 2 data cache access, read"],
- [0x0051, "l2d-cache-wr", "Attributable Level 2 data cache access, write"],
- [0x0052, "l2d-cache-refill-rd", "Attributable Level 2 data cache refill, read"],
- [0x0053, "l2d-cache-refill-wr", "Attributable Level 2 data cache refill, write"],
- # 0x0054-0x0055 - Reserved
- [0x0056, "l2d-cache-wb-victim", "Attributable Level 2 data cache Write-Back, victim"],
- [0x0057, "l2d-cache-wb-clean", "Level 2 data cache Write-Back, cleaning and coherency"],
- [0x0058, "l2d-cache-inval", "Attributable Level 2 data cache invalidate"],
- # 0x0059-0x005B - Reserved
- [0x005C, "l2d-tlb-refill-rd", "Attributable Level 2 data or unified TLB refill, read"],
- [0x005D, "l2d-tlb-refill-wr", "Attributable Level 2 data or unified TLB refill, write"],
- [0x005E, "l2d-tlb-rd", "Attributable Level 2 data or unified TLB access, read"],
- [0x005F, "l2d-tlb-wr", "Attributable Level 2 data or unified TLB access, write"],
- [0x0060, "bus-access-rd", "Bus access, read"],
- [0x0061, "bus-access-wr", "Bus access, write"],
- [0x0062, "bus-access-shared", "Bus access, Normal, Cacheable, Shareable"],
- [0x0063, "bus-access-not-shared", "Bus access, not Normal, Cacheable, Shareable"],
- [0x0064, "bus-access-normal", "Bus access, normal"],
- [0x0065, "bus-access-periph", "Bus access, peripheral"],
- [0x0066, "mem-access-rd", "Data memory access, read"],
- [0x0067, "mem-access-wr", "Data memory access, write"],
- [0x0068, "unaligned-ld-spec", "Unaligned access, read"],
- [0x0069, "unaligned-st-spec", "Unaligned access, write"],
- [0x006A, "unaligned-ldst-spec", "Unaligned access"],
- # 0x006B - Reserved
- [0x006C, "ldrex-spec", "Exclusive operation speculatively executed, LDREX or LDX"],
- [0x006D, "strex-pass-spec", "Exclusive operation speculatively executed, STREX or STX pass"],
- [0x006E, "strex-fail-spec", "Exclusive operation speculatively executed, STREX or STX fail"],
- [0x006F, "strex-spec", "Exclusive operation speculatively executed, STREX or STX"],
- [0x0070, "ld-spec", "Operation speculatively executed, load"],
- [0x0071, "st-spec", "Operation speculatively executed, store"],
- [0x0072, "ldst-spec", "Operation speculatively executed, load or store"],
- [0x0073, "dp-spec", "Operation speculatively executed, integer data processing"],
- [0x0074, "ase-spec", "Operation speculatively executed, Advanced SIMD instruction"],
- [0x0075, "vfp-spec", "Operation speculatively executed, floating-point instruction"],
- [0x0076, "pc-write-spec", "Operation speculatively executed, software change of the PC"],
- [0x0077, "crypto-spec", "Operation speculatively executed, Cryptographic instruction"],
- [0x0078, "br-immed-spec", "Branch speculatively executed, immediate branch"],
- [0x0079, "br-return-spec", "Branch speculatively executed, procedure return"],
- [0x007A, "br-indirect-spec", "Branch speculatively executed, indirect branch"],
- # 0x007B - Reserved
- [0x007C, "isb-spec", "Barrier speculatively executed, ISB"],
- [0x007D, "dsb-spec", "Barrier speculatively executed, DSB"],
- [0x007E, "dmb-spec", "Barrier speculatively executed, DMB"],
- # 0x007F-0x0080 - Reserved
- [0x0081, "exc-undef", "Exception taken, Other synchronous"],
- [0x0082, "exc-svc", "Exception taken, Supervisor Call"],
- [0x0083, "exc-pabort", "Exception taken, Instruction Abort"],
- [0x0084, "exc-dabort", "Exception taken, Data Abort and SError"],
- # 0x0085 - Reserved
- [0x0086, "exc-irq", "Exception taken, IRQ"],
- [0x0087, "exc-fiq", "Exception taken, FIQ"],
- [0x0088, "exc-smc", "Exception taken, Secure Monitor Call"],
- # 0x0089 - Reserved
- [0x008A, "exc-hvc", "Exception taken, Hypervisor Call"],
- [0x008B, "exc-trap-pabort", "Exception taken, Instruction Abort not Taken locallyb"],
- [0x008C, "exc-trap-dabort", "Exception taken, Data Abort or SError not Taken locallyb"],
- [0x008D, "exc-trap-other", "Exception taken, Other traps not Taken locallyb"],
- [0x008E, "exc-trap-irq", "Exception taken, IRQ not Taken locallyb"],
- [0x008F, "exc-trap-fiq", "Exception taken, FIQ not Taken locallyb"],
- [0x0090, "rc-ld-spec", "Release consistency operation speculatively executed, Load-Acquire"],
- [0x0091, "rc-st-spec", "Release consistency operation speculatively executed, Store-Release"],
- # 0x0092-0x009F - Reserved
- [0x00A0, "l3d-cache-rd", "Attributable Level 3 data or unified cache access, read"],
- [0x00A1, "l3d-cache-wr", "Attributable Level 3 data or unified cache access, write"],
- [0x00A2, "l3d-cache-refill-rd", "Attributable Level 3 data or unified cache refill, read"],
- [0x00A3, "l3d-cache-refill-wr", "Attributable Level 3 data or unified cache refill, write"],
- # 0x00A4-0x00A5 - Reserved
- [0x00A6, "l3d-cache-wb-victim", "Attributable Level 3 data or unified cache Write-Back, victim"],
- [0x00A7, "l3d-cache-wb-clean", "Attributable Level 3 data or unified cache Write-Back, cache clean"],
- [0x00A8, "l3d-cache-inval", "Attributable Level 3 data or unified cache access, invalidate"],
+ [0x00, "sw-incr", "software increment"],
+ [0x01, "l1-icache-refill", "level 1 instruction cache refill"],
+ [0x02, "l1-itlb-refill", "level 1 instruction TLB refill"],
+ [0x03, "l1-dcache-refill", "level 1 data cache refill"],
+ [0x04, "l1-dcache", "level 1 data cache access"],
+ [0x05, "l1-dtlb-refill", "level 1 data TLB refill"],
+ [0x06, "load-retired", "load (instruction architecturally executed)"],
+ [0x07, "store-retired", "store (instruction architecturally executed)"],
+ [0x08, "instruction-retired", "instructions (instruction architecturally executed)"],
+ [0x09, "exception-taken", "exception taken"],
+ [0x0a, "exception-return", "exception return (instruction architecturally executed)"],
+ [0x0b, "cid-write-retired", "write to CONTEXIDR (instruction architecturally executed)"],
+ [0x0c, "pc-write-retired", "software change of the PC (instruction architecturally executed)"],
+ [0x0d, "br-immed-retired", "immediate branch (instruction architecturally executed)"],
+ [0x0e, "br-return-retired", "procedure return (instruction architecturally executed)"],
+ [0x0f, "unaligned-ldst-retired", "unaligned load or store (instruction architecturally executed)"],
+ [0x10, "br-mis-pred", "mispredicted or not predicted branch speculatively executed"],
+ [0x11, "cpu-cycles", "cpu cycles"],
+ [0x12, "br-pred", "predictable branch speculatively executed"],
+ [0x13, "mem-access", "data memory access"],
+ [0x14, "l1-icache", "level 1 instruction cache access"],
+ [0x15, "l1-dcache-wb", "level 1 data cache write-back"],
+ [0x16, "l2-dcache", "level 2 data cache access"],
+ [0x17, "l2-dcache-refill", "level 2 data cache refill"],
+ [0x18, "l2-dcache-wb", "level 2 data cache write-back"],
+ [0x19, "bus-access", "bus access"],
+ [0x1a, "memory-error", "local memory error"],
+ [0x1b, "inst-spec", "operation speculatively executed"],
+ [0x1c, "ttbr-write-retired", "write to TTBR (instruction architecturally executed)"],
+ [0x1d, "bus-cycles", "bus cycle"],
+ # [0x1e, "chain", ""], // Not useful in user space.
+ [0x1f, "l1-dcache-allocate", "level 1 data cache allocation without refill"],
+ [0x20, "l2-dcache-allocate", "level 2 data cache allocation without refill"],
+ [0x21, "br-retired", "branch (instruction architecturally executed)"],
+ [0x22, "br-mis-pred-retired", "mispredicted branch (instruction architecturally executed)"],
+ [0x23, "stall-frontend", "no operation issued due to the frontend"],
+ [0x24, "stall-backend", "no operation issued due to the backend"],
+ [0x25, "l1-dtlb", "level 1 data or unified TLB access"],
+ [0x26, "l1-itlb", "level 1 instruction TLB access"],
+ [0x27, "l2-icache", "level 2 instruction cache access"],
+ [0x28, "l2-icache-refill", "level 2 instruction cache refill"],
+ [0x29, "l3-dcache-allocate", "level 3 data or unified cache allocation without refill"],
+ [0x2a, "l3-dcache-refill", "level 3 data or unified cache refill"],
+ [0x2b, "l3-dcache", "level 3 data or unified cache access"],
+ [0x2c, "l3-dcache-wb", "level 3 data or unified cache write-back"],
+ [0x2d, "l2-dtlb-refill", "level 2 data or unified TLB refill"],
+ [0x2e, "l2-itlb-refill", "level 2 instruction TLB refill"],
+ [0x2f, "l2-dtlb", "level 2 data or unified TLB access"],
+ [0x30, "l2-itlb", "level 2 instruction TLB access"],
]
generated_str = ""
for item in raw_types:
diff --git a/simpleperf/get_test_data.h b/simpleperf/get_test_data.h
index d9e0d988..67bf0ea5 100644
--- a/simpleperf/get_test_data.h
+++ b/simpleperf/get_test_data.h
@@ -137,7 +137,4 @@ static const std::string PERF_DATA_WITH_INTERPRETER_FRAMES = "perf_with_interpre
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";
-
#endif // SIMPLE_PERF_GET_TEST_DATA_H_
diff --git a/simpleperf/gtest_main.cpp b/simpleperf/gtest_main.cpp
index a6062281..6265d6a7 100644
--- a/simpleperf/gtest_main.cpp
+++ b/simpleperf/gtest_main.cpp
@@ -68,12 +68,6 @@ class ScopedEnablingPerf {
#endif // defined(__ANDROID__)
int main(int argc, char** argv) {
- // To test profiling apps, simpleperf_unit_test needs to copy itself to the app's directory,
- // and run the binary as simpleperf executable.
- if (android::base::Basename(argv[0]) == "simpleperf") {
- return RunSimpleperfCmd(argc, argv) ? 0 : 1;
- }
-
android::base::InitLogging(argv, android::base::StderrLogger);
android::base::LogSeverity log_severity = android::base::WARNING;
testdata_dir = std::string(dirname(argv[0])) + "/testdata";
diff --git a/simpleperf/nonlinux_support/nonlinux_support.cpp b/simpleperf/nonlinux_support/nonlinux_support.cpp
index 1726b589..19a3cb11 100644
--- a/simpleperf/nonlinux_support/nonlinux_support.cpp
+++ b/simpleperf/nonlinux_support/nonlinux_support.cpp
@@ -38,19 +38,3 @@ bool ReadSymbolsFromDexFile(const std::string&, const std::vector<uint64_t>&,
std::vector<DexFileSymbol>*) {
return true;
}
-
-namespace simpleperf {
-
-class DummyOfflineUnwinder : public OfflineUnwinder {
- public:
- bool UnwindCallChain(const ThreadEntry&, const RegSet&, const char*, size_t,
- std::vector<uint64_t>*, std::vector<uint64_t>*) override {
- return false;
- }
-};
-
-std::unique_ptr<OfflineUnwinder> OfflineUnwinder::Create(bool) {
- return std::unique_ptr<OfflineUnwinder>(new DummyOfflineUnwinder);
-}
-
-} // namespace simpleperf \ No newline at end of file
diff --git a/simpleperf/record.cpp b/simpleperf/record.cpp
index 3ce99acc..2d684a8a 100644
--- a/simpleperf/record.cpp
+++ b/simpleperf/record.cpp
@@ -44,10 +44,7 @@ static std::string RecordTypeToString(int record_type) {
{PERF_RECORD_SAMPLE, "sample"},
{PERF_RECORD_BUILD_ID, "build_id"},
{PERF_RECORD_MMAP2, "mmap2"},
- {PERF_RECORD_AUX, "aux"},
{PERF_RECORD_TRACING_DATA, "tracing_data"},
- {PERF_RECORD_AUXTRACE_INFO, "auxtrace_info"},
- {PERF_RECORD_AUXTRACE, "auxtrace"},
{SIMPLE_PERF_RECORD_KERNEL_SYMBOL, "kernel_symbol"},
{SIMPLE_PERF_RECORD_DSO, "dso"},
{SIMPLE_PERF_RECORD_SYMBOL, "symbol"},
@@ -877,20 +874,6 @@ std::vector<uint64_t> SampleRecord::GetCallChain(size_t* kernel_ip_count) const
return ips;
}
-AuxRecord::AuxRecord(const perf_event_attr& attr, char* p) : Record(p) {
- const char* end = p + size();
- p += header_size();
- data = reinterpret_cast<DataType*>(p);
- p += sizeof(DataType);
- sample_id.ReadFromBinaryFormat(attr, p, end);
-}
-
-void AuxRecord::DumpData(size_t indent) const {
- PrintIndented(indent, "aux_offset %" PRIu64 "\n", data->aux_offset);
- PrintIndented(indent, "aux_size %" PRIu64 "\n", data->aux_size);
- PrintIndented(indent, "flags 0x%" PRIx64 "\n", data->flags);
-}
-
BuildIdRecord::BuildIdRecord(char* p) : Record(p) {
const char* end = p + size();
p += header_size();
@@ -927,90 +910,6 @@ BuildIdRecord::BuildIdRecord(bool in_kernel, pid_t pid, const BuildId& build_id,
UpdateBinary(new_binary);
}
-AuxTraceInfoRecord::AuxTraceInfoRecord(char* p) : Record(p) {
- const char* end = p + size();
- p += header_size();
- data = reinterpret_cast<DataType*>(p);
- CHECK_EQ(data->aux_type, AUX_TYPE_ETM);
- CHECK_EQ(data->version, 0);
- for (uint32_t i = 0; i < data->nr_cpu; ++i) {
- CHECK_EQ(data->etm4_info[i].magic, MAGIC_ETM4);
- }
- p += sizeof(DataType) + data->nr_cpu * sizeof(ETM4Info);
- CHECK_EQ(p, end);
-}
-
-AuxTraceInfoRecord::AuxTraceInfoRecord(const DataType& data,
- const std::vector<ETM4Info>& etm4_info) {
- SetTypeAndMisc(PERF_RECORD_AUXTRACE_INFO, 0);
- SetSize(header_size() + sizeof(DataType) + sizeof(ETM4Info) * etm4_info.size());
- char* new_binary = new char[size()];
- char* p = new_binary;
- MoveToBinaryFormat(header, p);
- this->data = reinterpret_cast<DataType*>(p);
- MoveToBinaryFormat(data, p);
- for (auto& etm4 : etm4_info) {
- MoveToBinaryFormat(etm4, p);
- }
- UpdateBinary(new_binary);
-}
-
-void AuxTraceInfoRecord::DumpData(size_t indent) const {
- PrintIndented(indent, "aux_type %u\n", data->aux_type);
- PrintIndented(indent, "version %" PRIu64 "\n", data->version);
- PrintIndented(indent, "nr_cpu %u\n", data->nr_cpu);
- PrintIndented(indent, "pmu_type %u\n", data->pmu_type);
- PrintIndented(indent, "snapshot %" PRIu64 "\n", data->snapshot);
- indent++;
- for (int i = 0; i < data->nr_cpu; i++) {
- const ETM4Info& e = data->etm4_info[i];
- PrintIndented(indent, "magic 0x%" PRIx64 "\n", e.magic);
- PrintIndented(indent, "cpu %" PRIu64 "\n", e.cpu);
- PrintIndented(indent, "trcconfigr 0x%" PRIx64 "\n", e.trcconfigr);
- PrintIndented(indent, "trctraceidr 0x%" PRIx64 "\n", e.trctraceidr);
- PrintIndented(indent, "trcidr0 0x%" PRIx64 "\n", e.trcidr0);
- PrintIndented(indent, "trcidr1 0x%" PRIx64 "\n", e.trcidr1);
- PrintIndented(indent, "trcidr2 0x%" PRIx64 "\n", e.trcidr2);
- PrintIndented(indent, "trcidr8 0x%" PRIx64 "\n", e.trcidr8);
- PrintIndented(indent, "trcauthstatus 0x%" PRIx64 "\n", e.trcauthstatus);
- }
-}
-
-AuxTraceRecord::AuxTraceRecord(char* p) : Record(p) {
- const char* end = p + header.size;
- p += header_size();
- data = reinterpret_cast<DataType*>(p);
- p += sizeof(DataType);
- CHECK_EQ(p, end);
-}
-
-AuxTraceRecord::AuxTraceRecord(uint64_t aux_size, uint64_t offset, uint32_t idx, uint32_t tid,
- uint32_t cpu) {
- SetTypeAndMisc(PERF_RECORD_AUXTRACE, 0);
- SetSize(header_size() + sizeof(DataType));
- char* new_binary = new char[size()];
- char* p = new_binary;
- MoveToBinaryFormat(header, p);
- data = reinterpret_cast<DataType*>(p);
- data->aux_size = aux_size;
- data->offset = offset;
- data->reserved0 = 0;
- data->idx = idx;
- data->tid = tid;
- data->cpu = cpu;
- data->reserved1 = 0;
- UpdateBinary(new_binary);
-}
-
-void AuxTraceRecord::DumpData(size_t indent) const {
- PrintIndented(indent, "aux_size %" PRIu64 "\n", data->aux_size);
- PrintIndented(indent, "offset %" PRIu64 "\n", data->offset);
- PrintIndented(indent, "idx %u\n", data->idx);
- PrintIndented(indent, "tid %u\n", data->tid);
- PrintIndented(indent, "cpu %u\n", data->cpu);
- PrintIndented(indent, "location.file_offset %" PRIu64 "\n", location.file_offset);
-}
-
KernelSymbolRecord::KernelSymbolRecord(char* p) : Record(p) {
const char* end = p + size();
p += header_size();
@@ -1310,14 +1209,8 @@ std::unique_ptr<Record> ReadRecordFromBuffer(const perf_event_attr& attr, uint32
return std::unique_ptr<Record>(new LostRecord(attr, p));
case PERF_RECORD_SAMPLE:
return std::unique_ptr<Record>(new SampleRecord(attr, p));
- case PERF_RECORD_AUX:
- return std::unique_ptr<Record>(new AuxRecord(attr, p));
case PERF_RECORD_TRACING_DATA:
return std::unique_ptr<Record>(new TracingDataRecord(p));
- case PERF_RECORD_AUXTRACE_INFO:
- return std::unique_ptr<Record>(new AuxTraceInfoRecord(p));
- case PERF_RECORD_AUXTRACE:
- return std::unique_ptr<Record>(new AuxTraceRecord(p));
case SIMPLE_PERF_RECORD_KERNEL_SYMBOL:
return std::unique_ptr<Record>(new KernelSymbolRecord(p));
case SIMPLE_PERF_RECORD_DSO:
diff --git a/simpleperf/record.h b/simpleperf/record.h
index 163d5208..adde3f44 100644
--- a/simpleperf/record.h
+++ b/simpleperf/record.h
@@ -40,9 +40,6 @@ enum user_record_type {
PERF_RECORD_BUILD_ID,
PERF_RECORD_FINISHED_ROUND,
- PERF_RECORD_AUXTRACE_INFO = 70,
- PERF_RECORD_AUXTRACE = 71,
-
SIMPLE_PERF_RECORD_TYPE_START = 32768,
SIMPLE_PERF_RECORD_KERNEL_SYMBOL,
// TODO: remove DsoRecord and SymbolRecord.
@@ -425,19 +422,6 @@ struct SampleRecord : public Record {
void DumpData(size_t indent) const override;
};
-struct AuxRecord : public Record {
- struct DataType {
- uint64_t aux_offset;
- uint64_t aux_size;
- uint64_t flags;
- }* data;
-
- AuxRecord(const perf_event_attr& attr, char* p);
-
- protected:
- void DumpData(size_t indent) const override;
-};
-
// BuildIdRecord is defined in user-space, stored in BuildId feature section in
// record file.
struct BuildIdRecord : public Record {
@@ -454,66 +438,6 @@ struct BuildIdRecord : public Record {
void DumpData(size_t indent) const override;
};
-struct AuxTraceInfoRecord : public Record {
- // magic values to be compatible with linux perf
- static const uint32_t AUX_TYPE_ETM = 3;
- static const uint64_t MAGIC_ETM4 = 0x4040404040404040ULL;
-
- struct ETM4Info {
- uint64_t magic;
- uint64_t cpu;
- uint64_t trcconfigr;
- uint64_t trctraceidr;
- uint64_t trcidr0;
- uint64_t trcidr1;
- uint64_t trcidr2;
- uint64_t trcidr8;
- uint64_t trcauthstatus;
- };
-
- struct DataType {
- uint32_t aux_type;
- uint32_t reserved;
- uint64_t version;
- uint32_t nr_cpu;
- uint32_t pmu_type;
- uint64_t snapshot;
- ETM4Info etm4_info[0];
- }* data;
-
- explicit AuxTraceInfoRecord(char* p);
- AuxTraceInfoRecord(const DataType& data, const std::vector<ETM4Info>& etm4_info);
-
- protected:
- void DumpData(size_t indent) const override;
-};
-
-struct AuxTraceRecord : public Record {
- struct DataType {
- uint64_t aux_size;
- uint64_t offset;
- uint64_t reserved0; // reference
- uint32_t idx;
- uint32_t tid;
- uint32_t cpu;
- uint32_t reserved1;
- } * data;
- // AuxTraceRecord is followed by aux tracing data with size data->aux_size.
- // The location of aux tracing data in memory or file is kept in location.
- struct AuxDataLocation {
- const char* addr = nullptr;
- uint64_t file_offset = 0;
- } location;
-
- explicit AuxTraceRecord(char* p);
- AuxTraceRecord(uint64_t aux_size, uint64_t offset, uint32_t idx, uint32_t tid, uint32_t cpu);
-
- static size_t Size() { return sizeof(perf_event_header) + sizeof(DataType); }
-
- protected:
- void DumpData(size_t indent) const override;
-};
-
struct KernelSymbolRecord : public Record {
uint32_t kallsyms_size;
const char* kallsyms;
diff --git a/simpleperf/record_file.h b/simpleperf/record_file.h
index a9fa1c32..223c1081 100644
--- a/simpleperf/record_file.h
+++ b/simpleperf/record_file.h
@@ -30,7 +30,6 @@
#include "dso.h"
#include "event_attr.h"
-#include "event_type.h"
#include "perf_event.h"
#include "record.h"
#include "record_file_format.h"
@@ -56,7 +55,6 @@ class RecordFileWriter {
bool WriteFeatureString(int feature, const std::string& s);
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 WriteMetaInfoFeature(const std::unordered_map<std::string, std::string>& info_map);
bool WriteFeature(int feature, const std::vector<char>& data);
@@ -145,7 +143,6 @@ class RecordFileReader {
std::vector<std::string> ReadCmdlineFeature();
std::vector<BuildIdRecord> ReadBuildIdFeature();
std::string ReadFeatureString(int feature);
- std::vector<uint64_t> ReadAuxTraceFeature();
// File feature section contains many file information. This function reads
// one file information located at [read_pos]. [read_pos] is 0 at the first
@@ -155,13 +152,10 @@ class RecordFileReader {
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);
-
- const std::unordered_map<std::string, std::string>& GetMetaInfoFeature() { return meta_info_; }
+ bool ReadMetaInfoFeature(std::unordered_map<std::string, std::string>* info_map);
void LoadBuildIdAndFileFeatures(ThreadTree& thread_tree);
- bool ReadAuxData(uint32_t cpu, uint64_t aux_offset, void* buf, size_t size);
-
bool Close();
// For testing only.
@@ -173,13 +167,9 @@ class RecordFileReader {
bool ReadAttrSection();
bool ReadIdsForAttr(const PerfFileFormat::FileAttr& attr, std::vector<uint64_t>* ids);
bool ReadFeatureSectionDescriptors();
- bool ReadMetaInfoFeature();
- void UseRecordingEnvironment();
- std::unique_ptr<Record> ReadRecord();
+ std::unique_ptr<Record> ReadRecord(uint64_t* nbytes_read);
bool Read(void* buf, size_t len);
- bool ReadAtOffset(uint64_t offset, void* buf, size_t len);
void ProcessEventIdRecord(const EventIdRecord& r);
- bool BuildAuxDataLocation();
const std::string filename_;
FILE* record_fp_;
@@ -195,22 +185,6 @@ class RecordFileReader {
uint64_t read_record_size_;
- std::unordered_map<std::string, std::string> meta_info_;
- std::unique_ptr<ScopedCurrentArch> scoped_arch_;
- std::unique_ptr<ScopedEventTypes> scoped_event_types_;
-
- struct AuxDataLocation {
- uint64_t aux_offset;
- uint64_t aux_size;
- uint64_t file_offset;
-
- AuxDataLocation(uint64_t aux_offset, uint64_t aux_size, uint64_t file_offset)
- : aux_offset(aux_offset), aux_size(aux_size), file_offset(file_offset) {}
- };
- // It maps from a cpu id to the locations (file offsets in perf.data) of aux data received from
- // that cpu's aux buffer. It is used to locate aux data in perf.data.
- std::unordered_map<uint32_t, std::vector<AuxDataLocation>> aux_data_location_;
-
DISALLOW_COPY_AND_ASSIGN(RecordFileReader);
};
diff --git a/simpleperf/record_file_format.h b/simpleperf/record_file_format.h
index 4606abcc..ead42758 100644
--- a/simpleperf/record_file_format.h
+++ b/simpleperf/record_file_format.h
@@ -89,7 +89,6 @@ enum {
FEAT_BRANCH_STACK,
FEAT_PMU_MAPPINGS,
FEAT_GROUP_DESC,
- FEAT_AUXTRACE,
FEAT_LAST_FEATURE,
FEAT_SIMPLEPERF_START = 128,
diff --git a/simpleperf/record_file_reader.cpp b/simpleperf/record_file_reader.cpp
index b17086a3..4e246b91 100644
--- a/simpleperf/record_file_reader.cpp
+++ b/simpleperf/record_file_reader.cpp
@@ -49,7 +49,6 @@ static const std::map<int, std::string> feature_name_map = {
{FEAT_BRANCH_STACK, "branch_stack"},
{FEAT_PMU_MAPPINGS, "pmu_mappings"},
{FEAT_GROUP_DESC, "group_desc"},
- {FEAT_AUXTRACE, "auxtrace"},
{FEAT_FILE, "file"},
{FEAT_META_INFO, "meta_info"},
};
@@ -79,10 +78,9 @@ std::unique_ptr<RecordFileReader> RecordFileReader::CreateInstance(const std::st
}
auto reader = std::unique_ptr<RecordFileReader>(new RecordFileReader(filename, fp));
if (!reader->ReadHeader() || !reader->ReadAttrSection() ||
- !reader->ReadFeatureSectionDescriptors() || !reader->ReadMetaInfoFeature()) {
+ !reader->ReadFeatureSectionDescriptors()) {
return nullptr;
}
- reader->UseRecordingEnvironment();
return reader;
}
@@ -207,17 +205,6 @@ bool RecordFileReader::ReadIdsForAttr(const FileAttr& attr, std::vector<uint64_t
return true;
}
-void RecordFileReader::UseRecordingEnvironment() {
- std::string arch = ReadFeatureString(FEAT_ARCH);
- if (!arch.empty()) {
- scoped_arch_.reset(new ScopedCurrentArch(GetArchType(arch)));
- }
- 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));
- }
-}
-
bool RecordFileReader::ReadDataSection(
const std::function<bool(std::unique_ptr<Record>)>& callback) {
std::unique_ptr<Record> record;
@@ -241,7 +228,7 @@ bool RecordFileReader::ReadRecord(std::unique_ptr<Record>& record) {
}
record = nullptr;
if (read_record_size_ < header_.data.size) {
- record = ReadRecord();
+ record = ReadRecord(&read_record_size_);
if (record == nullptr) {
return false;
}
@@ -252,7 +239,7 @@ bool RecordFileReader::ReadRecord(std::unique_ptr<Record>& record) {
return true;
}
-std::unique_ptr<Record> RecordFileReader::ReadRecord() {
+std::unique_ptr<Record> RecordFileReader::ReadRecord(uint64_t* nbytes_read) {
char header_buf[Record::header_size()];
if (!Read(header_buf, Record::header_size())) {
return nullptr;
@@ -271,7 +258,7 @@ std::unique_ptr<Record> RecordFileReader::ReadRecord() {
return nullptr;
}
cur_size += bytes_to_read;
- read_record_size_ += header.size;
+ *nbytes_read += header.size;
if (!Read(header_buf, Record::header_size())) {
return nullptr;
}
@@ -281,7 +268,7 @@ std::unique_ptr<Record> RecordFileReader::ReadRecord() {
LOG(ERROR) << "SPLIT records are not followed by a SPLIT_END record.";
return nullptr;
}
- read_record_size_ += header.size;
+ *nbytes_read += header.size;
header = RecordHeader(buf.data());
p.reset(new char[header.size]);
memcpy(p.get(), buf.data(), buf.size());
@@ -293,7 +280,7 @@ std::unique_ptr<Record> RecordFileReader::ReadRecord() {
return nullptr;
}
}
- read_record_size_ += header.size;
+ *nbytes_read += header.size;
}
const perf_event_attr* attr = &file_attrs_[0].attr;
@@ -318,17 +305,7 @@ std::unique_ptr<Record> RecordFileReader::ReadRecord() {
}
}
}
- auto r = ReadRecordFromOwnedBuffer(*attr, header.type, p.release());
- if (r->type() == PERF_RECORD_AUXTRACE) {
- auto auxtrace = static_cast<AuxTraceRecord*>(r.get());
- auxtrace->location.file_offset = header_.data.offset + read_record_size_;
- read_record_size_ += auxtrace->data->aux_size;
- if (fseek(record_fp_, auxtrace->data->aux_size, SEEK_CUR) != 0) {
- PLOG(ERROR) << "fseek() failed";
- return nullptr;
- }
- }
- return r;
+ return ReadRecordFromOwnedBuffer(*attr, header.type, p.release());
}
bool RecordFileReader::Read(void* buf, size_t len) {
@@ -339,14 +316,6 @@ bool RecordFileReader::Read(void* buf, size_t len) {
return true;
}
-bool RecordFileReader::ReadAtOffset(uint64_t offset, void* buf, size_t len) {
- if (fseek(record_fp_, offset, SEEK_SET) != 0) {
- PLOG(ERROR) << "failed to seek to " << offset;
- return false;
- }
- return Read(buf, len);
-}
-
void RecordFileReader::ProcessEventIdRecord(const EventIdRecord& r) {
for (size_t i = 0; i < r.count; ++i) {
event_ids_for_file_attrs_[r.data[i].attr_id].push_back(r.data[i].event_id);
@@ -373,7 +342,11 @@ bool RecordFileReader::ReadFeatureSection(int feature, std::vector<char>* data)
if (section.size == 0) {
return true;
}
- if (!ReadAtOffset(section.offset, data->data(), data->size())) {
+ if (fseek(record_fp_, section.offset, SEEK_SET) != 0) {
+ PLOG(ERROR) << "fseek() failed";
+ return false;
+ }
+ if (!Read(data->data(), data->size())) {
return false;
}
return true;
@@ -436,25 +409,6 @@ std::string RecordFileReader::ReadFeatureString(int feature) {
return p;
}
-std::vector<uint64_t> RecordFileReader::ReadAuxTraceFeature() {
- std::vector<char> buf;
- if (!ReadFeatureSection(FEAT_AUXTRACE, &buf)) {
- return {};
- }
- std::vector<uint64_t> auxtrace_offset;
- const char* p = buf.data();
- const char* end = buf.data() + buf.size();
- while (p < end) {
- uint64_t offset;
- uint64_t size;
- MoveFromBinaryFormat(offset, p);
- auxtrace_offset.push_back(offset);
- MoveFromBinaryFormat(size, p);
- CHECK_EQ(size, AuxTraceRecord::Size());
- }
- return auxtrace_offset;
-}
-
bool RecordFileReader::ReadFileFeature(size_t& read_pos,
std::string* file_path,
uint32_t* file_type,
@@ -517,21 +471,19 @@ bool RecordFileReader::ReadFileFeature(size_t& read_pos,
return true;
}
-bool RecordFileReader::ReadMetaInfoFeature() {
- if (feature_section_descriptors_.count(FEAT_META_INFO)) {
- std::vector<char> buf;
- if (!ReadFeatureSection(FEAT_META_INFO, &buf)) {
- return false;
- }
- const char* p = buf.data();
- const char* end = buf.data() + buf.size();
- while (p < end) {
- const char* key = p;
- const char* value = key + strlen(key) + 1;
- CHECK(value < end);
- meta_info_[p] = value;
- p = value + strlen(value) + 1;
- }
+bool RecordFileReader::ReadMetaInfoFeature(std::unordered_map<std::string, std::string>* info_map) {
+ std::vector<char> buf;
+ if (!ReadFeatureSection(FEAT_META_INFO, &buf)) {
+ return false;
+ }
+ const char* p = buf.data();
+ const char* end = buf.data() + buf.size();
+ while (p < end) {
+ const char* key = p;
+ const char* value = key + strlen(key) + 1;
+ CHECK(value < end);
+ (*info_map)[p] = value;
+ p = value + strlen(value) + 1;
}
return true;
}
@@ -560,62 +512,6 @@ void RecordFileReader::LoadBuildIdAndFileFeatures(ThreadTree& thread_tree) {
}
}
-bool RecordFileReader::ReadAuxData(uint32_t cpu, uint64_t aux_offset, void* buf, size_t size) {
- long saved_pos = ftell(record_fp_);
- if (saved_pos == -1) {
- PLOG(ERROR) << "ftell() failed";
- return false;
- }
- if (aux_data_location_.empty() && !BuildAuxDataLocation()) {
- return false;
- }
- AuxDataLocation* location = nullptr;
- auto it = aux_data_location_.find(cpu);
- if (it != aux_data_location_.end()) {
- auto comp = [](uint64_t aux_offset, const AuxDataLocation& location) {
- return aux_offset < location.aux_offset;
- };
- auto location_it = std::upper_bound(it->second.begin(), it->second.end(), aux_offset, comp);
- if (location_it != it->second.begin()) {
- --location_it;
- if (location_it->aux_offset + location_it->aux_size >= aux_offset + size) {
- location = &*location_it;
- }
- }
- }
- if (location == nullptr) {
- LOG(ERROR) << "failed to find file offset of aux data: cpu " << cpu << ", aux_offset "
- << aux_offset << ", size " << size;
- return false;
- }
- if (!ReadAtOffset(aux_offset - location->aux_offset + location->file_offset, buf, size)) {
- return false;
- }
- if (fseek(record_fp_, saved_pos, SEEK_SET) != 0) {
- PLOG(ERROR) << "fseek() failed";
- return false;
- }
- return true;
-}
-
-bool RecordFileReader::BuildAuxDataLocation() {
- std::vector<uint64_t> auxtrace_offset = ReadAuxTraceFeature();
- if (auxtrace_offset.empty()) {
- LOG(ERROR) << "failed to read auxtrace feature section";
- return false;
- }
- std::unique_ptr<char[]> buf(new char[AuxTraceRecord::Size()]);
- for (auto offset : auxtrace_offset) {
- if (!ReadAtOffset(offset, buf.get(), AuxTraceRecord::Size())) {
- return false;
- }
- AuxTraceRecord auxtrace(buf.get());
- aux_data_location_[auxtrace.data->cpu].emplace_back(
- auxtrace.data->offset, auxtrace.data->aux_size, offset + auxtrace.size());
- }
- return true;
-}
-
std::vector<std::unique_ptr<Record>> RecordFileReader::DataSection() {
std::vector<std::unique_ptr<Record>> records;
ReadDataSection([&](std::unique_ptr<Record> record) {
diff --git a/simpleperf/record_file_test.cpp b/simpleperf/record_file_test.cpp
index 76d15ec7..7eb1b6e5 100644
--- a/simpleperf/record_file_test.cpp
+++ b/simpleperf/record_file_test.cpp
@@ -144,5 +144,7 @@ TEST_F(RecordFileTest, write_meta_info_feature_section) {
// Read from a record file.
std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile_.path);
ASSERT_TRUE(reader != nullptr);
- ASSERT_EQ(reader->GetMetaInfoFeature(), info_map);
+ std::unordered_map<std::string, std::string> read_info_map;
+ ASSERT_TRUE(reader->ReadMetaInfoFeature(&read_info_map));
+ ASSERT_EQ(read_info_map, info_map);
}
diff --git a/simpleperf/record_file_writer.cpp b/simpleperf/record_file_writer.cpp
index f718df49..ecc94ee5 100644
--- a/simpleperf/record_file_writer.cpp
+++ b/simpleperf/record_file_writer.cpp
@@ -129,12 +129,8 @@ bool RecordFileWriter::WriteRecord(const Record& record) {
// RECORD_SPLIT records, followed by a RECORD_SPLIT_END record.
constexpr uint32_t RECORD_SIZE_LIMIT = 65535;
if (record.size() <= RECORD_SIZE_LIMIT) {
- bool result = WriteData(record.Binary(), record.size());
- if (result && record.type() == PERF_RECORD_AUXTRACE) {
- auto auxtrace = static_cast<const AuxTraceRecord*>(&record);
- result = WriteData(auxtrace->location.addr, auxtrace->data->aux_size);
- }
- return result;
+ WriteData(record.Binary(), record.size());
+ return true;
}
CHECK_GT(record.type(), SIMPLE_PERF_RECORD_TYPE_START);
const char* p = record.Binary();
@@ -208,15 +204,6 @@ bool RecordFileWriter::ReadDataSection(const std::function<void(const Record*)>&
}
read_pos += header.size;
std::unique_ptr<Record> r = ReadRecordFromBuffer(event_attr_, header.type, record_buf.data());
- if (r->type() == PERF_RECORD_AUXTRACE) {
- auto auxtrace = static_cast<AuxTraceRecord*>(r.get());
- auxtrace->location.file_offset = data_section_offset_ + read_pos;
- if (fseek(record_fp_, auxtrace->data->aux_size, SEEK_CUR) != 0) {
- PLOG(ERROR) << "fseek() failed";
- return false;
- }
- read_pos += auxtrace->data->aux_size;
- }
callback(r.get());
}
return true;
@@ -310,16 +297,6 @@ bool RecordFileWriter::WriteBranchStackFeature() {
return WriteFeatureEnd(FEAT_BRANCH_STACK);
}
-bool RecordFileWriter::WriteAuxTraceFeature(const std::vector<uint64_t>& auxtrace_offset) {
- std::vector<uint64_t> data;
- for (auto offset : auxtrace_offset) {
- data.push_back(offset);
- data.push_back(AuxTraceRecord::Size());
- }
- return WriteFeatureBegin(FEAT_AUXTRACE) && Write(data.data(), data.size() * sizeof(uint64_t)) &&
- WriteFeatureEnd(FEAT_AUXTRACE);
-}
-
bool RecordFileWriter::WriteFileFeatures(const std::vector<Dso*>& files) {
for (Dso* dso : files) {
// Always want to dump dex file offsets for DSO_DEX_FILE type.
diff --git a/simpleperf/report_lib_interface.cpp b/simpleperf/report_lib_interface.cpp
index 70a274f4..97a95943 100644
--- a/simpleperf/report_lib_interface.cpp
+++ b/simpleperf/report_lib_interface.cpp
@@ -108,7 +108,6 @@ 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;
@@ -155,7 +154,6 @@ class ReportLib {
void ShowIpForUnknownSymbol() { thread_tree_.ShowIpForUnknownSymbol(); }
void ShowArtFrames(bool show) { show_art_frames_ = show; }
- void MergeJavaMethods(bool merge) { merge_java_methods_ = merge; }
Sample* GetNextSample();
Event* GetEventOfCurrentSample() { return &current_event_; }
@@ -189,14 +187,12 @@ class ReportLib {
std::vector<CallChainEntry> callchain_entries_;
std::string build_id_string_;
std::vector<EventInfo> events_;
+ std::unique_ptr<ScopedEventTypes> scoped_event_types_;
bool trace_offcpu_;
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_;
std::unique_ptr<Tracing> tracing_;
};
@@ -228,18 +224,18 @@ bool ReportLib::OpenRecordFileIfNecessary() {
return false;
}
record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
- auto& meta_info = record_file_reader_->GetMetaInfoFeature();
- if (auto it = meta_info.find("trace_offcpu"); it != meta_info.end()) {
- trace_offcpu_ = it->second == "true";
+ std::unordered_map<std::string, std::string> meta_info_map;
+ if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_META_INFO) &&
+ !record_file_reader_->ReadMetaInfoFeature(&meta_info_map)) {
+ return false;
}
- 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);
- }
- }
- }
+ auto it = meta_info_map.find("event_type_info");
+ if (it != meta_info_map.end()) {
+ scoped_event_types_.reset(new ScopedEventTypes(it->second));
+ }
+ it = meta_info_map.find("trace_offcpu");
+ if (it != meta_info_map.end()) {
+ trace_offcpu_ = it->second == "true";
}
}
return true;
@@ -307,8 +303,7 @@ void ReportLib::SetCurrentSample() {
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");
+ return android::base::EndsWith(map->dso->Path(), "/libart.so");
};
for (size_t i = 0; i < ips.size(); ++i) {
const MapEntry* map = thread_tree_.FindMap(current_thread_, ips[i], i < kernel_ip_count);
@@ -342,23 +337,6 @@ void ReportLib::SetCurrentSample() {
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]";
- }
- }
-
callchain_entries_.push_back(entry);
}
current_sample_.ip = callchain_entries_[0].ip;
@@ -491,10 +469,6 @@ void ShowArtFrames(ReportLib* report_lib, bool show) {
return report_lib->ShowArtFrames(show);
}
-void MergeJavaMethods(ReportLib* report_lib, bool merge) {
- return report_lib->MergeJavaMethods(merge);
-}
-
bool SetKallsymsFile(ReportLib* report_lib, const char* kallsyms_file) {
return report_lib->SetKallsymsFile(kallsyms_file);
}
diff --git a/simpleperf/sample_tree.h b/simpleperf/sample_tree.h
index f57fe786..a2ce19fa 100644
--- a/simpleperf/sample_tree.h
+++ b/simpleperf/sample_tree.h
@@ -77,7 +77,7 @@ class SampleTreeBuilder {
build_callchain_ = build_callchain;
use_caller_as_callchain_root_ = use_caller_as_callchain_root;
if (accumulate_callchain_) {
- offline_unwinder_ = OfflineUnwinder::Create(false);
+ offline_unwinder_.reset(new OfflineUnwinder(false));
}
}
@@ -145,7 +145,7 @@ class SampleTreeBuilder {
}
}
EntryT* callchain_sample =
- CreateCallChainSample(thread, sample, ip, in_kernel, callchain, acc_info);
+ CreateCallChainSample(sample, ip, in_kernel, callchain, acc_info);
if (callchain_sample == nullptr) {
break;
}
@@ -188,8 +188,8 @@ class SampleTreeBuilder {
AccumulateInfoT* acc_info) = 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,
+ virtual EntryT* CreateCallChainSample(const EntryT* sample, uint64_t ip,
+ bool in_kernel,
const std::vector<EntryT*>& callchain,
const AccumulateInfoT& acc_info) = 0;
virtual const ThreadEntry* GetThreadOfSample(EntryT*) = 0;
diff --git a/simpleperf/sample_tree_test.cpp b/simpleperf/sample_tree_test.cpp
index 4123b0e6..7aa778c0 100644
--- a/simpleperf/sample_tree_test.cpp
+++ b/simpleperf/sample_tree_test.cpp
@@ -76,7 +76,7 @@ class TestSampleTreeBuilder : public SampleTreeBuilder<SampleEntry, int> {
const BranchStackItemType&) override {
return nullptr;
};
- SampleEntry* CreateCallChainSample(const ThreadEntry*, const SampleEntry*, uint64_t, bool,
+ SampleEntry* CreateCallChainSample(const SampleEntry*, uint64_t, bool,
const std::vector<SampleEntry*>&,
const int&) override {
return nullptr;
diff --git a/simpleperf/scripts/app_profiler.py b/simpleperf/scripts/app_profiler.py
index 0080b7f9..e45580af 100755
--- a/simpleperf/scripts/app_profiler.py
+++ b/simpleperf/scripts/app_profiler.py
@@ -30,7 +30,7 @@ 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 utils import log_debug, log_info, log_exit, ReadElf, remove, str_to_bytes
NATIVE_LIBS_DIR_ON_DEVICE = '/data/local/tmp/native_libs/'
@@ -117,12 +117,6 @@ class NativeLibDownloader(object):
self.adb.check_run(['shell', 'mkdir', '-p', self.dir_on_device])
if os.path.exists(self.build_id_list_file):
os.remove(self.build_id_list_file)
- result, output = self.adb.run_and_return_output(['shell', 'ls', self.dir_on_device])
- if not result:
- return
- file_set = set(output.strip().split())
- if self.build_id_list_file not in file_set:
- return
self.adb.run(['pull', self.dir_on_device + self.build_id_list_file])
if os.path.exists(self.build_id_list_file):
with open(self.build_id_list_file, 'rb') as fh:
@@ -130,9 +124,7 @@ class NativeLibDownloader(object):
line = bytes_to_str(line).strip()
items = line.split('=')
if len(items) == 2:
- build_id, filename = items
- if filename in file_set:
- self.device_build_id_map[build_id] = filename
+ self.device_build_id_map[items[0]] = items[1]
remove(self.build_id_list_file)
def sync_natives_libs_on_device(self):
@@ -203,12 +195,11 @@ 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
adb_args = [self.adb.adb_path, 'shell'] + args
- log_info('run adb cmd: %s' % adb_args)
+ log_debug('run adb cmd: %s' % adb_args)
self.record_subproc = subprocess.Popen(adb_args)
def wait_profiling(self):
@@ -245,7 +236,7 @@ class ProfilerBase(object):
if not self.args.skip_collect_binaries:
binary_cache_args = [sys.executable,
os.path.join(get_script_dir(), 'binary_cache_builder.py')]
- binary_cache_args += ['-i', self.args.perf_data_path, '--log', self.args.log]
+ binary_cache_args += ['-i', self.args.perf_data_path]
if self.args.native_lib_dir:
binary_cache_args += ['-lib', self.args.native_lib_dir]
if self.args.disable_adb_root:
@@ -426,15 +417,12 @@ def main():
help="""Force adb to run in non root mode. By default, app_profiler.py
will try to switch to root mode to be able to profile released
Android apps.""")
- other_group.add_argument(
- '--log', choices=['debug', 'info', 'warning'], default='info', help='set log level')
def check_args(args):
if (not args.app) and (args.compile_java_code or args.activity or args.test):
log_exit('--compile_java_code, -a, -t can only be used when profiling an Android app.')
args = parser.parse_args()
- set_log_level(args.log)
check_args(args)
if args.app:
profiler = AppProfiler(args)
diff --git a/simpleperf/scripts/bin/android/arm/simpleperf b/simpleperf/scripts/bin/android/arm/simpleperf
index 0bbb47fc..11075809 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 72d505ce..ee4ea368 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 8de146d3..fde46521 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 5cd23524..9753db90 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 207eefe6..6a22ee95 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 c3f85c03..c4991ab5 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 fc48d068..c1783565 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 8ae69262..6839a959 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
index 24e3129c..47eb4d8d 100755
--- a/simpleperf/scripts/bin/windows/x86/libsimpleperf_report.dll
+++ b/simpleperf/scripts/bin/windows/x86/libsimpleperf_report.dll
Binary files differ
diff --git a/simpleperf/scripts/bin/windows/x86/libwinpthread-1.dll b/simpleperf/scripts/bin/windows/x86/libwinpthread-1.dll
index a41127ab..86de5d6e 100755
--- a/simpleperf/scripts/bin/windows/x86/libwinpthread-1.dll
+++ b/simpleperf/scripts/bin/windows/x86/libwinpthread-1.dll
Binary files differ
diff --git a/simpleperf/scripts/bin/windows/x86/simpleperf.exe b/simpleperf/scripts/bin/windows/x86/simpleperf.exe
index c8e4e14a..53933613 100755
--- a/simpleperf/scripts/bin/windows/x86/simpleperf.exe
+++ b/simpleperf/scripts/bin/windows/x86/simpleperf.exe
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 a55350b3..9f2b477c 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..ee5d7a3c 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 d221976a..53933613 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 97d169c5..50fcc548 100755
--- a/simpleperf/scripts/binary_cache_builder.py
+++ b/simpleperf/scripts/binary_cache_builder.py
@@ -27,7 +27,7 @@ import shutil
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 utils import ReadElf
def is_jit_symfile(dso_name):
return dso_name.split('/')[-1].startswith('TemporaryFile')
@@ -209,7 +209,7 @@ class BinaryCacheBuilder(object):
if os.path.isfile(file_path):
os.remove(file_path)
if self.adb.switch_to_root():
- self.adb.run(['shell', 'echo', '0', '>/proc/sys/kernel/kptr_restrict'])
+ self.adb.run(['shell', '"echo 0 >/proc/sys/kernel/kptr_restrict"'])
self.adb.run(['pull', '/proc/kallsyms', file_path])
@@ -223,10 +223,8 @@ def main():
parser.add_argument('--disable_adb_root', action='store_true', help="""
Force adb to run in non root mode.""")
parser.add_argument('--ndk_path', nargs=1, help='Find tools in the ndk path.')
- parser.add_argument(
- '--log', choices=['debug', 'info', 'warning'], default='info', help='set log level')
args = parser.parse_args()
- set_log_level(args.log)
+
ndk_path = None if not args.ndk_path else args.ndk_path[0]
builder = BinaryCacheBuilder(ndk_path, args.disable_adb_root)
symfs_dirs = flatten_arg_list(args.native_lib_dir)
diff --git a/simpleperf/scripts/inferno/data_types.py b/simpleperf/scripts/inferno/data_types.py
index 35ef4c0b..deb9f515 100644
--- a/simpleperf/scripts/inferno/data_types.py
+++ b/simpleperf/scripts/inferno/data_types.py
@@ -113,16 +113,15 @@ class FlameGraphCallSite(object):
self._get_next_callsite_id())
return child
- def trim_callchain(self, min_num_events, max_depth, depth=0):
+ def trim_callchain(self, min_num_events):
""" Remove call sites with num_events < min_num_events in the subtree.
Remaining children are collected in a list.
"""
- if depth <= max_depth:
- for key in self.child_dict:
- child = self.child_dict[key]
- if child.num_events >= min_num_events:
- child.trim_callchain(min_num_events, max_depth, depth + 1)
- self.children.append(child)
+ for key in self.child_dict:
+ child = self.child_dict[key]
+ if child.num_events >= min_num_events:
+ child.trim_callchain(min_num_events)
+ self.children.append(child)
# Relese child_dict since it will not be used.
self.child_dict = None
diff --git a/simpleperf/scripts/inferno/inferno.py b/simpleperf/scripts/inferno/inferno.py
index 12b9d904..78cc3437 100755
--- a/simpleperf/scripts/inferno/inferno.py
+++ b/simpleperf/scripts/inferno/inferno.py
@@ -40,7 +40,7 @@ import sys
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 utils import log_exit, log_info, AdbHelper, open_report_in_browser
from data_types import Process
from svg_renderer import get_proper_scaled_time_string, render_svg
@@ -143,7 +143,7 @@ def parse_samples(process, args, sample_filter_fn):
for thread in process.threads.values():
min_event_count = thread.num_events * args.min_callchain_percentage * 0.01
- thread.flamegraph.trim_callchain(min_event_count, args.max_callchain_depth)
+ thread.flamegraph.trim_callchain(min_event_count)
log_info("Parsed %s callchains." % process.num_samples)
@@ -289,11 +289,6 @@ def main():
It is used to limit nodes shown in the flamegraph. For example,
when set to 0.01, only callchains taking >= 0.01%% of the event
count of the owner thread are collected in the report.""")
- report_group.add_argument('--max_callchain_depth', default=1000000000, type=int, help="""
- Set maximum depth of callchains shown in the report. It is used
- to limit the nodes shown in the flamegraph and avoid processing
- limits. For example, when set to 10, callstacks will be cut after
- the tenth frame.""")
report_group.add_argument('--no_browser', action='store_true', help="""Don't open report
in browser.""")
report_group.add_argument('-o', '--report_path', default='report.html', help="""Set report
@@ -348,16 +343,11 @@ def main():
args.title = ''
args.title += '(One Flamegraph)'
- try:
- parse_samples(process, args, sample_filter_fn)
- generate_threads_offsets(process)
- report_path = output_report(process, args)
- if not args.no_browser:
- open_report_in_browser(report_path)
- except RuntimeError as r:
- if 'maximum recursion depth' in r.__str__():
- log_fatal("Recursion limit exceeded (%s), try --max_callchain_depth." % r)
- raise r
+ parse_samples(process, args, sample_filter_fn)
+ generate_threads_offsets(process)
+ report_path = output_report(process, args)
+ if not args.no_browser:
+ open_report_in_browser(report_path)
log_info("Flamegraph generated at '%s'." % report_path)
diff --git a/simpleperf/scripts/pprof_proto_generator.py b/simpleperf/scripts/pprof_proto_generator.py
index c6355947..d0b4da95 100755
--- a/simpleperf/scripts/pprof_proto_generator.py
+++ b/simpleperf/scripts/pprof_proto_generator.py
@@ -30,8 +30,8 @@ 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 utils import Addr2Nearestline, bytes_to_str, extant_dir, find_tool_path, flatten_arg_list
+from utils import log_info, log_exit, str_to_bytes
try:
import profile_pb2
except ImportError:
@@ -40,13 +40,13 @@ except ImportError:
def load_pprof_profile(filename):
profile = profile_pb2.Profile()
with open(filename, "rb") as f:
- profile.ParseFromString(f.read())
+ profile.ParseFromString(bytes_to_str(f.read()))
return profile
def store_pprof_profile(filename, profile):
with open(filename, 'wb') as f:
- f.write(profile.SerializeToString())
+ f.write(str_to_bytes(profile.SerializeToString()))
class PprofProfilePrinter(object):
@@ -256,8 +256,6 @@ class PprofProfileGenerator(object):
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']}
@@ -268,7 +266,6 @@ class PprofProfileGenerator(object):
else:
self.tid_filter = None
self.dso_filter = set(config['dso_filters']) if config.get('dso_filters') else None
- self.max_chain_length = config['max_chain_length']
self.profile = profile_pb2.Profile()
self.profile.string_table.append('')
self.string_table = {}
@@ -282,10 +279,6 @@ class PprofProfileGenerator(object):
self.function_map = {}
self.function_list = []
- # Map from dso_name in perf.data to (binary path, build_id).
- self.binary_map = {}
- self.read_elf = ReadElf(self.config['ndk_path'])
-
def gen(self):
# 1. Process all samples in perf.data, aggregate samples.
while True:
@@ -305,9 +298,9 @@ class PprofProfileGenerator(object):
sample.add_value(sample_type_id, 1)
sample.add_value(sample_type_id + 1, report_sample.period)
if self._filter_symbol(symbol):
- location_id = self.get_location_id(report_sample.ip, symbol)
+ location_id = self.get_location_id(symbol.vaddr_in_file, symbol)
sample.add_location_id(location_id)
- for i in range(max(0, callchain.nr - self.max_chain_length), callchain.nr):
+ for i in range(callchain.nr):
entry = callchain.entries[i]
if self._filter_symbol(symbol):
location_id = self.get_location_id(entry.ip, entry.symbol)
@@ -335,12 +328,12 @@ class PprofProfileGenerator(object):
if self.comm_filter:
if sample.thread_comm not in self.comm_filter:
return False
- if self.pid_filter:
- if sample.pid not in self.pid_filter:
- return False
- if self.tid_filter:
- if sample.tid not in self.tid_filter:
- return False
+ if self.pid_filter:
+ if sample.pid not in self.pid_filter:
+ return False
+ if self.tid_filter:
+ if sample.tid not in self.tid_filter:
+ return False
return True
def _filter_symbol(self, symbol):
@@ -377,10 +370,10 @@ class PprofProfileGenerator(object):
return sample_type_id
def get_location_id(self, ip, symbol):
- binary_path, build_id = self.get_binary(symbol.dso_name)
- mapping_id = self.get_mapping_id(symbol.mapping[0], binary_path, build_id)
+ mapping_id = self.get_mapping_id(symbol.mapping[0], symbol.dso_name)
location = Location(mapping_id, ip, symbol.vaddr_in_file)
- function_id = self.get_function_id(symbol.symbol_name, binary_path, symbol.symbol_addr)
+ function_id = self.get_function_id(symbol.symbol_name, symbol.dso_name,
+ symbol.symbol_addr)
if function_id:
# Add Line only when it has a valid function id, see http://b/36988814.
# Default line info only contains the function name
@@ -397,8 +390,11 @@ class PprofProfileGenerator(object):
self.location_map[location.key] = location
return location.id
- def get_mapping_id(self, report_mapping, filename, build_id):
+ def get_mapping_id(self, report_mapping, filename):
filename_id = self.get_string_id(filename)
+ build_id = self.lib.GetBuildIdForPath(filename)
+ if build_id and build_id[0:2] == "0x":
+ build_id = build_id[2:]
build_id_id = self.get_string_id(build_id)
mapping = Mapping(report_mapping.start, report_mapping.end,
report_mapping.pgoff, filename_id, build_id_id)
@@ -411,37 +407,6 @@ class PprofProfileGenerator(object):
self.mapping_map[mapping.key] = mapping
return mapping.id
- def get_binary(self, dso_name):
- """ Return (binary_path, build_id) for a given dso_name. """
- value = self.binary_map.get(dso_name)
- if value:
- return value
-
- binary_path = dso_name
- build_id = ''
-
- # The build ids in perf.data are padded to 20 bytes, but pprof needs without padding.
- # So read build id from the binary in binary_cache, and check it with build id in
- # perf.data.
- build_id_in_perf_data = self.lib.GetBuildIdForPath(dso_name)
- if build_id_in_perf_data:
- # Try elf_path in binary cache.
- elf_path = find_real_dso_path(dso_name, self.config['binary_cache_dir'])
- if elf_path:
- elf_build_id = self.read_elf.get_build_id(elf_path, False)
- if build_id_in_perf_data == self.read_elf.pad_build_id(elf_build_id):
- build_id = elf_build_id
- binary_path = elf_path
-
- if not build_id and build_id_in_perf_data.startswith('0x'):
- # Fallback to the way used by TrimZeroesFromBuildIDString() in quipper.
- build_id = build_id_in_perf_data[2:] # remove '0x'
- padding = '0' * 8
- while build_id.endswith(padding):
- build_id = build_id[:-len(padding)]
- self.binary_map[dso_name] = (binary_path, build_id)
- return (binary_path, build_id)
-
def get_mapping(self, mapping_id):
return self.mapping_list[mapping_id - 1] if mapping_id > 0 else None
@@ -474,12 +439,10 @@ 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']):
- log_info("Can't generate line information because can't find llvm-symbolizer.")
+ if not find_tool_path('addr2line', self.config['ndk_path']):
+ log_info("Can't generate line information because can't find addr2line.")
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)
+ addr2line = Addr2Nearestline(self.config['ndk_path'], self.config['binary_cache_dir'], True)
# 2. Put all needed addresses to it.
for location in self.location_list:
@@ -597,11 +560,7 @@ def main():
Use samples only in threads with selected thread ids.""")
parser.add_argument('--dso', nargs='+', action='append', help="""
Use samples only in selected binaries.""")
- parser.add_argument('--max_chain_length', type=int, default=1000000000, help="""
- Maximum depth of samples to be converted.""") # Large value as infinity standin.
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.')
args = parser.parse_args()
if args.show:
@@ -619,8 +578,6 @@ def main():
config['tid_filters'] = flatten_arg_list(args.tid)
config['dso_filters'] = flatten_arg_list(args.dso)
config['ndk_path'] = args.ndk_path
- config['show_art_frames'] = args.show_art_frames
- config['max_chain_length'] = args.max_chain_length
generator = PprofProfileGenerator(config)
profile = generator.gen()
store_pprof_profile(config['output_file'], profile)
diff --git a/simpleperf/scripts/report_html.py b/simpleperf/scripts/report_html.py
index f2292720..1a616fbc 100755
--- a/simpleperf/scripts/report_html.py
+++ b/simpleperf/scripts/report_html.py
@@ -127,20 +127,6 @@ class ProcessScope(object):
for thread in threads]
return result
- def merge_by_thread_name(self, process):
- self.event_count += process.event_count
- thread_list = list(self.threads.values()) + list(process.threads.values())
- new_threads = {} # map from thread name to ThreadScope
- for thread in thread_list:
- cur_thread = new_threads.get(thread.name)
- if cur_thread is None:
- new_threads[thread.name] = thread
- else:
- cur_thread.merge(thread)
- self.threads = {}
- for thread in new_threads.values():
- self.threads[thread.tid] = thread
-
class ThreadScope(object):
@@ -214,18 +200,6 @@ class ThreadScope(object):
result['rg'] = self.reverse_call_graph.gen_sample_info()
return result
- def merge(self, thread):
- self.event_count += thread.event_count
- self.sample_count += thread.sample_count
- for lib_id, lib in thread.libs.items():
- cur_lib = self.libs.get(lib_id)
- if cur_lib is None:
- self.libs[lib_id] = lib
- else:
- cur_lib.merge(lib)
- self.call_graph.merge(thread.call_graph)
- self.reverse_call_graph.merge(thread.reverse_call_graph)
-
class LibScope(object):
@@ -248,15 +222,6 @@ class LibScope(object):
for func in self.functions.values()]
return result
- def merge(self, lib):
- self.event_count += lib.event_count
- for func_id, function in lib.functions.items():
- cur_function = self.functions.get(func_id)
- if cur_function is None:
- self.functions[func_id] = function
- else:
- cur_function.merge(function)
-
class FunctionScope(object):
@@ -309,28 +274,6 @@ class FunctionScope(object):
result['a'] = items
return result
- def merge(self, function):
- self.sample_count += function.sample_count
- self.event_count += function.event_count
- self.subtree_event_count += function.subtree_event_count
- self.addr_hit_map = self.__merge_hit_map(self.addr_hit_map, function.addr_hit_map)
- self.line_hit_map = self.__merge_hit_map(self.line_hit_map, function.line_hit_map)
-
- @staticmethod
- def __merge_hit_map(map1, map2):
- if not map1:
- return map2
- if not map2:
- return map1
- for key, value2 in map2.items():
- value1 = map1.get(key)
- if value1 is None:
- map1[key] = value2
- else:
- value1[0] += value2[0]
- value1[1] += value2[1]
- return map1
-
class CallNode(object):
@@ -372,16 +315,6 @@ class CallNode(object):
result['c'] = [child.gen_sample_info() for child in self.children.values()]
return result
- def merge(self, node):
- self.event_count += node.event_count
- self.subtree_event_count += node.subtree_event_count
- for key, child in node.children.items():
- cur_child = self.children.get(key)
- if cur_child is None:
- self.children[key] = child
- else:
- cur_child.merge(child)
-
class LibSet(object):
""" Collection of shared libraries used in perf.data. """
@@ -625,24 +558,10 @@ class RecordData(object):
for thread in event.threads:
thread.update_subtree_event_count()
- def aggregate_by_thread_name(self):
- for event in self.events.values():
- new_processes = {} # from process name to ProcessScope
- for process in event.processes.values():
- cur_process = new_processes.get(process.name)
- if cur_process is None:
- new_processes[process.name] = process
- else:
- cur_process.merge_by_thread_name(process)
- event.processes = {}
- 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()
for event in self.events.values():
min_limit = event.event_count * min_func_percent * 0.01
- to_del_processes = []
for process in event.processes.values():
to_del_threads = []
for thread in process.threads.values():
@@ -652,10 +571,6 @@ class RecordData(object):
thread.limit_percents(min_limit, min_callchain_percent, hit_func_ids)
for thread in to_del_threads:
del process.threads[thread]
- if not process.threads:
- to_del_processes.append(process.pid)
- for process in to_del_processes:
- del event.processes[process]
self.functions.trim_functions(hit_func_ids)
def _get_event(self, event_name):
@@ -918,9 +833,6 @@ def main():
parser.add_argument('--no_browser', action='store_true', help="Don't open report in browser.")
parser.add_argument('--show_art_frames', action='store_true',
help='Show frames of internal methods in the ART Java interpreter.')
- 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()
# 1. Process args.
@@ -942,8 +854,6 @@ def main():
record_data = RecordData(binary_cache_path, ndk_path, build_addr_hit_map)
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):
diff --git a/simpleperf/scripts/script_testdata/aggregatable_perf1.data b/simpleperf/scripts/script_testdata/aggregatable_perf1.data
deleted file mode 100644
index 61f5258a..00000000
--- a/simpleperf/scripts/script_testdata/aggregatable_perf1.data
+++ /dev/null
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/aggregatable_perf2.data b/simpleperf/scripts/script_testdata/aggregatable_perf2.data
deleted file mode 100644
index 7b8224e9..00000000
--- a/simpleperf/scripts/script_testdata/aggregatable_perf2.data
+++ /dev/null
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/cpp_api-debug_Q.apk b/simpleperf/scripts/script_testdata/cpp_api-debug_Q.apk
deleted file mode 100644
index 62591ad5..00000000
--- a/simpleperf/scripts/script_testdata/cpp_api-debug_Q.apk
+++ /dev/null
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/cpp_api-debug_prev_Q.apk b/simpleperf/scripts/script_testdata/cpp_api-debug_prev_Q.apk
deleted file mode 100644
index 2ebf32b0..00000000
--- a/simpleperf/scripts/script_testdata/cpp_api-debug_prev_Q.apk
+++ /dev/null
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/cpp_api-profile_Q.apk b/simpleperf/scripts/script_testdata/cpp_api-profile_Q.apk
deleted file mode 100644
index 746b86cc..00000000
--- a/simpleperf/scripts/script_testdata/cpp_api-profile_Q.apk
+++ /dev/null
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/cpp_api-profile_prev_Q.apk b/simpleperf/scripts/script_testdata/cpp_api-profile_prev_Q.apk
deleted file mode 100644
index 9718824e..00000000
--- a/simpleperf/scripts/script_testdata/cpp_api-profile_prev_Q.apk
+++ /dev/null
Binary files differ
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/two_process_perf.data b/simpleperf/scripts/script_testdata/two_process_perf.data
deleted file mode 100644
index c61d5916..00000000
--- a/simpleperf/scripts/script_testdata/two_process_perf.data
+++ /dev/null
Binary files differ
diff --git a/simpleperf/scripts/simpleperf_report_lib.py b/simpleperf/scripts/simpleperf_report_lib.py
index 3e677be5..ab45b67a 100644
--- a/simpleperf/scripts/simpleperf_report_lib.py
+++ b/simpleperf/scripts/simpleperf_report_lib.py
@@ -240,7 +240,6 @@ class ReportLib(object):
self._SetKallsymsFileFunc = self._lib.SetKallsymsFile
self._ShowIpForUnknownSymbolFunc = self._lib.ShowIpForUnknownSymbol
self._ShowArtFramesFunc = self._lib.ShowArtFrames
- self._MergeJavaMethodsFunc = self._lib.MergeJavaMethods
self._GetNextSampleFunc = self._lib.GetNextSample
self._GetNextSampleFunc.restype = ct.POINTER(SampleStruct)
self._GetEventOfCurrentSampleFunc = self._lib.GetEventOfCurrentSample
@@ -295,17 +294,6 @@ class ReportLib(object):
""" Show frames of internal methods of the Java interpreter. """
self._ShowArtFramesFunc(self.getInstance(), show)
- def MergeJavaMethods(self, merge=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.
- Side effects:
- It only works at method level, not instruction level.
- It makes symbol.vaddr_in_file and symbol.mapping not accurate for jitted methods.
- Java methods are merged by default.
- """
- self._MergeJavaMethodsFunc(self.getInstance(), merge)
-
def SetKallsymsFile(self, kallsym_file):
""" 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))
diff --git a/simpleperf/scripts/test.py b/simpleperf/scripts/test.py
index b60d54d6..a3ef42fc 100755
--- a/simpleperf/scripts/test.py
+++ b/simpleperf/scripts/test.py
@@ -36,12 +36,9 @@ 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
@@ -57,149 +54,65 @@ 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
+from utils import is_python3, is_windows, Objdump, ReadElf, remove, SourceFileSearcher
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()
-
+def get_device_features():
+ adb = AdbHelper()
+ adb.check_run_and_return_output(['push',
+ 'bin/android/%s/simpleperf' % adb.get_device_arch(),
+ '/data/local/tmp'])
+ adb.check_run_and_return_output(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf'])
+ return adb.check_run_and_return_output(['shell', '/data/local/tmp/simpleperf', 'list',
+ '--show-features'])
+
+def is_trace_offcpu_supported():
+ if not hasattr(is_trace_offcpu_supported, 'value'):
+ is_trace_offcpu_supported.value = 'trace-offcpu' in get_device_features()
+ return is_trace_offcpu_supported.value
+
+
+def build_testdata():
+ """ Collect testdata from ../testdata and ../demo. """
+ from_testdata_path = os.path.join('..', 'testdata')
+ from_demo_path = os.path.join('..', 'demo')
+ from_script_testdata_path = 'script_testdata'
+ if (not os.path.isdir(from_testdata_path) or not os.path.isdir(from_demo_path) or
+ not from_script_testdata_path):
+ return
+ copy_testdata_list = ['perf_with_symbols.data', 'perf_with_trace_offcpu.data',
+ 'perf_with_tracepoint_event.data', 'perf_with_interpreter_frames.data']
+ copy_demo_list = ['SimpleperfExamplePureJava', 'SimpleperfExampleWithNative',
+ 'SimpleperfExampleOfKotlin']
+
+ testdata_path = "testdata"
+ remove(testdata_path)
+ os.mkdir(testdata_path)
+ for testdata in copy_testdata_list:
+ shutil.copy(os.path.join(from_testdata_path, testdata), testdata_path)
+ for demo in copy_demo_list:
+ shutil.copytree(os.path.join(from_demo_path, demo), os.path.join(testdata_path, demo))
+ for f in os.listdir(from_script_testdata_path):
+ shutil.copy(os.path.join(from_script_testdata_path, f), testdata_path)
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:]
+ args = [sys.executable] + args
use_shell = args[0].endswith('.bat')
try:
if not return_output:
- returncode = subprocess.call(args, shell=use_shell, stderr=TEST_LOGGER.log_fh)
+ returncode = subprocess.call(args, shell=use_shell)
else:
- subproc = subprocess.Popen(args, stdout=subprocess.PIPE,
- stderr=TEST_LOGGER.log_fh, shell=use_shell)
+ subproc = subprocess.Popen(args, stdout=subprocess.PIPE, shell=use_shell)
(output_data, _) = subproc.communicate()
output_data = bytes_to_str(output_data)
returncode = subproc.returncode
@@ -210,27 +123,12 @@ class TestBase(unittest.TestCase):
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)
+ cls.example_path = os.path.join("testdata", 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):
@@ -241,49 +139,65 @@ class TestExampleBase(TestBase):
log_fatal("can't find app-profiling.apk under " + cls.example_path)
cls.package_name = package_name
cls.activity_name = activity_name
+ cls.abi = "arm64"
+ if abi and abi != "arm64" and abi.find("arm") != -1:
+ cls.abi = "arm"
args = ["install", "-r"]
if abi:
args += ["--abi", abi]
args.append(cls.apk_path)
cls.adb.check_run(args)
cls.adb_root = adb_root
+ cls.compiled = False
cls.has_perf_data_for_report = False
+ android_version = cls.adb.get_android_version()
# 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__)
+ cls.use_compiled_java_code = android_version <= 8
+
+ def setUp(self):
+ if self.id().find('TraceOffCpu') != -1 and not is_trace_offcpu_supported():
+ self.skipTest('trace-offcpu is not supported on device')
+ cls = self.__class__
+ if not cls.has_perf_data_for_report:
+ cls.has_perf_data_for_report = True
+ self.run_app_profiler()
+ shutil.copy('perf.data', 'perf.data_for_report')
+ remove('binary_cache_for_report')
+ shutil.copytree('binary_cache', 'binary_cache_for_report')
+ else:
+ shutil.copy('perf.data_for_report', 'perf.data')
+ remove('binary_cache')
+ shutil.copytree('binary_cache_for_report', 'binary_cache')
@classmethod
def tearDownClass(cls):
- remove(cls.testcase_dir)
+ if hasattr(cls, 'test_result') and cls.test_result and not cls.test_result.wasSuccessful():
+ return
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)
+ remove("binary_cache")
+ remove("annotated_files")
+ remove("perf.data")
+ remove("report.txt")
+ remove("pprof.profile")
+ if cls.has_perf_data_for_report:
+ cls.has_perf_data_for_report = False
+ remove('perf.data_for_report')
+ remove('binary_cache_for_report')
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):
+ start_activity=True):
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:
+ if self.use_compiled_java_code and not self.__class__.compiled:
args.append('--compile_java_code')
+ self.__class__.compiled = True
if start_activity:
args += ["-a", self.activity_name]
args += ["-lib", self.example_path]
@@ -294,6 +208,12 @@ class TestExampleBase(TestBase):
if build_binary_cache:
self.check_exist(dirname="binary_cache")
+ 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_file_under_dir(self, dirname, filename):
self.check_exist(dirname=dirname)
for _, _, files in os.walk(dirname):
@@ -302,6 +222,16 @@ class TestExampleBase(TestBase):
return
self.fail("Failed to call check_file_under_dir(dir=%s, file=%s)" % (dirname, filename))
+
+ 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_strings_in_content(self, content, strings):
+ for s in strings:
+ self.assertNotEqual(content.find(s), -1, "s: %s, content: %s" % (s, content))
+
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]
@@ -415,6 +345,7 @@ class TestExampleBase(TestBase):
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'])
+ remove('perf2.data')
class TestExamplePureJava(TestExampleBase):
@@ -449,8 +380,8 @@ class TestExamplePureJava(TestExampleBase):
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"]
+ args = [sys.executable, "app_profiler.py", "--app", self.package_name,
+ "-r", "--duration 10000", "--disable_adb_root"]
subproc = subprocess.Popen(args)
time.sleep(3)
@@ -462,9 +393,8 @@ class TestExamplePureJava(TestExampleBase):
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'])
+ subproc = subprocess.Popen([sys.executable, '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()
@@ -533,15 +463,20 @@ class TestExamplePureJava(TestExampleBase):
self.check_inferno_report_html(
[('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run', 80)],
"report2.html")
+ remove("report2.html")
def test_inferno_in_another_dir(self):
test_dir = 'inferno_testdir'
+ saved_dir = os.getcwd()
+ remove(test_dir)
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.run_cmd(['python', os.path.join(saved_dir, '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"])
+ os.chdir(saved_dir)
+ remove(test_dir)
def test_report_html(self):
self.common_test_report_html()
@@ -641,13 +576,8 @@ class TestExampleWithNative(TestExampleBase):
"__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_with_lines=["native-lib.cpp", "BusyLoopThread"],
check_strings_without_lines=["BusyLoopThread"])
def test_inferno(self):
@@ -718,6 +648,7 @@ class TestExampleWithNativeJniCall(TestExampleBase):
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",
+ "com.example.simpleperf.simpleperfexamplewithnative.MixActivity.callFunction",
"Java_com_example_simpleperf_simpleperfexamplewithnative_MixActivity_callFunction"])
remove("annotated_files")
self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "BusyThread"])
@@ -737,32 +668,32 @@ class TestExampleWithNativeJniCall(TestExampleBase):
self.run_cmd([INFERNO_SCRIPT, "-sc"])
-class TestExampleWithNativeForce32Bit(TestExampleWithNative):
+class TestExampleWithNativeForceArm(TestExampleWithNative):
@classmethod
def setUpClass(cls):
cls.prepare("SimpleperfExampleWithNative",
"com.example.simpleperf.simpleperfexamplewithnative",
".MainActivity",
- abi=TEST_HELPER.get_32bit_abi())
+ abi="armeabi-v7a")
-class TestExampleWithNativeRootForce32Bit(TestExampleWithNativeRoot):
+class TestExampleWithNativeForceArmRoot(TestExampleWithNativeRoot):
@classmethod
def setUpClass(cls):
cls.prepare("SimpleperfExampleWithNative",
"com.example.simpleperf.simpleperfexamplewithnative",
".MainActivity",
- abi=TEST_HELPER.get_32bit_abi(),
+ abi="armeabi-v7a",
adb_root=False)
-class TestExampleWithNativeTraceOffCpuForce32Bit(TestExampleWithNativeTraceOffCpu):
+class TestExampleWithNativeTraceOffCpuForceArm(TestExampleWithNativeTraceOffCpu):
@classmethod
def setUpClass(cls):
cls.prepare("SimpleperfExampleWithNative",
"com.example.simpleperf.simpleperfexamplewithnative",
".SleepActivity",
- abi=TEST_HELPER.get_32bit_abi())
+ abi="armeabi-v7a")
class TestExampleOfKotlin(TestExampleBase):
@@ -881,8 +812,8 @@ class TestExampleOfKotlinTraceOffCpu(TestExampleBase):
class TestNativeProfiling(TestBase):
def setUp(self):
- super(TestNativeProfiling, self).setUp()
- self.is_rooted_device = TEST_HELPER.adb.switch_to_root()
+ self.adb = AdbHelper()
+ self.is_rooted_device = self.adb.switch_to_root()
def test_profile_cmd(self):
self.run_cmd(["app_profiler.py", "-cmd", "pm -l", "--disable_adb_root"])
@@ -899,7 +830,7 @@ class TestNativeProfiling(TestBase):
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']))
+ pid = int(self.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'])
@@ -912,15 +843,13 @@ class TestNativeProfiling(TestBase):
self.run_cmd(['app_profiler.py', '--system_wide', '-r', '--duration 1'])
-class TestReportLib(TestBase):
+class TestReportLib(unittest.TestCase):
def setUp(self):
- super(TestReportLib, self).setUp()
self.report_lib = ReportLib()
- self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_symbols.data'))
+ self.report_lib.SetRecordFile(os.path.join('testdata', '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')
@@ -954,7 +883,7 @@ class TestReportLib(TestBase):
self.assertTrue(found_sample)
def test_meta_info(self):
- self.report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_trace_offcpu.data'))
+ self.report_lib.SetRecordFile(os.path.join('testdata', '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")
@@ -963,7 +892,7 @@ class TestReportLib(TestBase):
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'))
+ self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_tracepoint_event.data'))
event_names = set()
while self.report_lib.GetNextSample():
event_names.add(self.report_lib.GetEventOfCurrentSample().name)
@@ -971,13 +900,13 @@ class TestReportLib(TestBase):
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.report_lib.SetRecordFile(os.path.join('testdata', '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'))
+ self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data'))
total_period = 0
sleep_function_period = 0
sleep_function_name = "SleepFunction(unsigned long long)"
@@ -998,7 +927,7 @@ class TestReportLib(TestBase):
def test_show_art_frames(self):
def has_art_frame(report_lib):
- report_lib.SetRecordFile(TEST_HELPER.testdata_path('perf_with_interpreter_frames.data'))
+ report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_interpreter_frames.data'))
result = False
while report_lib.GetNextSample():
callchain = report_lib.GetCallChainOfCurrentSample()
@@ -1018,33 +947,8 @@ class TestReportLib(TestBase):
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'))
+ self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_tracepoint_event.data'))
has_tracing_data = False
while self.report_lib.GetNextSample():
event = self.report_lib.GetEventOfCurrentSample()
@@ -1065,13 +969,13 @@ class TestRunSimpleperfOnDevice(TestBase):
self.run_cmd(['run_simpleperf_on_device.py', 'list', '--show-features'])
-class TestTools(TestBase):
+class TestTools(unittest.TestCase):
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
+ binary_cache_path = 'testdata'
test_map = {
'/simpleperf_runtest_two_functions_arm64': [
{
@@ -1160,7 +1064,7 @@ class TestTools(TestBase):
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))
+ self.assertEquals(len(expected_files), len(expected_functions))
actual_source = addr2line.get_addr_source(dso, test_addr['addr'])
self.assertTrue(actual_source is not None)
@@ -1173,7 +1077,7 @@ class TestTools(TestBase):
self.assertEqual(source[2], expected_functions[i])
def test_objdump(self):
- binary_cache_path = TEST_HELPER.testdata_dir
+ binary_cache_path = 'testdata'
test_map = {
'/simpleperf_runtest_two_functions_arm64': {
'start_addr': 0x668,
@@ -1224,7 +1128,7 @@ class TestTools(TestBase):
def test_readelf(self):
test_map = {
- 'simpleperf_runtest_two_functions_arm64': {
+ '/simpleperf_runtest_two_functions_arm64': {
'arch': 'arm64',
'build_id': '0xe8ecb3916d989dbdc068345c30f0c24300000000',
'sections': ['.interp', '.note.android.ident', '.note.gnu.build-id', '.dynsym',
@@ -1236,21 +1140,21 @@ class TestTools(TestBase):
'.debug_pubnames', '.debug_pubtypes', '.debug_line',
'.note.gnu.gold-version', '.symtab', '.strtab', '.shstrtab'],
},
- 'simpleperf_runtest_two_functions_arm': {
+ '/simpleperf_runtest_two_functions_arm': {
'arch': 'arm',
'build_id': '0x718f5b36c4148ee1bd3f51af89ed2be600000000',
},
- 'simpleperf_runtest_two_functions_x86_64': {
+ '/simpleperf_runtest_two_functions_x86_64': {
'arch': 'x86_64',
},
- 'simpleperf_runtest_two_functions_x86': {
+ '/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)
+ path = 'testdata' + 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))
@@ -1261,59 +1165,42 @@ class TestTools(TestBase):
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')])
+ searcher = SourceFileSearcher(['testdata'])
def format_path(path):
- return os.path.join(TEST_HELPER.testdata_dir, path.replace('/', os.sep))
+ return path.replace('/', os.sep)
# Find a C++ file with pure file name.
- self.assertEqual(
- format_path('SimpleperfExampleWithNative/app/src/main/cpp/native-lib.cpp'),
+ self.assertEquals(
+ format_path('testdata/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'),
+ self.assertEquals(
+ format_path('testdata/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/' +
+ self.assertEquals(
+ format_path('testdata/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/' +
+ self.assertEquals(
+ format_path('testdata/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()
+class TestNativeLibDownloader(unittest.TestCase):
def test_smoke(self):
+ adb = AdbHelper()
+
def is_lib_on_device(path):
- return self.adb.run(['shell', 'ls', path])
+ return adb.run(['shell', 'ls', path])
# 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'))
+ adb.run(['shell', 'rm', '-rf', '/data/local/tmp/native_libs'])
+ downloader = NativeLibDownloader(None, 'arm64', adb)
+ downloader.collect_native_libs_on_host(os.path.join(
+ 'testdata', '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)
@@ -1340,88 +1227,17 @@ class TestNativeLibDownloader(TestBase):
self.assertTrue(build_id not in downloader.device_build_id_map)
self.assertFalse(is_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'])
+ 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)
+ adb.run(['shell', 'rm', '-rf', '/data/local/tmp/native_libs'])
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)
+ self.run_cmd(['report_html.py', '-i', 'testdata/perf_with_long_callchain.data'])
class TestBinaryCacheBuilder(TestBase):
@@ -1429,11 +1245,11 @@ class TestBinaryCacheBuilder(TestBase):
readelf = ReadElf(None)
strip = find_tool_path('strip', arch='arm')
self.assertIsNotNone(strip)
- symfs_dir = os.path.join(self.test_dir, 'symfs_dir')
+ symfs_dir = os.path.join('testdata', 'symfs_dir')
remove(symfs_dir)
os.mkdir(symfs_dir)
filename = 'simpleperf_runtest_two_functions_arm'
- origin_file = TEST_HELPER.testdata_path(filename)
+ origin_file = os.path.join('testdata', 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)
@@ -1457,202 +1273,6 @@ class TestBinaryCacheBuilder(TestBase):
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])
- 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():
@@ -1664,19 +1284,12 @@ def get_all_tests():
return sorted(tests)
-def run_tests(tests, repeats):
- TEST_HELPER.build_testdata()
+def run_tests(tests):
+ os.chdir(get_script_dir())
+ build_testdata()
+ log_info('Run tests with python%d\n%s' % (3 if is_python3() else 2, '\n'.join(tests)))
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
+ unittest.main(argv=argv, failfast=True, verbosity=2, exit=False)
def main():
@@ -1685,16 +1298,12 @@ def main():
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
+ return
if args.test_from:
start_pos = 0
while start_pos < len(tests) and tests[start_pos] != args.test_from[0]:
@@ -1703,47 +1312,34 @@ def main():
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)]
+ pattern = re.compile(fnmatch.translate(args.pattern[0]))
+ new_tests = []
+ for test in tests:
+ if pattern.match(test):
+ new_tests.append(test)
+ tests = new_tests
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 AdbHelper().get_android_version() < 7:
+ log_info("Skip tests on Android version < N.")
+ sys.exit(0)
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 = []
+ current_version = 3 if is_python3() else 2
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:
+ if version != current_version:
argv = ['python3' if version == 3 else 'python']
- argv.append(TEST_HELPER.script_path('test.py'))
+ argv.append(os.path.join(get_script_dir(), '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
+ argv += ['--python-version', str(version)]
+ subprocess.check_call(argv)
+ else:
+ run_tests(tests)
if __name__ == '__main__':
- sys.exit(0 if main() else 1)
+ main()
diff --git a/simpleperf/scripts/update.py b/simpleperf/scripts/update.py
index 3a2ab8b9..65281aaf 100755
--- a/simpleperf/scripts/update.py
+++ b/simpleperf/scripts/update.py
@@ -22,6 +22,7 @@ import shutil
import stat
import textwrap
+
THIS_DIR = os.path.realpath(os.path.dirname(__file__))
@@ -33,57 +34,41 @@ class InstallEntry(object):
self.need_strip = need_strip
-MINGW = 'local:../../../../prebuilts/gcc/linux-x86/host/x86_64-w64-mingw32-4.8/x86_64-w64-mingw32/'
INSTALL_LIST = [
- # simpleperf on device.
- InstallEntry('MODULES-IN-system-extras-simpleperf',
- 'simpleperf/android/arm64/simpleperf_ndk64',
- 'android/arm64/simpleperf'),
- InstallEntry('MODULES-IN-system-extras-simpleperf',
- 'simpleperf/android/arm/simpleperf_ndk',
- 'android/arm/simpleperf'),
- InstallEntry('MODULES-IN-system-extras-simpleperf_x86',
- 'simpleperf/android/x86_64/simpleperf_ndk64',
- 'android/x86_64/simpleperf'),
- InstallEntry('MODULES-IN-system-extras-simpleperf_x86',
- 'simpleperf/android/x86/simpleperf_ndk',
- 'android/x86/simpleperf'),
-
- # simpleperf on host. Linux and macOS are 64-bit only these days.
- InstallEntry('MODULES-IN-system-extras-simpleperf',
- 'simpleperf/linux/x86_64/simpleperf_ndk64',
- 'linux/x86_64/simpleperf', True),
- InstallEntry('MODULES-IN-system-extras-simpleperf_mac',
- 'simpleperf/darwin/x86_64/simpleperf_ndk64',
- 'darwin/x86_64/simpleperf'),
- 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),
+ # simpleperf on device
+ InstallEntry('sdk_arm64-sdk', 'simpleperf', 'android/arm64/simpleperf'),
+ InstallEntry('sdk_arm64-sdk', 'simpleperf32', 'android/arm/simpleperf'),
+ InstallEntry('sdk_x86_64-sdk', 'simpleperf', 'android/x86_64/simpleperf'),
+ InstallEntry('sdk_x86_64-sdk', 'simpleperf32', 'android/x86/simpleperf'),
+
+ # simpleperf on host
+ InstallEntry('sdk_arm64-sdk', 'simpleperf_host', 'linux/x86_64/simpleperf', True),
+ InstallEntry('sdk_arm64-sdk', 'simpleperf_host32', 'linux/x86/simpleperf', True),
+ InstallEntry('sdk_mac', 'simpleperf_host', 'darwin/x86_64/simpleperf'),
+ InstallEntry('sdk_mac', 'simpleperf_host32', 'darwin/x86/simpleperf'),
+ # simpleperf.exe on x86_64 windows doesn't work, use simpleperf32.exe instead.
+ InstallEntry('sdk', 'simpleperf32.exe', 'windows/x86_64/simpleperf.exe', True),
+ InstallEntry('sdk', 'simpleperf32.exe', 'windows/x86/simpleperf.exe', True),
# libsimpleperf_report.so on host
- InstallEntry('MODULES-IN-system-extras-simpleperf',
- 'simpleperf/linux/x86_64/libsimpleperf_report.so',
- 'linux/x86_64/libsimpleperf_report.so', True),
- InstallEntry('MODULES-IN-system-extras-simpleperf_mac',
- 'simpleperf/darwin/x86_64/libsimpleperf_report.dylib',
+ InstallEntry('sdk_arm64-sdk', 'libsimpleperf_report.so', 'linux/x86_64/libsimpleperf_report.so',
+ True),
+ InstallEntry('sdk_arm64-sdk', 'libsimpleperf_report32.so', 'linux/x86/libsimpleperf_report.so',
+ True),
+ InstallEntry('sdk_mac', 'libsimpleperf_report.dylib',
'darwin/x86_64/libsimpleperf_report.dylib'),
- InstallEntry('MODULES-IN-system-extras-simpleperf',
- 'simpleperf/windows/x86_64/libsimpleperf_report.dll',
- 'windows/x86_64/libsimpleperf_report.dll', True),
- InstallEntry('MODULES-IN-system-extras-simpleperf',
- 'simpleperf/windows/x86/libsimpleperf_report.dll',
- 'windows/x86/libsimpleperf_report.dll', True),
+ InstallEntry('sdk_mac', 'libsimpleperf_report32.so', 'darwin/x86/libsimpleperf_report.dylib'),
+ InstallEntry('sdk', 'libsimpleperf_report.dll', 'windows/x86_64/libsimpleperf_report.dll',
+ True),
+ InstallEntry('sdk', 'libsimpleperf_report32.dll', 'windows/x86/libsimpleperf_report.dll', True),
# libwinpthread-1.dll on windows host
- InstallEntry(MINGW + '/bin/libwinpthread-1.dll', 'libwinpthread-1.dll',
+ InstallEntry('local:../../../../prebuilts/gcc/linux-x86/host/x86_64-w64-mingw32-4.8' +
+ '/x86_64-w64-mingw32/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),
+ InstallEntry('local:../../../../prebuilts/gcc/linux-x86/host/x86_64-w64-mingw32-4.8' +
+ '/x86_64-w64-mingw32/lib32/libwinpthread-1.dll', 'libwinpthread-1_32.dll',
+ 'windows/x86/libwinpthread-1.dll', False),
]
@@ -156,7 +141,6 @@ def install_entry(branch, build, install_dir, entry):
need_strip = entry.need_strip
fetch_artifact(branch, build, target, name)
- name = os.path.basename(name)
exe_stat = os.stat(name)
os.chmod(name, exe_stat.st_mode | stat.S_IEXEC)
if need_strip:
@@ -172,7 +156,7 @@ def get_args():
parser = argparse.ArgumentParser()
parser.add_argument(
- '-b', '--branch', default='aosp-simpleperf-release',
+ '-b', '--branch', default='aosp-master',
help='Branch to pull build from.')
parser.add_argument('--build', required=True, help='Build number to pull.')
parser.add_argument(
diff --git a/simpleperf/scripts/utils.py b/simpleperf/scripts/utils.py
index 5754f383..ea708c61 100644
--- a/simpleperf/scripts/utils.py
+++ b/simpleperf/scripts/utils.py
@@ -70,17 +70,6 @@ def log_exit(msg):
def disable_debug_log():
logging.getLogger().setLevel(logging.WARN)
-def set_log_level(level_name):
- if level_name == 'debug':
- level = logging.DEBUG
- elif level_name == 'info':
- level = logging.INFO
- elif level_name == 'warning':
- level = logging.WARNING
- else:
- log_fatal('unknown log level: %s' % level_name)
- logging.getLogger().setLevel(level)
-
def str_to_bytes(str_value):
if not is_python3():
return str_value
@@ -148,16 +137,15 @@ EXPECTED_TOOLS = {
'adb': {
'is_binutils': False,
'test_option': 'version',
- 'path_in_ndk': lambda _: '../platform-tools/adb',
+ 'path_in_ndk': '../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,
+ 'addr2line': {
+ 'is_binutils': True,
+ 'accept_tool_without_arch': True
},
'objdump': {
'is_binutils': True,
@@ -197,7 +185,7 @@ def find_tool_path(toolname, ndk_path=None, arch=None):
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 = tool_info['path_in_ndk']
path_in_ndk = path_in_ndk.replace('/', os.sep)
# 1. Find tool in the given ndk path.
@@ -326,7 +314,6 @@ class AdbHelper(object):
def get_android_version(self):
- """ 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
if build_version:
@@ -376,11 +363,15 @@ def open_report_in_browser(report_path):
def is_elf_file(path):
if os.path.isfile(path):
with open(path, 'rb') as fh:
- return fh.read(4) == b'\x7fELF'
+ data = fh.read(4)
+ if len(data) == 4 and bytes_to_str(data) == '\x7fELF':
+ return True
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 dso_path_in_record_file[0] != '/' or dso_path_in_record_file == '//anon':
+ return None
if binary_cache_path:
tmp_path = os.path.join(binary_cache_path, dso_path_in_record_file[1:])
if is_elf_file(tmp_path):
@@ -391,7 +382,7 @@ def find_real_dso_path(dso_path_in_record_file, binary_cache_path):
class Addr2Nearestline(object):
- """ Use llvm-symbolizer to convert (dso_path, func_addr, addr) to (source_file, line).
+ """ Use addr2line to convert (dso_path, func_addr, addr) to (source_file, line) pairs.
For instructions generated by C++ compilers without a matching statement in source code
(like stack corruption check, switch optimization, etc.), addr2line can't generate
line information. However, we want to assign the instruction to the nearest line before
@@ -435,9 +426,9 @@ class Addr2Nearestline(object):
self.source_lines = None
def __init__(self, ndk_path, binary_cache_path, with_function_name):
- self.symbolizer_path = 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.addr2line_path = find_tool_path('addr2line', ndk_path)
+ if not self.addr2line_path:
+ log_exit("Can't find addr2line. 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
@@ -504,11 +495,12 @@ class Addr2Nearestline(object):
break
if not addr_set:
return
- addr_request = '\n'.join(['0x%x' % addr for addr in sorted(addr_set)])
+ addr_request = '\n'.join(['%x' % addr for addr in sorted(addr_set)])
# 2. Use addr2line to collect line info.
try:
- subproc = subprocess.Popen(self._build_symbolizer_args(real_path),
+ option = '-ai' + ('fC' if self.with_function_name else '')
+ subproc = subprocess.Popen([self.addr2line_path, option, '-e', real_path],
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
(stdoutdata, _) = subproc.communicate(str_to_bytes(addr_request))
stdoutdata = bytes_to_str(stdoutdata)
@@ -519,9 +511,6 @@ class Addr2Nearestline(object):
need_function_name = self.with_function_name
cur_function_name = None
for line in stdoutdata.strip().split('\n'):
- line = line.strip()
- if not line:
- continue
if line[:2] == '0x':
# a new address
cur_line_list = addr_map[int(line, 16)] = []
@@ -530,16 +519,27 @@ class Addr2Nearestline(object):
need_function_name = False
else:
need_function_name = self.with_function_name
+ # a file:line.
if cur_line_list is None:
continue
- file_path, line_number = self._parse_source_location(line)
- if not file_path or not line_number:
+ # Handle lines like "C:\Users\...\file:32".
+ items = line.rsplit(':', 1)
+ if len(items) != 2:
+ continue
+ if '?' in line:
+ # if ? in line, it doesn't have a valid line info.
# An addr can have a list of (file, line), when the addr belongs to an inlined
# function. Sometimes only part of the list has ? mark. In this case, we think
# the line info is valid if the first line doesn't have ? mark.
if not cur_line_list:
cur_line_list = None
continue
+ (file_path, line_number) = items
+ line_number = line_number.split()[0] # Remove comments after line number
+ try:
+ line_number = int(line_number)
+ except ValueError:
+ continue
file_id = self._get_file_id(file_path)
if self.with_function_name:
func_id = self._get_func_id(cur_function_name)
@@ -561,29 +561,6 @@ 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]
- if self.with_function_name:
- args += ['-functions=linkage', '-demangle']
- else:
- args.append('-functions=none')
- return args
-
- def _parse_source_location(self, line):
- 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".
- items = line.rsplit(':', 2)
- if len(items) == 3:
- file_path, line_number = items[:2]
- if not file_path or ('?' in file_path) or not line_number or ('?' in line_number):
- return None, None
- try:
- line_number = int(line_number)
- except ValueError:
- return None, None
- return file_path, line_number
-
def _get_file_id(self, file_path):
file_id = self.file_name_to_id.get(file_path)
if file_id is None:
@@ -754,7 +731,7 @@ 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):
""" Get build id of an elf file. """
if is_elf_file(elf_file_path):
try:
@@ -763,22 +740,16 @@ class ReadElf(object):
result = re.search(r'Build ID:\s*(\S+)', output)
if result:
build_id = result.group(1)
- if with_padding:
- build_id = self.pad_build_id(build_id)
+ if len(build_id) < 40:
+ build_id += '0' * (40 - len(build_id))
+ else:
+ build_id = build_id[:40]
+ build_id = '0x' + build_id
return build_id
except subprocess.CalledProcessError:
pass
return ""
- @staticmethod
- def pad_build_id(build_id):
- """ Pad build id to 40 hex numbers (20 bytes). """
- if len(build_id) < 40:
- build_id += '0' * (40 - len(build_id))
- else:
- build_id = build_id[:40]
- return '0x' + build_id
-
def get_sections(self, elf_file_path):
""" Get sections of an elf file. """
section_names = []
diff --git a/simpleperf/testdata/DisplayBitmaps.apk b/simpleperf/testdata/DisplayBitmaps.apk
deleted file mode 100644
index dada9e9e..00000000
--- a/simpleperf/testdata/DisplayBitmaps.apk
+++ /dev/null
Binary files differ
diff --git a/simpleperf/testdata/DisplayBitmapsTest.apk b/simpleperf/testdata/DisplayBitmapsTest.apk
deleted file mode 100644
index f8cdc7d3..00000000
--- a/simpleperf/testdata/DisplayBitmapsTest.apk
+++ /dev/null
Binary files differ
diff --git a/simpleperf/testdata/EndlessTunnel.apk b/simpleperf/testdata/EndlessTunnel.apk
deleted file mode 100644
index 09afd7fb..00000000
--- a/simpleperf/testdata/EndlessTunnel.apk
+++ /dev/null
Binary files differ
diff --git a/simpleperf/testdata/data/app/simpleperf.demo.cpp_api/base.apk b/simpleperf/testdata/data/app/simpleperf.demo.cpp_api/base.apk
deleted file mode 100644
index 0b2d5309..00000000
--- a/simpleperf/testdata/data/app/simpleperf.demo.cpp_api/base.apk
+++ /dev/null
Binary files differ
diff --git a/simpleperf/testdata/data/symfs_with_build_id_list/build_id_list b/simpleperf/testdata/data/symfs_with_build_id_list/build_id_list
deleted file mode 100644
index 9b80e911..00000000
--- a/simpleperf/testdata/data/symfs_with_build_id_list/build_id_list
+++ /dev/null
@@ -1 +0,0 @@
-0x91b1c10fdd9fe2221dfec525497637f2229bfdbb=elf_for_build_id_check
diff --git a/simpleperf/testdata/data/symfs_with_build_id_list/elf_for_build_id_check b/simpleperf/testdata/data/symfs_with_build_id_list/elf_for_build_id_check
deleted file mode 100644
index 5c1a9dd8..00000000
--- a/simpleperf/testdata/data/symfs_with_build_id_list/elf_for_build_id_check
+++ /dev/null
Binary files differ
diff --git a/simpleperf/testdata/data/symfs_with_wrong_build_id_list/build_id_list b/simpleperf/testdata/data/symfs_with_wrong_build_id_list/build_id_list
deleted file mode 100644
index 4487061b..00000000
--- a/simpleperf/testdata/data/symfs_with_wrong_build_id_list/build_id_list
+++ /dev/null
@@ -1 +0,0 @@
-0x91b1c10fdd9fe2221dfec525497637f2229bfdbb=elf_for_build_id_list
diff --git a/simpleperf/testdata/etm/etm_test_loop b/simpleperf/testdata/etm/etm_test_loop
deleted file mode 100644
index ec715f1d..00000000
--- a/simpleperf/testdata/etm/etm_test_loop
+++ /dev/null
Binary files differ
diff --git a/simpleperf/testdata/etm/perf.data b/simpleperf/testdata/etm/perf.data
deleted file mode 100644
index 10481f59..00000000
--- a/simpleperf/testdata/etm/perf.data
+++ /dev/null
Binary files differ
diff --git a/simpleperf/testdata/perf_unwind_embedded_lib_in_apk.data b/simpleperf/testdata/perf_unwind_embedded_lib_in_apk.data
deleted file mode 100644
index 8a7c54e3..00000000
--- a/simpleperf/testdata/perf_unwind_embedded_lib_in_apk.data
+++ /dev/null
Binary files differ
diff --git a/simpleperf/thread_tree.cpp b/simpleperf/thread_tree.cpp
index 17061ce7..3b90d518 100644
--- a/simpleperf/thread_tree.cpp
+++ b/simpleperf/thread_tree.cpp
@@ -46,7 +46,6 @@ void ThreadTree::ForkThread(int pid, int tid, int ppid, int ptid) {
if (child->maps->maps.empty()) {
*child->maps = *parent->maps;
} else {
- CHECK_NE(child->maps, parent->maps);
for (auto& pair : parent->maps->maps) {
InsertMap(*child->maps, *pair.second);
}
@@ -54,39 +53,34 @@ void ThreadTree::ForkThread(int pid, int tid, int ppid, int ptid) {
}
}
-ThreadEntry* ThreadTree::FindThread(int tid) {
- if (auto it = thread_tree_.find(tid); it != thread_tree_.end()) {
- return it->second.get();
- }
- return nullptr;
-}
-
ThreadEntry* ThreadTree::FindThreadOrNew(int pid, int tid) {
auto it = thread_tree_.find(tid);
- if (it != thread_tree_.end() && pid == it->second.get()->pid) {
- return it->second.get();
- }
- if (it != thread_tree_.end()) {
- ExitThread(it->second.get()->pid, tid);
+ if (it == thread_tree_.end()) {
+ return CreateThread(pid, tid);
+ } else {
+ if (pid != it->second.get()->pid) {
+ // TODO: b/22185053.
+ LOG(DEBUG) << "unexpected (pid, tid) pair: expected ("
+ << it->second.get()->pid << ", " << tid << "), actual (" << pid
+ << ", " << tid << ")";
+ }
}
- return CreateThread(pid, tid);
+ return it->second.get();
}
ThreadEntry* ThreadTree::CreateThread(int pid, int tid) {
- const char* comm;
- std::shared_ptr<MapSet> maps;
+ MapSet* maps = nullptr;
if (pid == tid) {
- comm = "unknown";
- maps.reset(new MapSet);
+ maps = new MapSet;
+ map_set_storage_.push_back(std::unique_ptr<MapSet>(maps));
} else {
// Share maps among threads in the same thread group.
ThreadEntry* process = FindThreadOrNew(pid, pid);
- comm = process->comm;
maps = process->maps;
}
ThreadEntry* thread = new ThreadEntry{
pid, tid,
- comm,
+ "unknown",
maps,
};
auto pair = thread_tree_.insert(std::make_pair(tid, std::unique_ptr<ThreadEntry>(thread)));
@@ -94,13 +88,6 @@ ThreadEntry* ThreadTree::CreateThread(int pid, int tid) {
return thread;
}
-void ThreadTree::ExitThread(int pid, int tid) {
- auto it = thread_tree_.find(tid);
- if (it != thread_tree_.end() && pid == it->second.get()->pid) {
- thread_tree_.erase(it);
- }
-}
-
void ThreadTree::AddKernelMap(uint64_t start_addr, uint64_t len, uint64_t pgoff,
const std::string& filename) {
// kernel map len can be 0 when record command is not run in supervisor mode.
@@ -273,6 +260,7 @@ const Symbol* ThreadTree::FindKernelSymbol(uint64_t ip) {
void ThreadTree::ClearThreadAndMap() {
thread_tree_.clear();
thread_comm_storage_.clear();
+ map_set_storage_.clear();
kernel_maps_.maps.clear();
map_storage_.clear();
}
@@ -325,9 +313,6 @@ void ThreadTree::Update(const Record& record) {
} else if (record.type() == PERF_RECORD_FORK) {
const ForkRecord& r = *static_cast<const ForkRecord*>(&record);
ForkThread(r.data->pid, r.data->tid, r.data->ppid, r.data->ptid);
- } else if (record.type() == PERF_RECORD_EXIT) {
- const ExitRecord& r = *static_cast<const ExitRecord*>(&record);
- ExitThread(r.data->pid, r.data->tid);
} else if (record.type() == SIMPLE_PERF_RECORD_KERNEL_SYMBOL) {
const auto& r = *static_cast<const KernelSymbolRecord*>(&record);
Dso::SetKallsyms(std::move(r.kallsyms));
@@ -347,4 +332,12 @@ std::vector<Dso*> ThreadTree::GetAllDsos() const {
return result;
}
+std::vector<const ThreadEntry*> ThreadTree::GetAllThreads() const {
+ std::vector<const ThreadEntry*> threads;
+ for (auto& pair : thread_tree_) {
+ threads.push_back(pair.second.get());
+ }
+ return threads;
+}
+
} // namespace simpleperf
diff --git a/simpleperf/thread_tree.h b/simpleperf/thread_tree.h
index e9412736..3094d700 100644
--- a/simpleperf/thread_tree.h
+++ b/simpleperf/thread_tree.h
@@ -69,7 +69,7 @@ struct ThreadEntry {
int pid;
int tid;
const char* comm; // It always refers to the latest comm.
- std::shared_ptr<MapSet> maps; // maps is shared by threads in the same process.
+ MapSet* maps;
};
// ThreadTree contains thread information (in ThreadEntry) and mmap information
@@ -92,9 +92,7 @@ class 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);
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 AddThreadMap(int pid, int tid, uint64_t start_addr, uint64_t len,
@@ -127,6 +125,7 @@ class ThreadTree {
void Update(const Record& record);
std::vector<Dso*> GetAllDsos() const;
+ std::vector<const ThreadEntry*> GetAllThreads() const;
private:
ThreadEntry* CreateThread(int pid, int tid);
@@ -139,6 +138,7 @@ class ThreadTree {
std::unordered_map<int, std::unique_ptr<ThreadEntry>> thread_tree_;
std::vector<std::unique_ptr<std::string>> thread_comm_storage_;
+ std::vector<std::unique_ptr<MapSet>> map_set_storage_;
MapSet kernel_maps_;
std::vector<std::unique_ptr<MapEntry>> map_storage_;
MapEntry unknown_map_;
diff --git a/simpleperf/thread_tree_test.cpp b/simpleperf/thread_tree_test.cpp
index d00ebcb9..be3d8bfa 100644
--- a/simpleperf/thread_tree_test.cpp
+++ b/simpleperf/thread_tree_test.cpp
@@ -105,18 +105,3 @@ TEST_F(ThreadTreeTest, jit_maps_before_fork) {
ASSERT_TRUE(map != nullptr);
ASSERT_EQ(map->flags, map_flags::PROT_JIT_SYMFILE_MAP);
}
-
-TEST_F(ThreadTreeTest, reused_tid) {
- // Process 1 has thread 1 and 2.
- thread_tree_.ForkThread(1, 2, 1, 1);
- // Thread 2 exits.
- thread_tree_.ExitThread(1, 2);
- // Thread 1 forks process 2.
- thread_tree_.ForkThread(2, 2, 1, 1);
-}
-
-TEST_F(ThreadTreeTest, reused_tid_without_thread_exit) {
- // Similar to the above test, but the thread exit record is missing.
- thread_tree_.ForkThread(1, 2, 1, 1);
- thread_tree_.ForkThread(2, 2, 1, 1);
-}
diff --git a/simpleperf/utils.cpp b/simpleperf/utils.cpp
index b27fabd6..a5714b06 100644
--- a/simpleperf/utils.cpp
+++ b/simpleperf/utils.cpp
@@ -106,15 +106,16 @@ ArchiveHelper::~ArchiveHelper() {
bool ArchiveHelper::IterateEntries(
const std::function<bool(ZipEntry&, const std::string&)>& callback) {
void* iteration_cookie;
- if (StartIteration(handle_, &iteration_cookie) < 0) {
+ if (StartIteration(handle_, &iteration_cookie, nullptr, nullptr) < 0) {
LOG(ERROR) << "Failed to iterate " << filename_;
return false;
}
ZipEntry zentry;
- std::string zname;
+ ZipString zname;
int result;
while ((result = Next(iteration_cookie, &zentry, &zname)) == 0) {
- if (!callback(zentry, zname)) {
+ std::string name(zname.name, zname.name + zname.name_length);
+ if (!callback(zentry, name)) {
break;
}
}
@@ -127,7 +128,7 @@ bool ArchiveHelper::IterateEntries(
}
bool ArchiveHelper::FindEntry(const std::string& name, ZipEntry* entry) {
- int result = ::FindEntry(handle_, name, entry);
+ int result = ::FindEntry(handle_, ZipString(name.c_str()), entry);
if (result != 0) {
LOG(ERROR) << "Failed to find " << name << " in " << filename_;
return false;
diff --git a/simpleperf/utils.h b/simpleperf/utils.h
index 872e191d..3ca8f5cf 100644
--- a/simpleperf/utils.h
+++ b/simpleperf/utils.h
@@ -163,14 +163,4 @@ timeval SecondToTimeval(double time_in_sec);
std::string GetSimpleperfVersion();
-namespace {
-
-// from boost::hash_combine
-template <typename T>
-void HashCombine(size_t& seed, const T& val) {
- seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
-}
-
-} // namespace
-
#endif // SIMPLE_PERF_UTILS_H_
diff --git a/slideshow/Android.mk b/slideshow/Android.mk
index 192e64fb..6da4416e 100644
--- a/slideshow/Android.mk
+++ b/slideshow/Android.mk
@@ -6,9 +6,12 @@ include $(CLEAR_VARS)
LOCAL_SRC_FILES := slideshow.cpp
LOCAL_MODULE := slideshow
LOCAL_MODULE_TAGS := optional
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT_SBIN)
+LOCAL_UNSTRIPPED_PATH := $(TARGET_ROOT_OUT_SBIN_UNSTRIPPED)
LOCAL_CFLAGS := -D__STDC_LIMIT_MACROS -Werror
-LOCAL_SHARED_LIBRARIES := \
+LOCAL_STATIC_LIBRARIES := \
libminui \
libpng \
libbase \
diff --git a/squashfs_utils/Android.bp b/squashfs_utils/Android.bp
index c4c675c3..350ed888 100644
--- a/squashfs_utils/Android.bp
+++ b/squashfs_utils/Android.bp
@@ -30,8 +30,4 @@ cc_library {
sh_binary_host {
name: "mksquashfsimage.sh",
src: "mksquashfsimage.sh",
- required: [
- "img2simg",
- "mksquashfs",
- ],
}
diff --git a/taskstats/taskstats.c b/taskstats/taskstats.c
index f2cf16f7..361e99f5 100644
--- a/taskstats/taskstats.c
+++ b/taskstats/taskstats.c
@@ -234,13 +234,6 @@ void print_task_stats(const struct TaskStatistics* stats,
printf("--------------------------\n");
printf("%-25s%llu\n", "Voluntary switches:", s->nvcsw);
printf("%-25s%llu\n", "Involuntary switches:", s->nivcsw);
-
-#if TASKSTATS_VERSION > 8
- if (s->version > 8) {
- printf("%-25s%llu\n", "Thrashing count:", s->thrashing_count);
- printf("%-25s%llu\n", "Thrashing delay total:", s->thrashing_delay_total);
- }
-#endif
}
void print_usage() {
diff --git a/tests/fstest/recovery_test.cpp b/tests/fstest/recovery_test.cpp
index 793f8da2..62ca358a 100644
--- a/tests/fstest/recovery_test.cpp
+++ b/tests/fstest/recovery_test.cpp
@@ -217,8 +217,9 @@ class FsRecoveryTest : public ::testing::Test {
UMOUNT_BIN,
cache_str,
};
- return logwrap_fork_execvp(ARRAY_SIZE(umount_argv), umount_argv, nullptr,
- false, LOG_KLOG, false, nullptr) >= 0;
+ return android_fork_execvp_ext(ARRAY_SIZE(umount_argv), umount_argv,
+ NULL, true, LOG_KLOG, false, NULL,
+ NULL, 0) >= 0;
}
bool mountAll() {
@@ -229,8 +230,9 @@ class FsRecoveryTest : public ::testing::Test {
storage_str,
mountall_str,
};
- return logwrap_fork_execvp(ARRAY_SIZE(mountall_argv), mountall_argv, nullptr,
- false, LOG_KLOG, false, nullptr) >= 0;
+ return android_fork_execvp_ext(ARRAY_SIZE(mountall_argv), mountall_argv,
+ NULL, true, LOG_KLOG, false, NULL,
+ NULL, 0) >= 0;
}
int getCacheBlkFd() {
diff --git a/toolchain-extras/Android.bp b/toolchain-extras/Android.bp
index 7cc97344..5c839a1e 100644
--- a/toolchain-extras/Android.bp
+++ b/toolchain-extras/Android.bp
@@ -11,7 +11,6 @@ cc_library_static {
name: "libprofile-extras",
defaults: ["libprofile-defaults",],
- native_bridge_supported: true,
vendor_available: true,
vndk: {
enabled: true,
@@ -26,7 +25,6 @@ cc_library_static {
cc_library_static {
name: "libprofile-extras_ndk",
defaults: ["libprofile-defaults",],
- native_bridge_supported: true,
vendor_available: true,
vndk: {
enabled: true,
diff --git a/toolchain-extras/profile-extras.cpp b/toolchain-extras/profile-extras.cpp
index b7a9318a..7d78a0a5 100644
--- a/toolchain-extras/profile-extras.cpp
+++ b/toolchain-extras/profile-extras.cpp
@@ -31,8 +31,16 @@ extern "C" {
void __gcov_flush(void);
-static void gcov_signal_handler(__unused int signum) {
+// 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();
+ if (chained_gcov_signal_handler != SIG_ERR &&
+ chained_gcov_signal_handler != SIG_IGN &&
+ chained_gcov_signal_handler != SIG_DFL) {
+ (chained_gcov_signal_handler)(signum);
+ }
}
static const char kCoveragePropName[] = "debug.coverage.flush";
@@ -101,10 +109,15 @@ __attribute__((constructor)) int init_profile_extras(void) {
return 0;
init_profile_extras_once = 1;
+ // is this instance already registered?
+ if (chained_gcov_signal_handler != SIG_ERR) {
+ return -1;
+ }
sighandler_t ret1 = signal(GCOV_FLUSH_SIGNAL, gcov_signal_handler);
if (ret1 == SIG_ERR) {
return -1;
}
+ chained_gcov_signal_handler = ret1;
// Do not create thread running property_watch_loop for zygote (it can get
// invoked as zygote or app_process). This check is only needed for the
diff --git a/vbmeta_tools/Android.bp b/vbmeta_tools/Android.bp
deleted file mode 100644
index 73a9eb7f..00000000
--- a/vbmeta_tools/Android.bp
+++ /dev/null
@@ -1,26 +0,0 @@
-//
-// Copyright (C) 2019 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.
-//
-
-cc_binary {
- name: "vbmake",
- host_supported: true,
- shared_libs: [
- "libvbmeta",
- ],
- srcs: [
- "vbmake.cc",
- ],
-} \ No newline at end of file
diff --git a/vbmeta_tools/vbmake.cc b/vbmeta_tools/vbmake.cc
deleted file mode 100644
index 571b2ad2..00000000
--- a/vbmeta_tools/vbmake.cc
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <getopt.h>
-#include <sysexits.h>
-
-#include <libvbmeta/libvbmeta.h>
-
-using android::fs_mgr::WriteToSuperVBMetaFile;
-
-/* Prints program usage to |where|. */
-static int usage(int /* argc */, char* argv[]) {
- fprintf(stderr,
- "%s - command-line tool for creating Super VBMeta Image.\n"
- "\n"
- "Usage:\n"
- " %s [options]\n"
- "\n"
- "Required options:\n"
- " -o,--output=FILE Output file.\n"
- "\n"
- "Optional:\n"
- " -i,--image=VBMETA_NAME=FILE include the given vbmeta file as\n"
- " initial data for the super vbmeta.\n",
- argv[0], argv[0]);
- return EX_USAGE;
-}
-
-int main(int argc, char* argv[]) {
- struct option options[] = {
- { "help", no_argument, nullptr, 'h' },
- { "image", required_argument, nullptr, 'i' },
- { "output", required_argument, nullptr, 'o' },
- { nullptr, 0, nullptr, 0 },
- };
-
- std::string output_path;
- std::map<std::string, std::string> images;
-
- int rv;
- while ((rv = getopt_long_only(argc, argv, "i:o:", options, NULL)) != -1) {
- switch (rv) {
- case 'h':
- return usage(argc, argv);
- case 'i':
- {
- char* separator = strchr(optarg, '=');
- if (!separator || separator == optarg || !strlen(separator + 1)) {
- fprintf(stderr, "Expected VBMETA_NAME=FILE.\n");
- return EX_USAGE;
- }
- *separator = '\0';
-
- std::string vbmeta_name(optarg);
- std::string file(separator + 1);
- images[vbmeta_name] = file;
- break;
- }
- case 'o':
- output_path = optarg;
- break;
- default:
- break;
- }
- }
-
- // Check for empty arguments so we can print a more helpful message rather
- // than error on each individual missing argument.
- if (optind == 1) {
- return usage(argc, argv);
- }
-
- if (output_path.empty()) {
- fprintf(stderr, "--output must specify a valid path.\n");
- return EX_USAGE;
- }
-
- if (!WriteToSuperVBMetaFile(output_path.c_str(), images)) {
- return EX_CANTCREAT;
- }
-
- return EX_OK;
-}
diff --git a/verity/Android.bp b/verity/Android.bp
index 47379116..550b800b 100644
--- a/verity/Android.bp
+++ b/verity/Android.bp
@@ -12,8 +12,8 @@ cc_binary_host {
],
}
-java_binary_host {
- name: "verity_signer",
+java_library_host {
+ name: "VeritySigner",
srcs: [
"VeritySigner.java",
"Utils.java",
@@ -23,8 +23,8 @@ java_binary_host {
static_libs: ["bouncycastle-unbundled"],
}
-java_binary_host {
- name: "boot_signer",
+java_library_host {
+ name: "BootSignature",
srcs: [
"BootSignature.java",
"VeritySigner.java",
@@ -131,17 +131,22 @@ cc_test {
],
}
-python_binary_host {
- name: "build_verity_metadata",
- srcs: ["build_verity_metadata.py"],
- version: {
- py2: {
- enabled: true,
- embedded_launcher: true,
- },
- py3: {
- enabled: false,
- embedded_launcher: false,
- },
- },
+// VeritySigner should probably just be a java_binary
+sh_binary_host {
+ name: "verity_signer",
+ src: "verity_signer",
+ required: ["VeritySigner"],
+}
+
+// BootSignature should probably just be a java_binary
+sh_binary_host {
+ name: "boot_signer",
+ src: "boot_signer",
+ required: ["BootSignature"],
+}
+
+// This should probably be a python_binary_host
+sh_binary_host {
+ name: "build_verity_metadata.py",
+ src: "build_verity_metadata.py",
}
diff --git a/verity/boot_signer b/verity/boot_signer
new file mode 100755
index 00000000..e2ee42bf
--- /dev/null
+++ b/verity/boot_signer
@@ -0,0 +1,8 @@
+#! /bin/sh
+
+# Start-up script for BootSigner
+
+BOOTSIGNER_HOME=`dirname "$0"`
+BOOTSIGNER_HOME=`dirname "$BOOTSIGNER_HOME"`
+
+java -Xmx512M -jar "$BOOTSIGNER_HOME"/framework/BootSignature.jar "$@" \ No newline at end of file
diff --git a/verity/build_verity_metadata.py b/verity/build_verity_metadata.py
index 5a7d7d27..5a7d7d27 100644..100755
--- a/verity/build_verity_metadata.py
+++ b/verity/build_verity_metadata.py
diff --git a/verity/hash_tree_builder.cpp b/verity/hash_tree_builder.cpp
index d061ca5c..197c38e5 100644
--- a/verity/hash_tree_builder.cpp
+++ b/verity/hash_tree_builder.cpp
@@ -197,9 +197,7 @@ bool HashTreeBuilder::Update(const unsigned char* data, size_t len) {
return false;
}
leftover_.clear();
- if (data != nullptr) {
- data += append_len;
- }
+ data += append_len;
len -= append_len;
}
if (len % block_size_ != 0) {
diff --git a/verity/verity_signer b/verity/verity_signer
new file mode 100755
index 00000000..a4f337ae
--- /dev/null
+++ b/verity/verity_signer
@@ -0,0 +1,8 @@
+#! /bin/sh
+
+# Start-up script for VeritySigner
+
+VERITYSIGNER_HOME=`dirname "$0"`
+VERITYSIGNER_HOME=`dirname "$VERITYSIGNER_HOME"`
+
+java -Xmx512M -jar "$VERITYSIGNER_HOME"/framework/VeritySigner.jar "$@" \ No newline at end of file