summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-07-03 16:04:00 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-07-03 16:04:00 +0000
commit37d020f5143ef14aedc58f37785389f2e351096b (patch)
tree90bb3813b2e964d5e7e5c3cb42cc08f6e267240e
parent962b1c9357df8d1e03c7cff3f56805f5b19ce547 (diff)
parentd6f259b4285f74ed240a6392a7c2b2812ed1320f (diff)
downloadextras-android14-mainline-adbd-release.tar.gz
Snap for 10428683 from d6f259b4285f74ed240a6392a7c2b2812ed1320f to mainline-adbd-releaseaml_adb_340912530aml_adb_340912350aml_adb_340912200aml_adb_340912000android14-mainline-adbd-release
Change-Id: I85f6e896234d04412e3905041ad1db856dfb5456
-rw-r--r--app-launcher/Android.bp2
-rwxr-xr-xapp-launcher/app-launcher.sh (renamed from app-launcher/app-launcher)0
-rw-r--r--bootctl/Android.bp2
-rw-r--r--bootctl/bootctl.cpp183
-rwxr-xr-xboottime_tools/bootanalyze/bootanalyze.py153
-rwxr-xr-xboottime_tools/bootanalyze/bootanalyze.sh52
-rw-r--r--boottime_tools/bootanalyze/bugreport_anayze.py2
-rw-r--r--boottime_tools/bootanalyze/config.yaml1
-rw-r--r--boottime_tools/bootio/bootio_collector.cpp25
-rw-r--r--checkpoint_gc/checkpoint_gc.sh42
-rw-r--r--cppreopts/fstab.postinstall5
-rw-r--r--cpu_loads/Android.bp116
-rw-r--r--cpu_loads/README.md46
-rw-r--r--cpu_loads/memcpy-16kb.cpp32
-rw-r--r--cpu_loads/memcpy-2048kb.cpp32
-rw-r--r--cpu_loads/memcpy-byte.cpp33
-rw-r--r--cpu_loads/memcpy.cpp32
-rw-r--r--cpu_loads/pss.cpp40
-rw-r--r--cpu_loads/simd.cpp44
-rw-r--r--cpu_loads/while-true.cpp33
-rw-r--r--ext4_utils/Android.bp1
-rw-r--r--ext4_utils/Android.mk17
-rw-r--r--ext4_utils/ext4_utils.cpp16
-rw-r--r--ext4_utils/include/ext4_utils/ext4.h1
-rw-r--r--ext4_utils/include/ext4_utils/ext4_utils.h12
-rw-r--r--f2fs_utils/Android.bp8
-rw-r--r--f2fs_utils/f2fs_sparseblock.c2
-rwxr-xr-xf2fs_utils/mkf2fsuserimg.sh3
-rw-r--r--ioblame/androidFsParser.py10
-rw-r--r--ioblame/ioblame.py18
-rwxr-xr-xioblame/ioblame.sh36
-rw-r--r--ioshark/collect-straces-ftraces.sh8
-rw-r--r--ioshark/compile-only.sh4
-rw-r--r--libfec/fec_read.cpp8
-rw-r--r--libfscrypt/fscrypt.cpp4
-rw-r--r--libfscrypt/include/fscrypt/fscrypt.h2
-rw-r--r--libfscrypt/tests/fscrypt_test.cpp8
-rw-r--r--libjsonpb/parse/jsonpb.cpp4
-rw-r--r--memory_replay/Alloc.cpp59
-rw-r--r--memory_replay/Alloc.h25
-rw-r--r--memory_replay/AllocParser.cpp92
-rw-r--r--memory_replay/AllocParser.h (renamed from simpleperf/cmd_inject_impl.h)32
-rw-r--r--memory_replay/Android.bp17
-rw-r--r--memory_replay/File.cpp1
-rw-r--r--memory_replay/NativeInfo.cpp17
-rw-r--r--memory_replay/NativeInfo.h3
-rw-r--r--memory_replay/main.cpp18
-rw-r--r--memtrack/Android.bp9
-rw-r--r--module_ndk_libs/libnativehelper/Android.bp4
l---------mtectrl/.clang-format1
-rw-r--r--mtectrl/Android.bp23
-rw-r--r--mtectrl/TEST_MAPPING12
-rw-r--r--mtectrl/mtectrl.cc232
-rw-r--r--mtectrl/mtectrl.rc13
-rw-r--r--mtectrl/mtectrl_test.cc147
-rw-r--r--mtectrl/src/com/android/tests/mtectrl/MtectrlEndToEndTest.java92
-rw-r--r--partition_tools/dynamic_partitions_device_info.proto4
-rw-r--r--partition_tools/lpadd.cc1
-rw-r--r--partition_tools/lpdump.cc7
-rw-r--r--partition_tools/lpmake.cc62
-rw-r--r--partition_tools/lpunpack.cc114
-rw-r--r--perf_tools/Android.bp38
-rwxr-xr-xperf_tools/bats/lcan.py692
-rw-r--r--perf_tools/config.yaml15
-rw-r--r--perf_tools/parse_timestamp.py55
-rw-r--r--perf_tools/parse_timing.py197
-rwxr-xr-xperf_tools/progress_report.py159
-rw-r--r--perf_tools/report.proto21
-rwxr-xr-xperf_tools/sbtpull.py562
-rw-r--r--power_profile/camera_avg/LICENSE447
-rw-r--r--profcollectd/Android.bp14
-rw-r--r--profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl3
-rw-r--r--profcollectd/libprofcollectd/config.rs3
-rw-r--r--profcollectd/libprofcollectd/lib.rs2
-rw-r--r--profcollectd/libprofcollectd/logging_trace_provider.rs5
-rw-r--r--profcollectd/libprofcollectd/report.rs23
-rw-r--r--profcollectd/libprofcollectd/scheduler.rs26
-rw-r--r--profcollectd/libprofcollectd/service.rs7
-rw-r--r--profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs23
-rw-r--r--profcollectd/libprofcollectd/trace_provider.rs4
-rw-r--r--puncture_fs/Android.bp46
-rw-r--r--puncture_fs/NOTICE190
-rw-r--r--puncture_fs/puncture_fs.cpp266
-rw-r--r--simpleperf/Android.bp61
-rw-r--r--simpleperf/Android.mk11
-rw-r--r--simpleperf/ETMBranchListFile.cpp385
-rw-r--r--simpleperf/ETMBranchListFile.h152
-rw-r--r--simpleperf/ETMBranchListFile_test.cpp35
-rw-r--r--simpleperf/ETMDecoder.cpp32
-rw-r--r--simpleperf/ETMDecoder.h11
-rw-r--r--simpleperf/JITDebugReader.cpp2
-rw-r--r--simpleperf/MapRecordReader.cpp5
-rw-r--r--simpleperf/OfflineUnwinder.cpp40
-rw-r--r--simpleperf/ProbeEvents.cpp16
-rw-r--r--simpleperf/README.md10
-rw-r--r--simpleperf/RecordFilter.cpp33
-rw-r--r--simpleperf/RecordFilter.h12
-rw-r--r--simpleperf/RecordFilter_test.cpp16
-rw-r--r--simpleperf/RecordReadThread.cpp27
-rw-r--r--simpleperf/RecordReadThread.h7
-rw-r--r--simpleperf/RecordReadThread_test.cpp25
-rw-r--r--simpleperf/RegEx.cpp80
-rw-r--r--simpleperf/RegEx.h54
-rw-r--r--simpleperf/RegEx_test.cpp47
-rw-r--r--simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java8
-rw-r--r--simpleperf/cmd_api.cpp18
-rw-r--r--simpleperf/cmd_debug_unwind.cpp21
-rw-r--r--simpleperf/cmd_dumprecord.cpp96
-rw-r--r--simpleperf/cmd_inject.cpp355
-rw-r--r--simpleperf/cmd_inject_test.cpp26
-rw-r--r--simpleperf/cmd_kmem.cpp27
-rw-r--r--simpleperf/cmd_merge.cpp16
-rw-r--r--simpleperf/cmd_monitor.cpp6
-rw-r--r--simpleperf/cmd_record.cpp389
-rw-r--r--simpleperf/cmd_record_impl.h3
-rw-r--r--simpleperf/cmd_record_test.cpp123
-rw-r--r--simpleperf/cmd_report.cpp20
-rw-r--r--simpleperf/cmd_report_sample.cpp17
-rw-r--r--simpleperf/cmd_report_sample.proto16
-rw-r--r--simpleperf/cmd_report_sample_test.cpp6
-rw-r--r--simpleperf/cmd_report_test.cpp55
-rw-r--r--simpleperf/cmd_stat.cpp24
-rw-r--r--simpleperf/cmd_stat_test.cpp15
-rw-r--r--simpleperf/cmd_trace_sched.cpp23
-rw-r--r--simpleperf/command.h11
-rw-r--r--simpleperf/cpu_hotplug_test.cpp2
-rw-r--r--simpleperf/demo/README.md26
-rw-r--r--simpleperf/demo/SimpleperfExampleCpp/app/build.gradle6
-rwxr-xr-xsimpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/arm64-v8a/libnative-lib.sobin1400312 -> 1915368 bytes
-rwxr-xr-xsimpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/armeabi-v7a/libnative-lib.sobin968796 -> 1139628 bytes
-rwxr-xr-xsimpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/x86/libnative-lib.sobin1267432 -> 1542220 bytes
-rwxr-xr-xsimpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/x86_64/libnative-lib.sobin1456912 -> 1790712 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleCpp/app/build/outputs/apk/debug/app-debug.apkbin3948626 -> 4509156 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleCpp/app/release/app-release.apkbin0 -> 3651017 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleCpp/app/src/main/AndroidManifest.xml4
-rw-r--r--simpleperf/demo/SimpleperfExampleCpp/app/src/main/cpp/CMakeLists.txt2
-rw-r--r--simpleperf/demo/SimpleperfExampleCpp/build.gradle26
-rw-r--r--simpleperf/demo/SimpleperfExampleCpp/gradle/wrapper/gradle-wrapper.properties2
-rw-r--r--simpleperf/demo/SimpleperfExampleCpp/settings.gradle14
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/.idea/compiler.xml6
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/.idea/gradle.xml (renamed from simpleperf/demo/SimpleperfExampleOfKotlin/.idea/gradle.xml)2
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/.idea/misc.xml10
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/.idea/vcs.xml6
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/build.gradle39
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/build/outputs/apk/debug/app-debug.apkbin0 -> 3761762 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/proguard-rules.pro (renamed from simpleperf/demo/SimpleperfExamplePureJava/app/proguard-rules.pro)10
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/release/app-release.apkbin0 -> 3160868 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/release/output-metadata.json20
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/androidTest/java/simpleperf/example/java/ExampleInstrumentedTest.java26
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/AndroidManifest.xml36
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/MainActivity.java (renamed from simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java)8
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/MultiProcessActivity.java (renamed from simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessActivity.java)11
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/MultiProcessService.java (renamed from simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessService.java)5
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/SleepActivity.java (renamed from simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/SleepActivity.java)8
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/drawable-v24/ic_launcher_foreground.xml30
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/drawable/ic_launcher_background.xml170
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/layout/activity_main.xml (renamed from simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/layout/activity_main.xml)12
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/layout/activity_multi_process.xml (renamed from simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_multi_process.xml)12
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/layout/activity_sleep.xml (renamed from simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_sleep.xml)12
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml5
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml5
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-hdpi/ic_launcher.webpbin0 -> 1404 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-hdpi/ic_launcher_round.webpbin0 -> 2898 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-mdpi/ic_launcher.webpbin0 -> 982 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-mdpi/ic_launcher_round.webpbin0 -> 1772 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xhdpi/ic_launcher.webpbin0 -> 1900 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webpbin0 -> 3918 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxhdpi/ic_launcher.webpbin0 -> 2884 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webpbin0 -> 5914 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webpbin0 -> 3844 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webpbin0 -> 7778 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values-night/themes.xml16
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values/colors.xml10
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values/strings.xml3
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values/themes.xml16
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/xml/backup_rules.xml13
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/main/res/xml/data_extraction_rules.xml19
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/app/src/test/java/simpleperf/example/java/ExampleUnitTest.java (renamed from simpleperf/demo/SimpleperfExamplePureJava/app/src/test/java/com/example/simpleperf/simpleperfexamplepurejava/ExampleUnitTest.java)4
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/build.gradle5
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/gradle.properties (renamed from simpleperf/demo/SimpleperfExampleOfKotlin/gradle.properties)14
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/gradle/wrapper/gradle-wrapper.jarbin0 -> 59203 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/gradle/wrapper/gradle-wrapper.properties (renamed from simpleperf/demo/SimpleperfExampleOfKotlin/gradle/wrapper/gradle-wrapper.properties)6
-rwxr-xr-xsimpleperf/demo/SimpleperfExampleJava/gradlew (renamed from simpleperf/demo/SimpleperfExamplePureJava/gradlew)111
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/gradlew.bat (renamed from simpleperf/demo/SimpleperfExampleOfKotlin/gradlew.bat)53
-rw-r--r--simpleperf/demo/SimpleperfExampleJava/settings.gradle16
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/.idea/compiler.xml6
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/.idea/gradle.xml (renamed from simpleperf/demo/SimpleperfExamplePureJava/.idea/gradle.xml)2
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/.idea/misc.xml10
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/.idea/vcs.xml6
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/build.gradle44
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/build/outputs/apk/debug/app-debug.apkbin0 -> 4565009 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/proguard-rules.pro (renamed from simpleperf/demo/SimpleperfExampleOfKotlin/app/proguard-rules.pro)10
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/release/app-release.apkbin0 -> 3771798 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/release/output-metadata.json20
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/androidTest/java/simpleperf/example/kotlin/ExampleInstrumentedTest.kt (renamed from simpleperf/demo/SimpleperfExampleOfKotlin/app/src/androidTest/java/com/example/simpleperf/simpleperfexampleofkotlin/ExampleInstrumentedTest.kt)12
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/AndroidManifest.xml31
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/java/simpleperf/example/kotlin/MainActivity.kt (renamed from simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/java/com/example/simpleperf/simpleperfexampleofkotlin/MainActivity.kt)7
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/java/simpleperf/example/kotlin/SleepActivity.kt (renamed from simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/java/com/example/simpleperf/simpleperfexampleofkotlin/SleepActivity.kt)9
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/drawable-v24/ic_launcher_foreground.xml30
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/drawable/ic_launcher_background.xml170
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/layout/activity_main.xml (renamed from simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_main.xml)12
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/layout/activity_sleep.xml18
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml5
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml5
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-hdpi/ic_launcher.webpbin0 -> 1404 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.webpbin0 -> 2898 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-mdpi/ic_launcher.webpbin0 -> 982 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.webpbin0 -> 1772 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.webpbin0 -> 1900 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webpbin0 -> 3918 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.webpbin0 -> 2884 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webpbin0 -> 5914 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webpbin0 -> 3844 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webpbin0 -> 7778 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values-night/themes.xml16
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values/colors.xml10
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values/strings.xml3
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values/themes.xml16
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/xml/backup_rules.xml13
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/xml/data_extraction_rules.xml19
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/app/src/test/java/simpleperf/example/kotlin/ExampleUnitTest.kt (renamed from simpleperf/demo/SimpleperfExampleOfKotlin/app/src/test/java/com/example/simpleperf/simpleperfexampleofkotlin/ExampleUnitTest.kt)4
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/build.gradle6
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/gradle.properties (renamed from simpleperf/demo/SimpleperfExamplePureJava/gradle.properties)16
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/gradle/wrapper/gradle-wrapper.jarbin0 -> 59203 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/gradle/wrapper/gradle-wrapper.properties (renamed from simpleperf/demo/SimpleperfExamplePureJava/gradle/wrapper/gradle-wrapper.properties)6
-rwxr-xr-xsimpleperf/demo/SimpleperfExampleKotlin/gradlew (renamed from simpleperf/demo/SimpleperfExampleOfKotlin/gradlew)111
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/gradlew.bat (renamed from simpleperf/demo/SimpleperfExamplePureJava/gradlew.bat)53
-rw-r--r--simpleperf/demo/SimpleperfExampleKotlin/settings.gradle16
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/.gitignore9
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/.idea/caches/build_file_checksums.serbin552 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/.idea/codeStyles/Project.xml29
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/.idea/kotlinc.xml8
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/.idea/misc.xml34
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/.idea/modules.xml9
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/.idea/runConfigurations.xml12
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/.gitignore1
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/build.gradle37
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/build/outputs/apk/profiling/app-profiling.apkbin1964023 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling.gradle58
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/arm64-v8a/wrap.sh2
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/armeabi-v7a/wrap.sh2
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/armeabi/wrap.sh2
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/mips/wrap.sh2
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/mips64/wrap.sh2
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/x86/wrap.sh2
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/x86_64/wrap.sh2
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/AndroidManifest.xml26
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/layout/activity_sleep.xml9
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-hdpi/ic_launcher.pngbin3358 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.pngbin5084 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-mdpi/ic_launcher.pngbin2386 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.pngbin3179 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.pngbin4648 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.pngbin7381 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.pngbin7008 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngbin11545 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.pngbin9442 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngbin16109 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/values/colors.xml6
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/values/strings.xml3
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/values/styles.xml11
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/build.gradle28
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/gradle/wrapper/gradle-wrapper.jarbin53636 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExampleOfKotlin/settings.gradle1
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/.gitignore9
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/.idea/caches/build_file_checksums.serbin552 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/.idea/codeStyles/Project.xml29
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/.idea/compiler.xml22
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/.idea/copyright/profiles_settings.xml3
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/.idea/misc.xml33
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/.idea/modules.xml9
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/.idea/runConfigurations.xml12
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/.gitignore1
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/build.gradle32
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/profiling/app-profiling.apkbin1547238 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/profiling.gradle58
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/arm64-v8a/wrap.sh2
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/armeabi-v7a/wrap.sh2
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/armeabi/wrap.sh2
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/mips/wrap.sh2
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/mips64/wrap.sh2
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/x86/wrap.sh2
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/x86_64/wrap.sh2
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/androidTest/java/com/example/simpleperf/simpleperfexamplepurejava/ExampleInstrumentedTest.java27
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml29
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-hdpi/ic_launcher.pngbin3418 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-hdpi/ic_launcher_round.pngbin4208 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-mdpi/ic_launcher.pngbin2206 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-mdpi/ic_launcher_round.pngbin2555 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xhdpi/ic_launcher.pngbin4842 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xhdpi/ic_launcher_round.pngbin6114 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxhdpi/ic_launcher.pngbin7718 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngbin10056 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher.pngbin10486 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngbin14696 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/values/colors.xml6
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/values/strings.xml3
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/values/styles.xml11
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/build.gradle25
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/gradle/wrapper/gradle-wrapper.jarbin53636 -> 0 bytes
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/settings.gradle1
-rw-r--r--simpleperf/doc/README.md181
-rw-r--r--simpleperf/doc/android_application_profiling.md2
-rw-r--r--simpleperf/doc/collect_etm_data_for_autofdo.md110
-rw-r--r--simpleperf/doc/executable_commands_reference.md136
-rw-r--r--simpleperf/dso.cpp134
-rw-r--r--simpleperf/dso.h1
-rw-r--r--simpleperf/dso_test.cpp60
-rw-r--r--simpleperf/environment.cpp75
-rw-r--r--simpleperf/environment.h1
-rw-r--r--simpleperf/environment_test.cpp6
-rw-r--r--simpleperf/event_attr.cpp13
-rw-r--r--simpleperf/event_attr.h16
-rw-r--r--simpleperf/event_selection_set.cpp13
-rw-r--r--simpleperf/event_selection_set.h2
-rw-r--r--simpleperf/get_test_data.h4
-rw-r--r--simpleperf/gtest_main.cpp2
-rw-r--r--simpleperf/include/simpleperf_profcollect.hpp4
-rw-r--r--simpleperf/kallsyms.cpp5
-rw-r--r--simpleperf/kallsyms_test.cpp19
-rw-r--r--simpleperf/libsimpleperf_report_fuzzer.cpp29
-rw-r--r--simpleperf/nonlinux_support/nonlinux_support.cpp12
-rw-r--r--simpleperf/perf_regs.cpp52
-rw-r--r--simpleperf/perf_regs.h13
-rw-r--r--simpleperf/perf_regs_test.cpp3
-rw-r--r--simpleperf/profcollect.cpp91
-rw-r--r--simpleperf/record.cpp68
-rw-r--r--simpleperf/record.h14
-rw-r--r--simpleperf/record_file.h41
-rw-r--r--simpleperf/record_file_format.h4
-rw-r--r--simpleperf/record_file_reader.cpp439
-rw-r--r--simpleperf/record_file_test.cpp32
-rw-r--r--simpleperf/record_file_writer.cpp19
-rw-r--r--simpleperf/record_test.cpp31
-rw-r--r--simpleperf/report_lib_interface.cpp39
-rw-r--r--simpleperf/report_utils.cpp290
-rw-r--r--simpleperf/report_utils.h87
-rw-r--r--simpleperf/report_utils_test.cpp170
-rw-r--r--simpleperf/runtest/Android.bp7
-rw-r--r--simpleperf/runtest/etm_test_loop.cpp20
-rw-r--r--simpleperf/rust/lib.rs25
-rw-r--r--simpleperf/scripts/Android.bp37
-rwxr-xr-xsimpleperf/scripts/app_profiler.py2
-rwxr-xr-xsimpleperf/scripts/bin/android/arm/simpleperfbin2812664 -> 2884216 bytes
-rwxr-xr-xsimpleperf/scripts/bin/android/arm64/simpleperfbin3819224 -> 4043608 bytes
-rwxr-xr-xsimpleperf/scripts/bin/android/x86/simpleperfbin4386820 -> 4500836 bytes
-rwxr-xr-xsimpleperf/scripts/bin/android/x86_64/simpleperfbin4244008 -> 4330328 bytes
-rwxr-xr-xsimpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylibbin13980215 -> 12874544 bytes
-rwxr-xr-xsimpleperf/scripts/bin/darwin/x86_64/simpleperfbin13902701 -> 12814816 bytes
-rwxr-xr-xsimpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.sobin7208384 -> 7073984 bytes
-rwxr-xr-xsimpleperf/scripts/bin/linux/x86_64/simpleperfbin7181056 -> 7051752 bytes
-rwxr-xr-xsimpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dllbin5583872 -> 5368320 bytes
-rwxr-xr-xsimpleperf/scripts/bin/windows/x86_64/simpleperf.exebin4638208 -> 4611072 bytes
-rwxr-xr-xsimpleperf/scripts/binary_cache_builder.py389
-rwxr-xr-xsimpleperf/scripts/gecko_profile_generator.py125
-rw-r--r--simpleperf/scripts/inferno/Android.bp5
-rwxr-xr-xsimpleperf/scripts/pprof_proto_generator.py5
-rw-r--r--simpleperf/scripts/profile_pb2.py613
-rwxr-xr-xsimpleperf/scripts/report_sample.py6
-rw-r--r--simpleperf/scripts/simpleperf_report_lib.py16
-rw-r--r--simpleperf/scripts/simpleperf_utils.py10
-rw-r--r--simpleperf/scripts/test/app_test.py15
-rw-r--r--simpleperf/scripts/test/binary_cache_builder_test.py67
-rw-r--r--simpleperf/scripts/test/cpp_app_test.py16
-rwxr-xr-xsimpleperf/scripts/test/do_test.py26
-rw-r--r--simpleperf/scripts/test/gecko_profile_generator_test.py43
-rw-r--r--simpleperf/scripts/test/java_app_test.py81
-rw-r--r--simpleperf/scripts/test/kotlin_app_test.py59
-rw-r--r--simpleperf/scripts/test/pprof_proto_generator_test.py4
-rw-r--r--simpleperf/scripts/test/report_html_test.py24
-rw-r--r--simpleperf/scripts/test/report_lib_test.py23
-rw-r--r--simpleperf/scripts/test/report_sample_test.py28
-rw-r--r--simpleperf/scripts/test/script_testdata/perf_with_interpreter_frames.gecko.json7
-rw-r--r--simpleperf/scripts/test/script_testdata/perf_with_tracepoint_event.gecko.json542
-rw-r--r--simpleperf/scripts/test/test_utils.py6
-rw-r--r--simpleperf/scripts/test/tools_test.py17
-rw-r--r--simpleperf/test_util.cpp11
-rw-r--r--simpleperf/test_util.h4
-rw-r--r--simpleperf/testdata/DisplayBitmaps.apkbin2377615 -> 760019 bytes
-rw-r--r--simpleperf/testdata/DisplayBitmapsTest.apkbin2941948 -> 2861529 bytes
-rw-r--r--simpleperf/testdata/data/symfs_for_no_symbol_table_warning/elfbin6280 -> 0 bytes
-rw-r--r--simpleperf/testdata/etm/perf_etm.data (renamed from simpleperf/testdata/etm/perf.data)bin12321 -> 12321 bytes
-rw-r--r--simpleperf/testdata/etm/perf_inject.data1
-rw-r--r--simpleperf/testdata/etm/perf_with_missing_aux_data.databin0 -> 24552 bytes
-rw-r--r--simpleperf/thread_tree.cpp29
-rw-r--r--simpleperf/thread_tree.h6
-rw-r--r--simpleperf/thread_tree_test.cpp7
-rw-r--r--simpleperf/tracing.cpp128
-rw-r--r--simpleperf/tracing.h6
-rw-r--r--simpleperf/utils.cpp66
-rw-r--r--simpleperf/utils.h90
-rw-r--r--simpleperf/utils_test.cpp18
-rw-r--r--squashfs_utils/Android.bp2
-rw-r--r--tests/kernel.config/OWNERS6
-rw-r--r--toolchain-extras/Android.bp4
395 files changed, 9640 insertions, 4436 deletions
diff --git a/app-launcher/Android.bp b/app-launcher/Android.bp
index 3fbc88c8..f00dcb63 100644
--- a/app-launcher/Android.bp
+++ b/app-launcher/Android.bp
@@ -31,7 +31,7 @@ cc_binary_host {
sh_binary_host {
name: "app-launcher",
- src: "app-launcher",
+ src: "app-launcher.sh",
required: [
"computestats",
"computestatsf",
diff --git a/app-launcher/app-launcher b/app-launcher/app-launcher.sh
index 525e06ac..525e06ac 100755
--- a/app-launcher/app-launcher
+++ b/app-launcher/app-launcher.sh
diff --git a/bootctl/Android.bp b/bootctl/Android.bp
index f63871cf..106ff863 100644
--- a/bootctl/Android.bp
+++ b/bootctl/Android.bp
@@ -30,6 +30,8 @@ cc_binary {
"android.hardware.boot@1.0",
"android.hardware.boot@1.1",
"android.hardware.boot@1.2",
+ "android.hardware.boot-V1-ndk",
+ "libboot_control_client",
"libhidlbase",
"libutils",
],
diff --git a/bootctl/bootctl.cpp b/bootctl/bootctl.cpp
index 8ead010d..1bf91815 100644
--- a/bootctl/bootctl.cpp
+++ b/bootctl/bootctl.cpp
@@ -17,27 +17,19 @@
#include <optional>
#include <sstream>
+#include <BootControlClient.h>
#include <android/hardware/boot/1.2/IBootControl.h>
#include <sysexits.h>
using android::sp;
-using android::hardware::hidl_string;
-using android::hardware::Return;
+using aidl::android::hardware::boot::MergeStatus;
-using android::hardware::boot::V1_0::BoolResult;
-using android::hardware::boot::V1_0::CommandResult;
-using android::hardware::boot::V1_0::Slot;
-using android::hardware::boot::V1_1::IBootControl;
-using android::hardware::boot::V1_1::MergeStatus;
+using android::hal::BootControlClient;
+using android::hal::BootControlVersion;
+using android::hal::CommandResult;
-namespace V1_0 = android::hardware::boot::V1_0;
-namespace V1_1 = android::hardware::boot::V1_1;
-namespace V1_2 = android::hardware::boot::V1_2;
-
-enum BootCtlVersion { BOOTCTL_V1_0, BOOTCTL_V1_1, BOOTCTL_V1_2 };
-
-static void usage(FILE* where, BootCtlVersion bootVersion, int /* argc */, char* argv[]) {
+static void usage(FILE* where, BootControlVersion bootVersion, int /* argc */, char* argv[]) {
fprintf(where,
"%s - command-line wrapper for the boot HAL.\n"
"\n"
@@ -56,7 +48,7 @@ static void usage(FILE* where, BootCtlVersion bootVersion, int /* argc */, char*
" is-slot-marked-successful SLOT - Returns 0 only if SLOT is marked GOOD.\n"
" get-suffix SLOT - Prints suffix for SLOT.\n",
argv[0], argv[0]);
- if (bootVersion >= BOOTCTL_V1_1) {
+ if (bootVersion >= BootControlVersion::BOOTCTL_V1_1) {
fprintf(where,
" set-snapshot-merge-status STAT - Sets whether a snapshot-merge of any dynamic\n"
" partition is in progress. Valid STAT values\n"
@@ -69,32 +61,39 @@ static void usage(FILE* where, BootCtlVersion bootVersion, int /* argc */, char*
"SLOT parameter is the zero-based slot-number.\n");
}
-static int do_hal_info(const sp<V1_0::IBootControl> module) {
- module->interfaceDescriptor([&](const auto& descriptor) {
- fprintf(stdout, "HAL Version: %s\n", descriptor.c_str());
- });
+static constexpr auto ToString(BootControlVersion ver) {
+ switch (ver) {
+ case BootControlVersion::BOOTCTL_V1_0:
+ return "android.hardware.boot@1.0::IBootControl";
+ case BootControlVersion::BOOTCTL_V1_1:
+ return "android.hardware.boot@1.1::IBootControl";
+ case BootControlVersion::BOOTCTL_V1_2:
+ return "android.hardware.boot@1.2::IBootControl";
+ case BootControlVersion::BOOTCTL_AIDL:
+ return "android.hardware.boot@aidl::IBootControl";
+ }
+}
+
+static int do_hal_info(const BootControlClient* module) {
+ fprintf(stdout, "HAL Version: %s\n", ToString(module->GetVersion()));
return EX_OK;
}
-static int do_get_number_slots(sp<V1_0::IBootControl> module) {
- uint32_t numSlots = module->getNumberSlots();
+static int do_get_number_slots(BootControlClient* module) {
+ auto numSlots = module->GetNumSlots();
fprintf(stdout, "%u\n", numSlots);
return EX_OK;
}
-static int do_get_current_slot(sp<V1_0::IBootControl> module) {
- Slot curSlot = module->getCurrentSlot();
+static int do_get_current_slot(BootControlClient* module) {
+ auto curSlot = module->GetCurrentSlot();
fprintf(stdout, "%u\n", curSlot);
return EX_OK;
}
-static std::function<void(CommandResult)> generate_callback(CommandResult* crp) {
- return [=](CommandResult cr) { *crp = cr; };
-}
-
-static int handle_return(const Return<void>& ret, CommandResult cr, const char* errStr) {
- if (!ret.isOk()) {
- fprintf(stderr, errStr, ret.description().c_str());
+static int handle_return(CommandResult cr, const char* errStr) {
+ if (!cr.IsOk()) {
+ fprintf(stderr, errStr, cr.errMsg.c_str());
return EX_SOFTWARE;
} else if (!cr.success) {
fprintf(stderr, errStr, cr.errMsg.c_str());
@@ -103,51 +102,48 @@ static int handle_return(const Return<void>& ret, CommandResult cr, const char*
return EX_OK;
}
-static int do_mark_boot_successful(sp<V1_0::IBootControl> module) {
- CommandResult cr;
- Return<void> ret = module->markBootSuccessful(generate_callback(&cr));
- return handle_return(ret, cr, "Error marking as having booted successfully: %s\n");
+static int do_mark_boot_successful(BootControlClient* module) {
+ auto ret = module->MarkBootSuccessful();
+ return handle_return(ret, "Error marking as having booted successfully: %s\n");
}
-static int do_get_active_boot_slot(sp<V1_2::IBootControl> module) {
- uint32_t slot = module->getActiveBootSlot();
+static int do_get_active_boot_slot(BootControlClient* module) {
+ uint32_t slot = module->GetActiveBootSlot();
fprintf(stdout, "%u\n", slot);
return EX_OK;
}
-static int do_set_active_boot_slot(sp<V1_0::IBootControl> module, Slot slot_number) {
- CommandResult cr;
- Return<void> ret = module->setActiveBootSlot(slot_number, generate_callback(&cr));
- return handle_return(ret, cr, "Error setting active boot slot: %s\n");
+static int do_set_active_boot_slot(BootControlClient* module, int32_t slot_number) {
+ const auto cr = module->SetActiveBootSlot(slot_number);
+ return handle_return(cr, "Error setting active boot slot: %s\n");
}
-static int do_set_slot_as_unbootable(sp<V1_0::IBootControl> module, Slot slot_number) {
- CommandResult cr;
- Return<void> ret = module->setSlotAsUnbootable(slot_number, generate_callback(&cr));
- return handle_return(ret, cr, "Error setting slot as unbootable: %s\n");
+static int do_set_slot_as_unbootable(BootControlClient* module, int32_t slot_number) {
+ const auto cr = module->MarkSlotUnbootable(slot_number);
+ return handle_return(cr, "Error setting slot as unbootable: %s\n");
}
-static int handle_return(const Return<BoolResult>& ret, const char* errStr) {
- if (!ret.isOk()) {
- fprintf(stderr, errStr, ret.description().c_str());
+static int handle_return(const std::optional<bool>& ret, const char* errStr) {
+ if (!ret.has_value()) {
+ fprintf(stderr, errStr, "");
return EX_SOFTWARE;
- } else if (ret == BoolResult::INVALID_SLOT) {
- fprintf(stderr, errStr, "Invalid slot");
- return EX_SOFTWARE;
- } else if (ret == BoolResult::TRUE) {
+ }
+ if (ret.value()) {
+ printf("%d\n", ret.value());
return EX_OK;
}
+ printf("%d\n", ret.value());
return EX_SOFTWARE;
}
-static int do_is_slot_bootable(sp<V1_0::IBootControl> module, Slot slot_number) {
- Return<BoolResult> ret = module->isSlotBootable(slot_number);
- return handle_return(ret, "Error calling isSlotBootable(): %s\n");
+static int do_is_slot_bootable(BootControlClient* module, int32_t slot_number) {
+ const auto ret = module->IsSlotBootable(slot_number);
+ return handle_return(ret, "Error calling isSlotBootable()\n");
}
-static int do_is_slot_marked_successful(sp<V1_0::IBootControl> module, Slot slot_number) {
- Return<BoolResult> ret = module->isSlotMarkedSuccessful(slot_number);
- return handle_return(ret, "Error calling isSlotMarkedSuccessful(): %s\n");
+static int do_is_slot_marked_successful(BootControlClient* module, int32_t slot_number) {
+ const auto ret = module->IsSlotMarkedSuccessful(slot_number);
+ return handle_return(ret, "Error calling isSlotMarkedSuccessful()\n");
}
std::optional<MergeStatus> stringToMergeStatus(const std::string& status) {
@@ -159,7 +155,7 @@ std::optional<MergeStatus> stringToMergeStatus(const std::string& status) {
return {};
}
-static int do_set_snapshot_merge_status(sp<V1_1::IBootControl> module, BootCtlVersion bootVersion,
+static int do_set_snapshot_merge_status(BootControlClient* module, BootControlVersion bootVersion,
int argc, char* argv[]) {
if (argc != 3) {
usage(stderr, bootVersion, argc, argv);
@@ -174,10 +170,8 @@ static int do_set_snapshot_merge_status(sp<V1_1::IBootControl> module, BootCtlVe
return -1;
}
- if (!module->setSnapshotMergeStatus(status.value())) {
- return EX_SOFTWARE;
- }
- return EX_OK;
+ const auto ret = module->SetSnapshotMergeStatus(status.value());
+ return handle_return(ret, "Failed to set snapshot merge status: %s\n");
}
std::ostream& operator<<(std::ostream& os, MergeStatus state) {
@@ -197,7 +191,7 @@ std::ostream& operator<<(std::ostream& os, MergeStatus state) {
}
}
-static int do_get_snapshot_merge_status(sp<V1_1::IBootControl> module) {
+static int do_get_snapshot_merge_status(BootControlClient* module) {
MergeStatus ret = module->getSnapshotMergeStatus();
std::stringstream ss;
ss << ret;
@@ -205,19 +199,17 @@ static int do_get_snapshot_merge_status(sp<V1_1::IBootControl> module) {
return EX_OK;
}
-static int do_get_suffix(sp<V1_0::IBootControl> module, Slot slot_number) {
- std::function<void(hidl_string)> cb = [](hidl_string suffix) {
- fprintf(stdout, "%s\n", suffix.c_str());
- };
- Return<void> ret = module->getSuffix(slot_number, cb);
- if (!ret.isOk()) {
- fprintf(stderr, "Error calling getSuffix(): %s\n", ret.description().c_str());
+static int do_get_suffix(BootControlClient* module, int32_t slot_number) {
+ const auto ret = module->GetSuffix(slot_number);
+ if (ret.empty()) {
+ fprintf(stderr, "Error calling getSuffix()\n");
return EX_SOFTWARE;
}
+ printf("%s\n", ret.c_str());
return EX_OK;
}
-static uint32_t parse_slot(BootCtlVersion bootVersion, int pos, int argc, char* argv[]) {
+static uint32_t parse_slot(BootControlVersion bootVersion, int pos, int argc, char* argv[]) {
if (pos > argc - 1) {
usage(stderr, bootVersion, argc, argv);
exit(EX_USAGE);
@@ -234,25 +226,12 @@ static uint32_t parse_slot(BootCtlVersion bootVersion, int pos, int argc, char*
}
int main(int argc, char* argv[]) {
- sp<V1_0::IBootControl> v1_0_module;
- sp<V1_1::IBootControl> v1_1_module;
- sp<V1_2::IBootControl> v1_2_module;
- BootCtlVersion bootVersion = BOOTCTL_V1_0;
-
- v1_0_module = V1_0::IBootControl::getService();
- if (v1_0_module == nullptr) {
- fprintf(stderr, "Error getting bootctrl v1.0 module.\n");
+ const auto client = android::hal::BootControlClient::WaitForService();
+ if (client == nullptr) {
+ fprintf(stderr, "Failed to get bootctl module.\n");
return EX_SOFTWARE;
}
- v1_1_module = V1_1::IBootControl::castFrom(v1_0_module);
- if (v1_1_module != nullptr) {
- bootVersion = BOOTCTL_V1_1;
- }
-
- v1_2_module = V1_2::IBootControl::castFrom(v1_0_module);
- if (v1_2_module != nullptr) {
- bootVersion = BOOTCTL_V1_2;
- }
+ const auto bootVersion = client->GetVersion();
if (argc < 2) {
usage(stderr, bootVersion, argc, argv);
@@ -261,46 +240,46 @@ int main(int argc, char* argv[]) {
// Functions present from version 1.0
if (strcmp(argv[1], "hal-info") == 0) {
- return do_hal_info(v1_0_module);
+ return do_hal_info(client.get());
} else if (strcmp(argv[1], "get-number-slots") == 0) {
- return do_get_number_slots(v1_0_module);
+ return do_get_number_slots(client.get());
} else if (strcmp(argv[1], "get-current-slot") == 0) {
- return do_get_current_slot(v1_0_module);
+ return do_get_current_slot(client.get());
} else if (strcmp(argv[1], "mark-boot-successful") == 0) {
- return do_mark_boot_successful(v1_0_module);
+ return do_mark_boot_successful(client.get());
} else if (strcmp(argv[1], "set-active-boot-slot") == 0) {
- return do_set_active_boot_slot(v1_0_module, parse_slot(bootVersion, 2, argc, argv));
+ return do_set_active_boot_slot(client.get(), parse_slot(bootVersion, 2, argc, argv));
} else if (strcmp(argv[1], "set-slot-as-unbootable") == 0) {
- return do_set_slot_as_unbootable(v1_0_module, parse_slot(bootVersion, 2, argc, argv));
+ return do_set_slot_as_unbootable(client.get(), parse_slot(bootVersion, 2, argc, argv));
} else if (strcmp(argv[1], "is-slot-bootable") == 0) {
- return do_is_slot_bootable(v1_0_module, parse_slot(bootVersion, 2, argc, argv));
+ return do_is_slot_bootable(client.get(), parse_slot(bootVersion, 2, argc, argv));
} else if (strcmp(argv[1], "is-slot-marked-successful") == 0) {
- return do_is_slot_marked_successful(v1_0_module, parse_slot(bootVersion, 2, argc, argv));
+ return do_is_slot_marked_successful(client.get(), parse_slot(bootVersion, 2, argc, argv));
} else if (strcmp(argv[1], "get-suffix") == 0) {
- return do_get_suffix(v1_0_module, parse_slot(bootVersion, 2, argc, argv));
+ return do_get_suffix(client.get(), parse_slot(bootVersion, 2, argc, argv));
}
// Functions present from version 1.1
if (strcmp(argv[1], "set-snapshot-merge-status") == 0 ||
strcmp(argv[1], "get-snapshot-merge-status") == 0) {
- if (v1_1_module == nullptr) {
+ if (bootVersion < BootControlVersion::BOOTCTL_V1_1) {
fprintf(stderr, "Error getting bootctrl v1.1 module.\n");
return EX_SOFTWARE;
}
if (strcmp(argv[1], "set-snapshot-merge-status") == 0) {
- return do_set_snapshot_merge_status(v1_1_module, bootVersion, argc, argv);
+ return do_set_snapshot_merge_status(client.get(), bootVersion, argc, argv);
} else if (strcmp(argv[1], "get-snapshot-merge-status") == 0) {
- return do_get_snapshot_merge_status(v1_1_module);
+ return do_get_snapshot_merge_status(client.get());
}
}
if (strcmp(argv[1], "get-active-boot-slot") == 0) {
- if (v1_2_module == nullptr) {
+ if (bootVersion < BootControlVersion::BOOTCTL_V1_2) {
fprintf(stderr, "Error getting bootctrl v1.2 module.\n");
return EX_SOFTWARE;
}
- return do_get_active_boot_slot(v1_2_module);
+ return do_get_active_boot_slot(client.get());
}
// Parameter not matched, print usage
diff --git a/boottime_tools/bootanalyze/bootanalyze.py b/boottime_tools/bootanalyze/bootanalyze.py
index 5bed9f29..2b47a899 100755
--- a/boottime_tools/bootanalyze/bootanalyze.py
+++ b/boottime_tools/bootanalyze/bootanalyze.py
@@ -16,7 +16,7 @@
#
"""Tool to analyze logcat and dmesg logs.
-bootanalyze read logcat and dmesg loga and determines key points for boot.
+bootanalyze read logcat and dmesg logs and determines key points for boot.
"""
import argparse
@@ -28,12 +28,11 @@ import os
import re
import select
import subprocess
-import sys
import time
import threading
import yaml
-from datetime import datetime, date
+from datetime import datetime
TIME_DMESG = r"\[\s*(\d+\.\d+)\]"
TIME_LOGCAT = r"[0-9]+\.?[0-9]*"
@@ -41,7 +40,9 @@ KERNEL_TIME_KEY = "kernel"
BOOT_ANIM_END_TIME_KEY = "BootAnimEnd"
KERNEL_BOOT_COMPLETE = "BootComplete_kernel"
LOGCAT_BOOT_COMPLETE = "BootComplete"
+CARWATCHDOG_BOOT_COMPLETE = "CarWatchdogBootupProfilingComplete"
LAUNCHER_START = "LauncherStart"
+CARWATCHDOG_DUMP_COMMAND = "adb shell dumpsys android.automotive.watchdog.ICarWatchdog/default"
BOOT_TIME_TOO_BIG = 200.0
MAX_RETRIES = 5
DEBUG = False
@@ -50,6 +51,7 @@ ADB_CMD = "adb"
TIMING_THRESHOLD = 5.0
BOOT_PROP = r"\[ro\.boottime\.([^\]]+)\]:\s+\[(\d+)\]"
BOOTLOADER_TIME_PROP = r"\[ro\.boot\.boottime\]:\s+\[([^\]]+)\]"
+CARWATCHDOG_PARSER_CMD = 'perf_stats_parser'
max_wait_time = BOOT_TIME_TOO_BIG
@@ -84,7 +86,7 @@ def main():
value = float(kv[1])
components_to_monitor[key] = value
- cfg = yaml.load(args.config, Loader=yaml.FullLoader)
+ cfg = yaml.load(args.config, Loader=yaml.SafeLoader)
if args.stressfs:
if run_adb_cmd('install -r -g ' + args.stressfs) != 0:
@@ -102,12 +104,18 @@ def main():
if DEBUG_PATTERN:
print("search event:{} timing event:{}".format(search_events_pattern, timing_events_pattern))
+ now = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
+ boot_chart_file_path_prefix = "bootchart-" + now
+ systrace_file_path_prefix = "systrace-" + now
+
+ if args.output:
+ boot_chart_file_path_prefix = args.output + '/' + boot_chart_file_path_prefix
+ systrace_file_path_prefix = args.output + '/' + systrace_file_path_prefix
+
data_points = {}
kernel_timing_points = collections.OrderedDict()
logcat_timing_points = collections.OrderedDict()
boottime_points = collections.OrderedDict()
- boot_chart_file_name_prefix = "bootchart-" + datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
- systrace_file_name_prefix = "systrace-" + datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
shutdown_event_all = collections.OrderedDict()
shutdown_timing_event_all = collections.OrderedDict()
for it in range(0, args.iterate):
@@ -141,11 +149,16 @@ def main():
# Processing error
print("Failed to collect valid samples for run {0}".format(it))
continue
+
if args.bootchart:
- grab_bootchart(boot_chart_file_name_prefix + "_run_" + str(it))
+ grab_bootchart(boot_chart_file_path_prefix + "_run_" + str(it))
if args.systrace:
- grab_systrace(systrace_file_name_prefix + "_run_" + str(it))
+ grab_systrace(systrace_file_path_prefix + "_run_" + str(it))
+
+ if args.carwatchdog:
+ grab_carwatchdog_bootstats(args.output)
+
for k, v in processing_data.items():
if k not in data_points:
data_points[k] = []
@@ -332,6 +345,8 @@ def iterate(args, search_events_pattern, timings_pattern, shutdown_events_patter
logcat_stop_events = [LOGCAT_BOOT_COMPLETE, LAUNCHER_START]
if args.fs_check:
logcat_stop_events.append("FsStat")
+ if args.carwatchdog:
+ logcat_stop_events.append(CARWATCHDOG_BOOT_COMPLETE)
logcat_events, logcat_timing_events = collect_events(
search_events_pattern, ADB_CMD + ' logcat -b all -v epoch', timings_pattern,\
logcat_stop_events, False)
@@ -504,6 +519,8 @@ def init_arguments():
parser.add_argument('-r', '--reboot', dest='reboot',
action='store_true',
help='reboot device for measurement', )
+ parser.add_argument('-o', '--output', dest='output', type=str,
+ help='Output directory where results are stored')
parser.add_argument('-c', '--config', dest='config',
default='config.yaml', type=argparse.FileType('r'),
help='config file for the tool', )
@@ -543,6 +560,8 @@ def init_arguments():
parser.add_argument('-y', '--systrace', dest='systrace',
action='store_true',
help='collect systrace from the device. kernel trace should be already enabled', )
+ parser.add_argument('-W', '--carwatchdog', dest='carwatchdog', action='store_true',
+ help='collect carwatchdog boot stats')
parser.add_argument('-G', '--buffersize', dest='buffersize', action='store', type=str,
default=None,
help='set logcat buffersize')
@@ -677,6 +696,15 @@ def collect_events(search_events, command, timings, stop_events, disable_timing_
log_timeout(time_left, stop_events, events, timing_events)
break
polled_events = read_poll.poll(time_left * 1000.0)
+ # adb logcat subprocess is auto-terminated when the adb connection is lost.
+ # Thus, check for the subprocess return code and reconnect to the device if
+ # needed. Otherwise, the logcat events cannot be polled completely.
+ if process.poll() is not None:
+ print("adb might be disconnected?\nRetrying to connect.")
+ run_adb_cmd('wait-for-device')
+ print(" reconnected")
+ init = True
+ continue
if len(polled_events) == 0:
log_timeout(time_left, stop_events, events, timing_events)
break
@@ -830,14 +858,21 @@ def reboot(serial, use_stressfs, permissive, use_adb_reboot, adb_buffersize=None
if run_adb_cmd('logcat -G {}'.format(adb_buffersize)) != 0:
debug('Fail to set logcat buffer size as {}'.format(adb_buffersize))
-def run_adb_cmd(cmd):
- return subprocess.call(ADB_CMD + ' ' + cmd, shell=True)
+'''
+Runs adb command. If do_return_result is true then output of command is
+returned otherwise an empty string is returned.
+'''
+def run_adb_cmd(cmd, do_return_result=False):
+ if do_return_result:
+ return subprocess.check_output(ADB_CMD + ' ' + cmd, shell=True).decode('utf-8', 'ignore').strip()
+ subprocess.call(ADB_CMD + ' ' + cmd, shell=True)
+ return ""
-def run_adb_shell_cmd(cmd):
- return subprocess.call(ADB_CMD + ' shell ' + cmd, shell=True)
+def run_adb_shell_cmd(cmd, do_return_result=False):
+ return run_adb_cmd('shell ' + cmd, do_return_result)
-def run_adb_shell_cmd_as_root(cmd):
- return subprocess.call(ADB_CMD + ' shell su root ' + cmd, shell=True)
+def run_adb_shell_cmd_as_root(cmd, do_return_result=False):
+ return run_adb_shell_cmd('su root ' + cmd, do_return_result)
def logcat_time_func(offset_year):
def f(date_str):
@@ -856,21 +891,95 @@ def stddev(data):
variance = sq_diffs_sum / items_count
return math.sqrt(variance)
-def grab_bootchart(boot_chart_file_name):
- subprocess.call("$ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh", shell=True)
- print("Saving boot chart as " + boot_chart_file_name + ".tgz")
- subprocess.call('cp /tmp/android-bootchart/bootchart.tgz ./' + boot_chart_file_name + '.tgz',\
+def grab_bootchart(boot_chart_file_path):
+ subprocess.run("$ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh", shell=True,
+ stdout=subprocess.DEVNULL)
+ print("Saving boot chart as " + boot_chart_file_path + ".tgz")
+ subprocess.call('cp /tmp/android-bootchart/bootchart.tgz ' + boot_chart_file_path + '.tgz', \
shell=True)
- subprocess.call('cp ./bootchart.png ./' + boot_chart_file_name + '.png', shell=True)
+ subprocess.call('cp ./bootchart.png ' + boot_chart_file_path + '.png', shell=True)
-def grab_systrace(systrace_file_name):
- trace_file = systrace_file_name + "_trace.txt"
+def grab_systrace(systrace_file_path_prefix):
+ trace_file = systrace_file_path_prefix + "_trace.txt"
with open(trace_file, 'w') as f:
f.write("TRACE:\n")
run_adb_shell_cmd_as_root("cat /d/tracing/trace >> " + trace_file)
- html_file = systrace_file_name + ".html"
+ html_file = systrace_file_path_prefix + ".html"
subprocess.call("$ANDROID_BUILD_TOP/external/chromium-trace/systrace.py --from-file=" + trace_file + " -o " +\
html_file, shell=True)
+def capture_build_info(out_dir, build_info_file_name):
+ fingerprint = run_adb_shell_cmd('getprop ro.build.fingerprint', True)
+ brand = run_adb_shell_cmd('getprop ro.product.brand', True)
+ product = run_adb_shell_cmd('getprop ro.product.name', True)
+ device = run_adb_shell_cmd('getprop ro.product.device', True)
+ version_release = run_adb_shell_cmd('getprop ro.build.version.release', True)
+ id = run_adb_shell_cmd('getprop ro.build.id', True)
+ version_incremental = run_adb_shell_cmd('getprop ro.build.version.incremental', True)
+ type = run_adb_shell_cmd('getprop ro.build.type', True)
+ tags = run_adb_shell_cmd('getprop ro.build.tags', True)
+ sdk = run_adb_shell_cmd('getprop ro.build.version.sdk', True)
+ platform_minor = run_adb_shell_cmd('getprop ro.android.car.version.platform_minor', True)
+ codename = run_adb_shell_cmd('getprop ro.build.version.codename', True)
+ carwatchdog_collection_interval = run_adb_shell_cmd('getprop ro.carwatchdog.system_event_collection_interval', True)
+ carwatchdog_post_event_duration = run_adb_shell_cmd('getprop ro.carwatchdog.post_system_event_duration', True)
+ carwatchdog_top_n_category = run_adb_shell_cmd('getprop ro.carwatchdog.top_n_stats_per_category', True)
+ carwatchdog_top_n_subcategory = run_adb_shell_cmd('getprop ro.carwatchdog.top_n_stats_per_subcategory', True)
+
+ # TODO: Change file format to JSON to avoid custom parser
+ build_info = []
+ build_info.append('Build information: ')
+ build_info.append('-' * 20)
+ build_info.append('fingerprint: ' + fingerprint)
+ build_info.append('brand: ' + brand)
+ build_info.append('product: ' + product)
+ build_info.append('device: ' + device)
+ build_info.append('version.release: ' + version_release)
+ build_info.append('id: ' + id)
+ build_info.append('version.incremental: ' + version_incremental)
+ build_info.append('type: ' + type)
+ build_info.append('tags: ' + tags)
+ build_info.append('sdk: ' + sdk)
+ build_info.append('platform minor version: ' + platform_minor)
+ build_info.append('codename: ' + codename)
+ build_info.append('carwatchdog collection interval (s): ' + carwatchdog_collection_interval)
+ build_info.append('carwatchdog post event duration (s): ' + carwatchdog_post_event_duration)
+ build_info.append('carwatchdog top N packages: ' + carwatchdog_top_n_category)
+ build_info.append('carwatchdog top N processes: ' + carwatchdog_top_n_subcategory)
+
+ build_info_str = '\n'.join(build_info)
+
+ with open(out_dir + '/' + build_info_file_name, 'w') as f:
+ f.write(build_info_str)
+
+def generate_proto(dump_file, build_info_file, out_proto_file):
+ subprocess.run("{} -f {} -b {} -d {}".format(CARWATCHDOG_PARSER_CMD,
+ dump_file,
+ build_info_file,
+ out_proto_file),
+ shell=True, stdout=subprocess.DEVNULL)
+
+def grab_carwatchdog_bootstats(result_dir):
+ carwatchdog_state = run_adb_shell_cmd_as_root('getprop init.svc.carwatchdogd', True)
+ if carwatchdog_state != "running":
+ print('carwatchdog (-d) flag set but CarWatchdog is not running on device')
+ return
+ elif not result_dir:
+ print('carwatchdog needs the output directory to be specified.')
+ return
+ print("Capturing carwatchdog stats")
+ build_info_file_name = "device_build_info.txt"
+ capture_build_info(result_dir, build_info_file_name)
+
+ # Capture CW dump and save dump to txt
+ dump_file_name = result_dir + '/carwatchdog_dump.txt'
+ subprocess.call(CARWATCHDOG_DUMP_COMMAND + " > " + dump_file_name, shell=True)
+
+ # Generate proto from dump
+ build_info_file_path = result_dir + '/' + build_info_file_name
+ out_proto_file_path = result_dir + '/carwatchdog_perf_stats_out.pb'
+ generate_proto(dump_file_name, build_info_file_path, out_proto_file_path)
+
+
if __name__ == '__main__':
main()
diff --git a/boottime_tools/bootanalyze/bootanalyze.sh b/boottime_tools/bootanalyze/bootanalyze.sh
index 7fce2e1c..bada4656 100755
--- a/boottime_tools/bootanalyze/bootanalyze.sh
+++ b/boottime_tools/bootanalyze/bootanalyze.sh
@@ -16,13 +16,18 @@
readme() {
echo '
-Analyze boot-time & bootchart
+Analyze boot-time
e.g.
ANDROID_BUILD_TOP="$PWD" \
CONFIG_YMAL="$ANDROID_BUILD_TOP/system/extras/boottime_tools/bootanalyze/config.yaml" \
LOOPS=3 \
- RESULTS_DIR="$ANDROID_BUILD_TOP/bootAnalyzeResults" \
- $PWD/system/extras/boottime_tools/bootanalyze/bootanalyze.sh
+ RESULTS_DIR="$PWD/bootAnalyzeResults" \
+ $ANDROID_BUILD_TOP/system/extras/boottime_tools/bootanalyze/bootanalyze.sh
+
+Flags:
+-a : Uses "adb reboot" (instead of "adb shell su root svc power reboot") command to reboot
+-b : If set grabs bootchart
+-w : If set grabs carwatchdog perf stats
'
exit
}
@@ -48,6 +53,29 @@ fi
echo "RESULTS_DIR=$RESULTS_DIR"
mkdir -p $RESULTS_DIR
+ADB_REBOOT_FLAG=""
+BOOTCHART_FLAG=""
+CARWATCHDOG_FLAG=""
+
+while getopts 'abw' OPTION; do
+ case "$OPTION" in
+ a)
+ ADB_REBOOT_FLAG="-a"
+ ;;
+ b)
+ BOOTCHART_FLAG="-b"
+ ;;
+ w)
+ CARWATCHDOG_FLAG="-W"
+ ;;
+ ?)
+ echo 'Error: Invalid flag set'
+ readme
+ ;;
+ esac
+done
+shift "$(($OPTIND -1))"
+
adb shell 'touch /data/bootchart/enabled'
@@ -55,15 +83,25 @@ if [[ -z $LOOPS ]]; then
LOOPS=1
fi
echo "Analyzing boot-time for LOOPS=$LOOPS"
+BOOTCHART_TGZ="/tmp/android-bootchart/bootchart.tgz"
START=1
-SLEEP_SEC=30
+SLEEP_SEC=20
for (( l=$START; l<=$LOOPS; l++ )); do
- echo -n "Loop: $l"
+ echo "Loop: $l"
SECONDS=0
- $SCRIPT_DIR/bootanalyze.py -c $CONFIG_YMAL -G 4M -r -b > "$RESULTS_DIR/boot$l.txt"
+ mkdir $RESULTS_DIR/$l
+ $SCRIPT_DIR/bootanalyze.py -c $CONFIG_YMAL -G 4M -r \
+ $ADB_REBOOT_FLAG $BOOTCHART_FLAG $CARWATCHDOG_FLAG \
+ -o "$RESULTS_DIR/$l" 1> "$RESULTS_DIR/$l/boot.txt"
+ if [[ $? -ne 0 ]]; then
+ echo "bootanalyze.py failed"
+ exit 1
+ fi
echo "$SECONDS sec."
- cp /tmp/android-bootchart/bootchart.tgz "$RESULTS_DIR/bootchart$l.tgz"
+ if [ -f "$BOOTCHART_TGZ" ]; then
+ cp $BOOTCHART_TGZ "$RESULTS_DIR/$l/bootchart.tgz"
+ fi
echo "Sleep for $SLEEP_SEC sec."
sleep $SLEEP_SEC
done
diff --git a/boottime_tools/bootanalyze/bugreport_anayze.py b/boottime_tools/bootanalyze/bugreport_anayze.py
index 3ea7552e..07c8b8d8 100644
--- a/boottime_tools/bootanalyze/bugreport_anayze.py
+++ b/boottime_tools/bootanalyze/bugreport_anayze.py
@@ -138,7 +138,7 @@ class Parser:
self.re_log_start = re.compile(LOG_START_PATTERN)
self.re_log_end = re.compile(LOG_END_PATTERN)
self.f = bugreport_file
- cfg = yaml.load(config_file, Loader=yaml.FullLoader)
+ cfg = yaml.load(config_file, Loader=yaml.SafeLoader)
self.event_patterns = {key: re.compile(pattern)
for key, pattern in cfg['events'].iteritems()}
self.timing_patterns = {key: re.compile(pattern)
diff --git a/boottime_tools/bootanalyze/config.yaml b/boottime_tools/bootanalyze/config.yaml
index 83c1bcd8..a41cfadd 100644
--- a/boottime_tools/bootanalyze/config.yaml
+++ b/boottime_tools/bootanalyze/config.yaml
@@ -63,6 +63,7 @@ events:
BootComplete_kernel: processing action \(sys\.boot_completed=1\)
LauncherStart: START.*HOME.*(NexusLauncherActivity|GEL|LensPickerTrampolineActivity|SetupWizard|CarLauncher|launcher.*Launcher)
FsStat: fs_stat, partition:userdata stat:(0x\S+)
+ CarWatchdogBootupProfilingComplete: Switching to PERIODIC_COLLECTION and PERIODIC_MONITOR
shutdown_events:
ShutdownStart: ShutdownThread:\sNotifying thread to start shutdown
ShutdownBroadcast: ShutdownThread:\sSending shutdown broadcast
diff --git a/boottime_tools/bootio/bootio_collector.cpp b/boottime_tools/bootio/bootio_collector.cpp
index dc13525e..037a3e2b 100644
--- a/boottime_tools/bootio/bootio_collector.cpp
+++ b/boottime_tools/bootio/bootio_collector.cpp
@@ -66,7 +66,12 @@ void ClearPreviousResults(std::string path) {
int ReadIo(char *filename, AppSample *sample) {
FILE *file;
char line[MAX_LINE];
- unsigned int rchar, wchar, syscr, syscw, readbytes, writebytes;
+ unsigned int rchar = 0;
+ unsigned int wchar = 0;
+ unsigned int syscr = 0;
+ unsigned int syscw = 0;
+ unsigned int readbytes = 0;
+ unsigned int writebytes = 0;
file = fopen(filename, "r");
if (!file) return 1;
@@ -289,17 +294,17 @@ void PrintPids(DataContainer& data, std::unordered_map<int, uint64_t>& cpuDataMa
cpuLoad);
isFirstSample = false;
}
- printf("-----------------------------------------------------------------------------\n");
+ if (!newerSample) {
+ LOG(ERROR) << "newerSample is null";
+ } else {
+ printf("-----------------------------------------------------------------------------"
+ "\n");
#define NUMBER "%-13" PRId64
- printf("%-15s" NUMBER NUMBER NUMBER NUMBER NUMBER NUMBER "\n",
+ printf("%-15s" NUMBER NUMBER NUMBER NUMBER NUMBER NUMBER "\n",
#undef NUMBER
- "Total",
- newerSample->rchar(),
- newerSample->wchar(),
- newerSample->syscr(),
- newerSample->syscw(),
- newerSample->readbytes(),
- newerSample->writebytes());
+ "Total", newerSample->rchar(), newerSample->wchar(), newerSample->syscr(),
+ newerSample->syscw(), newerSample->readbytes(), newerSample->writebytes());
+ }
}
printf("\nAggregations\n%-10s%-13s%-13s%-13s\n",
"Total",
diff --git a/checkpoint_gc/checkpoint_gc.sh b/checkpoint_gc/checkpoint_gc.sh
index b3080463..10514b5f 100644
--- a/checkpoint_gc/checkpoint_gc.sh
+++ b/checkpoint_gc/checkpoint_gc.sh
@@ -28,6 +28,15 @@ SLEEP=5
TIME=0
MAX_TIME=1200
+# GC_URGENT_MID, will fall back to GC_URGENT_HIGH if unsupported
+GC_TYPE=3
+
+# If we fall back, start off with less impactful GC
+# To avoid long wait time, ramp up over time
+GC_SLEEP_MAX=150
+GC_SLEEP_MIN=50
+GC_SLEEP_STEP=5
+
# We only need to run this if we're using f2fs
if [ ! -f /dev/sys/fs/by-name/userdata/gc_urgent ]; then
exit 0
@@ -47,13 +56,31 @@ else
fi
log -pi -t checkpoint_gc Turning on GC for userdata
-echo 2 > /dev/sys/fs/by-name/userdata/gc_urgent || exit 1
+
+read OLD_SLEEP < /dev/sys/fs/by-name/userdata/gc_urgent_sleep_time || \
+ { log -pw -t checkpoint_gc Cannot read gc_urgent_sleep_time; exit 1; }
+GC_SLEEP=${GC_SLEEP_MAX}
+echo ${GC_SLEEP} > /dev/sys/fs/by-name/userdata/gc_urgent_sleep_time || \
+ { log -pw -t checkpoint_gc Cannot set gc_urgent_sleep_time; exit 1; }
+
+
+echo ${GC_TYPE} > /dev/sys/fs/by-name/userdata/gc_urgent \
+ || { GC_TYPE=1; log -pi -t checkpoint_gc GC_URGENT_MID not supported, using GC_URGENT_HIGH; }
+
+if [ ${GC_TYPE} -eq 1 ]; then
+ echo ${GC_TYPE} > /dev/sys/fs/by-name/userdata/gc_urgent || \
+ { echo ${OLD_SLEEP} > /dev/sys/fs/by-name/userdata/gc_urgent_sleep_time; \
+ log -pw -t checkpoint_gc Failed to set gc_urgent; exit 1; }
+else
+ # GC MID will wait for background I/O, so no need to start small
+ GC_SLEEP=${GC_SLEEP_MIN}
+fi
CURRENT=${START}
TODO=$((${START}-${THRESHOLD}))
while [ ${CURRENT} -gt ${THRESHOLD} ]; do
- log -pi -t checkpoint_gc ${METRIC}:${CURRENT} \(threshold:${THRESHOLD}\)
+ log -pi -t checkpoint_gc ${METRIC}:${CURRENT} \(threshold:${THRESHOLD}\) mode:${GC_TYPE} GC_SLEEP:${GC_SLEEP}
PROGRESS=`echo "(${START}-${CURRENT})/${TODO}"|bc -l`
if [[ $PROGRESS == -* ]]; then
PROGRESS=0
@@ -67,17 +94,22 @@ while [ ${CURRENT} -gt ${THRESHOLD} ]; do
sleep ${SLEEP}
TIME=$((${TIME}+${SLEEP}))
if [ ${TIME} -gt ${MAX_TIME} ]; then
- log -pi -t checkpoint_gc Timed out with gc threshold not met.
+ log -pw -t checkpoint_gc Timed out with gc threshold not met.
break
fi
+ if [ ${GC_SLEEP} -gt ${GC_SLEEP_MIN} ]; then
+ GC_SLEEP=$((${GC_SLEEP}-${GC_SLEEP_STEP}))
+ fi
# In case someone turns it off behind our back
- echo 2 > /dev/sys/fs/by-name/userdata/gc_urgent
+ echo ${GC_SLEEP} > /dev/sys/fs/by-name/userdata/gc_urgent_sleep_time
+ echo ${GC_TYPE} > /dev/sys/fs/by-name/userdata/gc_urgent
done
# It could be a while before the system reboots for the update...
# Leaving on low level GC can help ensure the boot for ota is faster
# If powerhints decides to turn it off, we'll just rely on normal GC
-log -pi -t checkpoint_gc Leaving on low GC for userdata
+log -pi -t checkpoint_gc Leaving on GC_URGENT_LOW for userdata
+echo ${OLD_SLEEP} > /dev/sys/fs/by-name/userdata/gc_urgent_sleep_time
echo 2 > /dev/sys/fs/by-name/userdata/gc_urgent
sync
diff --git a/cppreopts/fstab.postinstall b/cppreopts/fstab.postinstall
index 81272ebb..6802bb37 100644
--- a/cppreopts/fstab.postinstall
+++ b/cppreopts/fstab.postinstall
@@ -18,4 +18,9 @@
#<src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
system /postinstall ext4 ro,nosuid,nodev,noexec slotselect_other,logical
+system /postinstall erofs ro,nosuid,nodev,noexec slotselect_other,logical
/dev/block/by-name/system /postinstall ext4 ro,nosuid,nodev,noexec slotselect_other
+/dev/block/by-name/system /postinstall erofs ro,nosuid,nodev,noexec slotselect_other
+/dev/block/mapper/system /postinstall ext4 ro,nosuid,nodev,noexec slotselect_other
+/dev/block/mapper/system /postinstall erofs ro,nosuid,nodev,noexec slotselect_other
+
diff --git a/cpu_loads/Android.bp b/cpu_loads/Android.bp
new file mode 100644
index 00000000..f61aa324
--- /dev/null
+++ b/cpu_loads/Android.bp
@@ -0,0 +1,116 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+ name: "memcpy",
+ srcs: ["memcpy.cpp"],
+ cppflags: [
+ "-g",
+ "-O0",
+ "-Wall",
+ "-Werror",
+ "-Wno-missing-field-initializers",
+ "-Wno-sign-compare",
+ ],
+}
+
+cc_binary {
+ name: "memcpy-16kb",
+ srcs: ["memcpy-16kb.cpp"],
+ cppflags: [
+ "-g",
+ "-O0",
+ "-Wall",
+ "-Werror",
+ "-Wno-missing-field-initializers",
+ "-Wno-sign-compare",
+ ],
+}
+
+cc_binary {
+ name: "memcpy-2048kb",
+ srcs: ["memcpy-2048kb.cpp"],
+ cppflags: [
+ "-g",
+ "-O0",
+ "-Wall",
+ "-Werror",
+ "-Wno-missing-field-initializers",
+ "-Wno-sign-compare",
+ ],
+}
+
+cc_binary {
+ name: "memcpy-byte",
+ srcs: ["memcpy-byte.cpp"],
+ cppflags: [
+ "-g",
+ "-O0",
+ "-Wall",
+ "-Werror",
+ "-Wno-missing-field-initializers",
+ "-Wno-sign-compare",
+ ],
+}
+
+cc_binary {
+ name: "while-true",
+ srcs: ["while-true.cpp"],
+ cppflags: [
+ "-g",
+ "-O0",
+ "-Wall",
+ "-Werror",
+ "-Wno-missing-field-initializers",
+ "-Wno-sign-compare",
+ ],
+}
+
+cc_binary {
+ name: "simd",
+ srcs: ["simd.cpp"],
+ header_libs: [
+ "libeigen",
+ ],
+ cppflags: [
+ "-O2",
+ "-Wall",
+ "-Werror",
+ "-Wno-missing-field-initializers",
+ "-Wno-sign-compare",
+ ],
+}
+
+cc_binary {
+ name: "pss",
+ srcs: ["pss.cpp"],
+ shared_libs: [
+ "libmeminfo",
+ "libbase",
+ ],
+ cppflags: [
+ "-g",
+ "-O0",
+ "-Wall",
+ "-Werror",
+ "-Wno-missing-field-initializers",
+ "-Wno-sign-compare",
+ ],
+}
diff --git a/cpu_loads/README.md b/cpu_loads/README.md
new file mode 100644
index 00000000..7f223795
--- /dev/null
+++ b/cpu_loads/README.md
@@ -0,0 +1,46 @@
+# CPU test loads
+
+These are a collection of simple workloads designed to induce various levels of power consumption on the CPU and memory subsystems of an SOC. All of these workloads run in an infinite loop and are designed to be measured across a fixed duration. They are not benchmarks and provide no information about the performance of various cores; they are only designed to generate different amounts of load for the purposes of measuring power consumption.
+
+## Workloads
+
+- `simd` is a large double-precision matrix multiplication using Eigen
+- `memcpy` copies a 1GB buffer to a second 1GB buffer using bionic `memcpy`
+- `memcpy-16kb` copies a 16KB buffer to a second 16KB buffer using bionic `memcpy`
+- `memcpy-2048kb` copies a 2048KB buffer to a second 2048KB buffer using bionic `memcpy`
+- `memcpy-byte` copies a 1GB buffer to a second 1GB buffer using byte assignment
+- `while-true` stalls at a `while (true);`, which becomes an unconditional branch to the same instruction
+- `pss` allocates a 1GB buffer and repeatedly measures the process's PSS
+
+## Usage
+
+1. Build the tests for a given device with `mm`.
+2. Push the tests to the device; usually this is something like
+
+```
+adb push out/target/product/<device target>/system/bin/simd /data/local/tmp
+```
+
+3. Prepare the device to run the test. This usually means stopping the framework, locking a sustainable CPU frequency, and moving the shell to a cpuset containing only a single core. For example:
+
+```
+stop
+mkdir /dev/cpuset/cpu7
+echo 0 > /dev/cpuset/cpu7/mems
+echo 7 > /dev/cpuset/cpu7/cpus
+echo $$ > /dev/cpuset/cpu7/cgroup.procs
+
+cat /sys/devices/system/cpu/cpu7/cpufreq/scaling_available_frequencies
+# 500000 851000 984000 1106000 1277000 1426000 1582000 1745000 1826000 2048000 2188000 2252000 2401000 2507000 2630000 2704000 2802000 2850000
+echo 1826000 > /sys/devices/system/cpu/cpu7/cpufreq/scaling_min_freq
+echo 1826000 > /sys/devices/system/cpu/cpu7/cpufreq/scaling_max_freq
+```
+
+4. Run the tests on the device; there are no arguments.
+5. Measure power somehow. On a device with ODPM capabilities, this could be something like
+
+```
+dumpsys android.hardware.power.stats.IPowerStats/default | tail -27 && sleep 120 && killall memcpy-2048kb && echo "done" && dumpsys android.hardware.power.stats.IPowerStats/default | tail -27
+```
+
+from a separate `adb shell` to the shell running the test. Alternately, a breakout board with per-rail measurements or a separate battery monitor could be used.
diff --git a/cpu_loads/memcpy-16kb.cpp b/cpu_loads/memcpy-16kb.cpp
new file mode 100644
index 00000000..2b082b5c
--- /dev/null
+++ b/cpu_loads/memcpy-16kb.cpp
@@ -0,0 +1,32 @@
+#include <arpa/inet.h>
+#include <cutils/sockets.h>
+#include <fcntl.h>
+#include <hardware/gralloc.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <algorithm>
+#include <chrono>
+#include <fstream>
+#include <iostream>
+#include <numeric>
+#include <string>
+#include <tuple>
+#include <vector>
+
+using namespace std;
+
+#define BUFFER_SIZE (16 * 1024)
+
+int main(int, char**) {
+ void* src = malloc(BUFFER_SIZE);
+ for (size_t i = 0; i < BUFFER_SIZE; i++) {
+ ((char*)src)[i] = (char)i;
+ }
+ void* dst = malloc(BUFFER_SIZE);
+ while (true) {
+ memcpy(dst, src, BUFFER_SIZE);
+ }
+ ((char*)dst)[0] = 0;
+ return 0;
+}
diff --git a/cpu_loads/memcpy-2048kb.cpp b/cpu_loads/memcpy-2048kb.cpp
new file mode 100644
index 00000000..6954d313
--- /dev/null
+++ b/cpu_loads/memcpy-2048kb.cpp
@@ -0,0 +1,32 @@
+#include <arpa/inet.h>
+#include <cutils/sockets.h>
+#include <fcntl.h>
+#include <hardware/gralloc.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <algorithm>
+#include <chrono>
+#include <fstream>
+#include <iostream>
+#include <numeric>
+#include <string>
+#include <tuple>
+#include <vector>
+
+using namespace std;
+
+#define BUFFER_SIZE (2 * 1024 * 1024)
+
+int main(int, char**) {
+ void* src = malloc(BUFFER_SIZE);
+ for (size_t i = 0; i < BUFFER_SIZE; i++) {
+ ((char*)src)[i] = (char)i;
+ }
+ void* dst = malloc(BUFFER_SIZE);
+ while (true) {
+ memcpy(dst, src, BUFFER_SIZE);
+ }
+ ((char*)dst)[0] = 0;
+ return 0;
+}
diff --git a/cpu_loads/memcpy-byte.cpp b/cpu_loads/memcpy-byte.cpp
new file mode 100644
index 00000000..70293cb6
--- /dev/null
+++ b/cpu_loads/memcpy-byte.cpp
@@ -0,0 +1,33 @@
+#include <arpa/inet.h>
+#include <cutils/sockets.h>
+#include <fcntl.h>
+#include <hardware/gralloc.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <algorithm>
+#include <chrono>
+#include <fstream>
+#include <iostream>
+#include <numeric>
+#include <string>
+#include <tuple>
+#include <vector>
+
+using namespace std;
+
+#define BUFFER_SIZE (1024 * 1024 * 1024)
+
+int main(int, char**) {
+ volatile void* src = malloc(BUFFER_SIZE);
+ for (size_t i = 0; i < BUFFER_SIZE; i++) {
+ ((char*)src)[i] = (char)i;
+ }
+ volatile void* dst = malloc(BUFFER_SIZE);
+ while (true) {
+ for (size_t i = 0; i < BUFFER_SIZE; i++) {
+ ((char*)dst)[i] = ((char*)src)[i];
+ }
+ }
+ return 0;
+}
diff --git a/cpu_loads/memcpy.cpp b/cpu_loads/memcpy.cpp
new file mode 100644
index 00000000..38027a27
--- /dev/null
+++ b/cpu_loads/memcpy.cpp
@@ -0,0 +1,32 @@
+#include <arpa/inet.h>
+#include <cutils/sockets.h>
+#include <fcntl.h>
+#include <hardware/gralloc.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <algorithm>
+#include <chrono>
+#include <fstream>
+#include <iostream>
+#include <numeric>
+#include <string>
+#include <tuple>
+#include <vector>
+
+using namespace std;
+
+#define BUFFER_SIZE (1024 * 1024 * 1024)
+
+int main(int, char**) {
+ void* src = malloc(BUFFER_SIZE);
+ for (size_t i = 0; i < BUFFER_SIZE; i++) {
+ ((char*)src)[i] = (char)i;
+ }
+ void* dst = malloc(BUFFER_SIZE);
+ while (true) {
+ memcpy(dst, src, BUFFER_SIZE);
+ }
+ ((char*)dst)[0] = 0;
+ return 0;
+}
diff --git a/cpu_loads/pss.cpp b/cpu_loads/pss.cpp
new file mode 100644
index 00000000..1cc8145e
--- /dev/null
+++ b/cpu_loads/pss.cpp
@@ -0,0 +1,40 @@
+#include <arpa/inet.h>
+#include <cutils/sockets.h>
+#include <fcntl.h>
+#include <hardware/gralloc.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <algorithm>
+#include <chrono>
+#include <fstream>
+#include <iostream>
+#include <numeric>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include <android-base/stringprintf.h>
+#include <meminfo/procmeminfo.h>
+
+using namespace std;
+
+#define BUFFER_SIZE (1024 * 1024 * 1024)
+
+int main(int, char**) {
+ // waste a bunch of memory
+ void* src = malloc(BUFFER_SIZE);
+ for (size_t i = 0; i < BUFFER_SIZE; i++) {
+ ((char*)src)[i] = (char)i;
+ }
+ void* dst = malloc(BUFFER_SIZE);
+ memcpy(dst, src, BUFFER_SIZE);
+
+ uint64_t pss;
+ // should always return true
+ std::string pid_path = android::base::StringPrintf("/proc/%d/smaps", getpid());
+ while (android::meminfo::SmapsOrRollupPssFromFile(pid_path, &pss))
+ ;
+
+ return 0;
+}
diff --git a/cpu_loads/simd.cpp b/cpu_loads/simd.cpp
new file mode 100644
index 00000000..3add21fb
--- /dev/null
+++ b/cpu_loads/simd.cpp
@@ -0,0 +1,44 @@
+#include <arpa/inet.h>
+#include <cutils/sockets.h>
+#include <fcntl.h>
+#include <hardware/gralloc.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <algorithm>
+#include <chrono>
+#include <fstream>
+#include <iostream>
+#include <numeric>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#define EIGEN_RUNTIME_NO_MALLOC
+
+#include <Eigen/Dense>
+
+using namespace std;
+
+int main(int, char**) {
+ Eigen::MatrixXd a(8192, 8192);
+ Eigen::MatrixXd b(8192, 8192);
+ Eigen::MatrixXd c(8192, 8192);
+
+ for (int i = 0; i < 8192; i++) {
+ for (int j = 0; j < 8192; j++) {
+ a(i, j) = 1 + i * j;
+ b(i, j) = 2 + i * j;
+ c(i, j) = 3 + i * j;
+ }
+ }
+
+ cout << "starting" << endl;
+ while (true) {
+ a.noalias() += (b * c);
+ b(1, 5) += 5.0;
+ c(5, 1) -= 5.0;
+ }
+
+ return 0;
+}
diff --git a/cpu_loads/while-true.cpp b/cpu_loads/while-true.cpp
new file mode 100644
index 00000000..ac795c4a
--- /dev/null
+++ b/cpu_loads/while-true.cpp
@@ -0,0 +1,33 @@
+#include <arpa/inet.h>
+#include <cutils/sockets.h>
+#include <fcntl.h>
+#include <hardware/gralloc.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <algorithm>
+#include <chrono>
+#include <fstream>
+#include <iostream>
+#include <numeric>
+#include <string>
+#include <tuple>
+#include <vector>
+
+using namespace std;
+
+#define BUFFER_SIZE (1024 * 1024 * 1024)
+
+int main(int, char**) {
+ volatile void* src = malloc(BUFFER_SIZE);
+ for (size_t i = 0; i < BUFFER_SIZE; i++) {
+ ((char*)src)[i] = (char)i;
+ }
+ volatile void* dst = malloc(BUFFER_SIZE);
+
+ while (true)
+ ;
+
+ ((char*)dst)[0] = 0;
+ return 0;
+}
diff --git a/ext4_utils/Android.bp b/ext4_utils/Android.bp
index ba2c8ac0..b28e84f3 100644
--- a/ext4_utils/Android.bp
+++ b/ext4_utils/Android.bp
@@ -32,6 +32,7 @@ cc_library {
cflags: [
"-Werror",
"-fno-strict-aliasing",
+ "-D_FILE_OFFSET_BITS=64",
],
export_include_dirs: ["include"],
shared_libs: [
diff --git a/ext4_utils/Android.mk b/ext4_utils/Android.mk
deleted file mode 100644
index b55613fb..00000000
--- a/ext4_utils/Android.mk
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright 2010 The Android Open Source Project
-
-LOCAL_PATH:= $(call my-dir)
-
-#
-# -- All host/targets excluding windows
-#
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := mke2fs.conf
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/NOTICE
-LOCAL_SRC_FILES := $(LOCAL_MODULE)
-LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_IS_HOST_MODULE := true
-include $(BUILD_PREBUILT)
diff --git a/ext4_utils/ext4_utils.cpp b/ext4_utils/ext4_utils.cpp
index 632d8292..dde75903 100644
--- a/ext4_utils/ext4_utils.cpp
+++ b/ext4_utils/ext4_utils.cpp
@@ -83,12 +83,9 @@ int ext4_bg_has_super_block(int bg) {
/* Function to read the primary superblock */
void read_sb(int fd, struct ext4_super_block* sb) {
- off64_t ret;
+ if (lseek(fd, 1024, SEEK_SET) < 0) critical_error_errno("failed to seek to superblock");
- ret = lseek64(fd, 1024, SEEK_SET);
- if (ret < 0) critical_error_errno("failed to seek to superblock");
-
- ret = read(fd, sb, sizeof(*sb));
+ ssize_t ret = read(fd, sb, sizeof(*sb));
if (ret < 0) critical_error_errno("failed to read superblock");
if (ret != sizeof(*sb)) critical_error("failed to read all of superblock");
}
@@ -144,7 +141,8 @@ void ext4_create_fs_aux_info() {
aux_info.sb = aux_info.sb_block;
/* Alloc an array to hold the pointers to the backup superblocks */
- aux_info.backup_sb = (struct ext4_super_block**)calloc(aux_info.groups, sizeof(char*));
+ aux_info.backup_sb =
+ (struct ext4_super_block**)calloc(aux_info.groups, sizeof(struct ext4_super_block*));
if (!aux_info.sb) critical_error_errno("calloc");
@@ -276,17 +274,17 @@ static void read_block_group_descriptors(int fd) {
}
int read_ext(int fd, int verbose) {
- off64_t ret;
+ off_t ret;
struct ext4_super_block sb;
read_sb(fd, &sb);
ext4_parse_sb_info(&sb);
- ret = lseek64(fd, info.len, SEEK_SET);
+ ret = lseek(fd, info.len, SEEK_SET);
if (ret < 0) critical_error_errno("failed to seek to end of input image");
- ret = lseek64(fd, info.block_size * (aux_info.first_data_block + 1), SEEK_SET);
+ ret = lseek(fd, info.block_size * (aux_info.first_data_block + 1), SEEK_SET);
if (ret < 0) critical_error_errno("failed to seek to block group descriptors");
read_block_group_descriptors(fd);
diff --git a/ext4_utils/include/ext4_utils/ext4.h b/ext4_utils/include/ext4_utils/ext4.h
index 54e5d86a..df1ba579 100644
--- a/ext4_utils/include/ext4_utils/ext4.h
+++ b/ext4_utils/include/ext4_utils/ext4.h
@@ -526,6 +526,7 @@ struct ext4_super_block {
#define EXT4_FEATURE_COMPAT_EXT_ATTR 0x0008
#define EXT4_FEATURE_COMPAT_RESIZE_INODE 0x0010
#define EXT4_FEATURE_COMPAT_DIR_INDEX 0x0020
+#define EXT4_FEATURE_COMPAT_STABLE_INODES 0x0800
#define EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001
#define EXT4_FEATURE_RO_COMPAT_LARGE_FILE 0x0002
diff --git a/ext4_utils/include/ext4_utils/ext4_utils.h b/ext4_utils/include/ext4_utils/ext4_utils.h
index 48f3ee78..d6bef68d 100644
--- a/ext4_utils/include/ext4_utils/ext4_utils.h
+++ b/ext4_utils/include/ext4_utils/ext4_utils.h
@@ -21,11 +21,6 @@
extern "C" {
#endif
-#ifndef _GNU_SOURCE
-#define _GNU_SOURCE
-#endif
-#define _FILE_OFFSET_BITS 64
-#define _LARGEFILE64_SOURCE 1
#include <sys/types.h>
#include <unistd.h>
@@ -38,13 +33,6 @@ extern "C" {
#include <string.h>
#include <sys/types.h>
-#if defined(__APPLE__) && defined(__MACH__)
-#define lseek64 lseek
-#define ftruncate64 ftruncate
-#define mmap64 mmap
-#define off64_t off_t
-#endif
-
#include "ext4_sb.h"
extern int force;
diff --git a/f2fs_utils/Android.bp b/f2fs_utils/Android.bp
index dc74d6d3..e27e28ca 100644
--- a/f2fs_utils/Android.bp
+++ b/f2fs_utils/Android.bp
@@ -30,6 +30,7 @@ cc_library_shared {
include_dirs: [
"external/f2fs-tools/include",
+ "bionic/libc",
],
export_include_dirs: ["."],
@@ -48,16 +49,15 @@ cc_binary {
include_dirs: [
"external/f2fs-tools/include",
+ "bionic/libc",
],
}
-sh_binary {
- name: "mkf2fsuserimg.sh",
-
+sh_binary_host {
+ name: "mkf2fsuserimg",
src: "mkf2fsuserimg.sh",
required: [
"make_f2fs",
"sload_f2fs",
],
- host_supported: true,
}
diff --git a/f2fs_utils/f2fs_sparseblock.c b/f2fs_utils/f2fs_sparseblock.c
index df59d7c4..234ea7e8 100644
--- a/f2fs_utils/f2fs_sparseblock.c
+++ b/f2fs_utils/f2fs_sparseblock.c
@@ -26,7 +26,7 @@
#define D_DISP_u64(ptr, member) \
do { \
SLOGV("%-30s" \
- "\t\t[0x%#016llx : %llu]\n", \
+ "\t\t[0x%#016" PRIx64 " : %" PRIu64 "]\n", \
#member, le64_to_cpu((ptr)->member), le64_to_cpu((ptr)->member)); \
} while (0);
diff --git a/f2fs_utils/mkf2fsuserimg.sh b/f2fs_utils/mkf2fsuserimg.sh
index 59f9eea5..e95a0c51 100755
--- a/f2fs_utils/mkf2fsuserimg.sh
+++ b/f2fs_utils/mkf2fsuserimg.sh
@@ -163,13 +163,14 @@ function _build()
SLOAD_F2FS_CMD="sload_f2fs $SLOAD_OPTS $OUTPUT_FILE"
echo $SLOAD_F2FS_CMD
- MB_SIZE=`$SLOAD_F2FS_CMD | grep "Max image size" | awk '{print $5}'`
+ SLOAD_LOG=`$SLOAD_F2FS_CMD`
# allow 1: Filesystem errors corrected
ret=$?
if [ $ret -ne 0 ] && [ $ret -ne 1 ]; then
rm -f $OUTPUT_FILE
exit 4
fi
+ MB_SIZE=`echo "$SLOAD_LOG" | grep "Max image size" | awk '{print $5}'`
SIZE=$(((MB_SIZE + 6) * 1024 * 1024))
}
diff --git a/ioblame/androidFsParser.py b/ioblame/androidFsParser.py
index 6ea38dc3..c47b6f1d 100644
--- a/ioblame/androidFsParser.py
+++ b/ioblame/androidFsParser.py
@@ -14,16 +14,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-"""Trace parser for android_fs traces."""
+"""Trace parser for f2fs traces."""
import collections
import re
-# ex) bt_stack_manage-21277 [000] .... 5879.043608: android_fs_datawrite_start: entry_name /misc/bluedroid/bt_config.bak.new, offset 0, bytes 408, cmdline bt_stack_manage, pid 21277, i_size 0, ino 9103
-RE_WRITE_START = r".+-([0-9]+).*\s+([0-9]+\.[0-9]+):\s+android_fs_datawrite_start:\sentry_name\s(\S+)\,\soffset\s([0-9]+)\,\sbytes\s([0-9]+)\,\scmdline\s(\S+)\,\spid\s([0-9]+)\,\si_size\s([0-9]+)\,\sino\s([0-9]+)"
+# ex) bt_stack_manage-21277 [000] .... 5879.043608: f2fs_datawrite_start: entry_name /misc/bluedroid/bt_config.bak.new, offset 0, bytes 408, cmdline bt_stack_manage, pid 21277, i_size 0, ino 9103
+RE_WRITE_START = r".+-([0-9]+).*\s+([0-9]+\.[0-9]+):\s+f2fs_datawrite_start:\sentry_name\s(\S+)\,\soffset\s([0-9]+)\,\sbytes\s([0-9]+)\,\scmdline\s(\S+)\,\spid\s([0-9]+)\,\si_size\s([0-9]+)\,\sino\s([0-9]+)"
-# ex) dumpsys-21321 [001] .... 5877.599324: android_fs_dataread_start: entry_name /system/lib64/libbinder.so, offset 311296, bytes 4096, cmdline dumpsys, pid 21321, i_size 848848, ino 2397
-RE_READ_START = r".+-([0-9]+).*\s+([0-9]+\.[0-9]+):\s+android_fs_dataread_start:\sentry_name\s(\S+)\,\soffset\s([0-9]+)\,\sbytes\s([0-9]+)\,\scmdline\s(\S+)\,\spid\s([0-9]+)\,\si_size\s([0-9]+)\,\sino\s([0-9]+)"
+# ex) dumpsys-21321 [001] .... 5877.599324: f2fs_dataread_start: entry_name /system/lib64/libbinder.so, offset 311296, bytes 4096, cmdline dumpsys, pid 21321, i_size 848848, ino 2397
+RE_READ_START = r".+-([0-9]+).*\s+([0-9]+\.[0-9]+):\s+f2fs_dataread_start:\sentry_name\s(\S+)\,\soffset\s([0-9]+)\,\sbytes\s([0-9]+)\,\scmdline\s(\S+)\,\spid\s([0-9]+)\,\si_size\s([0-9]+)\,\sino\s([0-9]+)"
MIN_PID_BYTES = 1024 * 1024 # 1 MiB
SMALL_FILE_BYTES = 1024 # 1 KiB
diff --git a/ioblame/ioblame.py b/ioblame/ioblame.py
index e00a5ecc..d69dda4e 100644
--- a/ioblame/ioblame.py
+++ b/ioblame/ioblame.py
@@ -60,7 +60,7 @@ def signal_handler(sig, frame):
def init_arguments():
parser = argparse.ArgumentParser(
- description="Collect and process android_fs traces")
+ description="Collect and process f2fs traces")
parser.add_argument(
"-s",
"--serial",
@@ -73,14 +73,14 @@ def init_arguments():
default=False,
action="store_true",
dest="traceReads",
- help="Trace android_fs_dataread_start")
+ help="Trace f2fs_dataread_start")
parser.add_argument(
"-w",
"--trace_writes",
default=False,
action="store_true",
dest="traceWrites",
- help="Trace android_fs_datawrite_start")
+ help="Trace f2fs_datawrite_start")
parser.add_argument(
"-d",
"--trace_duration",
@@ -187,7 +187,7 @@ def setup_tracepoints(shouldTraceReads, shouldTraceWrites):
# This is a good point to check if the Android FS tracepoints are enabled in the
# kernel or not
isTraceEnabled = run_adb_shell_cmd(
- "'if [ -d /sys/kernel/tracing/events/android_fs ]; then echo 0; else echo 1; fi'"
+ "'if [ -d /sys/kernel/tracing/events/f2fs ]; then echo 0; else echo 1; fi'"
)
if isTraceEnabled == 0:
@@ -200,12 +200,12 @@ def setup_tracepoints(shouldTraceReads, shouldTraceWrites):
if shouldTraceReads:
run_adb_shell_cmd(
- "'echo 1 > /sys/kernel/tracing/events/android_fs/android_fs_dataread_start/enable'"
+ "'echo 1 > /sys/kernel/tracing/events/f2fs/f2fs_dataread_start/enable'"
)
if shouldTraceWrites:
run_adb_shell_cmd(
- "'echo 1 > /sys/kernel/tracing/events/android_fs/android_fs_datawrite_start/enable'"
+ "'echo 1 > /sys/kernel/tracing/events/f2fs/f2fs_datawrite_start/enable'"
)
run_adb_shell_cmd("'echo 1 > /sys/kernel/tracing/tracing_on'")
@@ -214,12 +214,12 @@ def setup_tracepoints(shouldTraceReads, shouldTraceWrites):
def clear_tracing(shouldTraceReads, shouldTraceWrites):
if shouldTraceReads:
run_adb_shell_cmd(
- "'echo 0 > /sys/kernel/tracing/events/android_fs/android_fs_dataread_start/enable'"
+ "'echo 0 > /sys/kernel/tracing/events/f2fs/f2fs_dataread_start/enable'"
)
if shouldTraceWrites:
run_adb_shell_cmd(
- "'echo 0 > /sys/kernel/tracing/events/android_fs/android_fs_datawrite_start/enable'"
+ "'echo 0 > /sys/kernel/tracing/events/f2fs/f2fs_datawrite_start/enable'"
)
run_adb_shell_cmd("'echo 0 > /sys/kernel/tracing/tracing_on'")
@@ -227,7 +227,7 @@ def clear_tracing(shouldTraceReads, shouldTraceWrites):
def start_streaming_trace(traceFile):
return run_bg_adb_shell_cmd(
- "'cat /sys/kernel/tracing/trace_pipe | grep -e android_fs_data -e android_fs_writepages'\
+ "'cat /sys/kernel/tracing/trace_pipe | grep -e f2fs_data -e f2fs_writepages'\
> {}".format(traceFile))
diff --git a/ioblame/ioblame.sh b/ioblame/ioblame.sh
index d6943e92..7b6d6465 100755
--- a/ioblame/ioblame.sh
+++ b/ioblame/ioblame.sh
@@ -157,7 +157,7 @@ disk_stats_delta_wr() {
clean_up_tracepoints() {
# This is a good point to check if the Android FS tracepoints are enabled in the
# kernel or not
- tracepoint_exists=`adb shell 'if [ -d /sys/kernel/debug/tracing/events/android_fs ]; then echo 0; else echo 1; fi' `
+ tracepoint_exists=`adb shell 'if [ -d /sys/kernel/debug/tracing/events/f2fs ]; then echo 0; else echo 1; fi' `
if [ $tracepoint_exists == 1 ]; then
echo "Android FS tracepoints not enabled in kernel. Exiting..."
exit 1
@@ -165,14 +165,14 @@ clean_up_tracepoints() {
adb shell 'echo 0 > /sys/kernel/debug/tracing/tracing_on'
adb shell 'echo 0 > /sys/kernel/debug/tracing/trace'
if [ $trace_reads == true ]; then
- adb shell 'echo 1 > /sys/kernel/debug/tracing/events/android_fs/android_fs_dataread_start/enable'
+ adb shell 'echo 1 > /sys/kernel/debug/tracing/events/f2fs/f2fs_dataread_start/enable'
fi
if [ $trace_writes == true ]; then
- adb shell 'echo 1 > /sys/kernel/debug/tracing/events/android_fs/android_fs_datawrite_start/enable'
+ adb shell 'echo 1 > /sys/kernel/debug/tracing/events/f2fs/f2fs_datawrite_start/enable'
fi
if [ $f2fs_fs == 1 ] ; then
if [ $trace_writepages == true ]; then
- adb shell 'echo 1 > /sys/kernel/debug/tracing/events/android_fs/android_fs_writepages/enable'
+ adb shell 'echo 1 > /sys/kernel/debug/tracing/events/f2fs/f2fs_writepages/enable'
fi
fi
adb shell 'echo 1 > /sys/kernel/debug/tracing/tracing_on'
@@ -197,14 +197,14 @@ streamtrace_end() {
copyout_trace() {
streamtrace_end
if [ $trace_reads == true ]; then
- adb shell 'echo 0 > /sys/kernel/debug/tracing/events/android_fs/android_fs_dataread_start/enable'
+ adb shell 'echo 0 > /sys/kernel/debug/tracing/events/f2fs/f2fs_dataread_start/enable'
fi
if [ $trace_writes == true ]; then
- adb shell 'echo 0 > /sys/kernel/debug/tracing/events/android_fs/android_fs_datawrite_start/enable'
+ adb shell 'echo 0 > /sys/kernel/debug/tracing/events/f2fs/f2fs_datawrite_start/enable'
fi
if [ $f2fs_fs == 1 ] ; then
if [ $trace_writepages == true ]; then
- adb shell 'echo 0 > /sys/kernel/debug/tracing/events/android_fs/android_fs_writepages/enable'
+ adb shell 'echo 0 > /sys/kernel/debug/tracing/events/f2fs/f2fs_writepages/enable'
fi
fi
adb shell 'echo 0 > /sys/kernel/debug/tracing/tracing_on'
@@ -218,11 +218,11 @@ prep_tracefile_common() {
}
prep_tracefile_rd() {
- prep_tracefile_common android_fs_dataread
+ prep_tracefile_common f2fs_dataread
# Strip away unnecessary stuff so we can compute latencies easily
- fgrep android_fs_dataread_start $infile > foo0
- # Throw away everything upto and including android_fs_dataread:
- cat foo0 | sed -n -e 's/^.*android_fs_dataread_start //p' > foo1
+ fgrep f2fs_dataread_start $infile > foo0
+ # Throw away everything upto and including f2fs_dataread:
+ cat foo0 | sed -n -e 's/^.*f2fs_dataread_start //p' > foo1
mv foo1 $infile
# At this stage, $infile should the following format :
# entry_name <filename> offset <offset> bytes <bytes> cmdline <cmdline> pid <pid> i_size <i_size> ino <ino>
@@ -230,9 +230,9 @@ prep_tracefile_rd() {
}
prep_tracefile_writepages() {
- prep_tracefile_common android_fs_writepages
- # Throw away everything up to and including android_fs_writepages_start:
- cat $infile | sed -n -e 's/^.*android_fs_writepages //p' > foo1
+ prep_tracefile_common f2fs_writepages
+ # Throw away everything up to and including f2fs_writepages_start:
+ cat $infile | sed -n -e 's/^.*f2fs_writepages //p' > foo1
mv foo1 $infile
# At this stage, $infile should the following format :
# entry_name <filename> bytes <bytes> ino <ino>
@@ -241,10 +241,10 @@ prep_tracefile_writepages() {
# Latencies not supported for Writes. 'Write End' is just when the data has been
# written back to page cache.
prep_tracefile_wr() {
- prep_tracefile_common android_fs_datawrite
- fgrep android_fs_datawrite_start $infile > foo0
- # Throw away everything upto and including android_fs_datawrite:
- cat foo0 | sed -n -e 's/^.*android_fs_datawrite_start //p' > foo1
+ prep_tracefile_common f2fs_datawrite
+ fgrep f2fs_datawrite_start $infile > foo0
+ # Throw away everything upto and including f2fs_datawrite:
+ cat foo0 | sed -n -e 's/^.*f2fs_datawrite_start //p' > foo1
mv foo1 $infile
# At this stage, $infile should the following format :
# entry_name <filename> offset <offset> bytes <bytes> cmdline <cmdline> pid <pid> i_size <i_size> ino <ino>
diff --git a/ioshark/collect-straces-ftraces.sh b/ioshark/collect-straces-ftraces.sh
index 6e83a248..3590f80a 100644
--- a/ioshark/collect-straces-ftraces.sh
+++ b/ioshark/collect-straces-ftraces.sh
@@ -26,9 +26,9 @@ process_strace()
prep_fstrace()
{
# Remove leading junk
- fgrep android_fs_data $1 | sed 's/^.* \[.*\] //' | sed s/://g | sed s/,//g > foo
+ fgrep f2fs_data $1 | sed 's/^.* \[.*\] //' | sed s/://g | sed s/,//g > foo
# Sanitize the filenames, removing spaces within the filename etc
- sed 's/android_fs_dataread_start/read/' foo > bar1
+ sed 's/f2fs_dataread_start/read/' foo > bar1
mv bar1 bar
# First column is timestamp SECONDS SINCE BOOT
awk '{ print $2, "ftrace", $3, $5, $7, $9, $13 }' bar > foo
@@ -125,13 +125,13 @@ catch_sigint()
enable_tracepoints()
{
- adb shell "echo 1 > /sys/kernel/debug/tracing/events/android_fs/android_fs_dataread_start/enable"
+ adb shell "echo 1 > /sys/kernel/debug/tracing/events/f2fs/f2fs_dataread_start/enable"
adb shell "echo 1 > /sys/kernel/debug/tracing/tracing_on"
}
disable_tracepoints()
{
- adb shell "echo 0 > /sys/kernel/debug/tracing/events/android_fs/android_fs_dataread_start/enable"
+ adb shell "echo 0 > /sys/kernel/debug/tracing/events/f2fs/f2fs_dataread_start/enable"
adb shell "echo 0 > /sys/kernel/debug/tracing/tracing_on"
}
diff --git a/ioshark/compile-only.sh b/ioshark/compile-only.sh
index 7a6b3cd2..ac703440 100644
--- a/ioshark/compile-only.sh
+++ b/ioshark/compile-only.sh
@@ -26,8 +26,8 @@ process_strace()
prep_fstrace()
{
# Remove leading junk
- fgrep android_fs_data $1 | sed 's/^.* \[.*\] //' | sed s/://g | sed s/,//g > foo
- sed 's/android_fs_dataread_start/read/' foo > bar1
+ fgrep f2fs_data $1 | sed 's/^.* \[.*\] //' | sed s/://g | sed s/,//g > foo
+ sed 's/f2fs_dataread_start/read/' foo > bar1
mv bar1 bar
# First column is timestamp SECONDS SINCE BOOT
awk '{ print $2, "ftrace", $3, $5, $7, $9, $13 }' bar > foo
diff --git a/libfec/fec_read.cpp b/libfec/fec_read.cpp
index 889f990d..28a7c898 100644
--- a/libfec/fec_read.cpp
+++ b/libfec/fec_read.cpp
@@ -325,8 +325,12 @@ static ssize_t verity_read(fec_handle *f, uint8_t *dest, size_t count,
/* copy raw data without error correction */
if (!raw_pread(f->fd, data, FEC_BLOCKSIZE, curr_offset)) {
- error("failed to read: %s", strerror(errno));
- return -1;
+ if (errno == EIO) {
+ warn("I/O error encounter when reading, attempting to recover using fec");
+ } else {
+ error("failed to read: %s", strerror(errno));
+ return -1;
+ }
}
if (likely(f->hashtree().check_block_hash_with_index(curr, data))) {
diff --git a/libfscrypt/fscrypt.cpp b/libfscrypt/fscrypt.cpp
index f6e97f11..174cecad 100644
--- a/libfscrypt/fscrypt.cpp
+++ b/libfscrypt/fscrypt.cpp
@@ -61,6 +61,7 @@ static const auto filenames_modes = std::vector<ModeLookupEntry>{
{"aes-256-cts"s, FSCRYPT_MODE_AES_256_CTS},
{"aes-256-heh"s, FSCRYPT_MODE_AES_256_HEH},
{"adiantum"s, FSCRYPT_MODE_ADIANTUM},
+ {"aes-256-hctr2"s, FSCRYPT_MODE_AES_256_HCTR2},
};
static bool LookupModeByName(const std::vector<struct ModeLookupEntry>& modes,
@@ -85,7 +86,8 @@ static bool LookupModeById(const std::vector<struct ModeLookupEntry>& modes, int
return false;
}
-bool fscrypt_is_native() {
+// Returns true if FBE (File Based Encryption) is enabled.
+bool IsFbeEnabled() {
char value[PROPERTY_VALUE_MAX];
property_get("ro.crypto.type", value, "none");
return !strcmp(value, "file");
diff --git a/libfscrypt/include/fscrypt/fscrypt.h b/libfscrypt/include/fscrypt/fscrypt.h
index f3779d00..11f37119 100644
--- a/libfscrypt/include/fscrypt/fscrypt.h
+++ b/libfscrypt/include/fscrypt/fscrypt.h
@@ -19,7 +19,7 @@
#include <string>
-bool fscrypt_is_native();
+bool IsFbeEnabled();
static const char* fscrypt_unencrypted_folder = "/unencrypted";
static const char* fscrypt_key_ref = "/unencrypted/ref";
diff --git a/libfscrypt/tests/fscrypt_test.cpp b/libfscrypt/tests/fscrypt_test.cpp
index 70eb1780..0cd79950 100644
--- a/libfscrypt/tests/fscrypt_test.cpp
+++ b/libfscrypt/tests/fscrypt_test.cpp
@@ -176,6 +176,14 @@ TEST(fscrypt, ParseOptions) {
EXPECT_FALSE(ParseOptionsForApiLevel(30, "aes-256-xts:aes-256-cts:v2:foo", &dummy_options));
EXPECT_FALSE(ParseOptionsForApiLevel(30, "aes-256-xts:aes-256-cts:blah", &dummy_options));
EXPECT_FALSE(ParseOptionsForApiLevel(30, "aes-256-xts:aes-256-cts:vblah", &dummy_options));
+
+ {
+ TEST_STRING(34, ":aes-256-hctr2", "aes-256-xts:aes-256-hctr2:v2");
+ EXPECT_EQ(2, options.version);
+ EXPECT_EQ(FSCRYPT_MODE_AES_256_XTS, options.contents_mode);
+ EXPECT_EQ(FSCRYPT_MODE_AES_256_HCTR2, options.filenames_mode);
+ EXPECT_EQ(FSCRYPT_POLICY_FLAGS_PAD_16, options.flags);
+ }
}
TEST(fscrypt, ComparePolicies) {
diff --git a/libjsonpb/parse/jsonpb.cpp b/libjsonpb/parse/jsonpb.cpp
index 3a042e71..e65a39d1 100644
--- a/libjsonpb/parse/jsonpb.cpp
+++ b/libjsonpb/parse/jsonpb.cpp
@@ -48,7 +48,7 @@ ErrorOr<std::string> MessageToJsonString(const Message& message) {
&json, options);
if (!status.ok()) {
- return MakeError<std::string>(status.error_message().as_string());
+ return MakeError<std::string>(status.message().as_string());
}
return ErrorOr<std::string>(std::move(json));
}
@@ -61,7 +61,7 @@ ErrorOr<std::monostate> JsonStringToMessage(const std::string& content, Message*
std::string binary;
auto status = JsonToBinaryString(resolver.get(), GetTypeUrl(*message), content, &binary);
if (!status.ok()) {
- return MakeError<std::monostate>(status.error_message().as_string());
+ return MakeError<std::monostate>(status.message().as_string());
}
if (!message->ParseFromString(binary)) {
return MakeError<std::monostate>("Fail to parse.");
diff --git a/memory_replay/Alloc.cpp b/memory_replay/Alloc.cpp
index af94ee5d..a6247105 100644
--- a/memory_replay/Alloc.cpp
+++ b/memory_replay/Alloc.cpp
@@ -14,72 +14,15 @@
* 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 "AllocParser.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:
diff --git a/memory_replay/Alloc.h b/memory_replay/Alloc.h
index d590fcba..f4dcc83c 100644
--- a/memory_replay/Alloc.h
+++ b/memory_replay/Alloc.h
@@ -16,34 +16,11 @@
#pragma once
-#include <string>
+#include "AllocParser.h"
// 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/AllocParser.cpp b/memory_replay/AllocParser.cpp
new file mode 100644
index 00000000..ac6664a2
--- /dev/null
+++ b/memory_replay/AllocParser.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <err.h>
+#include <inttypes.h>
+#include <stdio.h>
+
+#include "AllocParser.h"
+
+#include <iostream>
+
+void AllocGetData(const std::string& line, AllocEntry* entry) {
+ int op_prefix_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,
+ &op_prefix_pos) != 3) {
+ errx(1, "File Error: Failed to process %s", line.c_str());
+ }
+ std::string type(name);
+ if (type == "thread_done") {
+ entry->type = THREAD_DONE;
+ } else {
+ int args_offset = 0;
+ const char* args_beg = &line[op_prefix_pos];
+ if (type == "malloc") {
+ // Format:
+ // TID: malloc POINTER SIZE_OF_ALLOCATION
+ if (sscanf(args_beg, "%zu%n", &entry->size, &args_offset) != 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(args_beg, "%" SCNd64 " %zu%n", &entry->u.n_elements, &entry->size,
+ &args_offset) != 2) {
+ errx(1, "File Error: Failed to read calloc data %s", line.c_str());
+ }
+ entry->type = CALLOC;
+ } else if (type == "realloc") {
+ // Format:
+ // TID: realloc POINTER OLD_POINTER NEW_SIZE
+ if (sscanf(args_beg, "%" SCNx64 " %zu%n", &entry->u.old_ptr, &entry->size,
+ &args_offset) != 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(args_beg, "%" SCNd64 " %zu%n", &entry->u.align, &entry->size,
+ &args_offset) != 2) {
+ errx(1, "File Error: Failed to read memalign data %s", line.c_str());
+ }
+ entry->type = MEMALIGN;
+ } else {
+ errx(1, "File Error: Unknown type %s", type.c_str());
+ }
+
+ const char* timestamps_beg = &args_beg[args_offset];
+
+ // Timestamps come after the alloc args if present, for example,
+ // TID: malloc POINTER SIZE_OF_ALLOCATION START_TIME END_TIME
+ int n_match = sscanf(timestamps_beg, "%" SCNd64 " %" SCNd64, &entry->st, &entry->et);
+ if (n_match != EOF && n_match != 2) {
+ errx(1, "File Error: Failed to read timestamps %s", line.c_str());
+ }
+ }
+}
diff --git a/simpleperf/cmd_inject_impl.h b/memory_replay/AllocParser.h
index 455a9d16..e58be489 100644
--- a/simpleperf/cmd_inject_impl.h
+++ b/memory_replay/AllocParser.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,13 +16,31 @@
#pragma once
-#include <string>
-#include <vector>
+#include <sys/types.h>
-namespace simpleperf {
+#include <string>
-std::string BranchToProtoString(const std::vector<bool>& branch);
+enum AllocEnum : uint8_t {
+ MALLOC = 0,
+ CALLOC,
+ MEMALIGN,
+ REALLOC,
+ FREE,
+ THREAD_DONE,
+};
-std::vector<bool> ProtoStringToBranch(const std::string& s, size_t bit_size);
+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;
+ uint64_t st = 0;
+ uint64_t et = 0;
+};
-} // namespace simpleperf \ No newline at end of file
+void AllocGetData(const std::string& line, AllocEntry* entry);
diff --git a/memory_replay/Android.bp b/memory_replay/Android.bp
index 8fb9dbc1..c7de1feb 100644
--- a/memory_replay/Android.bp
+++ b/memory_replay/Android.bp
@@ -44,6 +44,17 @@ cc_defaults {
compile_multilib: "both",
}
+cc_library_static {
+ name: "liballoc_parser",
+ host_supported: true,
+ defaults: ["memory_flag_defaults"],
+
+ export_include_dirs: ["."],
+ srcs: [
+ "AllocParser.cpp",
+ ],
+}
+
cc_defaults {
name: "memory_replay_defaults",
defaults: ["memory_flag_defaults"],
@@ -63,7 +74,7 @@ cc_defaults {
],
static_libs: [
- "libasync_safe",
+ "liballoc_parser",
],
}
@@ -126,6 +137,10 @@ cc_benchmark {
"libziparchive",
],
+ static_libs: [
+ "liballoc_parser",
+ ],
+
data: [
"traces/*.zip",
],
diff --git a/memory_replay/File.cpp b/memory_replay/File.cpp
index 8bcd9041..e44c5007 100644
--- a/memory_replay/File.cpp
+++ b/memory_replay/File.cpp
@@ -29,6 +29,7 @@
#include <ziparchive/zip_archive.h>
#include "Alloc.h"
+#include "AllocParser.h"
#include "File.h"
std::string ZipGetContents(const char* filename) {
diff --git a/memory_replay/NativeInfo.cpp b/memory_replay/NativeInfo.cpp
index 3439a29d..8493b682 100644
--- a/memory_replay/NativeInfo.cpp
+++ b/memory_replay/NativeInfo.cpp
@@ -27,23 +27,12 @@
#include <unistd.h>
#include <android-base/unique_fd.h>
-#include <async_safe/log.h>
#include "NativeInfo.h"
-void NativePrintf(const char* fmt, ...) {
- va_list args;
- va_start(args, fmt);
- char buffer[512];
- int buffer_len = async_safe_format_buffer_va_list(buffer, sizeof(buffer), fmt, args);
- va_end(args);
-
- (void)write(STDOUT_FILENO, buffer, buffer_len);
-}
-
void NativeFormatFloat(char* buffer, size_t buffer_len, uint64_t value, uint64_t divisor) {
uint64_t hundreds = ((((value % divisor) * 1000) / divisor) + 5) / 10;
- async_safe_format_buffer(buffer, buffer_len, "%" PRIu64 ".%02" PRIu64, value / divisor, hundreds);
+ snprintf(buffer, buffer_len, "%" PRIu64 ".%02" PRIu64, value / divisor, hundreds);
}
// This function is not re-entrant since it uses a static buffer for
@@ -114,7 +103,7 @@ void NativePrintInfo(const char* preamble) {
// Avoid any allocations, so use special non-allocating printfs.
char buffer[256];
NativeFormatFloat(buffer, sizeof(buffer), rss_bytes, 1024 * 1024);
- NativePrintf("%sNative RSS: %zu bytes %sMB\n", preamble, rss_bytes, buffer);
+ dprintf(STDOUT_FILENO, "%sNative RSS: %zu bytes %sMB\n", preamble, rss_bytes, buffer);
NativeFormatFloat(buffer, sizeof(buffer), va_bytes, 1024 * 1024);
- NativePrintf("%sNative VA Space: %zu bytes %sMB\n", preamble, va_bytes, buffer);
+ dprintf(STDOUT_FILENO, "%sNative VA Space: %zu bytes %sMB\n", preamble, va_bytes, buffer);
}
diff --git a/memory_replay/NativeInfo.h b/memory_replay/NativeInfo.h
index c91eec29..a33db027 100644
--- a/memory_replay/NativeInfo.h
+++ b/memory_replay/NativeInfo.h
@@ -20,8 +20,5 @@ void NativeGetInfo(int smaps_fd, size_t* rss_bytes, size_t* va_bytes);
void NativePrintInfo(const char* preamble);
-// Does not support any floating point specifiers.
-void NativePrintf(const char* fmt, ...) __printflike(1, 2);
-
// Fill buffer as if %0.2f was chosen for value / divisor.
void NativeFormatFloat(char* buffer, size_t buffer_len, uint64_t value, uint64_t divisor);
diff --git a/memory_replay/main.cpp b/memory_replay/main.cpp
index e610305e..2dd66bb3 100644
--- a/memory_replay/main.cpp
+++ b/memory_replay/main.cpp
@@ -78,15 +78,15 @@ static void ProcessDump(const AllocEntry* entries, size_t num_entries, size_t ma
Pointers pointers(max_allocs);
Threads threads(&pointers, max_threads);
- NativePrintf("Maximum threads available: %zu\n", threads.max_threads());
- NativePrintf("Maximum allocations in dump: %zu\n", max_allocs);
- NativePrintf("Total pointers available: %zu\n\n", pointers.max_pointers());
+ dprintf(STDOUT_FILENO, "Maximum threads available: %zu\n", threads.max_threads());
+ dprintf(STDOUT_FILENO, "Maximum allocations in dump: %zu\n", max_allocs);
+ dprintf(STDOUT_FILENO, "Total pointers available: %zu\n\n", pointers.max_pointers());
NativePrintInfo("Initial ");
for (size_t i = 0; i < num_entries; i++) {
if (((i + 1) % 100000) == 0) {
- NativePrintf(" At line %zu:\n", i + 1);
+ dprintf(STDOUT_FILENO, " At line %zu:\n", i + 1);
NativePrintInfo(" ");
}
const AllocEntry& entry = entries[i];
@@ -139,7 +139,7 @@ static void ProcessDump(const AllocEntry* entries, size_t num_entries, size_t ma
char buffer[256];
uint64_t total_nsecs = threads.total_time_nsecs();
NativeFormatFloat(buffer, sizeof(buffer), total_nsecs, 1000000000);
- NativePrintf("Total Allocation/Free Time: %" PRIu64 "ns %ss\n", total_nsecs, buffer);
+ dprintf(STDOUT_FILENO, "Total Allocation/Free Time: %" PRIu64 "ns %ss\n", total_nsecs, buffer);
}
int main(int argc, char** argv) {
@@ -161,13 +161,13 @@ int main(int argc, char** argv) {
}
#if defined(__LP64__)
- NativePrintf("64 bit environment.\n");
+ dprintf(STDOUT_FILENO, "64 bit environment.\n");
#else
- NativePrintf("32 bit environment.\n");
+ dprintf(STDOUT_FILENO, "32 bit environment.\n");
#endif
#if defined(__BIONIC__)
- NativePrintf("Setting decay time to 1\n");
+ dprintf(STDOUT_FILENO, "Setting decay time to 1\n");
mallopt(M_DECAY_TIME, 1);
#endif
@@ -180,7 +180,7 @@ int main(int argc, char** argv) {
size_t num_entries;
GetUnwindInfo(argv[1], &entries, &num_entries);
- NativePrintf("Processing: %s\n", argv[1]);
+ dprintf(STDOUT_FILENO, "Processing: %s\n", argv[1]);
ProcessDump(entries, num_entries, max_threads);
diff --git a/memtrack/Android.bp b/memtrack/Android.bp
index d04c6ffa..2611029a 100644
--- a/memtrack/Android.bp
+++ b/memtrack/Android.bp
@@ -53,17 +53,8 @@ cc_binary {
static_executable: true,
static_libs: [
- "libc",
"liblog",
- "libc++abi",
- "libdl",
],
stl: "libc++_static",
-
- // Bug: 18389563 - Today, libc++_static and libgcc have duplicate sybols for
- // __aeabi_uidiv(). Allowing multiple definitions lets the build proceed, but
- // updating compiler-rt to be a superset of libgcc will allow this WAR to be
- // removed.
- ldflags: ["-Wl,-z,muldefs"],
}
diff --git a/module_ndk_libs/libnativehelper/Android.bp b/module_ndk_libs/libnativehelper/Android.bp
index 43043738..e5c63261 100644
--- a/module_ndk_libs/libnativehelper/Android.bp
+++ b/module_ndk_libs/libnativehelper/Android.bp
@@ -55,4 +55,8 @@ ndk_library {
name: "libnativehelper",
symbol_file: "libnativehelper.map.txt",
first_version: "S",
+ export_header_libs: [
+ "libnativehelper_ndk_headers",
+ "ndk_jni.h",
+ ],
}
diff --git a/mtectrl/.clang-format b/mtectrl/.clang-format
new file mode 120000
index 00000000..242a033c
--- /dev/null
+++ b/mtectrl/.clang-format
@@ -0,0 +1 @@
+../../core/.clang-format-2 \ No newline at end of file
diff --git a/mtectrl/Android.bp b/mtectrl/Android.bp
index bc84f8cd..423eec27 100644
--- a/mtectrl/Android.bp
+++ b/mtectrl/Android.bp
@@ -25,3 +25,26 @@ cc_binary {
],
init_rc: ["mtectrl.rc"],
}
+
+cc_test {
+ name: "mtectrl_test",
+ srcs: ["mtectrl_test.cc"],
+ test_suites: ["general-tests"],
+ // shell cannot use /system/bin/mtectrl
+ require_root: true,
+ shared_libs: [
+ "libbootloader_message",
+ "libbase",
+ ],
+ static_libs: [
+ "libgmock",
+ ]
+}
+
+java_test_host {
+ name: "mtectrl_end_to_end_test",
+ libs: ["tradefed"],
+ static_libs: ["frameworks-base-hostutils", "cts-install-lib-host"],
+ srcs: ["src/com/android/tests/mtectrl/MtectrlEndToEndTest.java"],
+ test_suites: ["general-tests"],
+}
diff --git a/mtectrl/TEST_MAPPING b/mtectrl/TEST_MAPPING
new file mode 100644
index 00000000..23c2e9db
--- /dev/null
+++ b/mtectrl/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "mtectrl_test"
+ }
+ ],
+ "hwasan-presubmit": [
+ {
+ "name": "mtectrl_test"
+ }
+ ]
+}
diff --git a/mtectrl/mtectrl.cc b/mtectrl/mtectrl.cc
index 0738f9ec..bea12a6c 100644
--- a/mtectrl/mtectrl.cc
+++ b/mtectrl/mtectrl.cc
@@ -14,43 +14,237 @@
* limitations under the License.
*/
+#include <getopt.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
#include <android-base/logging.h>
+#include <android-base/properties.h>
#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
#include <bootloader_message/bootloader_message.h>
+#include <functional>
#include <iostream>
-int main(int argc, char** argv) {
- if (argc != 2) {
- std::cerr
- << "Usage: " << argv[0]
- << " [none|memtag|memtag_once|memtag_kernel|memtag_kernel_once]\n";
- return 1;
+void AddItem(std::string* s, const char* item) {
+ if (!s->empty()) *s += ",";
+ *s += item;
+}
+
+bool CheckAndUnset(uint32_t& mode, uint32_t mask) {
+ bool is_set = mode & mask;
+ mode &= ~mask;
+ return is_set;
+}
+
+bool UpdateProp(const char* prop_name, const misc_memtag_message& m) {
+ uint32_t mode = m.memtag_mode;
+ std::string prop_str;
+ if (CheckAndUnset(mode, MISC_MEMTAG_MODE_MEMTAG)) AddItem(&prop_str, "memtag");
+ if (CheckAndUnset(mode, MISC_MEMTAG_MODE_MEMTAG_ONCE)) AddItem(&prop_str, "memtag-once");
+ if (CheckAndUnset(mode, MISC_MEMTAG_MODE_MEMTAG_KERNEL)) AddItem(&prop_str, "memtag-kernel");
+ if (CheckAndUnset(mode, MISC_MEMTAG_MODE_MEMTAG_KERNEL_ONCE))
+ AddItem(&prop_str, "memtag-kernel-once");
+ if (CheckAndUnset(mode, MISC_MEMTAG_MODE_MEMTAG_OFF)) AddItem(&prop_str, "memtag-off");
+ if (android::base::GetProperty(prop_name, "") != prop_str)
+ android::base::SetProperty(prop_name, prop_str);
+ if (mode) {
+ LOG(ERROR) << "MTE mode in misc message contained unknown bits: " << mode
+ << ". Ignoring and setting " << prop_name << " to " << prop_str;
}
- std::string value = argv[1];
- misc_memtag_message m = {.version = MISC_MEMTAG_MESSAGE_VERSION,
- .magic = MISC_MEMTAG_MAGIC_HEADER};
+ return mode == 0;
+}
+
+void PrintUsage(const char* progname) {
+ std::cerr
+ << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
+ "!!! YOU PROBABLY DO NOT NEED TO USE THIS !!!\n"
+ "!!! USE THE `arm64.memtag.bootctl` SYSTEM PROPERTY INSTEAD. !!!\n"
+ "!!! This program is an implementation detail that is used !!!\n"
+ "!!! by the system to apply MTE settings. !!!\n"
+ "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
+ "\n"
+ << "USAGE: " << progname
+ << "\n"
+ " [-s PROPERTY_NAME]\n"
+ " [none,][memtag,][memtag-once,][memtag-kernel,][memtag-kernel-once,][memtag-off,]\n"
+ " [default|force_on|force_off]\n"
+ " [-t PATH_TO_FAKE_MISC_PARTITION]\n"
+
+ "OPTIONS:\n"
+ " -s PROPERTY_NAME\n"
+ " Sets the system property 'PROPERTY_NAME' to the new MTE mode (if provided), or to\n"
+ " the current value from the /misc partition.\n"
+ " [none,][memtag,][memtag-once,][memtag-kernel,][memtag-kernel-once,][memtag-off,]\n"
+ " A set of MTE options to be applied, if provided. Multiple options may be\n"
+ " specified as a ','-delimited list, e.g. 'memtag,memtag-kernel'.\n"
+ " The options are described below:\n"
+ " - none: default settings for MTE for the product will be applied on next\n"
+ " reboot.\n"
+ " - memtag: MTE is persistently enabled in userspace upon the next reboot.\n"
+ " - memtag-once: MTE is enabled in userspace, only for the next reboot.\n"
+ " - memtag-kernel: MTE is persistently enabled in the kernel upon the next \n"
+ " reboot.\n"
+ " - memtag-kernel-once: MTE is enabled in the kernel, only for the next reboot.\n"
+ " - memtag-off: MTE is persistently disabled in both userspace and kernel upon \n"
+ " the next reboot.\n"
+ " [default|force_on|force_off]\n"
+ " An alternative method of configuring the MTE options to be applied, if provided.\n"
+ " This control is generally to be used by device_config only, and it overwrites\n"
+ " the previously described settings that are expected to be utilized by the user.\n"
+ " The options are described below:\n"
+ " - default: This flag is not overwriting the MTE mode, and so the setting\n"
+ " should be inherited from the userspace controls (if present), or the\n"
+ " default value from the bootloader's ROM.\n"
+ " - force_on: MTE is persistently enabled in userspace, overwriting the userspace\n"
+ " setting.\n"
+ " - force_off: MTE is persistently disabled in userspace and the kernel, \n"
+ " overwriting the userspace setting.\n";
+}
+
+int StringToMode(const char* value) {
+ int memtag_mode = 0;
for (const auto& field : android::base::Split(value, ",")) {
if (field == "memtag") {
- m.memtag_mode |= MISC_MEMTAG_MODE_MEMTAG;
+ memtag_mode |= MISC_MEMTAG_MODE_MEMTAG;
} else if (field == "memtag-once") {
- m.memtag_mode |= MISC_MEMTAG_MODE_MEMTAG_ONCE;
+ memtag_mode |= MISC_MEMTAG_MODE_MEMTAG_ONCE;
} else if (field == "memtag-kernel") {
- m.memtag_mode |= MISC_MEMTAG_MODE_MEMTAG_KERNEL;
+ memtag_mode |= MISC_MEMTAG_MODE_MEMTAG_KERNEL;
} else if (field == "memtag-kernel-once") {
- m.memtag_mode |= MISC_MEMTAG_MODE_MEMTAG_KERNEL_ONCE;
+ memtag_mode |= MISC_MEMTAG_MODE_MEMTAG_KERNEL_ONCE;
+ } else if (field == "memtag-off") {
+ memtag_mode |= MISC_MEMTAG_MODE_MEMTAG_OFF;
} else if (field != "none") {
- LOG(ERROR) << "Unknown value for arm64.memtag.bootctl: " << field;
+ LOG(ERROR) << "Unknown value for mode: " << field;
+ return -1;
+ }
+ }
+ return memtag_mode;
+}
+
+bool HandleOverride(const std::string& override_value, misc_memtag_message* m) {
+ if (override_value == "force_off") {
+ // If the force_off override is active, only allow MEMTAG_MODE_MEMTAG_ONCE.
+ m->memtag_mode |= MISC_MEMTAG_MODE_MEMTAG_OFF;
+ m->memtag_mode &= ~MISC_MEMTAG_MODE_MEMTAG;
+ } else if (override_value == "force_on") {
+ m->memtag_mode |= MISC_MEMTAG_MODE_MEMTAG;
+ m->memtag_mode &= ~MISC_MEMTAG_MODE_MEMTAG_OFF;
+ } else if (!override_value.empty() && override_value != "default") {
+ return false;
+ }
+ return true;
+}
+
+int main(int argc, char** argv) {
+ const char* set_prop = nullptr;
+ int opt;
+ std::function<bool(misc_memtag_message*, std::string*)> read_memtag_message =
+ ReadMiscMemtagMessage;
+ std::function<bool(const misc_memtag_message&, std::string*)> write_memtag_message =
+ WriteMiscMemtagMessage;
+
+ android::base::unique_fd fake_partition_fd;
+ while ((opt = getopt(argc, argv, "s:t:")) != -1) {
+ switch (opt) {
+ case 's':
+ // Set property in argument to state of misc partition. If given by
+ // itself, sets the property to the current state. We do this on device
+ // boot,
+ //
+ // Otherwise, applies new state and then sets property to newly applied
+ // state.
+ set_prop = optarg;
+ break;
+ case 't': {
+ // Use different fake misc partition for testing.
+ const char* filename = optarg;
+ fake_partition_fd.reset(open(filename, O_RDWR | O_CLOEXEC));
+ int raw_fd = fake_partition_fd.get();
+ CHECK_NE(raw_fd, -1);
+ CHECK_NE(ftruncate(raw_fd, sizeof(misc_memtag_message)), -1);
+ read_memtag_message = [raw_fd](misc_memtag_message* m, std::string*) {
+ CHECK(android::base::ReadFully(raw_fd, m, sizeof(*m)));
+ return true;
+ };
+ write_memtag_message = [raw_fd](const misc_memtag_message& m, std::string*) {
+ CHECK(android::base::WriteFully(raw_fd, &m, sizeof(m)));
+ return true;
+ };
+ break;
+ }
+ default:
+ PrintUsage(argv[0]);
+ return 1;
+ }
+ }
+
+ const char* value = optind < argc ? argv[optind++] : nullptr;
+ const char* override_value = optind < argc ? argv[optind++] : nullptr;
+
+ if (optind != argc) { // Unknown argument.
+ PrintUsage(argv[0]);
+ return 1;
+ }
+
+ if (!value && set_prop) {
+ // -s <property> is given on its own. This means we want to read the state
+ // of the misc partition into the property.
+ std::string err;
+ misc_memtag_message m = {};
+ if (!read_memtag_message(&m, &err)) {
+ LOG(ERROR) << "Failed to read memtag message: " << err;
return 1;
}
+ if (m.magic != MISC_MEMTAG_MAGIC_HEADER || m.version != MISC_MEMTAG_MESSAGE_VERSION) {
+ // This should not fail by construction.
+ CHECK(UpdateProp(set_prop, {}));
+ // This is an expected case, as the partition gets initialized to all zero.
+ return 0;
+ }
+ // Unlike above, setting the system property here can fail if the misc partition
+ // was corrupted by another program (e.g. the bootloader).
+ return UpdateProp(set_prop, m) ? 0 : 1;
+ }
+
+ if (!value) {
+ PrintUsage(argv[0]);
+ return 1;
+ }
+
+ misc_memtag_message m = {.version = MISC_MEMTAG_MESSAGE_VERSION,
+ .magic = MISC_MEMTAG_MAGIC_HEADER};
+ int memtag_mode = StringToMode(value);
+ bool valid_value = memtag_mode != -1;
+ m.memtag_mode = valid_value ? memtag_mode : 0;
+
+ bool valid_override = true;
+ if (override_value) valid_override = HandleOverride(override_value, &m);
+
+ if (!valid_value && !valid_override) {
+ return 1;
}
std::string err;
- if (!WriteMiscMemtagMessage(m, &err)) {
- LOG(ERROR) << "Failed to apply arm64.memtag.bootctl: " << value << ". "
- << err;
+ if (!write_memtag_message(m, &err)) {
+ LOG(ERROR) << "Failed to apply mode: " << value << ", override: " << override_value << err;
return 1;
} else {
- LOG(INFO) << "Applied arm64.memtag.bootctl: " << value;
- return 0;
+ const char* parse_error = "";
+ const char* verb = "Applied";
+ if (!valid_value) {
+ parse_error = " (invalid mode)";
+ verb = "Partially applied";
+ } else if (!valid_override) {
+ // else if because we bail out if both are false above.
+ parse_error = " (invalid override)";
+ verb = "Partially applied";
+ }
+ LOG(INFO) << verb << " mode: " << value << ", "
+ << "override: " << (override_value ? override_value : "") << parse_error;
+ // Because all the bits in memtag_mode were set above, this should never fail.
+ if (set_prop) CHECK(UpdateProp(set_prop, m));
+ return !valid_value || !valid_override;
}
}
diff --git a/mtectrl/mtectrl.rc b/mtectrl/mtectrl.rc
index de042376..a474cdcf 100644
--- a/mtectrl/mtectrl.rc
+++ b/mtectrl/mtectrl.rc
@@ -13,4 +13,15 @@
# limitations under the License.
on property:arm64.memtag.bootctl=*
- exec -- /system/bin/mtectrl ${arm64.memtag.bootctl}
+ exec -- /system/bin/mtectrl ${arm64.memtag.bootctl:-none} ${persist.device_config.runtime_native_boot.bootloader_override:-default}
+
+on property:persist.device_config.runtime_native_boot.bootloader_override=*
+ exec -- /system/bin/mtectrl ${arm64.memtag.bootctl:-none} ${persist.device_config.runtime_native_boot.bootloader_override:-default}
+
+# adbd gets initialized in init, so run before that. this makes sure that the
+# user does not change the value before we initialize it
+on early-init && property:ro.arm64.memtag.bootctl_supported=1
+ exec -- /system/bin/mtectrl -s arm64.memtag.bootctl
+
+on shutdown && property:ro.arm64.memtag.bootctl_supported=1
+ exec -- /system/bin/mtectrl ${arm64.memtag.bootctl:-none} ${persist.device_config.runtime_native_boot.bootloader_override:-default}
diff --git a/mtectrl/mtectrl_test.cc b/mtectrl/mtectrl_test.cc
new file mode 100644
index 00000000..5fe77f87
--- /dev/null
+++ b/mtectrl/mtectrl_test.cc
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <bootloader_message/bootloader_message.h>
+
+namespace {
+using ::testing::StartsWith;
+
+int mtectrl(const char* arg) {
+ std::string cmd = "mtectrl -t /data/local/tmp/misc_memtag ";
+ cmd += arg;
+ return system(cmd.c_str());
+}
+
+std::string GetMisc() {
+ std::string data;
+ CHECK(android::base::ReadFileToString("/data/local/tmp/misc_memtag", &data, false));
+ return data;
+}
+
+std::string TestProperty() {
+ return android::base::GetProperty("arm64.memtag.test_bootctl", "");
+}
+} // namespace
+
+class MteCtrlTest : public ::testing::Test {
+ void SetUp() override {
+ // Empty fake misc partition.
+ int fd = creat("/data/local/tmp/misc_memtag", 0600);
+ CHECK(fd != -1);
+ CHECK(ftruncate(fd, sizeof(misc_memtag_message)) != -1);
+ close(fd);
+ android::base::SetProperty("arm64.memtag.test_bootctl", "INVALID");
+ }
+ void TearDown() override {
+ CHECK(unlink("/data/local/tmp/misc_memtag") == 0);
+ }
+};
+
+TEST_F(MteCtrlTest, invalid) {
+ EXPECT_NE(mtectrl("memtag-invalid"), 0);
+ EXPECT_NE(mtectrl("memtag override-invalid"), 0);
+}
+
+TEST_F(MteCtrlTest, set_once) {
+ ASSERT_EQ(mtectrl("memtag-once"), 0);
+ EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x02"));
+}
+
+TEST_F(MteCtrlTest, set_once_kernel) {
+ ASSERT_EQ(mtectrl("memtag-once,memtag-kernel"), 0);
+ EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x06"));
+}
+
+TEST_F(MteCtrlTest, set_memtag) {
+ ASSERT_EQ(mtectrl("memtag"), 0);
+ EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x01"));
+}
+
+TEST_F(MteCtrlTest, set_memtag_force_off) {
+ ASSERT_EQ(mtectrl("memtag force_off"), 0);
+ EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x10"));
+}
+
+TEST_F(MteCtrlTest, read_memtag) {
+ ASSERT_EQ(mtectrl("memtag"), 0);
+ ASSERT_EQ(mtectrl("-s arm64.memtag.test_bootctl"), 0);
+ EXPECT_EQ(TestProperty(), "memtag");
+}
+
+TEST_F(MteCtrlTest, read_invalid_memtag_message) {
+ misc_memtag_message m = {.version = 1, .magic = 0xffff, .memtag_mode = MISC_MEMTAG_MODE_MEMTAG};
+ std::string m_str(reinterpret_cast<char*>(&m), sizeof(m));
+ android::base::WriteStringToFile(m_str, "/data/local/tmp/misc_memtag");
+ ASSERT_EQ(mtectrl("-s arm64.memtag.test_bootctl"), 0);
+ EXPECT_EQ(TestProperty(), "");
+}
+
+TEST_F(MteCtrlTest, read_invalid_memtag_mode) {
+ misc_memtag_message m = {.version = MISC_MEMTAG_MESSAGE_VERSION,
+ .magic = MISC_MEMTAG_MAGIC_HEADER,
+ .memtag_mode = MISC_MEMTAG_MODE_MEMTAG | 1u << 31};
+ std::string m_str(reinterpret_cast<char*>(&m), sizeof(m));
+ android::base::WriteStringToFile(m_str, "/data/local/tmp/misc_memtag");
+ ASSERT_NE(mtectrl("-s arm64.memtag.test_bootctl"), 0);
+ EXPECT_EQ(TestProperty(), "memtag");
+}
+
+TEST_F(MteCtrlTest, set_read_memtag) {
+ ASSERT_EQ(mtectrl("-s arm64.memtag.test_bootctl memtag"), 0);
+ EXPECT_EQ(TestProperty(), "memtag");
+}
+
+TEST_F(MteCtrlTest, set_read_force_off) {
+ ASSERT_EQ(mtectrl("-s arm64.memtag.test_bootctl memtag,memtag-once force_off"), 0);
+ EXPECT_EQ(TestProperty(), "memtag-once,memtag-off");
+}
+
+TEST_F(MteCtrlTest, override) {
+ ASSERT_EQ(mtectrl("memtag"), 0);
+ ASSERT_EQ(mtectrl("memtag-once"), 0);
+ EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x02"));
+}
+
+TEST_F(MteCtrlTest, read_empty) {
+ ASSERT_EQ(mtectrl("-s arm64.memtag.test_bootctl"), 0);
+ EXPECT_EQ(TestProperty(), "");
+}
+
+TEST_F(MteCtrlTest, force_off_invalid_mode) {
+ mtectrl("-s arm64.memtag.test_bootctl memtag-invalid force_off");
+ EXPECT_EQ(TestProperty(), "memtag-off");
+ EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x10"));
+}
+
+TEST_F(MteCtrlTest, force_on_invalid_mode) {
+ mtectrl("-s arm64.memtag.test_bootctl memtag-invalid force_on");
+ EXPECT_EQ(TestProperty(), "memtag");
+ EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x01"));
+}
+
+TEST_F(MteCtrlTest, mode_invalid_override) {
+ mtectrl("-s arm64.memtag.test_bootctl memtag force_invalid");
+ EXPECT_EQ(TestProperty(), "memtag");
+ EXPECT_THAT(GetMisc(), StartsWith("\x01\x5a\xfe\xfe\x5a\x01"));
+}
diff --git a/mtectrl/src/com/android/tests/mtectrl/MtectrlEndToEndTest.java b/mtectrl/src/com/android/tests/mtectrl/MtectrlEndToEndTest.java
new file mode 100644
index 00000000..fd3d4401
--- /dev/null
+++ b/mtectrl/src/com/android/tests/mtectrl/MtectrlEndToEndTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.mtectrl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assume.assumeThat;
+
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+// Test the protocol described in
+// https://source.android.com/docs/security/test/memory-safety/bootloader-support.
+// This will reboot the device multiple times, which is perfectly normal.
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class MtectrlEndToEndTest extends BaseHostJUnit4Test {
+ private static String mPreviousState = null;
+
+ @BeforeClassWithInfo
+ public static void setUp(TestInformation testInfo) throws Exception {
+ assumeThat(
+ testInfo.getDevice().getProperty("ro.arm64.memtag.bootctl_supported"),
+ equalTo("1"));
+ mPreviousState = testInfo.getDevice().getProperty("arm64.memtag.bootctl");
+ if (mPreviousState == null) {
+ mPreviousState = "";
+ }
+ }
+
+ @AfterClassWithInfo
+ public static void tearDown(TestInformation testInfo) throws Exception {
+ if (mPreviousState != null) {
+ testInfo.getDevice().setProperty("arm64.memtag.bootctl", mPreviousState);
+ testInfo.getDevice().reboot();
+ }
+ }
+
+ @Test
+ public void testMemtagOnce() throws Exception {
+ getDevice().setProperty("arm64.memtag.bootctl", "memtag-once");
+ getDevice().reboot();
+ assertThat(getDevice().getProperty("arm64.memtag.bootctl")).isAnyOf("", "none", null);
+ assertThat(getDevice().pullFileContents("/proc/cpuinfo")).contains("mte");
+ getDevice().reboot();
+ assertThat(getDevice().getProperty("arm64.memtag.bootctl")).isAnyOf("", "none", null);
+ assertThat(getDevice().pullFileContents("/proc/cpuinfo")).doesNotContain("mte");
+ }
+
+ @Test
+ public void testMemtag() throws Exception {
+ getDevice().setProperty("arm64.memtag.bootctl", "memtag");
+ getDevice().reboot();
+ assertThat(getDevice().getProperty("arm64.memtag.bootctl")).isEqualTo("memtag");
+ assertThat(getDevice().pullFileContents("/proc/cpuinfo")).contains("mte");
+ getDevice().reboot();
+ assertThat(getDevice().getProperty("arm64.memtag.bootctl")).isEqualTo("memtag");
+ assertThat(getDevice().pullFileContents("/proc/cpuinfo")).contains("mte");
+ }
+
+ @Test
+ public void testBoth() throws Exception {
+ getDevice().setProperty("arm64.memtag.bootctl", "memtag,memtag-once");
+ getDevice().reboot();
+ assertThat(getDevice().getProperty("arm64.memtag.bootctl")).isEqualTo("memtag");
+ assertThat(getDevice().pullFileContents("/proc/cpuinfo")).contains("mte");
+ getDevice().reboot();
+ assertThat(getDevice().getProperty("arm64.memtag.bootctl")).isEqualTo("memtag");
+ assertThat(getDevice().pullFileContents("/proc/cpuinfo")).contains("mte");
+ }
+}
diff --git a/partition_tools/dynamic_partitions_device_info.proto b/partition_tools/dynamic_partitions_device_info.proto
index e53b40e2..8800dac7 100644
--- a/partition_tools/dynamic_partitions_device_info.proto
+++ b/partition_tools/dynamic_partitions_device_info.proto
@@ -25,7 +25,7 @@ message DynamicPartitionsDeviceInfoProto {
bool enabled = 1;
bool retrofit = 2;
- // Next: 7
+ // Next: 8
message Partition {
string name = 1;
string group_name = 2 [json_name = "group_name"];
@@ -36,6 +36,8 @@ message DynamicPartitionsDeviceInfoProto {
uint64 fs_size = 5 [json_name = "fs_size"];
/** Used space of the filesystem. */
uint64 fs_used = 6 [json_name = "fs_used"];
+ /** Name of the filesystem. */
+ string fs_type = 7 [json_name = "fs_type"];
}
repeated Partition partitions = 3;
diff --git a/partition_tools/lpadd.cc b/partition_tools/lpadd.cc
index 7374e774..b2b1c74f 100644
--- a/partition_tools/lpadd.cc
+++ b/partition_tools/lpadd.cc
@@ -13,6 +13,7 @@
// limitations under the License.
#include <getopt.h>
+#include <string.h>
#include <sysexits.h>
#include <unistd.h>
diff --git a/partition_tools/lpdump.cc b/partition_tools/lpdump.cc
index 047b5ee0..97682940 100644
--- a/partition_tools/lpdump.cc
+++ b/partition_tools/lpdump.cc
@@ -229,6 +229,13 @@ static bool MergeFsUsage(DynamicPartitionsDeviceInfoProto* proto,
partition_proto->set_is_dynamic(false);
}
partition_proto->set_fs_size((uint64_t)vst.f_blocks * vst.f_frsize);
+
+ if (!entry.fs_type.empty()) {
+ partition_proto->set_fs_type(entry.fs_type);
+ } else {
+ partition_proto->set_fs_type("UNKNOWN");
+ }
+
if (vst.f_bavail <= vst.f_blocks) {
partition_proto->set_fs_used((uint64_t)(vst.f_blocks - vst.f_bavail) * vst.f_frsize);
}
diff --git a/partition_tools/lpmake.cc b/partition_tools/lpmake.cc
index e6c4e530..16dfec52 100644
--- a/partition_tools/lpmake.cc
+++ b/partition_tools/lpmake.cc
@@ -17,9 +17,11 @@
#include <getopt.h>
#include <inttypes.h>
#include <stdio.h>
+#include <stdlib.h>
#ifndef WIN32
#include <sysexits.h>
#endif
+#include <unistd.h>
#include <algorithm>
#include <memory>
@@ -27,6 +29,7 @@
#include <android-base/parseint.h>
#include <android-base/result.h>
#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
#include <liblp/builder.h>
#include <liblp/liblp.h>
@@ -35,12 +38,15 @@ using namespace android::fs_mgr;
using android::base::Error;
using android::base::Result;
+using android::base::unique_fd;
#ifdef WIN32
static constexpr int EX_OK = 0;
static constexpr int EX_USAGE = 1;
static constexpr int EX_SOFTWARE = 2;
static constexpr int EX_CANTCREAT = 3;
+#else
+static constexpr int O_BINARY = 0;
#endif
/* Prints program usage to |where|. */
@@ -165,16 +171,47 @@ struct PartitionInfo {
};
static uint64_t CalculateBlockDeviceSize(uint32_t alignment, uint32_t metadata_size,
+ uint32_t metadata_slots,
const std::vector<PartitionInfo>& partitions) {
- uint64_t ret = std::max(alignment, LP_PARTITION_RESERVED_BYTES +
- (LP_METADATA_GEOMETRY_SIZE + metadata_size) * 2) +
- partitions.size() * alignment;
+ uint64_t ret = LP_PARTITION_RESERVED_BYTES;
+ ret += LP_METADATA_GEOMETRY_SIZE * 2;
+
+ // Each metadata slot has a primary and backup copy.
+ ret += metadata_slots * metadata_size * 2;
+
+ if (alignment) {
+ uint64_t remainder = ret % alignment;
+ uint64_t to_add = alignment - remainder;
+ if (to_add > std::numeric_limits<uint64_t>::max() - ret) {
+ return 0;
+ }
+ ret += to_add;
+ }
+
+ ret += partitions.size() * alignment;
for (const auto& partition_info : partitions) {
ret += partition_info.size;
}
return ret;
}
+static bool GetFileSize(const std::string& path, uint64_t* size) {
+ unique_fd fd(open(path.c_str(), O_RDONLY | O_BINARY));
+ if (fd < 0) {
+ fprintf(stderr, "Could not open file: %s: %s\n", path.c_str(), strerror(errno));
+ return false;
+ }
+
+ auto offs = lseek(fd.get(), 0, SEEK_END);
+ if (offs < 0) {
+ fprintf(stderr, "Failed to seek file: %s: %s\n", path.c_str(), strerror(errno));
+ return false;
+ }
+
+ *size = offs;
+ return true;
+}
+
int main(int argc, char* argv[]) {
struct option options[] = {
{ "device-size", required_argument, nullptr, (int)Option::kDeviceSize },
@@ -182,7 +219,7 @@ int main(int argc, char* argv[]) {
{ "metadata-slots", required_argument, nullptr, (int)Option::kMetadataSlots },
{ "partition", required_argument, nullptr, (int)Option::kPartition },
{ "output", required_argument, nullptr, (int)Option::kOutput },
- { "help", no_argument, nullptr, (int)Option::kOutput },
+ { "help", no_argument, nullptr, (int)Option::kHelp },
{ "alignment-offset", required_argument, nullptr, (int)Option::kAlignmentOffset },
{ "alignment", required_argument, nullptr, (int)Option::kAlignment },
{ "sparse", no_argument, nullptr, (int)Option::kSparse },
@@ -347,7 +384,12 @@ int main(int argc, char* argv[]) {
}
if (auto_blockdevice_size) {
- blockdevice_size = CalculateBlockDeviceSize(alignment, metadata_size, partitions);
+ blockdevice_size =
+ CalculateBlockDeviceSize(alignment, metadata_size, metadata_slots, partitions);
+ if (!blockdevice_size) {
+ fprintf(stderr, "Invalid block device parameters.\n");
+ return EX_USAGE;
+ }
}
// Must specify a block device via the old method (--device-size etc) or
@@ -425,13 +467,21 @@ int main(int argc, char* argv[]) {
}
}
- for (const auto& partition_info : partitions) {
+ for (auto& partition_info : partitions) {
Partition* partition = builder->AddPartition(partition_info.name, partition_info.group_name,
partition_info.attribute_flags);
if (!partition) {
fprintf(stderr, "Could not add partition: %s\n", partition_info.name.c_str());
return EX_SOFTWARE;
}
+ if (!partition_info.size) {
+ // Deduce the size automatically.
+ if (auto iter = images.find(partition_info.name); iter != images.end()) {
+ if (!GetFileSize(iter->second, &partition_info.size)) {
+ return EX_SOFTWARE;
+ }
+ }
+ }
if (!builder->ResizePartition(partition, partition_info.size)) {
fprintf(stderr, "Not enough space on device for partition %s with size %" PRIu64 "\n",
partition_info.name.c_str(), partition_info.size);
diff --git a/partition_tools/lpunpack.cc b/partition_tools/lpunpack.cc
index 1f870c5d..b215c58e 100644
--- a/partition_tools/lpunpack.cc
+++ b/partition_tools/lpunpack.cc
@@ -34,11 +34,12 @@
using namespace android::fs_mgr;
using android::base::unique_fd;
+using android::base::borrowed_fd;
using SparsePtr = std::unique_ptr<sparse_file, decltype(&sparse_file_destroy)>;
class ImageExtractor final {
public:
- ImageExtractor(unique_fd&& image_fd, std::unique_ptr<LpMetadata>&& metadata,
+ ImageExtractor(std::vector<unique_fd>&& image_fds, std::unique_ptr<LpMetadata>&& metadata,
std::unordered_set<std::string>&& partitions, const std::string& output_dir);
bool Extract();
@@ -48,7 +49,7 @@ class ImageExtractor final {
bool ExtractPartition(const LpMetadataPartition* partition);
bool ExtractExtent(const LpMetadataExtent& extent, int output_fd);
- unique_fd image_fd_;
+ std::vector<unique_fd> image_fds_;
std::unique_ptr<LpMetadata> metadata_;
std::unordered_set<std::string> partitions_;
std::string output_dir_;
@@ -59,16 +60,15 @@ class ImageExtractor final {
// file format.
class SparseWriter final {
public:
- SparseWriter(int output_fd, int image_fd, uint32_t block_size);
+ SparseWriter(borrowed_fd output_fd, uint32_t block_size);
- bool WriteExtent(const LpMetadataExtent& extent);
+ bool WriteExtent(borrowed_fd image_fd, const LpMetadataExtent& extent);
bool Finish();
private:
bool WriteBlock(const uint8_t* data);
- int output_fd_;
- int image_fd_;
+ borrowed_fd output_fd_;
uint32_t block_size_;
off_t hole_size_ = 0;
};
@@ -81,7 +81,14 @@ static int usage(int /* argc */, char* argv[]) {
"Usage:\n"
" %s [options...] SUPER_IMAGE [OUTPUT_DIR]\n"
"\n"
+ "The SUPER_IMAGE argument is mandatory and expected to contain\n"
+ "the metadata. Additional super images are referenced with '-i' as needed to extract\n"
+ "the desired partition[s].\n"
+ "Default OUTPUT_DIR is '.'.\n"
+ "\n"
"Options:\n"
+ " -i, --image=IMAGE Use the given file as an additional super image.\n"
+ " This can be specified multiple times.\n"
" -p, --partition=NAME Extract the named partition. This can\n"
" be specified multiple times.\n"
" -S, --slot=NUM Slot number (default is 0).\n",
@@ -92,6 +99,7 @@ static int usage(int /* argc */, char* argv[]) {
int main(int argc, char* argv[]) {
// clang-format off
struct option options[] = {
+ { "image", required_argument, nullptr, 'i' },
{ "partition", required_argument, nullptr, 'p' },
{ "slot", required_argument, nullptr, 'S' },
{ nullptr, 0, nullptr, 0 },
@@ -100,6 +108,7 @@ int main(int argc, char* argv[]) {
uint32_t slot_num = 0;
std::unordered_set<std::string> partitions;
+ std::vector<std::string> image_files;
int rv, index;
while ((rv = getopt_long_only(argc, argv, "+p:sh", options, &index)) != -1) {
@@ -116,6 +125,9 @@ int main(int argc, char* argv[]) {
return usage(argc, argv);
}
break;
+ case 'i':
+ image_files.push_back(optarg);
+ break;
case 'p':
partitions.emplace(optarg);
break;
@@ -126,56 +138,66 @@ int main(int argc, char* argv[]) {
std::cerr << "Missing super image argument.\n";
return usage(argc, argv);
}
- std::string super_path = argv[optind++];
+ image_files.emplace(image_files.begin(), argv[optind++]);
std::string output_dir = ".";
if (optind + 1 <= argc) {
output_dir = argv[optind++];
}
- if (optind < argc) {
- std::cerr << "Unrecognized command-line arguments.\n";
- return usage(argc, argv);
- }
+ std::unique_ptr<LpMetadata> metadata;
+ std::vector<unique_fd> fds;
- // Done reading arguments; open super.img. PartitionOpener will decorate
- // relative paths with /dev/block/by-name, so get an absolute path here.
- std::string abs_super_path;
- if (!android::base::Realpath(super_path, &abs_super_path)) {
- std::cerr << "realpath failed: " << super_path << ": " << strerror(errno) << "\n";
- return EX_OSERR;
- }
+ for (std::size_t index = 0; index < image_files.size(); ++index) {
+ std::string super_path = image_files[index];
- unique_fd fd(open(super_path.c_str(), O_RDONLY | O_CLOEXEC));
- if (fd < 0) {
- std::cerr << "open failed: " << abs_super_path << ": " << strerror(errno) << "\n";
- return EX_OSERR;
- }
+ // Done reading arguments; open super.img. PartitionOpener will decorate
+ // relative paths with /dev/block/by-name, so get an absolute path here.
+ std::string abs_super_path;
+ if (!android::base::Realpath(super_path, &abs_super_path)) {
+ std::cerr << "realpath failed: " << super_path << ": " << strerror(errno) << "\n";
+ return EX_OSERR;
+ }
+
+ unique_fd fd(open(super_path.c_str(), O_RDONLY | O_CLOEXEC));
+ if (fd < 0) {
+ std::cerr << "open failed: " << abs_super_path << ": " << strerror(errno) << "\n";
+ return EX_OSERR;
+ }
- auto metadata = ReadMetadata(abs_super_path, slot_num);
- if (!metadata) {
SparsePtr ptr(sparse_file_import(fd, false, false), sparse_file_destroy);
if (ptr) {
- std::cerr << "This image appears to be a sparse image. It must be "
- "unsparsed to be"
- << " unpacked.\n";
+ std::cerr << "The image file '"
+ << super_path
+ << "' appears to be a sparse image. It must be unsparsed to be unpacked.\n";
return EX_USAGE;
}
- std::cerr << "Image does not appear to be in super-partition format.\n";
- return EX_USAGE;
+
+ if (!metadata) {
+ metadata = ReadMetadata(abs_super_path, slot_num);
+ if (!metadata) {
+ std::cerr << "Could not read metadata from the super image file '"
+ << super_path
+ << "'.\n";
+ return EX_USAGE;
+ }
+ }
+
+ fds.emplace_back(std::move(fd));
}
- ImageExtractor extractor(std::move(fd), std::move(metadata), std::move(partitions), output_dir);
+ // Now do actual extraction.
+ ImageExtractor extractor(std::move(fds), std::move(metadata), std::move(partitions), output_dir);
if (!extractor.Extract()) {
return EX_SOFTWARE;
}
return EX_OK;
}
-ImageExtractor::ImageExtractor(unique_fd&& image_fd, std::unique_ptr<LpMetadata>&& metadata,
+ImageExtractor::ImageExtractor(std::vector<unique_fd>&& image_fds, std::unique_ptr<LpMetadata>&& metadata,
std::unordered_set<std::string>&& partitions,
const std::string& output_dir)
- : image_fd_(std::move(image_fd)),
+ : image_fds_(std::move(image_fds)),
metadata_(std::move(metadata)),
partitions_(std::move(partitions)),
output_dir_(output_dir) {}
@@ -186,6 +208,7 @@ bool ImageExtractor::Extract() {
}
for (const auto& [name, info] : partition_map_) {
+ std::cout << "Attempting to extract partition '" << name << "'...\n";
if (!ExtractPartition(info)) {
return false;
}
@@ -217,13 +240,14 @@ bool ImageExtractor::ExtractPartition(const LpMetadataPartition* partition) {
for (uint32_t i = 0; i < partition->num_extents; i++) {
uint32_t index = partition->first_extent_index + i;
const LpMetadataExtent& extent = metadata_->extents[index];
+ std::cout << " Dealing with extent " << i << " from target source " << extent.target_source << "...\n";
if (extent.target_type != LP_TARGET_TYPE_LINEAR) {
std::cerr << "Unsupported target type in extent: " << extent.target_type << "\n";
return false;
}
- if (extent.target_source != 0) {
- std::cerr << "Split super devices are not supported.\n";
+ if (extent.target_source >= image_fds_.size()) {
+ std::cerr << "Insufficient number of super images passed, need at least " << extent.target_source + 1 << ".\n";
return false;
}
total_size += extent.num_sectors * LP_SECTOR_SIZE;
@@ -237,28 +261,28 @@ bool ImageExtractor::ExtractPartition(const LpMetadataPartition* partition) {
return false;
}
- SparseWriter writer(output_fd, image_fd_, metadata_->geometry.logical_block_size);
+ SparseWriter writer(output_fd, metadata_->geometry.logical_block_size);
// Extract each extent into output_fd.
for (uint32_t i = 0; i < partition->num_extents; i++) {
uint32_t index = partition->first_extent_index + i;
const LpMetadataExtent& extent = metadata_->extents[index];
- if (!writer.WriteExtent(extent)) {
+ if (!writer.WriteExtent(image_fds_[extent.target_source], extent)) {
return false;
}
}
return writer.Finish();
}
-SparseWriter::SparseWriter(int output_fd, int image_fd, uint32_t block_size)
- : output_fd_(output_fd), image_fd_(image_fd), block_size_(block_size) {}
+SparseWriter::SparseWriter(borrowed_fd output_fd, uint32_t block_size)
+ : output_fd_(output_fd), block_size_(block_size) {}
-bool SparseWriter::WriteExtent(const LpMetadataExtent& extent) {
+bool SparseWriter::WriteExtent(borrowed_fd image_fd, const LpMetadataExtent& extent) {
auto buffer = std::make_unique<uint8_t[]>(block_size_);
off_t super_offset = extent.target_data * LP_SECTOR_SIZE;
- if (lseek(image_fd_, super_offset, SEEK_SET) < 0) {
+ if (lseek(image_fd.get(), super_offset, SEEK_SET) < 0) {
std::cerr << "image lseek failed: " << strerror(errno) << "\n";
return false;
}
@@ -269,7 +293,7 @@ bool SparseWriter::WriteExtent(const LpMetadataExtent& extent) {
std::cerr << "extent is not block-aligned\n";
return false;
}
- if (!android::base::ReadFully(image_fd_, buffer.get(), block_size_)) {
+ if (!android::base::ReadFully(image_fd, buffer.get(), block_size_)) {
std::cerr << "read failed: " << strerror(errno) << "\n";
return false;
}
@@ -297,7 +321,7 @@ bool SparseWriter::WriteBlock(const uint8_t* data) {
}
if (hole_size_) {
- if (lseek(output_fd_, hole_size_, SEEK_CUR) < 0) {
+ if (lseek(output_fd_.get(), hole_size_, SEEK_CUR) < 0) {
std::cerr << "lseek failed: " << strerror(errno) << "\n";
return false;
}
@@ -312,12 +336,12 @@ bool SparseWriter::WriteBlock(const uint8_t* data) {
bool SparseWriter::Finish() {
if (hole_size_) {
- off_t offset = lseek(output_fd_, 0, SEEK_CUR);
+ off_t offset = lseek(output_fd_.get(), 0, SEEK_CUR);
if (offset < 0) {
std::cerr << "lseek failed: " << strerror(errno) << "\n";
return false;
}
- if (ftruncate(output_fd_, offset + hole_size_) < 0) {
+ if (ftruncate(output_fd_.get(), offset + hole_size_) < 0) {
std::cerr << "ftruncate failed: " << strerror(errno) << "\n";
return false;
}
diff --git a/perf_tools/Android.bp b/perf_tools/Android.bp
new file mode 100644
index 00000000..26b93511
--- /dev/null
+++ b/perf_tools/Android.bp
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+python_binary_host {
+ name: "progress_report",
+ srcs: ["progress_report.py"],
+ libs: [ "report_proto",],
+ version: {
+ py3: { embedded_launcher: true },
+ },
+ main: "progress_report.py",
+}
+
+python_library_host {
+ name: "report_proto",
+ srcs: ["*.proto"],
+ proto: {
+ canonical_path_from_root: false,
+ },
+}
diff --git a/perf_tools/bats/lcan.py b/perf_tools/bats/lcan.py
new file mode 100755
index 00000000..cfe381a4
--- /dev/null
+++ b/perf_tools/bats/lcan.py
@@ -0,0 +1,692 @@
+#!/usr/bin/python3
+# -------------------------------------------
+# logcat analysis
+# -------------------------------------------
+from ast import keyword
+from curses import keyname
+import argparse
+import os
+from string import digits
+from sbtaTools import TextFile
+import datetime
+import re
+import shlex
+import glob
+
+class LCItem:
+ def __init__(self, lCTimeProcessor):
+ self.dateTime = 0
+ self.relativeTime = 0
+ self.key = ""
+ self.moduleName = ""
+ self.keyword = ""
+ self.valueMsec = 0
+ self.lCTimeProcessor = lCTimeProcessor
+ self.lines = []
+
+ def set(self, dateTime, moduleName, keyText, valueMsec):
+ try:
+ self.dateTime = dateTime
+ self.relativeTime = self.lCTimeProcessor.toRelativeTime(self.dateTime)
+ self.moduleName = moduleName
+ self.keyword = keyText
+ self.key = moduleName+":" + keyText
+ self.valueMsec = valueMsec
+ except Exception as e:
+ errLine = "LCItem:set() ERROR Failed: " + str(e)
+ assert False, errLine
+
+ def copy(self, otherLCItem):
+ self.dateTime = otherLCItem.dataTime
+ self.relativeTime = otherLCItem.relativeTime
+ self.key = otherLCItem.key
+ self.moduleName = otherLCItem.moduleName
+ self.keyword = otherLCItem.keyword
+ self.valueMsec = otherLCItem.valueMsec
+ self.lCTimeProcessor = otherLCItem.lcTimeProcessor
+ self.lines = otherLCItem.lines
+
+ def appendLine(self, line):
+ self.lines.append(line)
+
+ def keyEqual(self, otherItem):
+ if self.key != otherItem.key:
+ return False
+ return True
+
+ def add(self, otherItem):
+ assert(self.key==otherItem.key)
+ self.lines.extend(otherItem.lines)
+ self.valueMsec = self.valueMsec + otherItem.valueMsec
+ return True
+
+ def addValue(self, otherLCItem):
+ if self.key=="":
+ self.copy(otherLCItem)
+ else:
+ assert(self.key==otherLCItem.key)
+ self.valueMsec = self.valueMsec + otherLCItem.valueMsec
+ return True
+
+ def divideValue(self, number): # scaler divide
+ self.valueMsec = self.valueMsec / number
+ return True
+
+ def key(self):
+ return self.key
+
+ def print(self):
+ #systemTime = self.lCTimeProcessor.toSystemTime(self.dateTime)
+ #relativeTime = self.lCTimeProcessor.toRelativeTime(self.dateTime)
+ newTimeString = str(self.relativeTime)
+ if (len(self.lines)>0):
+ print("{} {}: {} {:.4f} - {}".format(newTimeString, self.moduleName, self.keyword, self.valueMsec, self.lines[0]))
+ else:
+ print("{} {}: {} {:.4f} -".format(newTimeString, self.moduleName, self.keyword, self.valueMsec))
+
+ def printLines(self, prefix, min):
+ if (len(self.lines)<min):
+ return
+ for line in self.lines:
+ print(" {}{}".format(prefix, line))
+
+ def findModuleName(self, lineTextWords):
+ colonIndex = -1
+ try:
+ colonIndex = lineTextWords.index(":")
+ # address case of colon with no space
+ moduleName = lineTextWords[colonIndex-1]
+ except:
+ moduleName = ""
+ if colonIndex==-1:
+ for word in reversed(lineTextWords):
+ index = word.find(":")
+ if index!=-1:
+ moduleName = word[:index]
+ break
+ moduleName = moduleName.strip()
+ return colonIndex, moduleName
+
+ def parseLineWithTook(self, line):
+ maxLineLength = 100
+ stage = 0
+ try:
+ words = line.split(" ")
+ dataTimeO = self.lCTimeProcessor.parseTimeStamp(line)
+ if line.find("took to complete") != -1:
+ stage = 1
+ tookIndex = line.find(" took to complete:")
+ uptoEnd= line[:tookIndex]
+ lineTextWords = uptoEnd.split()
+ colonIndex, moduleName = self.findModuleName(lineTextWords)
+ keyword = " ".join([lineTextWords[6]])
+ value = re.findall(r'\d+', line[tookIndex:])[-1]
+ value = float(value)
+
+ elif line.find("took") != -1:
+ stage = 2
+ tookIndex = line.find(" took")
+ uptoEnd= line[:tookIndex]
+ uptoBracket = uptoEnd.rfind("(")
+ if uptoBracket != -1:
+ uptoEnd = uptoEnd[:uptoBracket]
+ #uptoEnd = uptoEnd.replace(":", "")
+ lineTextWords = shlex.split(uptoEnd)
+ colonIndex, moduleName = self.findModuleName(lineTextWords)
+ # if there is colon only take words after it
+ if colonIndex!=-1:
+ lineTextWords = lineTextWords[colonIndex+1:]
+ numWords = len(lineTextWords)
+ keyword = ""
+ stage = 3
+ try:
+ for i in range(max(numWords-3, 0), numWords, 1):
+ keyword = keyword + " " + lineTextWords[i]
+ except Exception as e:
+ errLine = "LCItem:parseLineWithTook() ERROR Failed to parse1: " + str(e)
+ print(errLine)
+ assert False, errLine
+
+ # reduce length
+ keyword = keyword[:maxLineLength]
+ keyword = keyword.strip()
+ # using regex expression to replace all numbers
+ keyword = re.sub(r'\d', "_", keyword)
+ value = 0
+ stage = 4
+ try:
+ multplier = 1
+ tookSubstring = line[tookIndex:]
+ secondsIndex = tookSubstring.find("seconds")
+ msIndex = tookSubstring.find("ms")
+ if (secondsIndex!=-1):
+ tookSubstring = tookSubstring[:secondsIndex]
+ multiplier = 1000
+ elif msIndex != -1:
+ tookSubstring = tookSubstring[:msIndex]
+ else:
+ # known exception
+ if tookSubstring.find("properties")==-1:
+ errLine = "LCItem:parseLineWithTook() ERROR invalid took in substring 1B {}".format(line)
+ print(errLine)
+ assert False, errLine
+ return False
+
+ values = re.findall(r'[\d\.\d]+', tookSubstring)
+ while "." in values:
+ values.remove(".")
+ value = float(values[-1])
+ if line.find("seconds") != -1:
+ value = value * multiplier
+ except Exception as e:
+ errLine = "LCItem:parseLineWithTook() ERROR Failed to parse2: " + str(e)
+ print(errLine)
+ assert False, errLine
+ stage = 5
+
+ else:
+ return False
+
+ stage = 6
+ self.set(dataTimeO, moduleName, keyword, value)
+ stage = 7
+ self.lines.append(line)
+
+ return True
+
+ except Exception as e:
+ errLine = "LCItem:parseLineWithTook() ERROR Failed to parse3:" + str(e)
+ print(errLine, stage)
+ assert False, errLine
+
+ def parseLine(self, line):
+ try:
+ words = line.split(" ")
+ dateTimeO = self.lCTimeProcessor.parseTimeStamp(line)
+ if (dateTimeO!=None):
+ #lcItem = LCItem(self.lCTimeProcessor)
+ newLine = line[19:].rstrip()
+ self.set(dateTimeO, "", newLine, 0)
+ #self.print()
+ return
+ else:
+ return None
+
+ except Exception as e:
+ errLine = "LCItem:parseLine() ERROR Failed to parse3:" + str(e)
+ print(errLine)
+ assert False, errLine
+
+ def find(self, keyword):
+ if self.key.find(keyword)!=-1:
+ return True
+ for line in self.lines:
+ if line.find(keyword)!=-1:
+ return True
+
+ def createLogLine(self):
+ line = ""
+ msecs = self.dateTime.strftime("%f")
+ timeString = self.dateTime.strftime("%m-%d %H:%M:%S.")
+ #timeString = timeString + msecs[]
+ return line
+
+class LCItemSet:
+ def __init__(self, item1, item2):
+ self.item1 = item1
+ self.item2 = item2
+ if (item1.key != "" and item2.key != ""):
+ assert(item1.key == item2.key)
+ if (item1.key!=""):
+ self.key = item1.key
+ else:
+ self.key = item2.key
+ self.diff = item2.valueMsec - item1.valueMsec
+
+ def __gt__(self, other):
+ if(self.diff>other.diff):
+ return True
+ else:
+ return False
+
+ def add(item):
+ assert(False)
+
+ def print(self, min, printAll):
+ self.diff = self.item2.valueMsec - self.item1.valueMsec
+ if abs(self.diff)<min:
+ return
+ flag = "12"
+ if self.item1.key=="":
+ flag = "-2"
+
+ if self.item2.key=="":
+ flag = "1-"
+
+ print("{}, {}, {}, {}, {}".format(self.key, self.item1.valueMsec, self.item2.valueMsec, self.diff, flag))
+ if printAll:
+ self.item1.printLines("1> ", 1)
+ self.item2.printLines("2> ", 1)
+
+class LCItemMap:
+ def __init__(self):
+ self.map = {}
+
+ def put(self, newItem):
+ item = self.map.get(newItem.key)
+ if item==None:
+ self.map[newItem.key] = newItem
+ else:
+ item.add(newItem)
+
+ def print(self):
+ for key in self.map:
+ self.map[key].print()
+
+ def find(self, keyword):
+ lCItems = []
+ for index, lCItem in self.map:
+ if lCItem.find(keyword):
+ lCItems.append(lCItem)
+ return lCItems
+
+ def addValues(self, other):
+ for index, item in other.map.items():
+ if item.key in self.map:
+ self.map[item.key].addValue(item)
+ else:
+ self.map[item.key] = item
+
+ def divideValue(self, number):
+ for index, item in self.map:
+ item.divideValue(number)
+
+class LCItemSetMap:
+ def __init__(self):
+ self.map = {}
+
+ def put(self, itemSet):
+ item = self.map.get(itemSet.key)
+ if item==None:
+ self.map[itemSet.key] = itemSet
+ else:
+ item.add(itemSet)
+
+ def printSorted(self, printAll):
+ a = sorted(self.map.items(), key=lambda x: (x[1], x[0]), reverse=True)
+ cumDif = 0
+ print("Key, Value1, Value2, diff")
+ for item in a:
+ item[1].print(1, printAll)
+ cumDif = cumDif + item[1].diff
+ print("CUMULATIVE DIFF: {}".format(cumDif))
+
+class LCTimeProcessor:
+ def __init__(self):
+ self.firstKernelTimeStamp = 0
+ self.lastKernelTimeStamp = 0
+ self.firstSystemTimesStamp = 0
+ self.lastTimeStamp = 0
+ self.zeroRelativeTime = 0
+ today = datetime.datetime.now()
+ year = str(today.year)
+ self.currentYear = year[-2:] # 2022/2023
+
+ def parseTimeStamp(self, line):
+ try:
+ if len(line)<19:
+ return None
+ currentYear = self.currentYear # 22
+ words = line.split(" ")
+ timeString = words[0]
+ #timeString = re.sub("[^0-9: -.]", "", timeString)
+ timeString = timeString.strip()
+ timeString = timeString[:18]
+ timeString = currentYear + "-" + timeString
+ dataTimeO = datetime.datetime.strptime(timeString, "%Y-%m-%d %H:%M:%S.%f")
+ return dataTimeO
+ except Exception as e:
+ # If no time stamp on this line
+ if line.find("beginning of")!=-1:
+ return None
+ errLine = "LCItem:parseTimeStamp() ERROR Failed to parse:" + str(e)
+ print(errLine)
+ assert False, errLine
+ return None
+
+
+ def process(self, line):
+ timeStamp = self.parseTimeStamp(line)
+ if timeStamp==None:
+ return False
+
+ if self.firstKernelTimeStamp==0:
+ self.firstKernelTimeStamp = timeStamp
+ else:
+ if timeStamp < self.firstKernelTimeStamp:
+ return False
+
+ timeChange = timeStamp - self.lastTimeStamp
+ if (timeChange.total_seconds() > 68*5):
+ if self.firstSystemTimesStamp ==0:
+ self.firstSystemTimesStamp = timeStamp
+ self.lastKernelTimeStamp = self.lastTimeStamp
+ self.zeroRelativeTime = self.toSystemTime(self.firstKernelTimeStamp)
+
+ self.lastTimeStamp = timeStamp
+ return True
+
+ def toSystemTime(self, timeStamp):
+ try:
+ # if no systemTime is found, it must all be system time
+ if self.firstSystemTimesStamp==0:
+ self.firstSystemTimesStamp = self.firstKernelTimeStamp
+ self.lastKernelTimeStamp = self.lastTimeStamp
+ self.zeroRelativeTime = self.firstKernelTimeStamp
+ return timeStamp
+ if timeStamp >= self.firstSystemTimesStamp:
+ return timeStamp
+ else:
+ timeChange = timeStamp - self.lastKernelTimeStamp
+ systemTime = self.firstSystemTimesStamp + timeChange
+ return systemTime
+ except Exception as e:
+ errLine = "LogLine:parseLine() ERROR Failed to parse3:" + str(e)
+ print(errLine)
+ assert False, errLine
+
+ def toRelativeTime(self, timeStamp):
+ systemTime = self.toSystemTime(timeStamp)
+ relativeTime = systemTime - self.zeroRelativeTime
+ return relativeTime
+
+ if timeStamp< self.firstSystemTimesStamp:
+ timeChange = timeStamp - self.lastKernelTimeStamp
+ systemTime = self.firstSystemTimesStamp + timeChange
+ return systemTime
+ else:
+ return timeStamp
+
+ def toString(self, timeStamp):
+ return timeStamp.strftime("%Y-%m-%d %H:%M:%S.%f")
+
+class LCLogLine:
+ def __init__(self, lCTimeProcessor):
+ self.dateTime = 0
+ self.relativeTime = 0
+ self.lineText = ""
+ self.lCTimeProcessor = lCTimeProcessor
+
+ def set(self, dateTime, lineText):
+ self.dateTime = dateTime
+ self.relativeTime = self.lCTimeProcessor.toRelativeTime(self.dateTime)
+ self.lineText = lineText
+
+ def print(self):
+ newTimeString = str(self.relativeTime)
+ print("{}{}".format(newTimeString, self.lineText))
+
+ def parseLine(self, line):
+ try:
+ dateTimeO = self.lCTimeProcessor.parseTimeStamp(line)
+ if (dateTimeO!=None):
+ lineText = line[19:].rstrip()
+ self.set(dateTimeO, lineText)
+ return
+ else:
+ return None
+
+ except Exception as e:
+ errLine = "LogLine:parseLine() ERROR Failed to parse3:" + str(e)
+ print(errLine)
+ assert False, errLine
+
+ def find(self, word):
+ if (self.lineText.find(word)!=-1):
+ return True
+ else:
+ return False
+
+ def findAll(self, words):
+ for word in words:
+ if (self.lineText.find(word)==-1):
+ return False
+ return True
+
+class LCLogFile(TextFile):
+ priorTimeStamp = 0.0
+ def __init__(self, _fileName = ""):
+ super(LCLogFile, self).__init__(_fileName)
+ self.linesWithTook = []
+ self.linesWithTookToComplete = []
+ self.linesWithoutTookToComplete = []
+ self.firstKernelTimeStamp = 0
+ self.lastKernelTimeStamp = 0
+ self.firstSystemTimesStamp = 0
+ self.lastTimeStamp = 0
+ self.lCTimeProcessor = LCTimeProcessor()
+ self.dumpLinesBeforeBeginning()
+
+ def dumpLinesBeforeBeginning(self):
+ # start from --------- beginning of kernel
+ beginningFound = False
+ _lines = []
+ for line in self.lines:
+ if beginningFound==True:
+ _lines.append(line)
+ self.lCTimeProcessor.process(line)
+
+ elif line.find("beginning of kernel") != -1:
+ beginningFound = True
+
+ self.lines = _lines
+
+
+ def scanTook(self):
+ lCItemMap = LCItemMap()
+ foundBeginning = False
+ for line in self.lines:
+ # start at beginning
+ if not foundBeginning:
+ if line.find("beginning of kernel=1") != -1:
+ foundBeginning = True
+ continue
+
+ # stop if boot complete
+ if line.find("sys.boot_completed=1") != -1:
+ break
+
+ if line.find("took") != -1:
+ self.linesWithTook.append(line.rstrip())
+
+ for line in self.linesWithTook:
+ lCItem = LCItem(self.lCTimeProcessor)
+ if lCItem.parseLineWithTook(line)==True:
+ lCItemMap.put(lCItem)
+
+ return lCItemMap
+
+ def print(self, numItems=None):
+ self.scanTook()
+
+ def convert(self, numItems=None):
+ lcLogLines = []
+ for line in self.lines:
+ lcLogLine = LCLogLine(self.lCTimeProcessor)
+ lcLogLine.parseLine(line)
+ lcLogLines.append(lcLogLine)
+ return lcLogLines
+'''
+ def createLCFile(self, fileName):
+ # create LCTimeProcessor
+ # create LCItem
+ # create LCLogLine
+ # write LCLogLine to file
+'''
+class ScanFile:
+ def __init__(self):
+ self.fileName = "none"
+
+ def scanKeyWords(self, fileName):
+ print("Scanning {}".format(fileName))
+ cmd = "grep \"apexd: wait for '\/dev\/loop-control'\" {}".format(fileName)
+ x = os.system(cmd)
+ cmd = "grep \"Service 'apexd-bootstrap\" {}".format(fileName)
+ x = os.system(cmd)
+ cmd = "grep apexd.status=activated {}".format(fileName)
+ x = os.system(cmd)
+ cmd = "grep \"Service 'bpfloader'\" {}".format(fileName)
+ x = os.system(cmd)
+ cmd = "grep \"sys.boot_completed=1\" {} | head -n 1".format(fileName)
+ x = os.system(cmd)
+
+ def scanTook(self, fileName):
+ lCLogFile = LCLogFile(fileName)
+ lCItemMap = lCLogFile.scanTook()
+
+ def convert(self, fileName):
+ lCLogFile = LCLogFile(fileName)
+ lcItems = lCLogFile.convert()
+ for lcItem in lcItems:
+ lcItem.print()
+
+ def phases(self, fileName):
+ keywordFile = TextFile("keywords")
+ #keywords = ['init first', 'init second', "Starting phase 200", "boot_completed"]
+
+ lCLogFile = LCLogFile(fileName)
+ keywordSets = []
+ for line in keywordFile.lines:
+ line = line.strip()
+ keywordSet = line.split(", ")
+ keywordSets.append(keywordSet)
+
+ lcLogLines = lCLogFile.convert()
+ for keywordSet in keywordSets:
+ for lcLogLine in lcLogLines:
+ if lcLogLine.findAll(keywordSet)==True:
+ lcLogLine.print()
+ break
+
+class Compare:
+ def __init__(self):
+ self.fileName = "none"
+
+ def compareLCItemMaps(self, lCItemMap1, lCItemMap2):
+ lCItemSetMap = LCItemSetMap()
+
+ for item1key in lCItemMap1.map:
+ found = False
+ for item2key in lCItemMap2.map:
+ if item2key==item1key:
+ lcItemSet = LCItemSet(lCItemMap1.map[item1key], lCItemMap2.map[item2key])
+ lCItemSetMap.put(lcItemSet)
+ found = True
+ break
+ # if item1Key is not in ItemMap2, add a null item
+ if found==False:
+ lCTimeProcessor = LCTimeProcessor()
+ nullLCItem = LCItem(lCTimeProcessor)
+ lcItemSet = LCItemSet(nullLCItem, lCItemMap1.map[item1key])
+ lCItemSetMap.put(lcItemSet)
+ found = True
+
+ lCItemSetMap.printSorted(printAll)
+ return lCItemSetMap
+
+ def compareFiles(self, fileName1, fileName2, printAll):
+ print("---------------------------------------------------------------")
+ print("lcan.py -cmp {} {}".format(fileName1, fileName2))
+ print("---------------------------------------------------------------")
+ lCLogFile1 = LCLogFile(fileName1)
+ lCItemMap1 = lCLogFile1.scanTook()
+ lCLogFile2 = LCLogFile(fileName2)
+ lCItemMap2 = lCLogFile2.scanTook()
+
+ lCItemSetMap = LCItemSetMap()
+
+ for item1key in lCItemMap1.map:
+ found = False
+ for item2key in lCItemMap2.map:
+ if item2key==item1key:
+ lcItemSet = LCItemSet(lCItemMap1.map[item1key], lCItemMap2.map[item2key])
+ lCItemSetMap.put(lcItemSet)
+ found = True
+ break
+ # if item1Key is not in ItemMap2, add a null item
+ if found==False:
+ lCTimeProcessor = LCTimeProcessor()
+ nullLCItem = LCItem(lCTimeProcessor)
+ lcItemSet = LCItemSet(nullLCItem, lCItemMap1.map[item1key])
+ lCItemSetMap.put(lcItemSet)
+ found = True
+
+ lCItemSetMap.printSorted(printAll)
+ return lCItemSetMap
+
+ def getAverageOfDir(self, buildId):
+ #get average values for build1
+ dirList = glob.glob("{}/LC-{}*.txt".format(buildId, buildId))
+ numFiles = len(dirList)
+ #iterate in numerical order
+ lCItemMapS = LCItemMap()
+ for index in range(numFiles):
+ fileName = "{}/LC-{}-{}.txt".format(buildId, buildId, index)
+ #for index, fileName in enumerate(dirList):
+ lCLogFile = LCLogFile(fileName)
+ lCItemMap = lCLogFile.scanTook()
+ lCItemMapS.addValues(lCItemMap)
+ lCItemMapS.divideValue(numFiles)
+ return lCItemMapS
+
+ def compareDirs(self, buildId1, buildId2, printAll):
+ print("---------------------------------------------------------------")
+ print("lcan.py -cmpd {} {} {}".format(buildId1, buildId2, printAll))
+ print("---------------------------------------------------------------")
+
+ #get average values for build1
+ lCItemMap1 = self.getAverageOfDir(buildId1)
+ lCItemMap2 = self.getAverageOfDir(buildId2)
+ self.compareLCItemMaps(self, lCItemMap1, lCItemMap2)
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument("-c", nargs=1, metavar=('<fileName>'), help="convert Logcat output to start from boot with converted timeStamps")
+parser.add_argument("-k", nargs=1, metavar=('<fileName>'), help="summary on keywords")
+parser.add_argument("-a", nargs=1, metavar=('<fileName>'), help="analyze file")
+parser.add_argument("-cmp", nargs=3, metavar=('<fileName1>', '<fileName2>', '<brief/all>'), help="compare logcat files")
+parser.add_argument("-cmpd", nargs=3, metavar=('<dirName1>', '<dirName2>', '<brief/all>'), help="compare logcat files")
+parser.add_argument("-p", nargs=1, metavar=('<fileName1>'), help="phase report on log files")
+args = parser.parse_args()
+
+if args.k!=None:
+ scanFile = ScanFile()
+ scanFile.scanKeyWords(args.k[0])
+
+if args.a!=None:
+ scanFile = ScanFile()
+ scanFile.scanTook(args.a[0])
+
+if args.c!=None:
+ scanFile = ScanFile()
+ scanFile.convert(args.c[0])
+
+if args.p!=None:
+ scanFile = ScanFile()
+ scanFile.phases(args.p[0])
+
+if args.cmp!=None:
+ printAll = False
+ compare = Compare()
+ if (len(args.cmp)>2):
+ if (args.cmp[2].find("all")!=-1):
+ printAll = True
+ compare.compareFiles(args.cmp[0], args.cmp[1], printAll)
+
+if args.cmpd!=None:
+ printAll = False
+ compare = Compare()
+ if (len(args.cmpd)>2):
+ if (args.cmpd[2].find("all")!=-1):
+ printAll = True
+ compare.compareDirs(args.cmpd[0], args.cmpd[1], printAll)
diff --git a/perf_tools/config.yaml b/perf_tools/config.yaml
new file mode 100644
index 00000000..4372ce20
--- /dev/null
+++ b/perf_tools/config.yaml
@@ -0,0 +1,15 @@
+# This file is used to store the keywords to be extracted
+# from the logcat.
+---
+- boot_progress_start
+- boot_progress_preload_start
+- boot_progress_preload_end
+- boot_progress_system_run
+- boot_progress_pms_start
+- boot_progress_pms_system_scan_start
+- boot_progress_pms_data_scan_start
+- boot_progress_pms_scan_end
+- boot_progress_pms_ready
+- boot_progress_ams_ready
+- boot_progress_enable_screen
+- car_helper_boot_phase
diff --git a/perf_tools/parse_timestamp.py b/perf_tools/parse_timestamp.py
new file mode 100644
index 00000000..bfac3f71
--- /dev/null
+++ b/perf_tools/parse_timestamp.py
@@ -0,0 +1,55 @@
+import sys
+import os
+from datetime import datetime
+
+# Usage:
+# replace_timestamp.py input.txt output.txt timestamp_string
+#
+# Description:
+# Replace timestamp in the input.txt with the difference timestamp to timestamp_string.
+#
+# Example: replace_timestamp.py input.txt output.txt "01-28 18:12:30.339".
+#
+def main():
+ filepath = sys.argv[1]
+ if not os.path.isfile(filepath):
+ print("File path {} does not exist. Exiting...".format(filepath))
+ sys.exit()
+
+ output_filepath = sys.argv[2]
+
+ timestamp_str = sys.argv[3]
+ date_time_obj = datetime.strptime(timestamp_str, '%m-%d %H:%M:%S.%f')
+
+ output_fp = open(output_filepath, 'w')
+ i = 1
+ with open(filepath, 'r', errors = 'ignore') as fp:
+ for line in fp:
+ newline = replace_timestamp_abs(line, timestamp_str, date_time_obj)
+ output_fp.write(newline)
+ i = i + 1
+ fp.close()
+ output_fp.close()
+
+
+def replace_timestamp_abs(line, timestamp_str, date_time_obj0):
+ if line[:5] != timestamp_str[:5]:
+ return line
+
+ index = line.find(" ", 6)
+ if index <= 0:
+ return line
+ substr0 = line[:index]
+ substr1 = line[index:]
+
+ try:
+ date_time_obj = datetime.strptime(substr0, '%m-%d %H:%M:%S.%f')
+ except ValueError:
+ return line
+
+ date_time_delta = date_time_obj - date_time_obj0
+ date_time_delta_str = str(date_time_delta)
+ return date_time_delta_str + substr1
+
+if __name__ == '__main__':
+ main()
diff --git a/perf_tools/parse_timing.py b/perf_tools/parse_timing.py
new file mode 100644
index 00000000..1ba23142
--- /dev/null
+++ b/perf_tools/parse_timing.py
@@ -0,0 +1,197 @@
+import sys
+import os
+from datetime import datetime
+
+# Usage:
+# python3 parse_timing.py logcat.txt "08-23 23:10:32.555" 10 200
+#
+# Description: extract events and timing in the log that start from timestamp "08-23 23:10:32.555"
+# till 10 seconds
+#
+# Usage:
+# python3 parse_timing.py logcat1.txt logcat2.txt 10 ts1 ts1 200
+#
+# Description: report the timing that the differences are bigger than 200
+#
+# Example:
+# python3 log_processing/parse_timing.py 8976224/logcat.txt 8879724/logcat.txt
+# "08-23 23:10:32.555" "07-29 06:39:06.254" 200
+def main():
+ if len(sys.argv) == 5:
+ process_one_log()
+ elif len(sys.argv) == 6:
+ compair_two_log()
+ else:
+ print("wrong number of arguments")
+
+def compair_two_log():
+ filepath1 = sys.argv[1]
+ print(filepath1)
+ if not os.path.isfile(filepath1):
+ print("File path {} does not exist. Exiting...".format(filepath1))
+ sys.exit()
+
+ filepath2 = sys.argv[2]
+ print(filepath2)
+ if not os.path.isfile(filepath2):
+ print("File path {} does not exist. Exiting...".format(filepath2))
+ sys.exit()
+
+ ts1 = datetime.strptime(sys.argv[3], '%m-%d %H:%M:%S.%f')
+ ts2 = datetime.strptime(sys.argv[4], '%m-%d %H:%M:%S.%f')
+ duration = float(sys.argv[5])*1000
+
+ # 1: took to complete 1000ms
+ # 2: took 33ms
+ # 3: took 33 ms or took 0.3 seconds
+ file1_events = [{}, {}, {}]
+ file2_events = [{}, {}, {}]
+
+ extract_events(filepath1, file1_events, ts1, duration)
+ extract_events(filepath2, file2_events, ts2, duration)
+
+
+ sum_events_timing(file1_events)
+ sum_events_timing(file2_events)
+
+ sum_all_events_timing_diff(file1_events, file2_events)
+
+ sys.exit()
+
+
+def process_one_log():
+ filepath = sys.argv[1]
+ print(filepath)
+ if not os.path.isfile(filepath):
+ print("File path {} does not exist. Exiting...".format(filepath))
+ sys.exit()
+
+ # 1: took to complete 1000ms
+ # 2: took 33ms
+ # 3: took 33 ms or took 0.3 seconds
+ events = [{}, {}, {}]
+ ts = datetime.strptime(sys.argv[2], '%m-%d %H:%M:%S.%f')
+ duration = float(sys.argv[3])*1000
+ extract_events(filepath, events, ts, duration)
+
+ timing = float(sys.argv[3])
+ print_sorted_all_events(events, timing)
+
+ sys.exit()
+
+def print_sorted_all_events(file_events, timing):
+ for i in range(len(file_events)):
+ print_sorted_events(file_events[i], timing)
+
+def print_sorted_events(events, timing):
+ for word in sorted(events, key=events.get, reverse=True):
+ if (events[word]) > timing:
+ print("event:{} \ttiming:{} ".format(word, events[word]))
+
+def sum_events_timing(events):
+ total_sum = 0;
+ for i in range(len(events)):
+ sum = 0
+ print("start summary for type {}".format(i))
+ for event in events[i]:
+ sum += events[i][event]
+ #print("event {} timing {} ".format(event, events[i][event]))
+ print("sum events type {} {} : timing {}".format(i, len(events), sum))
+ total_sum += sum
+ print("sum all type events timing {}\n".format(total_sum))
+
+def sum_events_timing_diff(type, file1_events, file2_events):
+ sum_diff = 0
+ max_diff = 0
+ regression_events = {}
+ print("start summary for type {}".format(type))
+ for event in file1_events:
+ val = file2_events.get(event)
+ if val != None:
+ diff = file1_events[event] - val
+ if diff > 100 and val > 100:
+ # print("regression event {} \t{}: {} \t{}: {} \tdiff: {}"
+ # .format(event, "case1", file1_events[event], "case2", val, diff))
+ regression_events[event] = diff
+ sum_diff += diff
+ max_diff = max(max_diff, diff)
+ print("\nsummary for timing type {} sum_diff {} max_diff {}".format(type, sum_diff, max_diff))
+ print_events(regression_events, file1_events, file2_events)
+
+def sum_all_events_timing_diff(file1_events, file2_events):
+ for i in range(len(file1_events)):
+ sum_events_timing_diff(i, file1_events[i], file2_events[i])
+
+def print_events(events, file1_events, file2_events):
+ for word in sorted(events, key=events.get, reverse=True):
+ if (events[word]) > 10:
+ print("{} \tdiff {} \t{} \t{}".format(word, events[word],file1_events[word], file2_events[word]))
+
+def find_took(words):
+ for i in range(len(words)):
+ if words[i] == 'took' or words[i] == "took:":
+ return i
+
+def extract_time(line, events):
+ if not "took" in line:
+ return
+
+ # 1: took to complete 1000ms
+ # 2: took 33ms
+ # 3: took 33 ms or took 0.3 seconds
+ words = line.strip().split(' ')
+ i = find_took(words)
+ index = 0;
+ str1 = " "
+ key = str1.join(words[8:i])
+
+ if words[i+1] == 'to' and words[i+2] == 'complete:':
+ index = 0;
+ val = float(words[i+3][:-2]);
+ elif words[i+1][-2:] == 'ms':
+ index = 1
+ val = float(words[i+1][:-2]);
+ elif len(words) > i+2:
+ index = 2
+ if words[i+2] == 'seconds':
+ val = float(words[i+1])*1000;
+ elif words[i+2] == 'ms':
+ val = float(words[i+1])
+ else:
+ return True
+
+ # print("index: {} key: {} val: {}".format(index, key, val));
+
+ if events[index].get(key) == None:
+ events[index][key] = val
+ return True
+ else:
+ # print("duplicate key: " + key + " line: " + line)
+ return True
+
+def check_time_range(line, ts, duration):
+ index = line.find(" ", 6)
+ if index <= 0:
+ return False
+
+ try:
+ current_time = datetime.strptime(line[:index], '%m-%d %H:%M:%S.%f')
+ except ValueError:
+ return False
+
+ deltatime = current_time - ts
+ if deltatime.total_seconds()*1000 < 0 or deltatime.total_seconds() > duration:
+ return False
+ return True
+
+def extract_events(filepath, events, ts, duration):
+ with open(filepath, errors='ignore') as fp:
+ for line in fp:
+ if check_time_range(line, ts, duration) == False:
+ continue
+ if extract_time(line, events) == False:
+ return
+
+
+if __name__ == '__main__':
+ main()
diff --git a/perf_tools/progress_report.py b/perf_tools/progress_report.py
new file mode 100755
index 00000000..f6a1c430
--- /dev/null
+++ b/perf_tools/progress_report.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import argparse
+from datetime import datetime
+import yaml
+import os
+import report_pb2
+import sys
+import traceback
+
+# Usage: python3 progress_report.py --logcat logcat.txt --config config.yaml --output_dir report_dir
+#
+# logcat.txt should contain the "boot_progress_start" and "boot_progress_enable_screen"".
+# config.yaml contains all the keywords to be extracted.
+# report_dir will contain three generated files:
+#
+# timestamp_log.txt: contains the same content as logcat.txt, but the timestamp is replaced
+# with relative time with boot_progress_start time.
+#
+# report_proto.txt: contains the report for the events related to the keywords.
+#
+# report.txt: contains logcat messages corresponding to the events captured in report_proto.txt
+
+def init_arguments():
+ parser = argparse.ArgumentParser(
+ prog = 'progrocess_report.py',
+ description='Extract timing information and generate a report.')
+ parser.add_argument(
+ '--logcat', type=str, required=True,
+ help = 'logcat file name')
+ parser.add_argument(
+ '--config', type=str, required=True,
+ help = 'configuration file for keywords')
+ parser.add_argument(
+ '--output_dir', type= str, required=True,
+ help = 'directory name to store the generated files')
+ return parser.parse_args()
+
+# Find boot_progress_start line and boot_progress_enable_screen find the time difference
+# return the start time string
+def find_boot_progress_start_end(fp):
+ start = ""
+ end = ""
+ for line in fp:
+ if "boot_progress_start" in line:
+ start = line
+ if "boot_progress_enable_screen" in line and len(start):
+ end = line
+ break
+
+ missing_error = ""
+ if start == "":
+ missing_error = "******logcat file missing boot_progress_start\n"
+ elif end == "":
+ missing_error += "******logcat file missing boot_progress_end "
+ if missing_error != "":
+ sys.exit("Missing required message in the logcat:\n" + missing_error)
+ return [start, end]
+
+# TODO(b/262259622): passing a tuple of (startDate, endDate)
+def replace_timestamp_abs(line, timestamp_str, date_time_obj0):
+ index = line.find(" ", 6)
+ if index <= 0:
+ return line
+ substr0 = line[:index]
+ substr1 = line[index:]
+
+ try:
+ date_time_obj = datetime.strptime(substr0, '%m-%d %H:%M:%S.%f')
+ except ValueError:
+ return line
+
+ date_time_delta = date_time_obj - date_time_obj0
+ date_time_delta_str = str(date_time_delta)
+ return date_time_delta_str + substr1
+
+def in_time_range(start, end, line):
+ try:
+ current_time = datetime.strptime(line[:18], '%m-%d %H:%M:%S.%f')
+ except ValueError:
+ return False
+
+ if current_time >= start and current_time <= end:
+ return True
+
+ return False
+
+# Here is an example of event we would like extract:
+# 09-15 16:04:15.655 root 991 991 I boot_progress_preload_start: 5440
+# for each event, it is a tuple of(timestamp, event_name, timing)
+def extract_event(line, keywords):
+ words = line.split(" ")
+ for keyword in keywords:
+ if keyword in words[-2]:
+ return (words[0], words[-2], words[-1])
+ return ()
+
+def write_to_new_file(timestamps, keywords, logcat_fp, timestamp_fixed_logcat_fp, report_fp,
+ report_proto_fp):
+ start_timestamp_obj = datetime.strptime(timestamps[0][:18], '%m-%d %H:%M:%S.%f')
+ end_timestamp_obj = datetime.strptime(timestamps[1][:18], '%m-%d %H:%M:%S.%f')
+ report = report_pb2.Report()
+ for line in logcat_fp:
+ ts_fixed_line = replace_timestamp_abs(line, timestamps[0][:18], start_timestamp_obj)
+ timestamp_fixed_logcat_fp.write(ts_fixed_line)
+ if in_time_range(start_timestamp_obj, end_timestamp_obj, line):
+ event = extract_event(ts_fixed_line, keywords)
+ if len(event) == 0:
+ continue
+
+ report_fp.write(ts_fixed_line)
+ record = report.record.add()
+ record.timestamp = event[0]
+ record.event = event[1]
+ record.timing = int(event[2])
+ report_proto_fp.write(str(report))
+
+def main():
+ args = init_arguments()
+
+ keywords = []
+ with open(args.config, 'r') as file:
+ keywords = yaml.safe_load(file)
+
+ if not os.path.isdir(args.output_dir):
+ os.mkdir(args.output_dir)
+ timestamp_fixed_logcat_fp = open(os.path.join(args.output_dir, "timestamp_fixed_log.txt"), 'w')
+ report_fp = open(os.path.join(args.output_dir, "report.txt"), 'w')
+ report_proto_fp = open(os.path.join(args.output_dir, "report_proto.txt"), 'w')
+ try:
+ with open(args.logcat, 'r', errors = 'ignore') as logcat_fp:
+ timestamps = find_boot_progress_start_end(logcat_fp)
+ logcat_fp.seek(0)
+ write_to_new_file(timestamps, keywords, logcat_fp, timestamp_fixed_logcat_fp, report_fp, report_proto_fp)
+ except Exception as e:
+ traceresult = traceback.format_exc()
+ print("Caught an exception: {}".format(traceback.format_exc()))
+
+ timestamp_fixed_logcat_fp.close()
+ report_fp.close()
+ report_proto_fp.close()
+
+if __name__ == '__main__':
+ main()
diff --git a/perf_tools/report.proto b/perf_tools/report.proto
new file mode 100644
index 00000000..fb9e839b
--- /dev/null
+++ b/perf_tools/report.proto
@@ -0,0 +1,21 @@
+syntax = "proto2";
+
+package report;
+
+message Keyword {
+ required string value = 1;
+}
+
+message Keywords {
+ repeated Keyword keyword = 1;
+}
+
+message Record {
+ required string timestamp = 1;
+ required string event = 2;
+ required int64 timing = 3;
+}
+
+message Report {
+ repeated Record record = 1;
+}
diff --git a/perf_tools/sbtpull.py b/perf_tools/sbtpull.py
new file mode 100755
index 00000000..c6af55ce
--- /dev/null
+++ b/perf_tools/sbtpull.py
@@ -0,0 +1,562 @@
+#!/usr/bin/python3
+#from calendar import c
+import sys
+import os
+import copy
+import argparse
+import statistics
+import glob
+import subprocess
+import re
+import time
+
+from string import digits
+
+class LogLine:
+ remove_digits = str.maketrans('', '', digits)
+ def __init__(self):
+ self.lineNum = 0
+ self.timeStamp = 0
+ self.delta = 0
+ self.deltaDiff = 0
+ self.text = "none"
+ self.textKey = "none"
+
+ def parse(self, index, line, priorTimeStamp):
+ _line = line.strip()
+ words = _line.split("]", 1)
+ timeString = words[0].strip(" [")
+ self.lineNum = index
+ self.timeStamp = float(timeString)
+ self.delta = self.timeStamp - priorTimeStamp
+ self.text = words[1][:150]
+ self.textKey = self.text.translate(self.remove_digits)
+ priorTimeStamp = self.timeStamp
+ return self
+
+ def getTextKey(self):
+ textKey = self.textKey
+ return textKey
+
+ def print(self):
+ print("I, {:5d}, T, {:8.4f}, D, {: .4f}, DD, ({: .4f}) {}".format(self.lineNum, self.timeStamp, self.delta, self.deltaDiff, self.text))
+
+ def toString(self):
+ return "I, {:5d}, T, {:8.4f}, D, {: .4f}, DD, ({: .4f}) {}".format(self.lineNum, self.timeStamp, self.delta, self.deltaDiff, self.text)
+
+def sortByDelta(item):
+ return item.delta
+
+def sortByTimeStamp(item):
+ return item.timeStamp
+
+class LogLineListStats:
+ def __init__(self):
+ self.numItems = 0
+ self.firstTimeStamp = 0
+ self.lastTimeStamp = 0
+ self.deltaSum = 0
+ self.deltaDiffSum = 0
+ self.status = "unknown"
+ self.name = "unknown"
+
+ def print(self):
+ print("Name {:25} NumItems {:4d} FirstTimeStamp {:.3f}, lastTimeStamp {:.3f}, deltaTime {:.3f} deltaSum {:.3f}, deltaDiffSum {:.3f} Status {}".format(self.name, self.numItems, self.firstTimeStamp, self.lastTimeStamp, (self.lastTimeStamp - self.firstTimeStamp), self.deltaSum, self.deltaDiffSum, self.status))
+
+ def add(self, _other):
+ if (_other.firstTimeStamp< self.firstTimeStamp):
+ self.firstTimeStamp = _other.firstTimeStamp
+
+ if (_other.lastTimeStamp > self.lastTimeStamp):
+ self.lastTimeStamp = _other.lastTimeStamp
+ self.deltaSum += _other.deltaSum
+
+
+# ------------------------------------------------------
+
+class LogLineList:
+
+ def __init__(self, _name= ""):
+ self.list = []
+ self.name = _name
+
+ def clear(self):
+ self.list.clear()
+
+ def append(self, item):
+ self.list.append(item)
+
+ def print(self, numItems=None):
+ printLineNum = 0
+ timeStart = 0
+ sumDelta = 0
+ sumDeltaDiff = 0
+ print("List: {}", self.name)
+ for item in self.list:
+ if (timeStart==0):
+ timeStart = item.timeStamp
+ timeOffset = item.timeStamp - timeStart
+ sumDelta += item.delta
+ sumDeltaDiff += item.deltaDiff
+ printLineNum += 1
+ printLine = "{:4d} {:.4f} {: .4f} ({: .4f}) | {} ".format(printLineNum, timeOffset, sumDelta, sumDeltaDiff, item.toString())
+ print(printLine)
+ if (numItems!=None):
+ numItems -= 1
+ if (numItems<=0):
+ break
+
+ def find(self, word):
+ itemList = []
+ for item in self.list:
+ if item.text.find(word) != -1:
+ itemList.append(item)
+ return itemList
+ def findFirst(self, word):
+ itemList = self.find(word)
+ if (itemList!=None):
+ if (len(itemList)>0):
+ return itemList[0]
+ return None
+
+ def findTextKey(self, textKey):
+ itemList = []
+ for item in self.list:
+ if item.getTextKey()==textKey:
+ itemList.append(item)
+ if (len(itemList)==0):
+ return None
+ return itemList[0]
+
+ def findItem(self, item):
+ textKey = item.getTextKey()
+ return self.findTextKey(textKey)
+
+ def findExactItem(self, item):
+ text = item.text
+ return self.find(text)
+
+ def filter(self, startKeyWord, endKeyWord, delta=0):
+ resultsList = LogLineList()
+ startTime = self.findFirst(startKeyWord).timeStamp
+ endTime = self.findFirst(endKeyWord).timeStamp
+ for item in self.list:
+ if ((item.timeStamp >= startTime) and (item.timeStamp<=endTime)):
+ if (item.timeStamp == startTime):
+ item.delta = 0
+ if ((item.delta > delta) or ((item.timeStamp == startTime))):
+ resultsList.append(item)
+ resultsList.name = self.name
+ return resultsList
+
+
+ def findCommon(self, otherList):
+ commonList = LogLineList()
+ commonList.name = self.name + "_common"
+ notCommonList = LogLineList()
+ notCommonList.name = self.name + "_notCommon"
+ numFoundItems = 0
+ numNotFoundItems = 0
+ for item in self.list:
+ dm1 = otherList.findExactItem(item)
+ _item = copy.deepcopy(item)
+ if dm1!=None:
+ commonList.append(_item)
+ numFoundItems += 1
+ else:
+ notCommonList.append(_item)
+ numNotFoundItems += 1
+ print("FindCommon {} {} {} {}".format(len(self.list), len(otherList.list), numFoundItems, numNotFoundItems ))
+ return commonList, notCommonList
+
+ def difference(self, otherList):
+ diffList = LogLineList()
+ diffList.name = otherList.name + "Diff"
+ for item in self.list:
+ thisItem = copy.deepcopy(item)
+ otherItem = otherList.findItem(item)
+ if (item.text.find("EXT4-fs (sda11): recovery complete")!=-1):
+ print("here")
+ if otherItem==None:
+ print("LogLineList::difference() !Error, other does not have {}".format(item.text))
+ else:
+ thisItem.deltaDiff = otherItem.delta - item.delta
+
+ diffList.append(thisItem)
+ return diffList
+
+ def analyze(self, checkPeriod = True, includeFirst = True):
+ numItems = 0
+ firstTimeStamp = 0
+ firstDelta = 0
+ lastTimeStamp = 0
+ deltaSum = 0
+ deltaDiffSum = 0
+ for item in self.list:
+ numItems += 1
+ deltaSum += item.delta
+ deltaDiffSum += item.deltaDiff
+ if firstTimeStamp==0:
+ firstTimeStamp = item.timeStamp
+ firstDelta = item.delta
+ deltaSum = 0
+ deltaDiffSum = 0
+ if (item.timeStamp<firstTimeStamp):
+ firstTimeStamp = item.timeStamp
+ firstDelta = item.delta
+
+ if (item.timeStamp > lastTimeStamp):
+ lastTimeStamp = item.timeStamp
+ timePeriod = lastTimeStamp - firstTimeStamp
+ status = "pass"
+ if (checkPeriod):
+ diff = timePeriod - deltaSum
+ if (abs(diff)>0.0001):
+ print("LogLineList::Analyze() {} ERROR! TimePeriod:{}, CumulativeDelta: {} ".format(self.name, timePeriod, deltaSum))
+ status = "ERROR"
+ logLineListStats = LogLineListStats()
+ logLineListStats.numItems = numItems
+ logLineListStats.firstTimeStamp = firstTimeStamp
+ logLineListStats.lastTimeStamp = lastTimeStamp
+ logLineListStats.deltaSum = deltaSum
+ logLineListStats.deltaDiffSum = deltaDiffSum
+ logLineListStats.status = status
+ logLineListStats.name = self.name
+ return logLineListStats
+
+ def addList(self, otherList):
+ self.list.extend(otherList.list)
+ self.list = sorted(self.list, key=sortByTimeStamp)
+
+
+class LogFile:
+ priorTimeStamp = 0.0
+ def __init__(self, _fileName = ""):
+ self.logLineList = LogLineList()
+ if (_fileName!=""):
+ self.load(_fileName)
+
+ def loadLines(self, lines):
+ logLineList = LogLineList()
+ for index, line in enumerate(lines):
+ logLine = LogLine().parse(index, line, self.priorTimeStamp)
+ self.priorTimeStamp = logLine.timeStamp
+ logLineList.append(logLine)
+ return logLineList
+
+ def load(self, _fileName):
+ self.name = _fileName
+ try:
+ file = open(_fileName, 'r')
+ lines = file.readlines()
+ self.logLineList = self.loadLines(lines)
+ file.close()
+ except:
+ print("Error, file '{}' does not exist".format(self.name))
+
+ def print(self, numItems=None):
+ self.logLineList.print(numItems)
+
+# -----------------------------------------------------
+
+class MetricSet:
+ def __init__(self, _names):
+ self.columnNames = _names
+ self.rowColArray = []
+ self.rowSum = []
+ self.rowMax = []
+ self.rowMin = []
+ self.rowStd = []
+ self.rowMedian = []
+ for col in self.columnNames:
+ self.rowSum.append(0)
+ self.rowMax.append(0)
+ self.rowMin.append(sys.maxsize)
+ self.rowStd.append(0)
+ self.rowMedian.append(0)
+
+ def appendSet(self, values):
+ self.rowColArray.append(values)
+
+ def print(self):
+ print("{}".format(" Line#"), end='')
+ for words in self.columnNames:
+ print(", '{}'".format(words), end='')
+ print("")
+
+ for row, values in enumerate(self.rowColArray):
+ print("{:6d}".format(row), end='')
+ for col, value in enumerate(values):
+ print(", {:.3f}".format(value), end='')
+ print("")
+
+ print("{}".format(" MAX"), end='')
+ for value in self.rowMax:
+ print(", {:.3f}".format(value), end='')
+ print("")
+
+
+ print("{}".format(" AVE"), end='')
+ for value in self.rowSum:
+ print(", {:.3f}".format(value), end='')
+ print("")
+
+ print("{}".format(" MIN"), end='')
+ for value in self.rowMin:
+ print(", {:2.3f}".format(value), end='')
+ print("")
+
+ print("{}".format(" STD"), end='')
+ for value in self.rowStd:
+ print(", {:2.3f}".format(value), end='')
+ print("")
+
+ print("{}".format("MEDIAN"), end='')
+ for value in self.rowMedian:
+ print(", {:2.3f}".format(value), end='')
+ print("")
+
+ def analyze(self):
+ stdCols = []
+ numCols = len(self.columnNames)
+ numRows = len(self.rowColArray)
+ for col in range(numCols):
+ stdCols.append([])
+
+ # compute sum
+ for row, values in enumerate(self.rowColArray):
+ for col, value in enumerate(values):
+ self.rowSum[col] += value
+ if value > self.rowMax[col]:
+ self.rowMax[col] = value
+ if value < self.rowMin[col]:
+ self.rowMin[col] = value
+
+ # compute std
+ for col in range(numCols):
+ for row in range(numRows):
+ try:
+ val = self.rowColArray[row][col]
+ stdCols[col].append(val)
+ except IndexError:
+ i = 3
+ for col, colList in enumerate(stdCols):
+ stdValue = 0
+ if (len(colList)>0):
+ stdValue = statistics.pstdev(colList)
+ stdMedian = statistics.median(colList)
+ self.rowStd[col] = stdValue
+ self.rowMedian[col] = stdMedian
+
+ #compute average
+ for col, value in enumerate(self.rowSum):
+ if numRows > 0:
+ self.rowSum[col] = self.rowSum[col] / numRows
+ else:
+ self.rowSum[col] = 0
+
+class AnalyzeFile:
+ initFirstTime = 0
+ initSecondTime = 0
+
+ def __init__(self, _fileName, _keyWords = ["init first", "init second", "boot_completed"]):
+ self.fileName = _fileName
+ self.logFile = LogFile(_fileName)
+ self.workingList = []
+ self.keyWords = _keyWords
+
+ def report(self):
+ print("-----------------------")
+ print("Reporting on '{}'".format(self.fileName))
+ for word in self.keyWords:
+ item = self.logFile.logLineList.findFirst(word)
+ item.print()
+ print("-----------------------")
+
+ def getMetrics(self, metricsSet):
+ values = []
+ for word in self.keyWords:
+ item = self.logFile.logLineList.findFirst(word)
+ if item is not None:
+ values.append(item.timeStamp)
+ else:
+ print("Did not find {} ".format(word))
+ metricsSet.appendSet(values)
+
+ def keyWordReport(self, keyword):
+ numItems = 0
+ cumd = 0
+ items = self.logFile.logLineList.find(keyword)
+ for item in items:
+ item.print()
+ numItems += 1
+ cumd += item.delta
+ print("Num {} found = {}, Sum delay = {:.2f} ".format(keyword, numItems, cumd))
+
+ for item in items:
+ lineKeywords = item.text.split(" ")
+ if (len(lineKeywords)>2):
+ if lineKeywords[2] == "Service":
+ tookIndex = item.text.find("took")
+ if (tookIndex!=None):
+ tookTime = item.text[tookIndex:tookIndex+10]
+ print("{} took {}".format(lineKeywords[3], tookTime))
+
+
+class Analyzer:
+ def __init__(self):
+ self.fileName = []
+
+ def rebootAndRunCmdToFile(self, fileNamePrefix, msgPrefix, Cmd, numTimes, startIndex):
+ captured = False
+ error = False
+ filenameNum = ""
+ for i in range(numTimes):
+ postfix = str(i+startIndex)
+ filenameNum = fileNamePrefix + "-" + postfix + ".txt"
+ print(msgPrefix + " to {}".format(filenameNum))
+ # try 5 times to capure status 'boot_completed'
+ for i in range(5):
+ captured = False
+ rebootCmd = "adb shell su root reboot"
+ fullCmd = Cmd + " > {}".format(filenameNum)
+ x = os.system(rebootCmd)
+ if (x!=0):
+ print("Error")
+ error = True
+ break
+ time.sleep(45)
+ x = os.system(fullCmd)
+ if (x!=0):
+ print("Error")
+ error = True
+ break
+ # check for boot complete
+ try:
+ checkBootComplete = "grep boot_complete {}".format(filenameNum)
+ output = subprocess.check_output(checkBootComplete, shell=True)
+ captured = True
+ break
+ except:
+ captured = False
+ print("trying again for {}".format(filenameNum))
+ if not captured:
+ print("ERROR - failed to capture {}".format(filenameNum))
+ if error:
+ os.system("rm {}".format(filenameNum))
+ return captured
+
+ def getBuildID(self):
+ buildIDCmd = "adb shell su root getprop ro.build.version.incremental"
+ buildString = subprocess.check_output(buildIDCmd, shell = True)
+ numberList = re.findall(r'\d+', buildString.decode('ISO-8859-1') )
+ if (numberList==None): return 0
+ if (len(numberList)==0): return 0
+ buildID = numberList[0]
+ return buildID
+
+ def pullDmesgLogs(self, BuildID, numTimes, startIndex):
+ fileNamePrefix = BuildID
+ msgPrefix = "Pulling Kernel dmesg logs"
+ cmd = "adb shell su root dmesg"
+ return self.rebootAndRunCmdToFile(fileNamePrefix, msgPrefix, cmd, numTimes, startIndex)
+
+ def pullLogcatLogs(self, BuildID, numTimes, startIndex):
+ fileNamePrefix = "LC-"+BuildID
+ msgPrefix = "Pulling Kernel Logcat"
+ cmd = "adb logcat -b all -d"
+ return self.rebootAndRunCmdToFile(fileNamePrefix, msgPrefix, cmd, numTimes, startIndex)
+
+ def runBootAnalyze(self, filename, numTimes, startIndex):
+ ABT = os.environ["ANDROID_BUILD_TOP"]
+ if (len(ABT)<=0):
+ print("ERROR - ANDROID_BUILD_TOP not set")
+ BAFILE = "BA-" + filename + "-" + str(numTimes + startIndex) + ".txt"
+ BACmd = ABT + "/system/extras/boottime_tools/bootanalyze/bootanalyze.py -c " + ABT + "/system/extras/boottime_tools/bootanalyze/config.yaml -n 20 -r -t > " + BAFILE
+ print(BACmd)
+ x = os.system(BACmd)
+ if (x!=0):
+ print("ERROR running bootanalze")
+ return False
+ return True
+
+ def pullAll(self):
+ BuildID = self.getBuildID()
+ Cmd = "adb bugreport bugreport-{}".format(BuildID)
+ print(Cmd)
+ x = os.system(Cmd)
+ if (x!=0):
+ print("ERROR Pulling all data")
+ return False
+ self.pullDmesgLogs(BuildID, 20, 0)
+ self.pullLogcatLogs(BuildID, 2, 0)
+ self.runBootAnalyze(BuildID, 20, 0)
+ self.summaryReportOnDmesgLogFiles(BuildID, 20)
+
+ def summaryReportOnDmesgLogFiles(self, BuildID, numFiles):
+ metricKeyWords = ["init first", "init second", "boot_completed"]
+ metricSet = MetricSet(metricKeyWords)
+ print("Summary report on log files with build ID {}".format(BuildID))
+ dirList = glob.glob("{}*.txt".format(BuildID))
+ numFilesAnalyzed = 0
+ for index, file in enumerate(dirList):
+ analyzeFile = AnalyzeFile(file, metricKeyWords)
+ #check it's a kernel log file
+ item = analyzeFile.logFile.logLineList.findFirst("build.fingerprint")
+ if (item!=None):
+ #check if it has the correct build ID
+ if (item.text.find(BuildID)==-1):
+ continue
+ else:
+ print("BuildID {} not found in file {} fingerprint {}".format(BuildID, file, item))
+ continue
+ analyzeFile.getMetrics(metricSet)
+ numFilesAnalyzed += 1
+ if ((index+1)>=numFiles):
+ break
+ if (numFilesAnalyzed>0):
+ metricSet.analyze()
+ metricSet.print()
+ else:
+ print("No files criteria {}* and build.fingerprint with {}".format(BuildID, BuildID))
+
+ def rename(self, BuildID1, BuildID2, fileType):
+ print("Summary report on log files with build ID {}".format(BuildID1))
+ dirList = glob.glob("*{}*".format(BuildID1))
+ for index, file in enumerate(dirList):
+ findRes = file.find(BuildID1)
+ if (findRes!=-1):
+ newFile = file.replace(BuildID1, BuildID2, 1)
+ newFile += fileType
+ os.system("mv {} {}".format(file, newFile))
+
+
+parser = argparse.ArgumentParser(description='pull all data files from seahawk and run dmesg summary report. The data files will be prefixed with the build ID')
+
+parser.add_argument("-plc", nargs=3, metavar=('<BuildID>', '<numTimes>', '<startIndex>'), help="pull logcat numTimes from seahawk")
+parser.add_argument("-pdm", nargs=3, metavar=('<BuildID>', '<numTimes>', '<startIndex>'), help="pull dmesg logs numTimes from seahawk")
+parser.add_argument("-pba", nargs=2, metavar=('<BuildID>', '<numTimes>'), help="pull bootanalyze numTimes from seahawk")
+parser.add_argument("-rd", nargs=2, metavar=('<BuildID>', '<numFiles>'), help="summary report on <numFiles> dmesg log files named <BuildID>-*.txt in current directory")
+parser.add_argument("-pA", action='store_true', help="pull all data from seahawk a default number of times")
+parser.add_argument("-t", nargs="*", help="test - do not use")
+args = parser.parse_args()
+
+
+if args.pdm!=None:
+ Analyzer().pullDmesgLogs(args.pdm[0], int(args.pdm[1]), int(args.pdm[2]))
+
+if args.plc!=None:
+ Analyzer().pullLogcatLogs(args.plc[0], int(args.plc[1]), int(args.plc[2]))
+
+if args.pba!=None:
+ Analyzer().runBootAnalyze(args.pba[0], int(args.pba[1]), 0)
+
+if args.pA!=None:
+ Analyzer().pullAll()
+
+if args.rd!=None:
+ Analyzer().summaryReportOnDmesgLogFiles(args.rd[0], int(args.rd[1]))
+
+if args.t!=None:
+ Analyzer().getBuildID()
+
diff --git a/power_profile/camera_avg/LICENSE b/power_profile/camera_avg/LICENSE
index 4f229463..35cf914e 100644
--- a/power_profile/camera_avg/LICENSE
+++ b/power_profile/camera_avg/LICENSE
@@ -1,6 +1,3 @@
-Apache License
---------------
-
Version 2.0, January 2004
http://www.apache.org/licenses/
@@ -201,447 +198,3 @@ Apache License
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.
-
-All image and audio files (including *.png, *.jpg, *.svg, *.mp3, *.wav
-and *.ogg) are licensed under the CC-BY-NC license. All other files are
-licensed under the Apache 2 license.
-
-CC-BY-NC License
-----------------
-
-Attribution-NonCommercial-ShareAlike 4.0 International
-
-=======================================================================
-
-Creative Commons Corporation ("Creative Commons") is not a law firm and
-does not provide legal services or legal advice. Distribution of
-Creative Commons public licenses does not create a lawyer-client or
-other relationship. Creative Commons makes its licenses and related
-information available on an "as-is" basis. Creative Commons gives no
-warranties regarding its licenses, any material licensed under their
-terms and conditions, or any related information. Creative Commons
-disclaims all liability for damages resulting from their use to the
-fullest extent possible.
-
-Using Creative Commons Public Licenses
-
-Creative Commons public licenses provide a standard set of terms and
-conditions that creators and other rights holders may use to share
-original works of authorship and other material subject to copyright
-and certain other rights specified in the public license below. The
-following considerations are for informational purposes only, are not
-exhaustive, and do not form part of our licenses.
-
- Considerations for licensors: Our public licenses are
- intended for use by those authorized to give the public
- permission to use material in ways otherwise restricted by
- copyright and certain other rights. Our licenses are
- irrevocable. Licensors should read and understand the terms
- and conditions of the license they choose before applying it.
- Licensors should also secure all rights necessary before
- applying our licenses so that the public can reuse the
- material as expected. Licensors should clearly mark any
- material not subject to the license. This includes other CC-
- licensed material, or material used under an exception or
- limitation to copyright. More considerations for licensors:
- wiki.creativecommons.org/Considerations_for_licensors
-
- Considerations for the public: By using one of our public
- licenses, a licensor grants the public permission to use the
- licensed material under specified terms and conditions. If
- the licensor's permission is not necessary for any reason--for
- example, because of any applicable exception or limitation to
- copyright--then that use is not regulated by the license. Our
- licenses grant only permissions under copyright and certain
- other rights that a licensor has authority to grant. Use of
- the licensed material may still be restricted for other
- reasons, including because others have copyright or other
- rights in the material. A licensor may make special requests,
- such as asking that all changes be marked or described.
- Although not required by our licenses, you are encouraged to
- respect those requests where reasonable. More_considerations
- for the public:
- wiki.creativecommons.org/Considerations_for_licensees
-
-=======================================================================
-
-Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
-Public License
-
-By exercising the Licensed Rights (defined below), You accept and agree
-to be bound by the terms and conditions of this Creative Commons
-Attribution-NonCommercial-ShareAlike 4.0 International Public License
-("Public License"). To the extent this Public License may be
-interpreted as a contract, You are granted the Licensed Rights in
-consideration of Your acceptance of these terms and conditions, and the
-Licensor grants You such rights in consideration of benefits the
-Licensor receives from making the Licensed Material available under
-these terms and conditions.
-
-
-Section 1 -- Definitions.
-
- a. Adapted Material means material subject to Copyright and Similar
- Rights that is derived from or based upon the Licensed Material
- and in which the Licensed Material is translated, altered,
- arranged, transformed, or otherwise modified in a manner requiring
- permission under the Copyright and Similar Rights held by the
- Licensor. For purposes of this Public License, where the Licensed
- Material is a musical work, performance, or sound recording,
- Adapted Material is always produced where the Licensed Material is
- synched in timed relation with a moving image.
-
- b. Adapter's License means the license You apply to Your Copyright
- and Similar Rights in Your contributions to Adapted Material in
- accordance with the terms and conditions of this Public License.
-
- c. BY-NC-SA Compatible License means a license listed at
- creativecommons.org/compatiblelicenses, approved by Creative
- Commons as essentially the equivalent of this Public License.
-
- d. Copyright and Similar Rights means copyright and/or similar rights
- closely related to copyright including, without limitation,
- performance, broadcast, sound recording, and Sui Generis Database
- Rights, without regard to how the rights are labeled or
- categorized. For purposes of this Public License, the rights
- specified in Section 2(b)(1)-(2) are not Copyright and Similar
- Rights.
-
- e. Effective Technological Measures means those measures that, in the
- absence of proper authority, may not be circumvented under laws
- fulfilling obligations under Article 11 of the WIPO Copyright
- Treaty adopted on December 20, 1996, and/or similar international
- agreements.
-
- f. Exceptions and Limitations means fair use, fair dealing, and/or
- any other exception or limitation to Copyright and Similar Rights
- that applies to Your use of the Licensed Material.
-
- g. License Elements means the license attributes listed in the name
- of a Creative Commons Public License. The License Elements of this
- Public License are Attribution, NonCommercial, and ShareAlike.
-
- h. Licensed Material means the artistic or literary work, database,
- or other material to which the Licensor applied this Public
- License.
-
- i. Licensed Rights means the rights granted to You subject to the
- terms and conditions of this Public License, which are limited to
- all Copyright and Similar Rights that apply to Your use of the
- Licensed Material and that the Licensor has authority to license.
-
- j. Licensor means the individual(s) or entity(ies) granting rights
- under this Public License.
-
- k. NonCommercial means not primarily intended for or directed towards
- commercial advantage or monetary compensation. For purposes of
- this Public License, the exchange of the Licensed Material for
- other material subject to Copyright and Similar Rights by digital
- file-sharing or similar means is NonCommercial provided there is
- no payment of monetary compensation in connection with the
- exchange.
-
- l. Share means to provide material to the public by any means or
- process that requires permission under the Licensed Rights, such
- as reproduction, public display, public performance, distribution,
- dissemination, communication, or importation, and to make material
- available to the public including in ways that members of the
- public may access the material from a place and at a time
- individually chosen by them.
-
- m. Sui Generis Database Rights means rights other than copyright
- resulting from Directive 96/9/EC of the European Parliament and of
- the Council of 11 March 1996 on the legal protection of databases,
- as amended and/or succeeded, as well as other essentially
- equivalent rights anywhere in the world.
-
- n. You means the individual or entity exercising the Licensed Rights
- under this Public License. Your has a corresponding meaning.
-
-
-Section 2 -- Scope.
-
- a. License grant.
-
- 1. Subject to the terms and conditions of this Public License,
- the Licensor hereby grants You a worldwide, royalty-free,
- non-sublicensable, non-exclusive, irrevocable license to
- exercise the Licensed Rights in the Licensed Material to:
-
- a. reproduce and Share the Licensed Material, in whole or
- in part, for NonCommercial purposes only; and
-
- b. produce, reproduce, and Share Adapted Material for
- NonCommercial purposes only.
-
- 2. Exceptions and Limitations. For the avoidance of doubt, where
- Exceptions and Limitations apply to Your use, this Public
- License does not apply, and You do not need to comply with
- its terms and conditions.
-
- 3. Term. The term of this Public License is specified in Section
- 6(a).
-
- 4. Media and formats; technical modifications allowed. The
- Licensor authorizes You to exercise the Licensed Rights in
- all media and formats whether now known or hereafter created,
- and to make technical modifications necessary to do so. The
- Licensor waives and/or agrees not to assert any right or
- authority to forbid You from making technical modifications
- necessary to exercise the Licensed Rights, including
- technical modifications necessary to circumvent Effective
- Technological Measures. For purposes of this Public License,
- simply making modifications authorized by this Section 2(a)
- (4) never produces Adapted Material.
-
- 5. Downstream recipients.
-
- a. Offer from the Licensor -- Licensed Material. Every
- recipient of the Licensed Material automatically
- receives an offer from the Licensor to exercise the
- Licensed Rights under the terms and conditions of this
- Public License.
-
- b. Additional offer from the Licensor -- Adapted Material.
- Every recipient of Adapted Material from You
- automatically receives an offer from the Licensor to
- exercise the Licensed Rights in the Adapted Material
- under the conditions of the Adapter's License You apply.
-
- c. No downstream restrictions. You may not offer or impose
- any additional or different terms or conditions on, or
- apply any Effective Technological Measures to, the
- Licensed Material if doing so restricts exercise of the
- Licensed Rights by any recipient of the Licensed
- Material.
-
- 6. No endorsement. Nothing in this Public License constitutes or
- may be construed as permission to assert or imply that You
- are, or that Your use of the Licensed Material is, connected
- with, or sponsored, endorsed, or granted official status by,
- the Licensor or others designated to receive attribution as
- provided in Section 3(a)(1)(A)(i).
-
- b. Other rights.
-
- 1. Moral rights, such as the right of integrity, are not
- licensed under this Public License, nor are publicity,
- privacy, and/or other similar personality rights; however, to
- the extent possible, the Licensor waives and/or agrees not to
- assert any such rights held by the Licensor to the limited
- extent necessary to allow You to exercise the Licensed
- Rights, but not otherwise.
-
- 2. Patent and trademark rights are not licensed under this
- Public License.
-
- 3. To the extent possible, the Licensor waives any right to
- collect royalties from You for the exercise of the Licensed
- Rights, whether directly or through a collecting society
- under any voluntary or waivable statutory or compulsory
- licensing scheme. In all other cases the Licensor expressly
- reserves any right to collect such royalties, including when
- the Licensed Material is used other than for NonCommercial
- purposes.
-
-
-Section 3 -- License Conditions.
-
-Your exercise of the Licensed Rights is expressly made subject to the
-following conditions.
-
- a. Attribution.
-
- 1. If You Share the Licensed Material (including in modified
- form), You must:
-
- a. retain the following if it is supplied by the Licensor
- with the Licensed Material:
-
- i. identification of the creator(s) of the Licensed
- Material and any others designated to receive
- attribution, in any reasonable manner requested by
- the Licensor (including by pseudonym if
- designated);
-
- ii. a copyright notice;
-
- iii. a notice that refers to this Public License;
-
- iv. a notice that refers to the disclaimer of
- warranties;
-
- v. a URI or hyperlink to the Licensed Material to the
- extent reasonably practicable;
-
- b. indicate if You modified the Licensed Material and
- retain an indication of any previous modifications; and
-
- c. indicate the Licensed Material is licensed under this
- Public License, and include the text of, or the URI or
- hyperlink to, this Public License.
-
- 2. You may satisfy the conditions in Section 3(a)(1) in any
- reasonable manner based on the medium, means, and context in
- which You Share the Licensed Material. For example, it may be
- reasonable to satisfy the conditions by providing a URI or
- hyperlink to a resource that includes the required
- information.
- 3. If requested by the Licensor, You must remove any of the
- information required by Section 3(a)(1)(A) to the extent
- reasonably practicable.
-
- b. ShareAlike.
-
- In addition to the conditions in Section 3(a), if You Share
- Adapted Material You produce, the following conditions also apply.
-
- 1. The Adapter's License You apply must be a Creative Commons
- license with the same License Elements, this version or
- later, or a BY-NC-SA Compatible License.
-
- 2. You must include the text of, or the URI or hyperlink to, the
- Adapter's License You apply. You may satisfy this condition
- in any reasonable manner based on the medium, means, and
- context in which You Share Adapted Material.
-
- 3. You may not offer or impose any additional or different terms
- or conditions on, or apply any Effective Technological
- Measures to, Adapted Material that restrict exercise of the
- rights granted under the Adapter's License You apply.
-
-
-Section 4 -- Sui Generis Database Rights.
-
-Where the Licensed Rights include Sui Generis Database Rights that
-apply to Your use of the Licensed Material:
-
- a. for the avoidance of doubt, Section 2(a)(1) grants You the right
- to extract, reuse, reproduce, and Share all or a substantial
- portion of the contents of the database for NonCommercial purposes
- only;
-
- b. if You include all or a substantial portion of the database
- contents in a database in which You have Sui Generis Database
- Rights, then the database in which You have Sui Generis Database
- Rights (but not its individual contents) is Adapted Material,
- including for purposes of Section 3(b); and
-
- c. You must comply with the conditions in Section 3(a) if You Share
- all or a substantial portion of the contents of the database.
-
-For the avoidance of doubt, this Section 4 supplements and does not
-replace Your obligations under this Public License where the Licensed
-Rights include other Copyright and Similar Rights.
-
-
-Section 5 -- Disclaimer of Warranties and Limitation of Liability.
-
- a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
- EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
- AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
- ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
- IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
- WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
- PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
- ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
- KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
- ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
-
- b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
- TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
- NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
- INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
- COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
- USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
- ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
- DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
- IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
-
- c. The disclaimer of warranties and limitation of liability provided
- above shall be interpreted in a manner that, to the extent
- possible, most closely approximates an absolute disclaimer and
- waiver of all liability.
-
-
-Section 6 -- Term and Termination.
-
- a. This Public License applies for the term of the Copyright and
- Similar Rights licensed here. However, if You fail to comply with
- this Public License, then Your rights under this Public License
- terminate automatically.
-
- b. Where Your right to use the Licensed Material has terminated under
- Section 6(a), it reinstates:
-
- 1. automatically as of the date the violation is cured, provided
- it is cured within 30 days of Your discovery of the
- violation; or
-
- 2. upon express reinstatement by the Licensor.
-
- For the avoidance of doubt, this Section 6(b) does not affect any
- right the Licensor may have to seek remedies for Your violations
- of this Public License.
-
- c. For the avoidance of doubt, the Licensor may also offer the
- Licensed Material under separate terms or conditions or stop
- distributing the Licensed Material at any time; however, doing so
- will not terminate this Public License.
-
- d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
- License.
-
-
-Section 7 -- Other Terms and Conditions.
-
- a. The Licensor shall not be bound by any additional or different
- terms or conditions communicated by You unless expressly agreed.
-
- b. Any arrangements, understandings, or agreements regarding the
- Licensed Material not stated herein are separate from and
- independent of the terms and conditions of this Public License.
-
-
-Section 8 -- Interpretation.
-
- a. For the avoidance of doubt, this Public License does not, and
- shall not be interpreted to, reduce, limit, restrict, or impose
- conditions on any use of the Licensed Material that could lawfully
- be made without permission under this Public License.
-
- b. To the extent possible, if any provision of this Public License is
- deemed unenforceable, it shall be automatically reformed to the
- minimum extent necessary to make it enforceable. If the provision
- cannot be reformed, it shall be severed from this Public License
- without affecting the enforceability of the remaining terms and
- conditions.
-
- c. No term or condition of this Public License will be waived and no
- failure to comply consented to unless expressly agreed to by the
- Licensor.
-
- d. Nothing in this Public License constitutes or may be interpreted
- as a limitation upon, or waiver of, any privileges and immunities
- that apply to the Licensor or You, including from the legal
- processes of any jurisdiction or authority.
-
-=======================================================================
-
-Creative Commons is not a party to its public licenses.
-Notwithstanding, Creative Commons may elect to apply one of its public
-licenses to material it publishes and in those instances will be
-considered the "Licensor." Except for the limited purpose of indicating
-that material is shared under a Creative Commons public license or as
-otherwise permitted by the Creative Commons policies published at
-creativecommons.org/policies, Creative Commons does not authorize the
-use of the trademark "Creative Commons" or any other trademark or logo
-of Creative Commons without its prior written consent including,
-without limitation, in connection with any unauthorized modifications
-to any of its public licenses or any other arrangements,
-understandings, or agreements concerning use of licensed material. For
-the avoidance of doubt, this paragraph does not form part of the public
-licenses.
-
-Creative Commons may be contacted at creativecommons.org.
-
diff --git a/profcollectd/Android.bp b/profcollectd/Android.bp
index 122e4f61..787ca22b 100644
--- a/profcollectd/Android.bp
+++ b/profcollectd/Android.bp
@@ -31,9 +31,21 @@ license {
],
}
+rust_defaults {
+ name: "profcollectd_defaults",
+ arch: {
+ riscv64: {
+ // libprofcollectd doesn't build for riscv64
+ enabled: false,
+ },
+ },
+}
+
rust_binary {
name: "profcollectctl",
+ defaults: ["profcollectd_defaults"],
+
srcs: ["profcollectctl.rs"],
rustlibs: [
@@ -45,6 +57,8 @@ rust_binary {
rust_binary {
name: "profcollectd",
+ defaults: ["profcollectd_defaults"],
+
srcs: ["profcollectd.rs"],
rustlibs: [
diff --git a/profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl b/profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl
index bfc24446..07699309 100644
--- a/profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl
+++ b/profcollectd/binder/com/android/server/profcollect/IProfCollectd.aidl
@@ -24,7 +24,8 @@ interface IProfCollectd {
void terminate();
void trace_once(@utf8InCpp String tag);
void process();
- @utf8InCpp String report();
+ /** -1 if there is no usageSetting */
+ @utf8InCpp String report(int usageSetting);
@utf8InCpp String get_supported_provider();
void registerProviderStatusCallback(IProviderStatusCallback cb);
}
diff --git a/profcollectd/libprofcollectd/config.rs b/profcollectd/libprofcollectd/config.rs
index d68f02e7..2888aab7 100644
--- a/profcollectd/libprofcollectd/config.rs
+++ b/profcollectd/libprofcollectd/config.rs
@@ -40,6 +40,7 @@ lazy_static! {
pub static ref REPORT_OUTPUT_DIR: &'static Path = Path::new("/data/misc/profcollectd/report/");
pub static ref CONFIG_FILE: &'static Path =
Path::new("/data/misc/profcollectd/output/config.json");
+ pub static ref LOG_FILE: &'static Path = Path::new("/data/misc/profcollectd/output/trace.log");
}
/// Dynamic configs, stored in config.json.
@@ -151,7 +152,7 @@ pub fn clear_data() -> Result<()> {
read_dir(path)?
.filter_map(|e| e.ok())
.map(|e| e.path())
- .filter(|e| e.is_file())
+ .filter(|e| e.is_file() && e != *LOG_FILE)
.try_for_each(remove_file)?;
Ok(())
}
diff --git a/profcollectd/libprofcollectd/lib.rs b/profcollectd/libprofcollectd/lib.rs
index da178f27..c0e12e32 100644
--- a/profcollectd/libprofcollectd/lib.rs
+++ b/profcollectd/libprofcollectd/lib.rs
@@ -114,7 +114,7 @@ pub fn process() -> Result<()> {
/// Process traces and report profile.
pub fn report() -> Result<String> {
- Ok(get_profcollectd_service()?.report()?)
+ Ok(get_profcollectd_service()?.report(report::NO_USAGE_SETTING)?)
}
/// Clear all local data.
diff --git a/profcollectd/libprofcollectd/logging_trace_provider.rs b/profcollectd/libprofcollectd/logging_trace_provider.rs
index fda4c66a..d9fd35e9 100644
--- a/profcollectd/libprofcollectd/logging_trace_provider.rs
+++ b/profcollectd/libprofcollectd/logging_trace_provider.rs
@@ -36,7 +36,7 @@ impl TraceProvider for LoggingTraceProvider {
true
}
- fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration) {
+ fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration, binary_filter: &str) {
let trace_file = trace_provider::get_path(trace_dir, tag, LOGGING_TRACEFILE_EXTENSION);
log::info!(
@@ -51,6 +51,9 @@ impl TraceProvider for LoggingTraceProvider {
log::info!("Process event triggered");
Ok(())
}
+
+ fn set_log_file(&self, filename: &Path) {}
+ fn reset_log_file(&self) {}
}
impl LoggingTraceProvider {
diff --git a/profcollectd/libprofcollectd/report.rs b/profcollectd/libprofcollectd/report.rs
index 69dff0c6..cbce1d61 100644
--- a/profcollectd/libprofcollectd/report.rs
+++ b/profcollectd/libprofcollectd/report.rs
@@ -32,11 +32,18 @@ use zip::ZipWriter;
use crate::config::Config;
+pub const NO_USAGE_SETTING: i32 = -1;
+
lazy_static! {
pub static ref UUID_CONTEXT: Context = Context::new(0);
}
-pub fn pack_report(profile: &Path, report: &Path, config: &Config) -> Result<String> {
+pub fn pack_report(
+ profile: &Path,
+ report: &Path,
+ config: &Config,
+ usage_setting: i32,
+) -> Result<String> {
let mut report = PathBuf::from(report);
let report_filename = get_report_filename(&config.node_id)?;
report.push(&report_filename);
@@ -67,9 +74,14 @@ pub fn pack_report(profile: &Path, report: &Path, config: &Config) -> Result<Str
let mut f = File::open(e)?;
let mut buffer = Vec::new();
f.read_to_end(&mut buffer)?;
- zip.write_all(&*buffer)?;
+ zip.write_all(&buffer)?;
Ok(())
})?;
+
+ if usage_setting != NO_USAGE_SETTING {
+ zip.start_file("usage_setting", options)?;
+ zip.write_all(usage_setting.to_string().as_bytes())?;
+ }
zip.finish()?;
Ok(report_filename)
@@ -79,14 +91,17 @@ fn get_report_filename(node_id: &MacAddr6) -> Result<String> {
let since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
let ts =
Timestamp::from_unix(&*UUID_CONTEXT, since_epoch.as_secs(), since_epoch.subsec_nanos());
- let uuid = Uuid::new_v1(ts, node_id.as_bytes())?;
+ let uuid = Uuid::new_v1(
+ ts,
+ node_id.as_bytes().try_into().expect("Invalid number of bytes in V1 UUID"),
+ );
Ok(uuid.to_string())
}
/// Get report creation timestamp through its filename (version 1 UUID).
pub fn get_report_ts(filename: &str) -> Result<SystemTime> {
let uuid_ts = Uuid::parse_str(filename)?
- .to_timestamp()
+ .get_timestamp()
.ok_or_else(|| anyhow!("filename is not a valid V1 UUID."))?
.to_unix();
Ok(SystemTime::UNIX_EPOCH + Duration::new(uuid_ts.0, uuid_ts.1))
diff --git a/profcollectd/libprofcollectd/scheduler.rs b/profcollectd/libprofcollectd/scheduler.rs
index f58c4995..5558f581 100644
--- a/profcollectd/libprofcollectd/scheduler.rs
+++ b/profcollectd/libprofcollectd/scheduler.rs
@@ -25,7 +25,7 @@ use std::sync::Mutex;
use std::thread;
use std::time::{Duration, Instant};
-use crate::config::{Config, PROFILE_OUTPUT_DIR, TRACE_OUTPUT_DIR};
+use crate::config::{Config, LOG_FILE, PROFILE_OUTPUT_DIR, TRACE_OUTPUT_DIR};
use crate::trace_provider::{self, TraceProvider};
use anyhow::{anyhow, ensure, Context, Result};
@@ -41,6 +41,7 @@ pub struct Scheduler {
impl Scheduler {
pub fn new() -> Result<Self> {
let p = trace_provider::get_trace_provider()?;
+ p.lock().map_err(|e| anyhow!(e.to_string()))?.set_log_file(&LOG_FILE);
Ok(Scheduler {
termination_ch: None,
trace_provider: p,
@@ -68,11 +69,12 @@ impl Scheduler {
Ok(_) => break,
Err(_) => {
// Did not receive a termination signal, initiate trace event.
- if check_space_limit(*TRACE_OUTPUT_DIR, &config).unwrap() {
+ if check_space_limit(&TRACE_OUTPUT_DIR, &config).unwrap() {
trace_provider.lock().unwrap().trace(
&TRACE_OUTPUT_DIR,
"periodic",
&config.sampling_period,
+ &config.binary_filter,
);
}
}
@@ -94,8 +96,13 @@ impl Scheduler {
pub fn one_shot(&self, config: &Config, tag: &str) -> Result<()> {
let trace_provider = self.trace_provider.clone();
- if check_space_limit(*TRACE_OUTPUT_DIR, config)? {
- trace_provider.lock().unwrap().trace(&TRACE_OUTPUT_DIR, tag, &config.sampling_period);
+ if check_space_limit(&TRACE_OUTPUT_DIR, config)? {
+ trace_provider.lock().unwrap().trace(
+ &TRACE_OUTPUT_DIR,
+ tag,
+ &config.sampling_period,
+ &config.binary_filter,
+ );
}
Ok(())
}
@@ -158,6 +165,17 @@ impl Scheduler {
}
});
}
+
+ pub fn clear_trace_log(&self) -> Result<()> {
+ let provider = self.trace_provider.lock().map_err(|e| anyhow!(e.to_string()))?;
+ provider.reset_log_file();
+ let mut result = Ok(());
+ if LOG_FILE.exists() {
+ result = fs::remove_file(*LOG_FILE).map_err(|e| anyhow!(e));
+ }
+ provider.set_log_file(&LOG_FILE);
+ result
+ }
}
/// Run if space usage is under limit.
diff --git a/profcollectd/libprofcollectd/service.rs b/profcollectd/libprofcollectd/service.rs
index 3f338024..3188888f 100644
--- a/profcollectd/libprofcollectd/service.rs
+++ b/profcollectd/libprofcollectd/service.rs
@@ -79,11 +79,11 @@ impl IProfCollectd for ProfcollectdBinderService {
.context("Failed to process profiles.")
.map_err(err_to_binder_status)
}
- fn report(&self) -> BinderResult<String> {
+ fn report(&self, usage_setting: i32) -> BinderResult<String> {
self.process()?;
let lock = &mut *self.lock();
- pack_report(&PROFILE_OUTPUT_DIR, &REPORT_OUTPUT_DIR, &lock.config)
+ pack_report(&PROFILE_OUTPUT_DIR, &REPORT_OUTPUT_DIR, &lock.config, usage_setting)
.context("Failed to create profile report.")
.map_err(err_to_binder_status)
}
@@ -131,7 +131,8 @@ impl ProfcollectdBinderService {
log::info!("Config change detected, resetting profcollect.");
clear_data()?;
- write(*CONFIG_FILE, &new_config.to_string())?;
+ write(*CONFIG_FILE, new_config.to_string())?;
+ new_scheduler.clear_trace_log()?;
}
// Clear profile reports out of rentention period.
diff --git a/profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs b/profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs
index f25f5ffc..2239a182 100644
--- a/profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs
+++ b/profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs
@@ -38,14 +38,17 @@ impl TraceProvider for SimpleperfEtmTraceProvider {
simpleperf_profcollect::has_device_support()
}
- fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration) {
+ fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration, binary_filter: &str) {
let trace_file = trace_provider::get_path(trace_dir, tag, ETM_TRACEFILE_EXTENSION);
+ // Record ETM data for kernel space only when it's not filtered out by binary_filter. So we
+ // can get more ETM data for user space when ETM data for kernel space isn't needed.
+ let record_scope = if binary_filter.contains("kernel") {
+ simpleperf_profcollect::RecordScope::BOTH
+ } else {
+ simpleperf_profcollect::RecordScope::USERSPACE
+ };
- simpleperf_profcollect::record(
- &*trace_file,
- sampling_period,
- simpleperf_profcollect::RecordScope::BOTH,
- );
+ simpleperf_profcollect::record(&trace_file, sampling_period, binary_filter, record_scope);
}
fn process(&self, trace_dir: &Path, profile_dir: &Path, binary_filter: &str) -> Result<()> {
@@ -76,6 +79,14 @@ impl TraceProvider for SimpleperfEtmTraceProvider {
.filter(is_etm_extension)
.try_for_each(process_trace_file)
}
+
+ fn set_log_file(&self, filename: &Path) {
+ simpleperf_profcollect::set_log_file(filename);
+ }
+
+ fn reset_log_file(&self) {
+ simpleperf_profcollect::reset_log_file();
+ }
}
impl SimpleperfEtmTraceProvider {
diff --git a/profcollectd/libprofcollectd/trace_provider.rs b/profcollectd/libprofcollectd/trace_provider.rs
index 13059199..133fa5cb 100644
--- a/profcollectd/libprofcollectd/trace_provider.rs
+++ b/profcollectd/libprofcollectd/trace_provider.rs
@@ -30,8 +30,10 @@ use crate::logging_trace_provider::LoggingTraceProvider;
pub trait TraceProvider {
fn get_name(&self) -> &'static str;
fn is_ready(&self) -> bool;
- fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration);
+ fn trace(&self, trace_dir: &Path, tag: &str, sampling_period: &Duration, binary_filter: &str);
fn process(&self, trace_dir: &Path, profile_dir: &Path, binary_filter: &str) -> Result<()>;
+ fn set_log_file(&self, filename: &Path);
+ fn reset_log_file(&self);
}
pub fn get_trace_provider() -> Result<Arc<Mutex<dyn TraceProvider + Send>>> {
diff --git a/puncture_fs/Android.bp b/puncture_fs/Android.bp
deleted file mode 100644
index aef7ba43..00000000
--- a/puncture_fs/Android.bp
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (C) 2014 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
- default_applicable_licenses: ["system_extras_puncture_fs_license"],
-}
-
-// Added automatically by a large-scale-change
-// See: http://go/android-license-faq
-license {
- name: "system_extras_puncture_fs_license",
- visibility: [":__subpackages__"],
- license_kinds: [
- "SPDX-license-identifier-Apache-2.0",
- ],
- license_text: [
- "NOTICE",
- ],
-}
-
-cc_binary {
- name: "puncture_fs",
-
- srcs: ["puncture_fs.cpp"],
- cflags: [
- "-Wall",
- "-Werror",
- ],
-
- shared_libs: [
- "libc",
- "liblog",
- "liblogwrap",
- ],
-}
diff --git a/puncture_fs/NOTICE b/puncture_fs/NOTICE
deleted file mode 100644
index 316b4eb5..00000000
--- a/puncture_fs/NOTICE
+++ /dev/null
@@ -1,190 +0,0 @@
-
- Copyright (c) 2014, 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/puncture_fs/puncture_fs.cpp b/puncture_fs/puncture_fs.cpp
deleted file mode 100644
index 9c01e3b0..00000000
--- a/puncture_fs/puncture_fs.cpp
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright (C) 2014 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 <errno.h>
-#include <fcntl.h>
-#include <getopt.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <logwrap/logwrap.h>
-#include <sys/stat.h>
-#include <sys/statvfs.h>
-#include <utils/Log.h>
-
-#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
-
-#define MAX_IO_WRITE_CHUNK_SIZE 0x100000
-
-#ifndef min
-#define min(a,b) ((a) < (b) ? (a) : (b))
-#endif
-
-typedef unsigned long u64;
-
-static void usage(const char * const progname) {
- fprintf(stderr,
- "Usage: %s [-s <seed>] -h <hole size in bytes> -t <total hole size in bytes> "
- "path\n",
- progname);
-}
-
-static u64 get_free_space(const char * const path) {
- struct statvfs s;
-
- if (statvfs(path, &s) < 0) {
- fprintf(stderr, "\nerrno: %d. Failed to get free disk space on %s\n",
- errno, path);
- return 0;
- } else {
- return (u64)s.f_bsize * (u64)s.f_bfree;
- }
-}
-
-static u64 get_random_num(const u64 start, const u64 end) {
- if (end - start <= 0)
- return start;
- assert(RAND_MAX >= 0x7FFFFFFF);
- if ((end - start) > 0x7FFFFFFF)
- return start + (((u64)random() << 31) | (u64)random()) % (end - start);
- return start + (random() % (end - start));
-}
-
-static char get_random_char() {
- return 'A' + random() % ('Z' - 'A');
-}
-
-static bool create_unique_file(const char * const dir_path, const u64 size,
- const u64 id, char * const base,
- const u64 base_length) {
- u64 length = 0;
- int fd;
- char file_path[FILENAME_MAX];
- bool ret = true;
-
- base[random() % min(base_length, size)] = get_random_char();
-
- sprintf(file_path, "%s/file_%lu", dir_path, id);
- fd = open(file_path, O_WRONLY | O_CREAT | O_SYNC, 0777);
- if (fd < 0) {
- // We suppress ENOSPC erros as that is common as we approach the
- // last few MBs of the fs as we don't account for the size of the newly
- // added meta data after the initial free space computation.
- if (errno != 28) {
- fprintf(stderr, "\nerrno: %d. Failed to create %s\n", errno, file_path);
- }
- return false;
- }
- while (length + base_length < size) {
- if (write(fd, base, base_length) < 0) {
- if (errno != 28) {
- fprintf(stderr, "\nerrno: %d. Failed to write %lu bytes to %s\n",
- errno, base_length, file_path);
- }
- ret = false;
- goto done;
- }
- length += base_length;
- }
- if (write(fd, base, size - length) < 0) {
- if (errno != 28) {
- fprintf(stderr, "\nerrno: %d. Failed to write last %lu bytes to %s\n",
- errno, size - length, file_path);
- }
- ret = false;
- goto done;
- }
-done:
- if (close(fd) < 0) {
- fprintf(stderr, "\nFailed to close %s\n", file_path);
- ret = false;
- }
- return ret;
-}
-
-static bool create_unique_dir(char *dir, const char * const root_path) {
- char random_string[15];
- int i;
-
- for (i = 0; i < 14; ++i) {
- random_string[i] = get_random_char();
- }
- random_string[14] = '\0';
-
- sprintf(dir, "%s/%s", root_path, random_string);
-
- if (mkdir(dir, 0777) < 0) {
- fprintf(stderr, "\nerrno: %d. Failed to create %s\n", errno, dir);
- return false;
- }
- return true;
-}
-
-static bool puncture_fs (const char * const path, const u64 total_size,
- const u64 hole_size, const u64 total_hole_size) {
- u64 increments = (hole_size * total_size) / total_hole_size;
- u64 hole_max;
- 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", ""};
- u64 file_id = 1;
- char *base_file_data;
- u64 i = 0;
-
- if (!create_unique_dir(stay_dir, path) ||
- !create_unique_dir(delete_dir, path)) {
- return false;
- }
-
- base_file_data = (char*) malloc(MAX_IO_WRITE_CHUNK_SIZE);
- for (i = 0; i < MAX_IO_WRITE_CHUNK_SIZE; ++i) {
- base_file_data[i] = get_random_char();
- }
- fprintf(stderr, "\n");
- while (ending_max <= total_size) {
- fprintf(stderr, "\rSTAGE 1/2: %d%% Complete",
- (int) (100.0 * starting_max / total_size));
- hole_max = get_random_num(starting_max, ending_max);
-
- do {
- hole_max = get_random_num(starting_max, ending_max);
- } while (hole_max == starting_max);
-
- create_unique_file(stay_dir,
- hole_max - starting_max,
- file_id++,
- base_file_data,
- MAX_IO_WRITE_CHUNK_SIZE);
- create_unique_file(delete_dir,
- hole_size,
- file_id++,
- base_file_data,
- MAX_IO_WRITE_CHUNK_SIZE);
-
- starting_max = hole_max + hole_size;
- ending_max += increments;
- }
- create_unique_file(stay_dir,
- (ending_max - increments - starting_max),
- file_id++,
- base_file_data,
- MAX_IO_WRITE_CHUNK_SIZE);
- fprintf(stderr, "\rSTAGE 1/2: 100%% Complete\n");
- 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) {
- fprintf(stderr, "\nFailed to delete %s\n", rm_bin_argv[2]);
- return false;
- }
- fprintf(stderr, "\rSTAGE 2/2: 100%% Complete\n");
- return true;
-}
-
-int main (const int argc, char ** const argv) {
- int opt;
- int mandatory_opt;
- char *path = NULL;
- int seed = time(NULL);
-
- u64 total_size = 0;
- u64 hole_size = 0;
- u64 total_hole_size = 0;
-
- mandatory_opt = 2;
- while ((opt = getopt(argc, argv, "s:h:t:")) != -1) {
- switch(opt) {
- case 's':
- seed = atoi(optarg);
- break;
- case 'h':
- hole_size = atoll(optarg);
- mandatory_opt--;
- break;
- case 't':
- total_hole_size = atoll(optarg);
- mandatory_opt--;
- break;
- default:
- usage(argv[0]);
- exit(EXIT_FAILURE);
- }
- }
- if (mandatory_opt) {
- usage(argv[0]);
- exit(EXIT_FAILURE);
- }
- if (optind >= argc) {
- fprintf(stderr, "\nExpected path name after options.\n");
- usage(argv[0]);
- exit(EXIT_FAILURE);
- }
- path = argv[optind++];
-
- if (optind < argc) {
- fprintf(stderr, "\nUnexpected argument: %s\n", argv[optind]);
- usage(argv[0]);
- exit(EXIT_FAILURE);
- }
-
- srandom(seed);
- fprintf(stderr, "\nRandom seed is: %d\n", seed);
-
- total_size = get_free_space(path);
- if (!total_size) {
- exit(EXIT_FAILURE);
- }
- if (total_size < total_hole_size || total_hole_size < hole_size) {
- fprintf(stderr, "\nInvalid sizes: total available size should be "
- "larger than total hole size which is larger than "
- "hole size\n");
- exit(EXIT_FAILURE);
- }
-
- if (!puncture_fs(path, total_size, hole_size, total_hole_size)) {
- exit(EXIT_FAILURE);
- }
- return 0;
-}
diff --git a/simpleperf/Android.bp b/simpleperf/Android.bp
index 58943931..475213b3 100644
--- a/simpleperf/Android.bp
+++ b/simpleperf/Android.bp
@@ -65,6 +65,11 @@ cc_defaults {
local_include_dirs: ["nonlinux_support/include"],
},
},
+ arch: {
+ riscv64: {
+ enabled: false,
+ },
+ },
}
// linked as a separate library because using OpenCSD headers needs to enable exception
@@ -98,6 +103,20 @@ cc_library_static {
},
}
+// Build regex support in a separate library to catch std::regex_error exception.
+cc_library_static {
+ name: "libsimpleperf_regex",
+ host_supported: true,
+ srcs: ["RegEx.cpp"],
+ cppflags: ["-fexceptions"],
+ static_libs: ["libbase"],
+ target: {
+ windows: {
+ enabled: true,
+ },
+ },
+}
+
cc_defaults {
name: "simpleperf_static_libs",
defaults: [
@@ -107,6 +126,7 @@ cc_defaults {
host_supported: true,
static_libs: [
"libsimpleperf_etm_decoder",
+ "libsimpleperf_regex",
"libbase",
"liblog",
"libutils",
@@ -121,11 +141,7 @@ cc_defaults {
"libprocinfo",
"libevent",
"libc++fs",
- ],
- },
- android: {
- static_libs: [
- "libc",
+ "librustc_demangle_static",
],
},
linux_glibc_x86_64: {
@@ -156,6 +172,7 @@ cc_defaults {
],
static_libs: [
"libsimpleperf_etm_decoder",
+ "libsimpleperf_regex",
"libopencsd_decoder",
],
target: {
@@ -170,6 +187,7 @@ cc_defaults {
static_libs: [
"libc++fs",
"libdexfile_support",
+ "librustc_demangle_static",
],
runtime_libs: [
"libdexfile", // libdexfile_support dependency
@@ -224,6 +242,7 @@ cc_defaults {
"command.cpp",
"dso.cpp",
"etm_branch_list.proto",
+ "ETMBranchListFile.cpp",
"event_attr.cpp",
"event_type.cpp",
"kallsyms.cpp",
@@ -389,6 +408,9 @@ cc_binary {
target: {
android: {
static_executable: true,
+ static_libs: [
+ "libc",
+ ],
},
android_arm: {
dist: {
@@ -576,6 +598,7 @@ cc_defaults {
"read_elf_test.cpp",
"read_symbol_map_test.cpp",
"RecordFilter_test.cpp",
+ "RegEx_test.cpp",
"record_file_test.cpp",
"record_test.cpp",
"report_utils_test.cpp",
@@ -684,6 +707,7 @@ cc_library_static {
whole_static_libs: [
"libgmock",
"libsimpleperf",
+ "libsimpleperf_regex",
],
}
@@ -709,19 +733,6 @@ cc_test {
},
}
-python_library_host {
- name: "simpleperf_report_lib",
- srcs: [
- "scripts/simpleperf_report_lib.py",
- "scripts/simpleperf_utils.py",
- ],
- data: [
- "scripts/bin/darwin/x86_64/libsimpleperf_report.dylib",
- "scripts/bin/linux/x86_64/libsimpleperf_report.so",
- "scripts/bin/windows/**/*.dll",
- ],
-}
-
filegroup {
name: "system-extras-simpleperf-testdata",
srcs: ["CtsSimpleperfTestCases_testdata/**/*"],
@@ -730,7 +741,7 @@ filegroup {
cc_fuzz {
name: "libsimpleperf_report_fuzzer",
defaults: [
- "simpleperf_shared_libs",
+ "simpleperf_static_libs",
],
host_supported: true,
srcs: [
@@ -738,17 +749,15 @@ cc_fuzz {
],
static_libs: [
"libsimpleperf",
- "libLLVMObject",
- "libLLVMBitReader",
- "libLLVMMC",
- "libLLVMMCParser",
- "libLLVMCore",
- "libLLVMSupport",
],
target: {
+ linux: {
+ // Fuzzer may not be able to load libdexfile. So statically link it.
+ static_libs: ["libdexfile_static"],
+ },
windows: {
enabled: false,
},
},
- corpus: ["testdata/*.data"],
+ corpus: ["testdata/**/*.data"],
}
diff --git a/simpleperf/Android.mk b/simpleperf/Android.mk
index 954c28c0..5c443e90 100644
--- a/simpleperf/Android.mk
+++ b/simpleperf/Android.mk
@@ -17,7 +17,10 @@ LOCAL_PATH := $(call my-dir)
# simpleperf_script.zip (for release in ndk)
# ============================================================
-SIMPLEPERF_SCRIPT_LIST := \
+SIMPLEPERF_SCRIPT_FILE_LIST := \
+ $(call all-named-files-under,*.proto,.)
+
+SIMPLEPERF_SCRIPT_DIR_LIST := \
app_api \
doc \
demo \
@@ -25,13 +28,15 @@ SIMPLEPERF_SCRIPT_LIST := \
scripts \
testdata
-SIMPLEPERF_SCRIPT_LIST := $(addprefix -D $(LOCAL_PATH)/,$(SIMPLEPERF_SCRIPT_LIST))
+SIMPLEPERF_SCRIPT_FILE_LIST := $(addprefix -f $(LOCAL_PATH)/,$(SIMPLEPERF_SCRIPT_FILE_LIST))
+SIMPLEPERF_SCRIPT_DIR_LIST := $(addprefix -D $(LOCAL_PATH)/,$(SIMPLEPERF_SCRIPT_DIR_LIST))
SIMPLEPERF_SCRIPT_PATH := \
$(call intermediates-dir-for,PACKAGING,simplerperf_script,HOST)/simpleperf_script.zip
$(SIMPLEPERF_SCRIPT_PATH) : $(SOONG_ZIP)
- $(hide) $(SOONG_ZIP) -d -o $@ -C system/extras/simpleperf $(SIMPLEPERF_SCRIPT_LIST)
+ $(hide) $(SOONG_ZIP) -d -o $@ -C system/extras/simpleperf $(SIMPLEPERF_SCRIPT_FILE_LIST) \
+ $(SIMPLEPERF_SCRIPT_DIR_LIST)
$(call declare-1p-target,$(SIMPLEPERF_SCRIPT_PATH),system/extras)
diff --git a/simpleperf/ETMBranchListFile.cpp b/simpleperf/ETMBranchListFile.cpp
new file mode 100644
index 00000000..c92cf8cc
--- /dev/null
+++ b/simpleperf/ETMBranchListFile.cpp
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ETMBranchListFile.h"
+
+#include "ETMDecoder.h"
+#include "system/extras/simpleperf/etm_branch_list.pb.h"
+
+namespace simpleperf {
+
+static constexpr const char* ETM_BRANCH_LIST_PROTO_MAGIC = "simpleperf:EtmBranchList";
+
+std::string BranchToProtoString(const std::vector<bool>& branch) {
+ size_t bytes = (branch.size() + 7) / 8;
+ std::string res(bytes, '\0');
+ for (size_t i = 0; i < branch.size(); i++) {
+ if (branch[i]) {
+ res[i >> 3] |= 1 << (i & 7);
+ }
+ }
+ return res;
+}
+
+std::vector<bool> ProtoStringToBranch(const std::string& s, size_t bit_size) {
+ std::vector<bool> branch(bit_size, false);
+ for (size_t i = 0; i < bit_size; i++) {
+ if (s[i >> 3] & (1 << (i & 7))) {
+ branch[i] = true;
+ }
+ }
+ return branch;
+}
+
+static std::optional<proto::ETMBranchList_Binary::BinaryType> ToProtoBinaryType(DsoType dso_type) {
+ switch (dso_type) {
+ case DSO_ELF_FILE:
+ return proto::ETMBranchList_Binary::ELF_FILE;
+ case DSO_KERNEL:
+ return proto::ETMBranchList_Binary::KERNEL;
+ case DSO_KERNEL_MODULE:
+ return proto::ETMBranchList_Binary::KERNEL_MODULE;
+ default:
+ LOG(ERROR) << "unexpected dso type " << dso_type;
+ return std::nullopt;
+ }
+}
+
+bool BranchListBinaryMapToString(const BranchListBinaryMap& binary_map, std::string& s) {
+ proto::ETMBranchList branch_list_proto;
+ branch_list_proto.set_magic(ETM_BRANCH_LIST_PROTO_MAGIC);
+ std::vector<char> branch_buf;
+ for (const auto& p : binary_map) {
+ const BinaryKey& key = p.first;
+ const BranchListBinaryInfo& binary = p.second;
+ auto binary_proto = branch_list_proto.add_binaries();
+
+ binary_proto->set_path(key.path);
+ if (!key.build_id.IsEmpty()) {
+ binary_proto->set_build_id(key.build_id.ToString().substr(2));
+ }
+ auto opt_binary_type = ToProtoBinaryType(binary.dso_type);
+ if (!opt_binary_type.has_value()) {
+ return false;
+ }
+ binary_proto->set_type(opt_binary_type.value());
+
+ for (const auto& addr_p : binary.branch_map) {
+ auto addr_proto = binary_proto->add_addrs();
+ addr_proto->set_addr(addr_p.first);
+
+ for (const auto& branch_p : addr_p.second) {
+ const std::vector<bool>& branch = branch_p.first;
+ auto branch_proto = addr_proto->add_branches();
+
+ branch_proto->set_branch(BranchToProtoString(branch));
+ branch_proto->set_branch_size(branch.size());
+ branch_proto->set_count(branch_p.second);
+ }
+ }
+
+ if (binary.dso_type == DSO_KERNEL) {
+ binary_proto->mutable_kernel_info()->set_kernel_start_addr(key.kernel_start_addr);
+ }
+ }
+ if (!branch_list_proto.SerializeToString(&s)) {
+ LOG(ERROR) << "failed to serialize branch list binary map";
+ return false;
+ }
+ return true;
+}
+
+static std::optional<DsoType> ToDsoType(proto::ETMBranchList_Binary::BinaryType binary_type) {
+ switch (binary_type) {
+ case proto::ETMBranchList_Binary::ELF_FILE:
+ return DSO_ELF_FILE;
+ case proto::ETMBranchList_Binary::KERNEL:
+ return DSO_KERNEL;
+ case proto::ETMBranchList_Binary::KERNEL_MODULE:
+ return DSO_KERNEL_MODULE;
+ default:
+ LOG(ERROR) << "unexpected binary type " << binary_type;
+ return std::nullopt;
+ }
+}
+
+static UnorderedBranchMap BuildUnorderedBranchMap(const proto::ETMBranchList_Binary& binary_proto) {
+ UnorderedBranchMap branch_map;
+ for (size_t i = 0; i < binary_proto.addrs_size(); i++) {
+ const auto& addr_proto = binary_proto.addrs(i);
+ auto& b_map = branch_map[addr_proto.addr()];
+ for (size_t j = 0; j < addr_proto.branches_size(); j++) {
+ const auto& branch_proto = addr_proto.branches(j);
+ std::vector<bool> branch =
+ ProtoStringToBranch(branch_proto.branch(), branch_proto.branch_size());
+ b_map[branch] = branch_proto.count();
+ }
+ }
+ return branch_map;
+}
+
+bool StringToBranchListBinaryMap(const std::string& s, BranchListBinaryMap& binary_map) {
+ proto::ETMBranchList branch_list_proto;
+ if (!branch_list_proto.ParseFromString(s)) {
+ PLOG(ERROR) << "failed to read ETMBranchList msg";
+ return false;
+ }
+ if (branch_list_proto.magic() != ETM_BRANCH_LIST_PROTO_MAGIC) {
+ PLOG(ERROR) << "not in format etm_branch_list.proto";
+ return false;
+ }
+
+ for (size_t i = 0; i < branch_list_proto.binaries_size(); i++) {
+ const auto& binary_proto = branch_list_proto.binaries(i);
+ BinaryKey key(binary_proto.path(), BuildId(binary_proto.build_id()));
+ if (binary_proto.has_kernel_info()) {
+ key.kernel_start_addr = binary_proto.kernel_info().kernel_start_addr();
+ }
+ BranchListBinaryInfo& binary = binary_map[key];
+ auto dso_type = ToDsoType(binary_proto.type());
+ if (!dso_type) {
+ LOG(ERROR) << "invalid binary type " << binary_proto.type();
+ return false;
+ }
+ binary.dso_type = dso_type.value();
+ binary.branch_map = BuildUnorderedBranchMap(binary_proto);
+ }
+ return true;
+}
+
+class ETMThreadTreeWhenRecording : public ETMThreadTree {
+ public:
+ ETMThreadTreeWhenRecording(bool dump_maps_from_proc)
+ : dump_maps_from_proc_(dump_maps_from_proc) {}
+
+ ThreadTree& GetThreadTree() { return thread_tree_; }
+ void ExcludePid(pid_t pid) { exclude_pid_ = pid; }
+
+ const ThreadEntry* FindThread(int tid) override {
+ const ThreadEntry* thread = thread_tree_.FindThread(tid);
+ if (thread == nullptr) {
+ if (dump_maps_from_proc_) {
+ thread = FindThreadFromProc(tid);
+ }
+ if (thread == nullptr) {
+ return nullptr;
+ }
+ }
+ if (exclude_pid_ && exclude_pid_ == thread->pid) {
+ return nullptr;
+ }
+
+ if (dump_maps_from_proc_) {
+ DumpMapsFromProc(thread->pid);
+ }
+ return thread;
+ }
+
+ void DisableThreadExitRecords() override { thread_tree_.DisableThreadExitRecords(); }
+ const MapSet& GetKernelMaps() override { return thread_tree_.GetKernelMaps(); }
+
+ private:
+ const ThreadEntry* FindThreadFromProc(int tid) {
+ std::string comm;
+ pid_t pid;
+ if (ReadThreadNameAndPid(tid, &comm, &pid)) {
+ thread_tree_.SetThreadName(pid, tid, comm);
+ return thread_tree_.FindThread(tid);
+ }
+ return nullptr;
+ }
+
+ void DumpMapsFromProc(int pid) {
+ if (dumped_processes_.count(pid) == 0) {
+ dumped_processes_.insert(pid);
+ std::vector<ThreadMmap> maps;
+ if (GetThreadMmapsInProcess(pid, &maps)) {
+ for (const auto& map : maps) {
+ thread_tree_.AddThreadMap(pid, pid, map.start_addr, map.len, map.pgoff, map.name);
+ }
+ }
+ }
+ }
+
+ ThreadTree thread_tree_;
+ bool dump_maps_from_proc_;
+ std::unordered_set<int> dumped_processes_;
+ std::optional<pid_t> exclude_pid_;
+};
+
+class ETMBranchListGeneratorImpl : public ETMBranchListGenerator {
+ public:
+ ETMBranchListGeneratorImpl(bool dump_maps_from_proc)
+ : thread_tree_(dump_maps_from_proc), binary_filter_(nullptr) {}
+
+ void SetExcludePid(pid_t pid) override { thread_tree_.ExcludePid(pid); }
+ void SetBinaryFilter(const RegEx* binary_name_regex) override {
+ binary_filter_.SetRegex(binary_name_regex);
+ }
+
+ bool ProcessRecord(const Record& r, bool& consumed) override;
+ BranchListBinaryMap GetBranchListBinaryMap() override;
+
+ private:
+ struct AuxRecordData {
+ uint64_t start;
+ uint64_t end;
+ bool formatted;
+ AuxRecordData(uint64_t start, uint64_t end, bool formatted)
+ : start(start), end(end), formatted(formatted) {}
+ };
+
+ struct PerCpuData {
+ std::vector<uint8_t> aux_data;
+ uint64_t data_offset = 0;
+ std::queue<AuxRecordData> aux_records;
+ };
+
+ bool ProcessAuxRecord(const AuxRecord& r);
+ bool ProcessAuxTraceRecord(const AuxTraceRecord& r);
+ void ProcessBranchList(const ETMBranchList& branch_list);
+
+ ETMThreadTreeWhenRecording thread_tree_;
+ uint64_t kernel_map_start_addr_ = 0;
+ BinaryFilter binary_filter_;
+ std::map<uint32_t, PerCpuData> cpu_map_;
+ std::unique_ptr<ETMDecoder> etm_decoder_;
+ std::unordered_map<Dso*, BranchListBinaryInfo> branch_list_binary_map_;
+};
+
+bool ETMBranchListGeneratorImpl::ProcessRecord(const Record& r, bool& consumed) {
+ consumed = true; // No need to store any records.
+ uint32_t type = r.type();
+ if (type == PERF_RECORD_AUXTRACE_INFO) {
+ etm_decoder_ = ETMDecoder::Create(*static_cast<const AuxTraceInfoRecord*>(&r), thread_tree_);
+ if (!etm_decoder_) {
+ return false;
+ }
+ etm_decoder_->RegisterCallback(
+ [this](const ETMBranchList& branch) { ProcessBranchList(branch); });
+ return true;
+ }
+ if (type == PERF_RECORD_AUX) {
+ return ProcessAuxRecord(*static_cast<const AuxRecord*>(&r));
+ }
+ if (type == PERF_RECORD_AUXTRACE) {
+ return ProcessAuxTraceRecord(*static_cast<const AuxTraceRecord*>(&r));
+ }
+ if (type == PERF_RECORD_MMAP && r.InKernel()) {
+ auto& mmap_r = *static_cast<const MmapRecord*>(&r);
+ if (android::base::StartsWith(mmap_r.filename, DEFAULT_KERNEL_MMAP_NAME)) {
+ kernel_map_start_addr_ = mmap_r.data->addr;
+ }
+ }
+ thread_tree_.GetThreadTree().Update(r);
+ return true;
+}
+
+bool ETMBranchListGeneratorImpl::ProcessAuxRecord(const AuxRecord& r) {
+ OverflowResult result = SafeAdd(r.data->aux_offset, r.data->aux_size);
+ if (result.overflow || r.data->aux_size > SIZE_MAX) {
+ LOG(ERROR) << "invalid aux record";
+ return false;
+ }
+ size_t size = r.data->aux_size;
+ uint64_t start = r.data->aux_offset;
+ uint64_t end = result.value;
+ PerCpuData& data = cpu_map_[r.Cpu()];
+ if (start >= data.data_offset && end <= data.data_offset + data.aux_data.size()) {
+ // The ETM data is available. Process it now.
+ uint8_t* p = data.aux_data.data() + (start - data.data_offset);
+ if (!etm_decoder_) {
+ LOG(ERROR) << "ETMDecoder isn't created";
+ return false;
+ }
+ return etm_decoder_->ProcessData(p, size, !r.Unformatted(), r.Cpu());
+ }
+ // The ETM data isn't available. Put the aux record into queue.
+ data.aux_records.emplace(start, end, !r.Unformatted());
+ return true;
+}
+
+bool ETMBranchListGeneratorImpl::ProcessAuxTraceRecord(const AuxTraceRecord& r) {
+ OverflowResult result = SafeAdd(r.data->offset, r.data->aux_size);
+ if (result.overflow || r.data->aux_size > SIZE_MAX) {
+ LOG(ERROR) << "invalid auxtrace record";
+ return false;
+ }
+ size_t size = r.data->aux_size;
+ uint64_t start = r.data->offset;
+ uint64_t end = result.value;
+ PerCpuData& data = cpu_map_[r.Cpu()];
+ data.data_offset = start;
+ CHECK(r.location.addr != nullptr);
+ data.aux_data.resize(size);
+ memcpy(data.aux_data.data(), r.location.addr, size);
+
+ // Process cached aux records.
+ while (!data.aux_records.empty() && data.aux_records.front().start < end) {
+ const AuxRecordData& aux = data.aux_records.front();
+ if (aux.start >= start && aux.end <= end) {
+ uint8_t* p = data.aux_data.data() + (aux.start - start);
+ if (!etm_decoder_) {
+ LOG(ERROR) << "ETMDecoder isn't created";
+ return false;
+ }
+ if (!etm_decoder_->ProcessData(p, aux.end - aux.start, aux.formatted, r.Cpu())) {
+ return false;
+ }
+ }
+ data.aux_records.pop();
+ }
+ return true;
+}
+
+void ETMBranchListGeneratorImpl::ProcessBranchList(const ETMBranchList& branch_list) {
+ if (!binary_filter_.Filter(branch_list.dso)) {
+ return;
+ }
+ auto& branch_map = branch_list_binary_map_[branch_list.dso].branch_map;
+ ++branch_map[branch_list.addr][branch_list.branch];
+}
+
+BranchListBinaryMap ETMBranchListGeneratorImpl::GetBranchListBinaryMap() {
+ BranchListBinaryMap binary_map;
+ for (auto& p : branch_list_binary_map_) {
+ Dso* dso = p.first;
+ BranchListBinaryInfo& binary = p.second;
+ binary.dso_type = dso->type();
+ BuildId build_id;
+ GetBuildId(*dso, build_id);
+ BinaryKey key(dso->Path(), build_id);
+ if (binary.dso_type == DSO_KERNEL) {
+ if (kernel_map_start_addr_ == 0) {
+ LOG(WARNING) << "Can't convert kernel ip addresses without kernel start addr. So remove "
+ "branches for the kernel.";
+ continue;
+ }
+ key.kernel_start_addr = kernel_map_start_addr_;
+ }
+ binary_map[key] = std::move(binary);
+ }
+ return binary_map;
+}
+
+std::unique_ptr<ETMBranchListGenerator> ETMBranchListGenerator::Create(bool dump_maps_from_proc) {
+ return std::unique_ptr<ETMBranchListGenerator>(
+ new ETMBranchListGeneratorImpl(dump_maps_from_proc));
+}
+
+ETMBranchListGenerator::~ETMBranchListGenerator() {}
+
+} // namespace simpleperf
diff --git a/simpleperf/ETMBranchListFile.h b/simpleperf/ETMBranchListFile.h
new file mode 100644
index 00000000..fd595c56
--- /dev/null
+++ b/simpleperf/ETMBranchListFile.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "ETMDecoder.h"
+#include "RegEx.h"
+#include "thread_tree.h"
+#include "utils.h"
+
+namespace simpleperf {
+
+// When processing binary info in an input file, the binaries are identified by their path.
+// But this isn't sufficient when merging binary info from multiple input files. Because
+// binaries for the same path may be changed between generating input files. So after processing
+// each input file, we create BinaryKeys to identify binaries, which consider path, build_id and
+// kernel_start_addr (for vmlinux). kernel_start_addr affects how addresses in BranchListBinaryInfo
+// are interpreted for vmlinux.
+struct BinaryKey {
+ std::string path;
+ BuildId build_id;
+ uint64_t kernel_start_addr = 0;
+
+ BinaryKey() {}
+
+ BinaryKey(const std::string& path, BuildId build_id) : path(path), build_id(build_id) {}
+
+ BinaryKey(Dso* dso, uint64_t kernel_start_addr) : path(dso->Path()) {
+ build_id = Dso::FindExpectedBuildIdForPath(dso->Path());
+ if (dso->type() == DSO_KERNEL) {
+ this->kernel_start_addr = kernel_start_addr;
+ }
+ }
+
+ bool operator==(const BinaryKey& other) const {
+ return path == other.path && build_id == other.build_id &&
+ kernel_start_addr == other.kernel_start_addr;
+ }
+};
+
+struct BinaryKeyHash {
+ size_t operator()(const BinaryKey& key) const noexcept {
+ size_t seed = 0;
+ HashCombine(seed, key.path);
+ HashCombine(seed, key.build_id);
+ if (key.kernel_start_addr != 0) {
+ HashCombine(seed, key.kernel_start_addr);
+ }
+ return seed;
+ }
+};
+
+using UnorderedBranchMap =
+ std::unordered_map<uint64_t, std::unordered_map<std::vector<bool>, uint64_t>>;
+
+struct BranchListBinaryInfo {
+ DsoType dso_type;
+ UnorderedBranchMap branch_map;
+
+ void Merge(const BranchListBinaryInfo& other) {
+ for (auto& other_p : other.branch_map) {
+ auto it = branch_map.find(other_p.first);
+ if (it == branch_map.end()) {
+ branch_map[other_p.first] = std::move(other_p.second);
+ } else {
+ auto& map2 = it->second;
+ for (auto& other_p2 : other_p.second) {
+ auto it2 = map2.find(other_p2.first);
+ if (it2 == map2.end()) {
+ map2[other_p2.first] = other_p2.second;
+ } else {
+ OverflowSafeAdd(it2->second, other_p2.second);
+ }
+ }
+ }
+ }
+ }
+
+ BranchMap GetOrderedBranchMap() const {
+ BranchMap result;
+ for (const auto& p : branch_map) {
+ uint64_t addr = p.first;
+ const auto& b_map = p.second;
+ result[addr] = std::map<std::vector<bool>, uint64_t>(b_map.begin(), b_map.end());
+ }
+ return result;
+ }
+};
+
+using BranchListBinaryMap = std::unordered_map<BinaryKey, BranchListBinaryInfo, BinaryKeyHash>;
+
+bool BranchListBinaryMapToString(const BranchListBinaryMap& binary_map, std::string& s);
+bool StringToBranchListBinaryMap(const std::string& s, BranchListBinaryMap& binary_map);
+
+class BinaryFilter {
+ public:
+ BinaryFilter(const RegEx* binary_name_regex) : binary_name_regex_(binary_name_regex) {}
+
+ void SetRegex(const RegEx* binary_name_regex) {
+ binary_name_regex_ = binary_name_regex;
+ dso_filter_cache_.clear();
+ }
+
+ bool Filter(Dso* dso) {
+ auto lookup = dso_filter_cache_.find(dso);
+ if (lookup != dso_filter_cache_.end()) {
+ return lookup->second;
+ }
+ bool match = Filter(dso->Path());
+ dso_filter_cache_.insert({dso, match});
+ return match;
+ }
+
+ bool Filter(const std::string& path) {
+ return binary_name_regex_ == nullptr || binary_name_regex_->Search(path);
+ }
+
+ private:
+ const RegEx* binary_name_regex_;
+ std::unordered_map<Dso*, bool> dso_filter_cache_;
+};
+
+// Convert ETM data into branch lists while recording.
+class ETMBranchListGenerator {
+ public:
+ static std::unique_ptr<ETMBranchListGenerator> Create(bool dump_maps_from_proc);
+
+ virtual ~ETMBranchListGenerator();
+ virtual void SetExcludePid(pid_t pid) = 0;
+ virtual void SetBinaryFilter(const RegEx* binary_name_regex) = 0;
+ virtual bool ProcessRecord(const Record& r, bool& consumed) = 0;
+ virtual BranchListBinaryMap GetBranchListBinaryMap() = 0;
+};
+
+// for testing
+std::string BranchToProtoString(const std::vector<bool>& branch);
+std::vector<bool> ProtoStringToBranch(const std::string& s, size_t bit_size);
+
+} // namespace simpleperf
diff --git a/simpleperf/ETMBranchListFile_test.cpp b/simpleperf/ETMBranchListFile_test.cpp
new file mode 100644
index 00000000..227c68b3
--- /dev/null
+++ b/simpleperf/ETMBranchListFile_test.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "ETMBranchListFile.h"
+
+using namespace simpleperf;
+
+TEST(ETMBranchListFile, branch_to_proto_string) {
+ std::vector<bool> branch;
+ for (size_t i = 0; i < 100; i++) {
+ branch.push_back(i % 2 == 0);
+ std::string s = BranchToProtoString(branch);
+ for (size_t j = 0; j <= i; j++) {
+ bool b = s[j >> 3] & (1 << (j & 7));
+ ASSERT_EQ(b, branch[j]);
+ }
+ std::vector<bool> branch2 = ProtoStringToBranch(s, branch.size());
+ ASSERT_EQ(branch, branch2);
+ }
+}
diff --git a/simpleperf/ETMDecoder.cpp b/simpleperf/ETMDecoder.cpp
index 62fe997c..9cf43851 100644
--- a/simpleperf/ETMDecoder.cpp
+++ b/simpleperf/ETMDecoder.cpp
@@ -67,7 +67,10 @@ class ETMV4IDecodeTree {
public:
ETMV4IDecodeTree()
: error_logger_(std::bind(&ETMV4IDecodeTree::ProcessError, this, std::placeholders::_1)) {
- frame_decoder_.Configure(OCSD_DFRMTR_FRAME_MEM_ALIGN);
+ ocsd_err_t err = frame_decoder_.Init();
+ CHECK_EQ(err, OCSD_OK);
+ err = frame_decoder_.Configure(OCSD_DFRMTR_FRAME_MEM_ALIGN);
+ CHECK_EQ(err, OCSD_OK);
frame_decoder_.getErrLogAttachPt()->attach(&error_logger_);
}
@@ -178,11 +181,9 @@ class PacketSink : public IPktDataIn<EtmV4ITrcPacket> {
// For each trace_id, when given an addr, find the thread and map it belongs to.
class MapLocator : public PacketCallback {
public:
- MapLocator(ThreadTree& thread_tree)
+ MapLocator(ETMThreadTree& thread_tree)
: PacketCallback(PacketCallback::MAP_LOCATOR), thread_tree_(thread_tree) {}
- ThreadTree& GetThreadTree() { return thread_tree_; }
-
// Return current thread id of a trace_id. If not available, return -1.
pid_t GetTid(uint8_t trace_id) const { return trace_data_[trace_id].tid; }
@@ -242,7 +243,7 @@ class MapLocator : public PacketCallback {
bool use_vmid = false; // use vmid for PID
};
- ThreadTree& thread_tree_;
+ ETMThreadTree& thread_tree_;
TraceData trace_data_[256];
};
@@ -286,15 +287,22 @@ class MemAccess : public ITargetMemAccess {
// addr.
if (!map->in_kernel) {
data.buffer_map = map;
- data.buffer = memory == nullptr ? nullptr : (memory->getBufferStart() + map->pgoff);
data.buffer_start = map->start_addr;
data.buffer_end = map->get_end_addr();
+ if (memory != nullptr && memory->getBufferSize() > map->pgoff &&
+ (memory->getBufferSize() - map->pgoff >= map->len)) {
+ data.buffer = memory->getBufferStart() + map->pgoff;
+ } else {
+ data.buffer = nullptr;
+ }
}
}
*num_bytes = copy_size;
return OCSD_OK;
}
+ void InvalidateMemAccCache(const uint8_t cs_trace_id) override {}
+
private:
llvm::MemoryBuffer* GetMemoryBuffer(Dso* dso) {
auto it = elf_map_.find(dso);
@@ -639,7 +647,11 @@ class BranchListParser : public PacketCallback {
// 2. Supports dumping data at different stages.
class ETMDecoderImpl : public ETMDecoder {
public:
- ETMDecoderImpl(ThreadTree& thread_tree) : thread_tree_(thread_tree) {}
+ ETMDecoderImpl(ETMThreadTree& thread_tree) : thread_tree_(thread_tree) {
+ // If the aux record for a thread is processed after it's thread exit record, we can't find
+ // the thread's maps when processing ETM data. To handle this, disable thread exit records.
+ thread_tree.DisableThreadExitRecords();
+ }
void CreateDecodeTree(const AuxTraceInfoRecord& auxtrace_info) {
uint8_t trace_id = 0;
@@ -793,7 +805,7 @@ class ETMDecoderImpl : public ETMDecoder {
}
// map ip address to binary path and binary offset
- ThreadTree& thread_tree_;
+ ETMThreadTree& thread_tree_;
// handle to build OpenCSD decoder
ETMV4IDecodeTree decode_tree_;
// map from cpu to trace id
@@ -830,7 +842,7 @@ bool ParseEtmDumpOption(const std::string& s, ETMDumpOption* option) {
}
std::unique_ptr<ETMDecoder> ETMDecoder::Create(const AuxTraceInfoRecord& auxtrace_info,
- ThreadTree& thread_tree) {
+ ETMThreadTree& thread_tree) {
auto decoder = std::make_unique<ETMDecoderImpl>(thread_tree);
decoder->CreateDecodeTree(auxtrace_info);
return std::unique_ptr<ETMDecoder>(decoder.release());
@@ -967,4 +979,4 @@ android::base::expected<void, std::string> ConvertBranchMapToInstrRanges(
return {};
}
-} // namespace simpleperf \ No newline at end of file
+} // namespace simpleperf
diff --git a/simpleperf/ETMDecoder.h b/simpleperf/ETMDecoder.h
index 5f9daebd..e8ae7f7d 100644
--- a/simpleperf/ETMDecoder.h
+++ b/simpleperf/ETMDecoder.h
@@ -61,10 +61,19 @@ struct ETMBranchList {
std::vector<bool> branch;
};
+// ThreadTree interface used by ETMDecoder
+class ETMThreadTree {
+ public:
+ virtual ~ETMThreadTree() {}
+ virtual void DisableThreadExitRecords() = 0;
+ virtual const ThreadEntry* FindThread(int tid) = 0;
+ virtual const MapSet& GetKernelMaps() = 0;
+};
+
class ETMDecoder {
public:
static std::unique_ptr<ETMDecoder> Create(const AuxTraceInfoRecord& auxtrace_info,
- ThreadTree& thread_tree);
+ ETMThreadTree& thread_tree);
virtual ~ETMDecoder() {}
virtual void EnableDump(const ETMDumpOption& option) = 0;
diff --git a/simpleperf/JITDebugReader.cpp b/simpleperf/JITDebugReader.cpp
index 28058940..2d4b132b 100644
--- a/simpleperf/JITDebugReader.cpp
+++ b/simpleperf/JITDebugReader.cpp
@@ -47,7 +47,7 @@ using android::base::StringPrintf;
// If the size of a symfile is larger than EXPECTED_MAX_SYMFILE_SIZE, we don't want to read it
// remotely.
-static constexpr size_t MAX_JIT_SYMFILE_SIZE = 1024 * 1024u;
+static constexpr size_t MAX_JIT_SYMFILE_SIZE = 1 * kMegabyte;
// It takes about 30us-130us on Pixel (depending on the cpu frequency) to check if the descriptors
// have been updated (most time spent in process_vm_preadv). We want to know if the JIT debug info
diff --git a/simpleperf/MapRecordReader.cpp b/simpleperf/MapRecordReader.cpp
index 8853e03b..5731eb8e 100644
--- a/simpleperf/MapRecordReader.cpp
+++ b/simpleperf/MapRecordReader.cpp
@@ -164,7 +164,10 @@ bool MapRecordThread::ReadMapRecords(const std::function<bool(Record*)>& callbac
PLOG(ERROR) << "fread() failed";
return false;
}
- RecordHeader header(buffer.data());
+ RecordHeader header;
+ if (!header.Parse(buffer.data())) {
+ return false;
+ }
if (buffer.size() < header.size) {
buffer.resize(header.size);
}
diff --git a/simpleperf/OfflineUnwinder.cpp b/simpleperf/OfflineUnwinder.cpp
index bbc488b4..c847bca7 100644
--- a/simpleperf/OfflineUnwinder.cpp
+++ b/simpleperf/OfflineUnwinder.cpp
@@ -27,16 +27,19 @@
#include <unwindstack/MachineArm64.h>
#include <unwindstack/MachineX86.h>
#include <unwindstack/MachineX86_64.h>
+#include <unwindstack/MachineRiscv64.h>
#include <unwindstack/Maps.h>
#include <unwindstack/RegsArm.h>
#include <unwindstack/RegsArm64.h>
#include <unwindstack/RegsX86.h>
#include <unwindstack/RegsX86_64.h>
+#include <unwindstack/RegsRiscv64.h>
#include <unwindstack/Unwinder.h>
#include <unwindstack/UserArm.h>
#include <unwindstack/UserArm64.h>
#include <unwindstack/UserX86.h>
#include <unwindstack/UserX86_64.h>
+#include <unwindstack/UserRiscv64.h>
#include "JITDebugReader.h"
#include "OfflineUnwinder_impl.h"
@@ -140,6 +143,43 @@ unwindstack::Regs* OfflineUnwinderImpl::GetBacktraceRegs(const RegSet& regs) {
x86_64_user_regs.rip = regs.data[PERF_REG_X86_IP];
return unwindstack::RegsX86_64::Read(&x86_64_user_regs);
}
+ case ARCH_RISCV64: {
+ unwindstack::riscv64_user_regs riscv64_user_regs;
+ memset(&riscv64_user_regs, 0, sizeof(riscv64_user_regs));
+ riscv64_user_regs.regs[PERF_REG_RISCV_PC] = regs.data[PERF_REG_RISCV_PC];
+ riscv64_user_regs.regs[PERF_REG_RISCV_RA] = regs.data[PERF_REG_RISCV_RA];
+ riscv64_user_regs.regs[PERF_REG_RISCV_SP] = regs.data[PERF_REG_RISCV_SP];
+ riscv64_user_regs.regs[PERF_REG_RISCV_GP] = regs.data[PERF_REG_RISCV_GP];
+ riscv64_user_regs.regs[PERF_REG_RISCV_TP] = regs.data[PERF_REG_RISCV_TP];
+ riscv64_user_regs.regs[PERF_REG_RISCV_T0] = regs.data[PERF_REG_RISCV_T0];
+ riscv64_user_regs.regs[PERF_REG_RISCV_T1] = regs.data[PERF_REG_RISCV_T1];
+ riscv64_user_regs.regs[PERF_REG_RISCV_T2] = regs.data[PERF_REG_RISCV_T2];
+ riscv64_user_regs.regs[PERF_REG_RISCV_S0] = regs.data[PERF_REG_RISCV_S0];
+ riscv64_user_regs.regs[PERF_REG_RISCV_S1] = regs.data[PERF_REG_RISCV_S1];
+ riscv64_user_regs.regs[PERF_REG_RISCV_A0] = regs.data[PERF_REG_RISCV_A0];
+ riscv64_user_regs.regs[PERF_REG_RISCV_A1] = regs.data[PERF_REG_RISCV_A1];
+ riscv64_user_regs.regs[PERF_REG_RISCV_A2] = regs.data[PERF_REG_RISCV_A2];
+ riscv64_user_regs.regs[PERF_REG_RISCV_A3] = regs.data[PERF_REG_RISCV_A3];
+ riscv64_user_regs.regs[PERF_REG_RISCV_A4] = regs.data[PERF_REG_RISCV_A4];
+ riscv64_user_regs.regs[PERF_REG_RISCV_A5] = regs.data[PERF_REG_RISCV_A5];
+ riscv64_user_regs.regs[PERF_REG_RISCV_A6] = regs.data[PERF_REG_RISCV_A6];
+ riscv64_user_regs.regs[PERF_REG_RISCV_A7] = regs.data[PERF_REG_RISCV_A7];
+ riscv64_user_regs.regs[PERF_REG_RISCV_S2] = regs.data[PERF_REG_RISCV_S2];
+ riscv64_user_regs.regs[PERF_REG_RISCV_S3] = regs.data[PERF_REG_RISCV_S3];
+ riscv64_user_regs.regs[PERF_REG_RISCV_S4] = regs.data[PERF_REG_RISCV_S4];
+ riscv64_user_regs.regs[PERF_REG_RISCV_S5] = regs.data[PERF_REG_RISCV_S5];
+ riscv64_user_regs.regs[PERF_REG_RISCV_S6] = regs.data[PERF_REG_RISCV_S6];
+ riscv64_user_regs.regs[PERF_REG_RISCV_S7] = regs.data[PERF_REG_RISCV_S7];
+ riscv64_user_regs.regs[PERF_REG_RISCV_S8] = regs.data[PERF_REG_RISCV_S8];
+ riscv64_user_regs.regs[PERF_REG_RISCV_S9] = regs.data[PERF_REG_RISCV_S9];
+ riscv64_user_regs.regs[PERF_REG_RISCV_S10] = regs.data[PERF_REG_RISCV_S10];
+ riscv64_user_regs.regs[PERF_REG_RISCV_S11] = regs.data[PERF_REG_RISCV_S11];
+ riscv64_user_regs.regs[PERF_REG_RISCV_T3] = regs.data[PERF_REG_RISCV_T3];
+ riscv64_user_regs.regs[PERF_REG_RISCV_T4] = regs.data[PERF_REG_RISCV_T4];
+ riscv64_user_regs.regs[PERF_REG_RISCV_T5] = regs.data[PERF_REG_RISCV_T5];
+ riscv64_user_regs.regs[PERF_REG_RISCV_T6] = regs.data[PERF_REG_RISCV_T6];
+ return unwindstack::RegsRiscv64::Read(&riscv64_user_regs);
+ }
default:
return nullptr;
}
diff --git a/simpleperf/ProbeEvents.cpp b/simpleperf/ProbeEvents.cpp
index 636923f9..616bafe4 100644
--- a/simpleperf/ProbeEvents.cpp
+++ b/simpleperf/ProbeEvents.cpp
@@ -19,7 +19,6 @@
#include <inttypes.h>
#include <memory>
-#include <regex>
#include <string>
#include <android-base/file.h>
@@ -29,6 +28,7 @@
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
+#include "RegEx.h"
#include "environment.h"
#include "event_type.h"
#include "utils.h"
@@ -55,14 +55,14 @@ bool ProbeEvents::ParseKprobeEventName(const std::string& kprobe_cmd, ProbeEvent
// Parse given name.
event->group_name = "kprobes";
- std::regex name_reg(R"(:([a-zA-Z_][\w_]*/)?([a-zA-Z_][\w_]*))");
- std::smatch matches;
- if (std::regex_search(args[0], matches, name_reg)) {
- if (matches[1].length() > 0) {
- event->group_name = matches[1].str();
+ auto name_reg = RegEx::Create(R"(:([a-zA-Z_][\w_]*/)?([a-zA-Z_][\w_]*))");
+ auto match = name_reg->SearchAll(args[0]);
+ if (match->IsValid()) {
+ if (match->GetField(1).length() > 0) {
+ event->group_name = match->GetField(1);
event->group_name.pop_back();
}
- event->event_name = matches[2].str();
+ event->event_name = match->GetField(2);
return true;
}
@@ -88,7 +88,7 @@ bool ProbeEvents::ParseKprobeEventName(const std::string& kprobe_cmd, ProbeEvent
}
}
std::string s = StringPrintf("%c_%s_%" PRId64, probe_type, symbol.c_str(), offset);
- event->event_name = std::regex_replace(s, std::regex(R"(\.|:)"), "_");
+ event->event_name = RegEx::Create(R"(\.|:)")->Replace(s, "_").value();
return true;
}
diff --git a/simpleperf/README.md b/simpleperf/README.md
index c1d23688..50fd2432 100644
--- a/simpleperf/README.md
+++ b/simpleperf/README.md
@@ -1,6 +1,14 @@
# Simpleperf
-This file is documentation for simpleperf maintainers.
+Android Studio includes a graphical front end to Simpleperf, documented in
+[Inspect CPU activity with CPU Profiler](https://developer.android.com/studio/profile/cpu-profiler).
+Most users will prefer to use that instead of using Simpleperf directly.
+
+If you prefer to use the command line, Simpleperf is a versatile command-line
+CPU profiling tool included in the NDK for Mac, Linux, and Windows.
+
+This file contains documentation for simpleperf maintainers.
+
There is also [user documentation](doc/README.md).
## Building new prebuilts
diff --git a/simpleperf/RecordFilter.cpp b/simpleperf/RecordFilter.cpp
index 7818d32b..34054286 100644
--- a/simpleperf/RecordFilter.cpp
+++ b/simpleperf/RecordFilter.cpp
@@ -248,8 +248,8 @@ RecordFilter::~RecordFilter() {}
bool RecordFilter::ParseOptions(OptionValueMap& options) {
for (bool exclude : {true, false}) {
std::string prefix = exclude ? "--exclude-" : "--include-";
- for (const OptionValue& value : options.PullValues(prefix + "pid")) {
- if (auto pids = GetTidsFromString(*value.str_value, false); pids) {
+ if (auto strs = options.PullStringValues(prefix + "pid"); !strs.empty()) {
+ if (auto pids = GetPidsFromStrings(strs, false, false); pids) {
AddPids(pids.value(), exclude);
} else {
return false;
@@ -263,10 +263,14 @@ bool RecordFilter::ParseOptions(OptionValueMap& options) {
}
}
for (const OptionValue& value : options.PullValues(prefix + "process-name")) {
- AddProcessNameRegex(*value.str_value, exclude);
+ if (!AddProcessNameRegex(*value.str_value, exclude)) {
+ return false;
+ }
}
for (const OptionValue& value : options.PullValues(prefix + "thread-name")) {
- AddThreadNameRegex(*value.str_value, exclude);
+ if (!AddThreadNameRegex(*value.str_value, exclude)) {
+ return false;
+ }
}
for (const OptionValue& value : options.PullValues(prefix + "uid")) {
if (auto uids = ParseUintVector<uint32_t>(*value.str_value); uids) {
@@ -296,16 +300,24 @@ void RecordFilter::AddTids(const std::set<pid_t>& tids, bool exclude) {
cond.tids.insert(tids.begin(), tids.end());
}
-void RecordFilter::AddProcessNameRegex(const std::string& process_name, bool exclude) {
+bool RecordFilter::AddProcessNameRegex(const std::string& process_name, bool exclude) {
RecordFilterCondition& cond = GetCondition(exclude);
cond.used = true;
- cond.process_name_regs.emplace_back(process_name, std::regex::optimize);
+ if (auto regex = RegEx::Create(process_name); regex != nullptr) {
+ cond.process_name_regs.emplace_back(std::move(regex));
+ return true;
+ }
+ return false;
}
-void RecordFilter::AddThreadNameRegex(const std::string& thread_name, bool exclude) {
+bool RecordFilter::AddThreadNameRegex(const std::string& thread_name, bool exclude) {
RecordFilterCondition& cond = GetCondition(exclude);
cond.used = true;
- cond.thread_name_regs.emplace_back(thread_name, std::regex::optimize);
+ if (auto regex = RegEx::Create(thread_name); regex != nullptr) {
+ cond.thread_name_regs.emplace_back(std::move(regex));
+ return true;
+ }
+ return false;
}
void RecordFilter::AddUids(const std::set<uint32_t>& uids, bool exclude) {
@@ -382,9 +394,10 @@ bool RecordFilter::CheckCondition(const SampleRecord* r, const RecordFilterCondi
return false;
}
-bool RecordFilter::SearchInRegs(const std::string& s, const std::vector<std::regex>& regs) {
+bool RecordFilter::SearchInRegs(std::string_view s,
+ const std::vector<std::unique_ptr<RegEx>>& regs) {
for (auto& reg : regs) {
- if (std::regex_search(s, reg)) {
+ if (reg->Search(s)) {
return true;
}
}
diff --git a/simpleperf/RecordFilter.h b/simpleperf/RecordFilter.h
index 4c5ee584..cf413c12 100644
--- a/simpleperf/RecordFilter.h
+++ b/simpleperf/RecordFilter.h
@@ -19,12 +19,12 @@
#include <sys/types.h>
#include <optional>
-#include <regex>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
+#include "RegEx.h"
#include "command.h"
#include "record.h"
#include "thread_tree.h"
@@ -97,8 +97,8 @@ struct RecordFilterCondition {
bool used = false;
std::set<pid_t> pids;
std::set<pid_t> tids;
- std::vector<std::regex> process_name_regs;
- std::vector<std::regex> thread_name_regs;
+ std::vector<std::unique_ptr<RegEx>> process_name_regs;
+ std::vector<std::unique_ptr<RegEx>> thread_name_regs;
std::set<uint32_t> uids;
};
@@ -114,8 +114,8 @@ class RecordFilter {
bool ParseOptions(OptionValueMap& options);
void AddPids(const std::set<pid_t>& pids, bool exclude);
void AddTids(const std::set<pid_t>& tids, bool exclude);
- void AddProcessNameRegex(const std::string& process_name, bool exclude);
- void AddThreadNameRegex(const std::string& thread_name, bool exclude);
+ bool AddProcessNameRegex(const std::string& process_name, bool exclude);
+ bool AddThreadNameRegex(const std::string& thread_name, bool exclude);
void AddUids(const std::set<uint32_t>& uids, bool exclude);
bool SetFilterFile(const std::string& filename);
@@ -132,7 +132,7 @@ class RecordFilter {
private:
bool CheckCondition(const SampleRecord* r, const RecordFilterCondition& condition);
- bool SearchInRegs(const std::string& s, const std::vector<std::regex>& regs);
+ bool SearchInRegs(std::string_view s, const std::vector<std::unique_ptr<RegEx>>& regs);
std::optional<uint32_t> GetUidForProcess(pid_t pid);
const ThreadTree& thread_tree_;
diff --git a/simpleperf/RecordFilter_test.cpp b/simpleperf/RecordFilter_test.cpp
index 3099e452..88345edd 100644
--- a/simpleperf/RecordFilter_test.cpp
+++ b/simpleperf/RecordFilter_test.cpp
@@ -71,7 +71,7 @@ TEST_F(RecordFilterTest, exclude_tid) {
}
TEST_F(RecordFilterTest, exclude_process_name_regex) {
- filter.AddProcessNameRegex("processA", true);
+ ASSERT_TRUE(filter.AddProcessNameRegex("processA", true));
thread_tree.SetThreadName(1, 1, "processA1");
thread_tree.SetThreadName(2, 2, "processB1");
ASSERT_FALSE(filter.Check(GetRecord(1, 1)));
@@ -79,7 +79,7 @@ TEST_F(RecordFilterTest, exclude_process_name_regex) {
}
TEST_F(RecordFilterTest, exclude_thread_name_regex) {
- filter.AddThreadNameRegex("threadA", true);
+ ASSERT_TRUE(filter.AddThreadNameRegex("threadA", true));
thread_tree.SetThreadName(1, 1, "processA_threadA");
thread_tree.SetThreadName(1, 2, "processA_threadB");
ASSERT_FALSE(filter.Check(GetRecord(1, 1)));
@@ -109,7 +109,7 @@ TEST_F(RecordFilterTest, include_tid) {
}
TEST_F(RecordFilterTest, include_process_name_regex) {
- filter.AddProcessNameRegex("processA", false);
+ ASSERT_TRUE(filter.AddProcessNameRegex("processA", false));
thread_tree.SetThreadName(1, 1, "processA1");
thread_tree.SetThreadName(2, 2, "processB1");
ASSERT_TRUE(filter.Check(GetRecord(1, 1)));
@@ -117,7 +117,7 @@ TEST_F(RecordFilterTest, include_process_name_regex) {
}
TEST_F(RecordFilterTest, include_thread_name_regex) {
- filter.AddThreadNameRegex("threadA", false);
+ ASSERT_TRUE(filter.AddThreadNameRegex("threadA", false));
thread_tree.SetThreadName(1, 1, "processA_threadA");
thread_tree.SetThreadName(1, 2, "processA_threadB");
ASSERT_TRUE(filter.Check(GetRecord(1, 1)));
@@ -285,15 +285,15 @@ TEST_F(RecordFilterTest, parse_options) {
filter_cmd.Run({prefix + "process-name", "processA", prefix + "process-name", "processB"}));
auto& process_regs = filter.GetCondition(exclude).process_name_regs;
ASSERT_EQ(process_regs.size(), 2);
- ASSERT_TRUE(std::regex_match("processA", process_regs[0]));
- ASSERT_TRUE(std::regex_match("processB", process_regs[1]));
+ ASSERT_TRUE(process_regs[0]->Match("processA"));
+ ASSERT_TRUE(process_regs[1]->Match("processB"));
ASSERT_TRUE(
filter_cmd.Run({prefix + "thread-name", "threadA", prefix + "thread-name", "threadB"}));
auto& thread_regs = filter.GetCondition(exclude).thread_name_regs;
ASSERT_EQ(thread_regs.size(), 2);
- ASSERT_TRUE(std::regex_match("threadA", thread_regs[0]));
- ASSERT_TRUE(std::regex_match("threadB", thread_regs[1]));
+ ASSERT_TRUE(thread_regs[0]->Match("threadA"));
+ ASSERT_TRUE(thread_regs[1]->Match("threadB"));
ASSERT_TRUE(filter_cmd.Run({prefix + "uid", "1,2", prefix + "uid", "3"}));
ASSERT_EQ(filter.GetCondition(exclude).uids, std::set<uint32_t>({1, 2, 3}));
diff --git a/simpleperf/RecordReadThread.cpp b/simpleperf/RecordReadThread.cpp
index 666be876..3e492ce2 100644
--- a/simpleperf/RecordReadThread.cpp
+++ b/simpleperf/RecordReadThread.cpp
@@ -29,8 +29,8 @@
namespace simpleperf {
-static constexpr size_t kDefaultLowBufferLevel = 10 * 1024 * 1024u;
-static constexpr size_t kDefaultCriticalBufferLevel = 5 * 1024 * 1024u;
+static constexpr size_t kDefaultLowBufferLevel = 10 * kMegabyte;
+static constexpr size_t kDefaultCriticalBufferLevel = 5 * kMegabyte;
RecordBuffer::RecordBuffer(size_t buffer_size)
: read_head_(0), write_head_(0), buffer_size_(buffer_size), buffer_(new char[buffer_size]) {}
@@ -236,6 +236,9 @@ RecordReadThread::RecordReadThread(size_t record_buffer_size, const perf_event_a
}
record_buffer_low_level_ = std::min(record_buffer_size / 4, kDefaultLowBufferLevel);
record_buffer_critical_level_ = std::min(record_buffer_size / 6, kDefaultCriticalBufferLevel);
+ LOG(VERBOSE) << "user buffer size = " << record_buffer_size
+ << ", low_level size = " << record_buffer_low_level_
+ << ", critical_level size = " << record_buffer_critical_level_;
if (!allow_cutting_samples) {
record_buffer_low_level_ = record_buffer_critical_level_;
}
@@ -530,7 +533,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++;
+ stat_.userspace_lost_samples++;
return;
}
size_t stack_size_limit = stack_size_in_sample_record_;
@@ -577,10 +580,10 @@ void RecordReadThread::PushRecordToRecordBuffer(KernelRecordReader* kernel_recor
memcpy(p + pos + new_stack_size, &new_stack_size, sizeof(uint64_t));
record_buffer_.FinishWrite();
if (new_stack_size < dyn_stack_size) {
- stat_.cut_stack_samples++;
+ stat_.userspace_cut_stack_samples++;
}
} else {
- stat_.lost_samples++;
+ stat_.userspace_lost_samples++;
}
return;
}
@@ -600,13 +603,18 @@ void RecordReadThread::PushRecordToRecordBuffer(KernelRecordReader* kernel_recor
// only after we have collected the aux data.
event_fds_disabled_by_kernel_.insert(kernel_record_reader->GetEventFd());
}
+ } else if (header.type == PERF_RECORD_LOST) {
+ LostRecord r;
+ if (r.Parse(attr_, p, p + header.size)) {
+ stat_.kernelspace_lost_records += static_cast<size_t>(r.lost);
+ }
}
record_buffer_.FinishWrite();
} else {
if (header.type == PERF_RECORD_SAMPLE) {
- stat_.lost_samples++;
+ stat_.userspace_lost_samples++;
} else {
- stat_.lost_non_samples++;
+ stat_.userspace_lost_non_samples++;
}
}
}
@@ -625,10 +633,11 @@ void RecordReadThread::ReadAuxDataFromKernelBuffer(bool* has_data) {
*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_) {
+ char* p = nullptr;
+ if ((record_buffer_.GetFreeSize() < alloc_size + record_buffer_critical_level_) ||
+ (p = record_buffer_.AllocWriteSpace(alloc_size)) == nullptr) {
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);
diff --git a/simpleperf/RecordReadThread.h b/simpleperf/RecordReadThread.h
index 47dfbb20..658f9ea7 100644
--- a/simpleperf/RecordReadThread.h
+++ b/simpleperf/RecordReadThread.h
@@ -90,9 +90,10 @@ class RecordParser {
};
struct RecordStat {
- size_t lost_samples = 0;
- size_t lost_non_samples = 0;
- size_t cut_stack_samples = 0;
+ size_t kernelspace_lost_records = 0;
+ size_t userspace_lost_samples = 0;
+ size_t userspace_lost_non_samples = 0;
+ size_t userspace_cut_stack_samples = 0;
uint64_t aux_data_size = 0;
uint64_t lost_aux_data_size = 0;
};
diff --git a/simpleperf/RecordReadThread_test.cpp b/simpleperf/RecordReadThread_test.cpp
index b61c9beb..9917c650 100644
--- a/simpleperf/RecordReadThread_test.cpp
+++ b/simpleperf/RecordReadThread_test.cpp
@@ -77,7 +77,7 @@ TEST(RecordParser, smoke) {
std::unique_ptr<RecordFileReader> reader =
RecordFileReader::CreateInstance(GetTestData(PERF_DATA_NO_UNWIND));
ASSERT_TRUE(reader);
- RecordParser parser(*reader->AttrSection()[0].attr);
+ RecordParser parser(reader->AttrSection()[0].attr);
auto process_record = [&](std::unique_ptr<Record> record) {
if (record->type() == PERF_RECORD_MMAP || record->type() == PERF_RECORD_COMM ||
record->type() == PERF_RECORD_FORK || record->type() == PERF_RECORD_SAMPLE) {
@@ -370,9 +370,9 @@ TEST_F(RecordReadThreadTest, process_sample_record) {
thread.SetBufferLevels(record_buffer_size, record_buffer_size);
read_record(r);
ASSERT_FALSE(r);
- ASSERT_EQ(thread.GetStat().lost_samples, 1u);
- ASSERT_EQ(thread.GetStat().lost_non_samples, 0u);
- ASSERT_EQ(thread.GetStat().cut_stack_samples, 1u);
+ ASSERT_EQ(thread.GetStat().userspace_lost_samples, 1u);
+ ASSERT_EQ(thread.GetStat().userspace_lost_non_samples, 0u);
+ ASSERT_EQ(thread.GetStat().userspace_cut_stack_samples, 1u);
}
// Test that the data notification exists until the RecordBuffer is empty. So we can read all
@@ -421,9 +421,9 @@ TEST_F(RecordReadThreadTest, no_cut_samples) {
received_samples++;
}
ASSERT_GT(received_samples, 0u);
- ASSERT_GT(thread.GetStat().lost_samples, 0u);
- ASSERT_EQ(thread.GetStat().lost_samples, total_samples - received_samples);
- ASSERT_EQ(thread.GetStat().cut_stack_samples, 0u);
+ ASSERT_GT(thread.GetStat().userspace_lost_samples, 0u);
+ ASSERT_EQ(thread.GetStat().userspace_lost_samples, total_samples - received_samples);
+ ASSERT_EQ(thread.GetStat().userspace_cut_stack_samples, 0u);
}
TEST_F(RecordReadThreadTest, exclude_perf) {
@@ -476,16 +476,17 @@ struct FakeAuxData {
};
TEST_F(RecordReadThreadTest, read_aux_data) {
+ ScopedEventTypes scoped_types("cs-etm,0,0");
const EventType* type = FindEventTypeByName("cs-etm");
- if (type == nullptr) {
- GTEST_LOG_(INFO) << "Omit this test as cs-etm event type isn't available";
- return;
- }
+ ASSERT_TRUE(type != nullptr);
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
+ // one buffer too big to fit in record buffer, failing at checking free size
+ aux_data.emplace_back(1024, 0, '3', 0, true);
+ // one buffer too big to fit in record buffer, failing at AllocWriteSpace()
+ aux_data.emplace_back(800, 0, '4', 0, true);
size_t test_index = 0;
auto SetBuf1 = [&](char** buf1) {
diff --git a/simpleperf/RegEx.cpp b/simpleperf/RegEx.cpp
new file mode 100644
index 00000000..97bb45bb
--- /dev/null
+++ b/simpleperf/RegEx.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RegEx.h"
+
+#include <regex>
+
+#include <android-base/logging.h>
+
+namespace simpleperf {
+
+RegExMatch::~RegExMatch() {}
+
+class RegExMatchImpl : public RegExMatch {
+ public:
+ RegExMatchImpl(std::string_view s, const std::regex& re)
+ : match_it_(s.data(), s.data() + s.size(), re) {}
+
+ bool IsValid() const override { return match_it_ != std::cregex_iterator(); }
+
+ std::string GetField(size_t index) const override { return match_it_->str(index); }
+
+ void MoveToNextMatch() override { ++match_it_; }
+
+ private:
+ std::cregex_iterator match_it_;
+};
+
+class RegExImpl : public RegEx {
+ public:
+ RegExImpl(std::string_view pattern)
+ : RegEx(pattern), re_(pattern_, std::regex::ECMAScript | std::regex::optimize) {}
+
+ bool Match(std::string_view s) const override {
+ return std::regex_match(s.begin(), s.end(), re_);
+ }
+ bool Search(std::string_view s) const override {
+ return std::regex_search(s.begin(), s.end(), re_);
+ }
+ std::unique_ptr<RegExMatch> SearchAll(std::string_view s) const override {
+ return std::unique_ptr<RegExMatch>(new RegExMatchImpl(s, re_));
+ }
+ std::optional<std::string> Replace(const std::string& s,
+ const std::string& format) const override {
+ try {
+ return {std::regex_replace(s, re_, format)};
+ } catch (std::regex_error& e) {
+ LOG(ERROR) << "regex_error: " << e.what() << ", pattern " << pattern_ << ", format "
+ << format;
+ return std::nullopt;
+ }
+ }
+
+ private:
+ std::regex re_;
+};
+
+std::unique_ptr<RegEx> RegEx::Create(std::string_view pattern) {
+ try {
+ return std::make_unique<RegExImpl>(pattern);
+ } catch (std::regex_error& e) {
+ LOG(ERROR) << "regex_error: " << e.what() << ", pattern " << pattern;
+ return nullptr;
+ }
+}
+
+} // namespace simpleperf
diff --git a/simpleperf/RegEx.h b/simpleperf/RegEx.h
new file mode 100644
index 00000000..c1e4f511
--- /dev/null
+++ b/simpleperf/RegEx.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+
+namespace simpleperf {
+
+class RegExMatch {
+ public:
+ virtual ~RegExMatch();
+ virtual bool IsValid() const = 0;
+ virtual std::string GetField(size_t index) const = 0;
+ virtual void MoveToNextMatch() = 0;
+};
+
+// A wrapper of std::regex, converting std::regex_error exception into return value.
+class RegEx {
+ public:
+ static std::unique_ptr<RegEx> Create(std::string_view pattern);
+ virtual ~RegEx() {}
+ const std::string& GetPattern() const { return pattern_; }
+
+ virtual bool Match(std::string_view s) const = 0;
+ virtual bool Search(std::string_view s) const = 0;
+ // Always return a not-null RegExMatch. If no match, RegExMatch->IsValid() is false.
+ virtual std::unique_ptr<RegExMatch> SearchAll(std::string_view s) const = 0;
+ virtual std::optional<std::string> Replace(const std::string& s,
+ const std::string& format) const = 0;
+
+ protected:
+ RegEx(std::string_view pattern) : pattern_(pattern) {}
+
+ std::string pattern_;
+};
+
+} // namespace simpleperf
diff --git a/simpleperf/RegEx_test.cpp b/simpleperf/RegEx_test.cpp
new file mode 100644
index 00000000..cbb25ae0
--- /dev/null
+++ b/simpleperf/RegEx_test.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RegEx.h"
+
+#include <gtest/gtest.h>
+
+using namespace simpleperf;
+
+TEST(RegEx, smoke) {
+ auto re = RegEx::Create("b+");
+ ASSERT_EQ(re->GetPattern(), "b+");
+ ASSERT_FALSE(re->Search("aaa"));
+ ASSERT_TRUE(re->Search("aba"));
+ ASSERT_FALSE(re->Match("aba"));
+ ASSERT_TRUE(re->Match("bbb"));
+
+ auto match = re->SearchAll("aaa");
+ ASSERT_FALSE(match->IsValid());
+ match = re->SearchAll("ababb");
+ ASSERT_TRUE(match->IsValid());
+ ASSERT_EQ(match->GetField(0), "b");
+ match->MoveToNextMatch();
+ ASSERT_TRUE(match->IsValid());
+ ASSERT_EQ(match->GetField(0), "bb");
+ match->MoveToNextMatch();
+ ASSERT_FALSE(match->IsValid());
+
+ ASSERT_EQ(re->Replace("ababb", "c").value(), "acac");
+}
+
+TEST(RegEx, invalid_pattern) {
+ ASSERT_TRUE(RegEx::Create("?hello") == nullptr);
+}
diff --git a/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java b/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java
index a95a4040..aa840d89 100644
--- a/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java
+++ b/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java
@@ -132,7 +132,7 @@ public class ProfileSession {
*/
public synchronized void startRecording(@NonNull List<String> args) {
if (mState != State.NOT_YET_STARTED) {
- throw new AssertionError("startRecording: session in wrong state " + mState);
+ throw new IllegalStateException("startRecording: session in wrong state " + mState);
}
for (String arg : args) {
if (arg.equals("--trace-offcpu")) {
@@ -151,7 +151,7 @@ public class ProfileSession {
*/
public synchronized void pauseRecording() {
if (mState != State.STARTED) {
- throw new AssertionError("pauseRecording: session in wrong state " + mState);
+ throw new IllegalStateException("pauseRecording: session in wrong state " + mState);
}
if (mTraceOffCpu) {
throw new AssertionError(
@@ -166,7 +166,7 @@ public class ProfileSession {
*/
public synchronized void resumeRecording() {
if (mState != State.PAUSED) {
- throw new AssertionError("resumeRecording: session in wrong state " + mState);
+ throw new IllegalStateException("resumeRecording: session in wrong state " + mState);
}
sendCmd("resume");
mState = State.STARTED;
@@ -177,7 +177,7 @@ public class ProfileSession {
*/
public synchronized void stopRecording() {
if (mState != State.STARTED && mState != State.PAUSED) {
- throw new AssertionError("stopRecording: session in wrong state " + mState);
+ throw new IllegalStateException("stopRecording: session in wrong state " + mState);
}
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P + 1
&& mSimpleperfPath.equals(SIMPLEPERF_PATH_IN_IMAGE)) {
diff --git a/simpleperf/cmd_api.cpp b/simpleperf/cmd_api.cpp
index c407edfc..f3ce2bb9 100644
--- a/simpleperf/cmd_api.cpp
+++ b/simpleperf/cmd_api.cpp
@@ -17,7 +17,6 @@
#include <stdio.h>
#include <memory>
-#include <regex>
#include <string>
#include <thread>
#include <vector>
@@ -30,6 +29,7 @@
#include <android-base/unique_fd.h>
#include <ziparchive/zip_writer.h>
+#include "RegEx.h"
#include "cmd_api_impl.h"
#include "command.h"
#include "environment.h"
@@ -126,19 +126,15 @@ std::optional<uint32_t> PrepareCommand::GetAppUid() {
PLOG(ERROR) << "failed to run `pm list packages -U`";
return std::nullopt;
}
- std::regex re(R"(package:([\w\.]+)\s+uid:(\d+))");
- std::sregex_iterator match_it(content.begin(), content.end(), re);
- std::sregex_iterator match_end;
- while (match_it != match_end) {
- std::smatch match = *match_it++;
- std::string name = match.str(1);
+ auto re = RegEx::Create(R"(package:([\w\.]+)\s+uid:(\d+))");
+ auto match = re->SearchAll(content);
+ while (match->IsValid()) {
+ std::string name = match->GetField(1);
uint32_t uid;
- if (!android::base::ParseUint(match.str(2), &uid)) {
- continue;
- }
- if (name == app_name_) {
+ if (name == app_name_ && android::base::ParseUint(match->GetField(2), &uid)) {
return uid;
}
+ match->MoveToNextMatch();
}
LOG(ERROR) << "failed to find package " << app_name_;
return std::nullopt;
diff --git a/simpleperf/cmd_debug_unwind.cpp b/simpleperf/cmd_debug_unwind.cpp
index 560a1834..52d3c96c 100644
--- a/simpleperf/cmd_debug_unwind.cpp
+++ b/simpleperf/cmd_debug_unwind.cpp
@@ -146,7 +146,9 @@ class RecordFileProcessor {
}
// 2. Load feature sections.
- reader_->LoadBuildIdAndFileFeatures(thread_tree_);
+ if (!reader_->LoadBuildIdAndFileFeatures(thread_tree_)) {
+ return false;
+ }
ScopedCurrentArch scoped_arch(
GetArchType(reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH)));
unwinder_->LoadMetaInfo(reader_->GetMetaInfoFeature());
@@ -401,12 +403,13 @@ class TestFileGenerator : public RecordFileProcessor {
bool WriteMapsForSample(const SampleRecord& r) {
ThreadEntry* thread = thread_tree_.FindThread(r.tid_data.tid);
if (thread != nullptr && thread->maps) {
- auto attr = reader_->AttrSection()[0].attr;
- auto event_id = reader_->AttrSection()[0].ids[0];
+ const EventAttrIds& attrs = reader_->AttrSection();
+ const perf_event_attr& attr = attrs[0].attr;
+ uint64_t event_id = attrs[0].ids[0];
for (const auto& p : thread->maps->maps) {
const MapEntry* map = p.second;
- Mmap2Record map_record(*attr, false, r.tid_data.pid, r.tid_data.tid, map->start_addr,
+ Mmap2Record map_record(attr, false, r.tid_data.pid, r.tid_data.tid, map->start_addr,
map->len, map->pgoff, map->flags, map->dso->Path(), event_id,
r.Timestamp());
if (!writer_->WriteRecord(map_record)) {
@@ -423,7 +426,7 @@ class TestFileGenerator : public RecordFileProcessor {
}
std::unordered_set<int> feature_types_to_copy = {
PerfFileFormat::FEAT_ARCH, PerfFileFormat::FEAT_CMDLINE, PerfFileFormat::FEAT_META_INFO};
- const size_t BUFFER_SIZE = 64 * 1024;
+ const size_t BUFFER_SIZE = 64 * kKilobyte;
std::string buffer(BUFFER_SIZE, '\0');
for (const auto& p : reader_->FeatureSectionDescriptors()) {
auto feat_type = p.first;
@@ -445,13 +448,17 @@ class TestFileGenerator : public RecordFileProcessor {
}
} else if (feat_type == PerfFileFormat::FEAT_FILE ||
feat_type == PerfFileFormat::FEAT_FILE2) {
- size_t read_pos = 0;
+ uint64_t read_pos = 0;
FileFeature file_feature;
- while (reader_->ReadFileFeature(read_pos, &file_feature)) {
+ bool error = false;
+ while (reader_->ReadFileFeature(read_pos, file_feature, error)) {
if (kept_binaries_.count(file_feature.path) && !writer_->WriteFileFeature(file_feature)) {
return false;
}
}
+ if (error) {
+ return false;
+ }
} else if (feat_type == PerfFileFormat::FEAT_BUILD_ID) {
std::vector<BuildIdRecord> build_ids = reader_->ReadBuildIdFeature();
std::vector<BuildIdRecord> write_build_ids;
diff --git a/simpleperf/cmd_dumprecord.cpp b/simpleperf/cmd_dumprecord.cpp
index 142ae5cb..0b8df15a 100644
--- a/simpleperf/cmd_dumprecord.cpp
+++ b/simpleperf/cmd_dumprecord.cpp
@@ -15,6 +15,7 @@
*/
#include <inttypes.h>
+#include <stdint.h>
#include <map>
#include <string>
@@ -25,6 +26,7 @@
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#include "ETMBranchListFile.h"
#include "ETMDecoder.h"
#include "command.h"
#include "dso.h"
@@ -177,6 +179,18 @@ ExtractFieldFn GetExtractFieldFunction(const TracingField& field) {
return ExtractUnknownField;
}
+class ETMThreadTreeForDumpCmd : public ETMThreadTree {
+ public:
+ ETMThreadTreeForDumpCmd(ThreadTree& thread_tree) : thread_tree_(thread_tree) {}
+
+ void DisableThreadExitRecords() override { thread_tree_.DisableThreadExitRecords(); }
+ const ThreadEntry* FindThread(int tid) override { return thread_tree_.FindThread(tid); }
+ const MapSet& GetKernelMaps() override { return thread_tree_.GetKernelMaps(); }
+
+ private:
+ ThreadTree& thread_tree_;
+};
+
class DumpRecordCommand : public Command {
public:
DumpRecordCommand()
@@ -201,7 +215,7 @@ class DumpRecordCommand : public Command {
void ProcessSampleRecord(const SampleRecord& r);
void ProcessCallChainRecord(const CallChainRecord& r);
SymbolInfo GetSymbolInfo(uint32_t pid, uint32_t tid, uint64_t ip, bool in_kernel);
- void ProcessTracingData(const TracingDataRecord& r);
+ bool ProcessTracingData(const TracingDataRecord& r);
bool DumpAuxData(const AuxRecord& aux);
bool DumpFeatureSection();
@@ -211,6 +225,7 @@ class DumpRecordCommand : public Command {
std::unique_ptr<RecordFileReader> record_file_reader_;
std::unique_ptr<ETMDecoder> etm_decoder_;
+ std::unique_ptr<ETMThreadTree> etm_thread_tree_;
ThreadTree thread_tree_;
std::vector<EventInfo> events_;
@@ -305,11 +320,11 @@ void DumpRecordCommand::DumpFileHeader() {
}
void DumpRecordCommand::DumpAttrSection() {
- std::vector<EventAttrWithId> attrs = record_file_reader_->AttrSection();
+ const EventAttrIds& attrs = record_file_reader_->AttrSection();
for (size_t i = 0; i < attrs.size(); ++i) {
const auto& attr = attrs[i];
printf("attr %zu:\n", i + 1);
- DumpPerfEventAttr(*attr.attr, 1);
+ DumpPerfEventAttr(attr.attr, 1);
if (!attr.ids.empty()) {
printf(" ids:");
for (const auto& id : attr.ids) {
@@ -322,7 +337,9 @@ void DumpRecordCommand::DumpAttrSection() {
bool DumpRecordCommand::DumpDataSection() {
thread_tree_.ShowIpForUnknownSymbol();
- record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
+ if (!record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_)) {
+ return false;
+ }
auto record_callback = [&](std::unique_ptr<Record> r) { return ProcessRecord(r.get()); };
return record_file_reader_->ReadDataSection(record_callback);
@@ -341,7 +358,8 @@ bool DumpRecordCommand::ProcessRecord(Record* r) {
ProcessCallChainRecord(*static_cast<CallChainRecord*>(r));
break;
case PERF_RECORD_AUXTRACE_INFO: {
- etm_decoder_ = ETMDecoder::Create(*static_cast<AuxTraceInfoRecord*>(r), thread_tree_);
+ etm_thread_tree_.reset(new ETMThreadTreeForDumpCmd(thread_tree_));
+ etm_decoder_ = ETMDecoder::Create(*static_cast<AuxTraceInfoRecord*>(r), *etm_thread_tree_);
if (etm_decoder_) {
etm_decoder_->EnableDump(etm_dump_option_);
} else {
@@ -355,7 +373,7 @@ bool DumpRecordCommand::ProcessRecord(Record* r) {
}
case PERF_RECORD_TRACING_DATA:
case SIMPLE_PERF_RECORD_TRACING_DATA: {
- ProcessTracingData(*static_cast<TracingDataRecord*>(r));
+ res = ProcessTracingData(*static_cast<TracingDataRecord*>(r));
break;
}
}
@@ -413,29 +431,41 @@ SymbolInfo DumpRecordCommand::GetSymbolInfo(uint32_t pid, uint32_t tid, uint64_t
}
bool DumpRecordCommand::DumpAuxData(const AuxRecord& aux) {
+ if (aux.data->aux_size > SIZE_MAX) {
+ LOG(ERROR) << "invalid aux size";
+ return false;
+ }
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)) {
+ std::vector<uint8_t> data;
+ bool error = false;
+ if (!record_file_reader_->ReadAuxData(aux.Cpu(), aux.data->aux_offset, size, data, error)) {
+ return !error;
+ }
+ if (!etm_decoder_) {
+ LOG(ERROR) << "ETMDecoder isn't created";
return false;
}
- return etm_decoder_->ProcessData(data.get(), size, !aux.Unformatted(), aux.Cpu());
+ return etm_decoder_->ProcessData(data.data(), size, !aux.Unformatted(), aux.Cpu());
}
return true;
}
-void DumpRecordCommand::ProcessTracingData(const TracingDataRecord& r) {
- Tracing tracing(std::vector<char>(r.data, r.data + r.data_size));
- std::vector<EventAttrWithId> attrs = record_file_reader_->AttrSection();
+bool DumpRecordCommand::ProcessTracingData(const TracingDataRecord& r) {
+ auto tracing = Tracing::Create(std::vector<char>(r.data, r.data + r.data_size));
+ if (!tracing) {
+ return false;
+ }
+ const EventAttrIds& attrs = record_file_reader_->AttrSection();
events_.resize(attrs.size());
for (size_t i = 0; i < attrs.size(); i++) {
auto& attr = attrs[i].attr;
auto& event = events_[i];
- if (attr->type != PERF_TYPE_TRACEPOINT) {
+ if (attr.type != PERF_TYPE_TRACEPOINT) {
continue;
}
- TracingFormat format = tracing.GetTracingFormatHavingId(attr->config);
+ TracingFormat format = tracing->GetTracingFormatHavingId(attr.config);
event.tp_fields = format.fields;
// Decide dump function for each field.
for (size_t j = 0; j < event.tp_fields.size(); j++) {
@@ -444,6 +474,7 @@ void DumpRecordCommand::ProcessTracingData(const TracingDataRecord& r) {
event.tp_data_size += field.elem_count * field.elem_size;
}
}
+ return true;
}
bool DumpRecordCommand::DumpFeatureSection() {
@@ -469,9 +500,10 @@ bool DumpRecordCommand::DumpFeatureSection() {
PrintIndented(1, "cmdline: %s\n", android::base::Join(cmdline, ' ').c_str());
} else if (feature == FEAT_FILE || feature == FEAT_FILE2) {
FileFeature file;
- size_t read_pos = 0;
+ uint64_t read_pos = 0;
+ bool error = false;
PrintIndented(1, "file:\n");
- while (record_file_reader_->ReadFileFeature(read_pos, &file)) {
+ while (record_file_reader_->ReadFileFeature(read_pos, file, error)) {
PrintIndented(2, "file_path %s\n", file.path.c_str());
PrintIndented(2, "file_type %s\n", DsoTypeToString(file.type));
PrintIndented(2, "min_vaddr 0x%" PRIx64 "\n", file.min_vaddr);
@@ -488,6 +520,9 @@ bool DumpRecordCommand::DumpFeatureSection() {
}
}
}
+ if (error) {
+ return false;
+ }
} else if (feature == FEAT_META_INFO) {
PrintIndented(1, "meta_info:\n");
for (auto& pair : record_file_reader_->GetMetaInfoFeature()) {
@@ -506,6 +541,35 @@ bool DumpRecordCommand::DumpFeatureSection() {
PrintIndented(2, "size: %" PRIu64 "\n", file.size);
}
}
+ } else if (feature == FEAT_ETM_BRANCH_LIST) {
+ std::string data;
+ if (!record_file_reader_->ReadFeatureSection(FEAT_ETM_BRANCH_LIST, &data)) {
+ return false;
+ }
+ BranchListBinaryMap binary_map;
+ if (!StringToBranchListBinaryMap(data, binary_map)) {
+ return false;
+ }
+ PrintIndented(1, "etm_branch_list:\n");
+ for (const auto& [key, binary] : binary_map) {
+ PrintIndented(2, "path: %s\n", key.path.c_str());
+ PrintIndented(2, "build_id: %s\n", key.build_id.ToString().c_str());
+ PrintIndented(2, "binary_type: %s\n", DsoTypeToString(binary.dso_type));
+ if (binary.dso_type == DSO_KERNEL) {
+ PrintIndented(2, "kernel_start_addr: 0x%" PRIx64 "\n", key.kernel_start_addr);
+ }
+ for (const auto& [addr, branches] : binary.GetOrderedBranchMap()) {
+ PrintIndented(3, "addr: 0x%" PRIx64 "\n", addr);
+ for (const auto& [branch, count] : branches) {
+ std::string s = "0b";
+ for (auto it = branch.rbegin(); it != branch.rend(); ++it) {
+ s.push_back(*it ? '1' : '0');
+ }
+ PrintIndented(3, "branch: %s\n", s.c_str());
+ PrintIndented(3, "count: %" PRIu64 "\n", count);
+ }
+ }
+ }
}
}
return true;
diff --git a/simpleperf/cmd_inject.cpp b/simpleperf/cmd_inject.cpp
index b2c573d4..d3f66cd7 100644
--- a/simpleperf/cmd_inject.cpp
+++ b/simpleperf/cmd_inject.cpp
@@ -14,19 +14,20 @@
* limitations under the License.
*/
+#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <memory>
#include <optional>
-#include <regex>
#include <string>
#include <android-base/parseint.h>
#include <android-base/strings.h>
+#include "ETMBranchListFile.h"
#include "ETMDecoder.h"
-#include "cmd_inject_impl.h"
+#include "RegEx.h"
#include "command.h"
#include "record_file.h"
#include "system/extras/simpleperf/etm_branch_list.pb.h"
@@ -35,31 +36,8 @@
namespace simpleperf {
-std::string BranchToProtoString(const std::vector<bool>& branch) {
- size_t bytes = (branch.size() + 7) / 8;
- std::string res(bytes, '\0');
- for (size_t i = 0; i < branch.size(); i++) {
- if (branch[i]) {
- res[i >> 3] |= 1 << (i & 7);
- }
- }
- return res;
-}
-
-std::vector<bool> ProtoStringToBranch(const std::string& s, size_t bit_size) {
- std::vector<bool> branch(bit_size, false);
- for (size_t i = 0; i < bit_size; i++) {
- if (s[i >> 3] & (1 << (i & 7))) {
- branch[i] = true;
- }
- }
- return branch;
-}
-
namespace {
-constexpr const char* ETM_BRANCH_LIST_PROTO_MAGIC = "simpleperf:EtmBranchList";
-
using AddrPair = std::pair<uint64_t, uint64_t>;
struct AddrPairHash {
@@ -76,53 +54,6 @@ enum class OutputFormat {
BranchList,
};
-// When processing binary info in an input file, the binaries are identified by their path.
-// But this isn't sufficient when merging binary info from multiple input files. Because
-// binaries for the same path may be changed between generating input files. So after processing
-// each input file, we create BinaryKeys to identify binaries, which consider path, build_id and
-// kernel_start_addr (for vmlinux). kernel_start_addr affects how addresses in BranchListBinaryInfo
-// are interpreted for vmlinux.
-struct BinaryKey {
- std::string path;
- BuildId build_id;
- uint64_t kernel_start_addr = 0;
-
- BinaryKey() {}
-
- BinaryKey(const std::string& path, BuildId build_id) : path(path), build_id(build_id) {}
-
- BinaryKey(Dso* dso, uint64_t kernel_start_addr) : path(dso->Path()) {
- build_id = Dso::FindExpectedBuildIdForPath(dso->Path());
- if (dso->type() == DSO_KERNEL) {
- this->kernel_start_addr = kernel_start_addr;
- }
- }
-
- bool operator==(const BinaryKey& other) const {
- return path == other.path && build_id == other.build_id &&
- kernel_start_addr == other.kernel_start_addr;
- }
-};
-
-struct BinaryKeyHash {
- size_t operator()(const BinaryKey& key) const noexcept {
- size_t seed = 0;
- HashCombine(seed, key.path);
- HashCombine(seed, key.build_id);
- if (key.kernel_start_addr != 0) {
- HashCombine(seed, key.kernel_start_addr);
- }
- return seed;
- }
-};
-
-static void OverflowSafeAdd(uint64_t& dest, uint64_t add) {
- if (__builtin_add_overflow(dest, add, &dest)) {
- LOG(WARNING) << "Branch count overflow happened.";
- dest = UINT64_MAX;
- }
-}
-
struct AutoFDOBinaryInfo {
uint64_t first_load_segment_addr = 0;
std::unordered_map<AddrPair, uint64_t, AddrPairHash> range_count_map;
@@ -155,78 +86,50 @@ struct AutoFDOBinaryInfo {
}
};
-using UnorderedBranchMap =
- std::unordered_map<uint64_t, std::unordered_map<std::vector<bool>, uint64_t>>;
-
-struct BranchListBinaryInfo {
- DsoType dso_type;
- UnorderedBranchMap branch_map;
-
- void Merge(const BranchListBinaryInfo& other) {
- for (auto& other_p : other.branch_map) {
- auto it = branch_map.find(other_p.first);
- if (it == branch_map.end()) {
- branch_map[other_p.first] = std::move(other_p.second);
- } else {
- auto& map2 = it->second;
- for (auto& other_p2 : other_p.second) {
- auto it2 = map2.find(other_p2.first);
- if (it2 == map2.end()) {
- map2[other_p2.first] = other_p2.second;
- } else {
- OverflowSafeAdd(it2->second, other_p2.second);
- }
- }
- }
- }
- }
-
- BranchMap GetOrderedBranchMap() const {
- BranchMap result;
- for (const auto& p : branch_map) {
- uint64_t addr = p.first;
- const auto& b_map = p.second;
- result[addr] = std::map<std::vector<bool>, uint64_t>(b_map.begin(), b_map.end());
- }
- return result;
- }
-};
-
using AutoFDOBinaryCallback = std::function<void(const BinaryKey&, AutoFDOBinaryInfo&)>;
using BranchListBinaryCallback = std::function<void(const BinaryKey&, BranchListBinaryInfo&)>;
-class ThreadTreeWithFilter : public ThreadTree {
+class ETMThreadTreeWithFilter : public ETMThreadTree {
public:
void ExcludePid(pid_t pid) { exclude_pid_ = pid; }
+ ThreadTree& GetThreadTree() { return thread_tree_; }
+ void DisableThreadExitRecords() override { thread_tree_.DisableThreadExitRecords(); }
- ThreadEntry* FindThread(int tid) const override {
- ThreadEntry* thread = ThreadTree::FindThread(tid);
+ const ThreadEntry* FindThread(int tid) override {
+ const ThreadEntry* thread = thread_tree_.FindThread(tid);
if (thread != nullptr && exclude_pid_ && thread->pid == exclude_pid_) {
return nullptr;
}
return thread;
}
+ const MapSet& GetKernelMaps() override { return thread_tree_.GetKernelMaps(); }
+
private:
+ ThreadTree thread_tree_;
std::optional<pid_t> exclude_pid_;
};
-class DsoFilter {
+class BinaryFilter {
public:
- DsoFilter(const std::regex& binary_name_regex) : binary_name_regex_(binary_name_regex) {}
+ BinaryFilter(const RegEx* binary_name_regex) : binary_name_regex_(binary_name_regex) {}
- bool FilterDso(Dso* dso) {
+ bool Filter(Dso* dso) {
auto lookup = dso_filter_cache_.find(dso);
if (lookup != dso_filter_cache_.end()) {
return lookup->second;
}
- bool match = std::regex_search(dso->Path(), binary_name_regex_);
+ bool match = Filter(dso->Path());
dso_filter_cache_.insert({dso, match});
return match;
}
+ bool Filter(const std::string& path) {
+ return binary_name_regex_ == nullptr || binary_name_regex_->Search(path);
+ }
+
private:
- std::regex binary_name_regex_;
+ const RegEx* binary_name_regex_;
std::unordered_map<Dso*, bool> dso_filter_cache_;
};
@@ -247,11 +150,11 @@ static uint64_t GetFirstLoadSegmentVaddr(Dso* dso) {
class PerfDataReader {
public:
PerfDataReader(const std::string& filename, bool exclude_perf, ETMDumpOption etm_dump_option,
- const std::regex& binary_name_regex)
+ const RegEx* binary_name_regex)
: filename_(filename),
exclude_perf_(exclude_perf),
etm_dump_option_(etm_dump_option),
- dso_filter_(binary_name_regex) {}
+ binary_filter_(binary_name_regex) {}
void SetCallback(const AutoFDOBinaryCallback& callback) { autofdo_callback_ = callback; }
void SetCallback(const BranchListBinaryCallback& callback) { branch_list_callback_ = callback; }
@@ -261,6 +164,9 @@ class PerfDataReader {
if (!record_file_reader_) {
return false;
}
+ if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_ETM_BRANCH_LIST)) {
+ return ProcessETMBranchListFeature();
+ }
if (exclude_perf_) {
const auto& info_map = record_file_reader_->GetMetaInfoFeature();
if (auto it = info_map.find("recording_process"); it == info_map.end()) {
@@ -275,7 +181,9 @@ class PerfDataReader {
thread_tree_.ExcludePid(pid);
}
}
- record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
+ if (!record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_.GetThreadTree())) {
+ return false;
+ }
if (!record_file_reader_->ReadDataSection([this](auto r) { return ProcessRecord(r.get()); })) {
return false;
}
@@ -291,8 +199,34 @@ class PerfDataReader {
}
private:
+ bool ProcessETMBranchListFeature() {
+ if (exclude_perf_) {
+ LOG(WARNING) << "--exclude-perf has no effect on perf.data with etm branch list";
+ }
+ if (autofdo_callback_) {
+ LOG(ERROR) << "convert to autofdo format isn't support on perf.data with etm branch list";
+ return false;
+ }
+ CHECK(branch_list_callback_);
+ std::string s;
+ if (!record_file_reader_->ReadFeatureSection(PerfFileFormat::FEAT_ETM_BRANCH_LIST, &s)) {
+ return false;
+ }
+ BranchListBinaryMap binary_map;
+ if (!StringToBranchListBinaryMap(s, binary_map)) {
+ return false;
+ }
+ for (auto& [key, binary] : binary_map) {
+ if (!binary_filter_.Filter(key.path)) {
+ continue;
+ }
+ branch_list_callback_(key, binary);
+ }
+ return true;
+ }
+
bool ProcessRecord(Record* r) {
- thread_tree_.Update(*r);
+ thread_tree_.GetThreadTree().Update(*r);
if (r->type() == PERF_RECORD_AUXTRACE_INFO) {
etm_decoder_ = ETMDecoder::Create(*static_cast<AuxTraceInfoRecord*>(r), thread_tree_);
if (!etm_decoder_) {
@@ -308,14 +242,19 @@ class PerfDataReader {
}
} else if (r->type() == PERF_RECORD_AUX) {
AuxRecord* aux = static_cast<AuxRecord*>(r);
- uint64_t aux_size = aux->data->aux_size;
+ if (aux->data->aux_size > SIZE_MAX) {
+ LOG(ERROR) << "invalid aux size";
+ return false;
+ }
+ size_t aux_size = aux->data->aux_size;
if (aux_size > 0) {
- if (aux_data_buffer_.size() < aux_size) {
- aux_data_buffer_.resize(aux_size);
+ bool error = false;
+ if (!record_file_reader_->ReadAuxData(aux->Cpu(), aux->data->aux_offset, aux_size,
+ aux_data_buffer_, error)) {
+ return !error;
}
- if (!record_file_reader_->ReadAuxData(aux->Cpu(), aux->data->aux_offset,
- aux_data_buffer_.data(), aux_size)) {
- LOG(ERROR) << "failed to read aux data in " << filename_;
+ if (!etm_decoder_) {
+ LOG(ERROR) << "ETMDecoder isn't created";
return false;
}
return etm_decoder_->ProcessData(aux_data_buffer_.data(), aux_size, !aux->Unformatted(),
@@ -331,7 +270,7 @@ class PerfDataReader {
}
void ProcessInstrRange(const ETMInstrRange& instr_range) {
- if (!dso_filter_.FilterDso(instr_range.dso)) {
+ if (!binary_filter_.Filter(instr_range.dso)) {
return;
}
@@ -339,7 +278,7 @@ class PerfDataReader {
}
void ProcessBranchList(const ETMBranchList& branch_list) {
- if (!dso_filter_.FilterDso(branch_list.dso)) {
+ if (!binary_filter_.Filter(branch_list.dso)) {
return;
}
@@ -381,14 +320,14 @@ class PerfDataReader {
const std::string filename_;
bool exclude_perf_;
ETMDumpOption etm_dump_option_;
- DsoFilter dso_filter_;
+ BinaryFilter binary_filter_;
AutoFDOBinaryCallback autofdo_callback_;
BranchListBinaryCallback branch_list_callback_;
std::vector<uint8_t> aux_data_buffer_;
std::unique_ptr<ETMDecoder> etm_decoder_;
std::unique_ptr<RecordFileReader> record_file_reader_;
- ThreadTreeWithFilter thread_tree_;
+ ETMThreadTreeWithFilter thread_tree_;
uint64_t kernel_map_start_addr_ = 0;
// Store results for AutoFDO.
std::unordered_map<Dso*, AutoFDOBinaryInfo> autofdo_binary_map_;
@@ -399,82 +338,34 @@ class PerfDataReader {
// Read a protobuf file specified by etm_branch_list.proto, and generate BranchListBinaryInfo.
class BranchListReader {
public:
- BranchListReader(const std::string& filename, const std::regex binary_name_regex)
- : filename_(filename), binary_name_regex_(binary_name_regex) {}
+ BranchListReader(const std::string& filename, const RegEx* binary_name_regex)
+ : filename_(filename), binary_filter_(binary_name_regex) {}
void SetCallback(const BranchListBinaryCallback& callback) { callback_ = callback; }
bool Read() {
- auto fd = FileHelper::OpenReadOnly(filename_);
- if (!fd.ok()) {
- PLOG(ERROR) << "failed to open " << filename_;
- return false;
- }
-
- proto::ETMBranchList branch_list_proto;
- if (!branch_list_proto.ParseFromFileDescriptor(fd)) {
- PLOG(ERROR) << "failed to read msg from " << filename_;
+ std::string s;
+ if (!android::base::ReadFileToString(filename_, &s)) {
+ PLOG(ERROR) << "failed to read " << filename_;
return false;
}
- if (branch_list_proto.magic() != ETM_BRANCH_LIST_PROTO_MAGIC) {
- PLOG(ERROR) << "file not in format etm_branch_list.proto: " << filename_;
+ BranchListBinaryMap binary_map;
+ if (!StringToBranchListBinaryMap(s, binary_map)) {
+ PLOG(ERROR) << "file is in wrong format: " << filename_;
return false;
}
-
- for (size_t i = 0; i < branch_list_proto.binaries_size(); i++) {
- const auto& binary_proto = branch_list_proto.binaries(i);
- if (!std::regex_search(binary_proto.path(), binary_name_regex_)) {
+ for (auto& [key, binary] : binary_map) {
+ if (!binary_filter_.Filter(key.path)) {
continue;
}
- BinaryKey key(binary_proto.path(), BuildId(binary_proto.build_id()));
- if (binary_proto.has_kernel_info()) {
- key.kernel_start_addr = binary_proto.kernel_info().kernel_start_addr();
- }
- BranchListBinaryInfo binary;
- auto dso_type = ToDsoType(binary_proto.type());
- if (!dso_type) {
- LOG(ERROR) << "invalid binary type in " << filename_;
- return false;
- }
- binary.dso_type = dso_type.value();
- binary.branch_map = BuildUnorderedBranchMap(binary_proto);
callback_(key, binary);
}
return true;
}
private:
- std::optional<DsoType> ToDsoType(proto::ETMBranchList_Binary::BinaryType binary_type) {
- switch (binary_type) {
- case proto::ETMBranchList_Binary::ELF_FILE:
- return DSO_ELF_FILE;
- case proto::ETMBranchList_Binary::KERNEL:
- return DSO_KERNEL;
- case proto::ETMBranchList_Binary::KERNEL_MODULE:
- return DSO_KERNEL_MODULE;
- default:
- LOG(ERROR) << "unexpected binary type " << binary_type;
- return std::nullopt;
- }
- }
-
- UnorderedBranchMap BuildUnorderedBranchMap(const proto::ETMBranchList_Binary& binary_proto) {
- UnorderedBranchMap branch_map;
- for (size_t i = 0; i < binary_proto.addrs_size(); i++) {
- const auto& addr_proto = binary_proto.addrs(i);
- auto& b_map = branch_map[addr_proto.addr()];
- for (size_t j = 0; j < addr_proto.branches_size(); j++) {
- const auto& branch_proto = addr_proto.branches(j);
- std::vector<bool> branch =
- ProtoStringToBranch(branch_proto.branch(), branch_proto.branch_size());
- b_map[branch] = branch_proto.count();
- }
- }
- return branch_map;
- }
-
const std::string filename_;
- const std::regex binary_name_regex_;
+ BinaryFilter binary_filter_;
BranchListBinaryCallback callback_;
};
@@ -610,6 +501,7 @@ class AutoFDOWriter {
}
// Write the binary path in comment.
+ fprintf(output_fp.get(), "// build_id: %s\n", key.build_id.ToString().c_str());
fprintf(output_fp.get(), "// %s\n\n", key.path.c_str());
}
return true;
@@ -630,84 +522,30 @@ struct BranchListMerger {
}
}
- std::unordered_map<BinaryKey, BranchListBinaryInfo, BinaryKeyHash> binary_map;
+ BranchListBinaryMap binary_map;
};
// Write branch lists to a protobuf file specified by etm_branch_list.proto.
class BranchListWriter {
public:
- bool Write(const std::string& output_filename,
- const std::unordered_map<BinaryKey, BranchListBinaryInfo, BinaryKeyHash>& binary_map) {
+ bool Write(const std::string& output_filename, const BranchListBinaryMap& binary_map) {
// Don't produce empty output file.
if (binary_map.empty()) {
LOG(INFO) << "Skip empty output file.";
unlink(output_filename.c_str());
return true;
}
- std::unique_ptr<FILE, decltype(&fclose)> output_fp(fopen(output_filename.c_str(), "wb"),
- fclose);
- if (!output_fp) {
- PLOG(ERROR) << "failed to write to " << output_filename;
+ std::string s;
+ if (!BranchListBinaryMapToString(binary_map, s)) {
+ LOG(ERROR) << "invalid BranchListBinaryMap";
return false;
}
-
- proto::ETMBranchList branch_list_proto;
- branch_list_proto.set_magic(ETM_BRANCH_LIST_PROTO_MAGIC);
- std::vector<char> branch_buf;
- for (const auto& p : binary_map) {
- const BinaryKey& key = p.first;
- const BranchListBinaryInfo& binary = p.second;
- auto binary_proto = branch_list_proto.add_binaries();
-
- binary_proto->set_path(key.path);
- if (!key.build_id.IsEmpty()) {
- binary_proto->set_build_id(key.build_id.ToString().substr(2));
- }
- auto opt_binary_type = ToProtoBinaryType(binary.dso_type);
- if (!opt_binary_type.has_value()) {
- return false;
- }
- binary_proto->set_type(opt_binary_type.value());
-
- for (const auto& addr_p : binary.branch_map) {
- auto addr_proto = binary_proto->add_addrs();
- addr_proto->set_addr(addr_p.first);
-
- for (const auto& branch_p : addr_p.second) {
- const std::vector<bool>& branch = branch_p.first;
- auto branch_proto = addr_proto->add_branches();
-
- branch_proto->set_branch(BranchToProtoString(branch));
- branch_proto->set_branch_size(branch.size());
- branch_proto->set_count(branch_p.second);
- }
- }
-
- if (binary.dso_type == DSO_KERNEL) {
- binary_proto->mutable_kernel_info()->set_kernel_start_addr(key.kernel_start_addr);
- }
- }
- if (!branch_list_proto.SerializeToFileDescriptor(fileno(output_fp.get()))) {
+ if (!android::base::WriteStringToFile(s, output_filename)) {
PLOG(ERROR) << "failed to write to " << output_filename;
return false;
}
return true;
}
-
- private:
- std::optional<proto::ETMBranchList_Binary::BinaryType> ToProtoBinaryType(DsoType dso_type) {
- switch (dso_type) {
- case DSO_ELF_FILE:
- return proto::ETMBranchList_Binary::ELF_FILE;
- case DSO_KERNEL:
- return proto::ETMBranchList_Binary::KERNEL;
- case DSO_KERNEL_MODULE:
- return proto::ETMBranchList_Binary::KERNEL_MODULE;
- default:
- LOG(ERROR) << "unexpected dso type " << dso_type;
- return std::nullopt;
- }
- }
};
class InjectCommand : public Command {
@@ -783,7 +621,10 @@ class InjectCommand : public Command {
}
if (auto value = options.PullValue("--binary"); value) {
- binary_name_regex_ = *value->str_value;
+ binary_name_regex_ = RegEx::Create(*value->str_value);
+ if (binary_name_regex_ == nullptr) {
+ return false;
+ }
}
if (auto value = options.PullValue("--dump-etm"); value) {
if (!ParseEtmDumpOption(*value->str_value, &etm_dump_option_)) {
@@ -849,7 +690,8 @@ class InjectCommand : public Command {
autofdo_writer.AddAutoFDOBinary(key, binary);
};
for (const auto& input_filename : input_filenames_) {
- PerfDataReader reader(input_filename, exclude_perf_, etm_dump_option_, binary_name_regex_);
+ PerfDataReader reader(input_filename, exclude_perf_, etm_dump_option_,
+ binary_name_regex_.get());
reader.SetCallback(callback);
if (!reader.Read()) {
return false;
@@ -864,7 +706,8 @@ class InjectCommand : public Command {
branch_list_merger.AddBranchListBinary(key, binary);
};
for (const auto& input_filename : input_filenames_) {
- PerfDataReader reader(input_filename, exclude_perf_, etm_dump_option_, binary_name_regex_);
+ PerfDataReader reader(input_filename, exclude_perf_, etm_dump_option_,
+ binary_name_regex_.get());
reader.SetCallback(callback);
if (!reader.Read()) {
return false;
@@ -881,7 +724,7 @@ class InjectCommand : public Command {
branch_list_merger.AddBranchListBinary(key, binary);
};
for (const auto& input_filename : input_filenames_) {
- BranchListReader reader(input_filename, binary_name_regex_);
+ BranchListReader reader(input_filename, binary_name_regex_.get());
reader.SetCallback(callback);
if (!reader.Read()) {
return false;
@@ -913,7 +756,7 @@ class InjectCommand : public Command {
branch_list_merger.AddBranchListBinary(key, binary);
};
for (const auto& input_filename : input_filenames_) {
- BranchListReader reader(input_filename, binary_name_regex_);
+ BranchListReader reader(input_filename, binary_name_regex_.get());
reader.SetCallback(callback);
if (!reader.Read()) {
return false;
@@ -924,7 +767,7 @@ class InjectCommand : public Command {
return branch_list_writer.Write(output_filename_, branch_list_merger.binary_map);
}
- std::regex binary_name_regex_{""}; // Default to match everything.
+ std::unique_ptr<RegEx> binary_name_regex_;
bool exclude_perf_ = false;
std::vector<std::string> input_filenames_;
std::string output_filename_ = "perf_inject.data";
diff --git a/simpleperf/cmd_inject_test.cpp b/simpleperf/cmd_inject_test.cpp
index c93a15d6..23347b90 100644
--- a/simpleperf/cmd_inject_test.cpp
+++ b/simpleperf/cmd_inject_test.cpp
@@ -18,7 +18,6 @@
#include <android-base/test_utils.h>
#include <gtest/gtest.h>
-#include "cmd_inject_impl.h"
#include "command.h"
#include "get_test_data.h"
#include "test_util.h"
@@ -101,20 +100,6 @@ TEST(cmd_inject, output_option) {
CheckMatchingExpectedData(autofdo_data);
}
-TEST(cmd_inject, branch_to_proto_string) {
- std::vector<bool> branch;
- for (size_t i = 0; i < 100; i++) {
- branch.push_back(i % 2 == 0);
- std::string s = BranchToProtoString(branch);
- for (size_t j = 0; j <= i; j++) {
- bool b = s[j >> 3] & (1 << (j & 7));
- ASSERT_EQ(b, branch[j]);
- }
- std::vector<bool> branch2 = ProtoStringToBranch(s, branch.size());
- ASSERT_EQ(branch, branch2);
- }
-}
-
TEST(cmd_inject, skip_empty_output_file) {
TemporaryFile tmpfile;
close(tmpfile.release());
@@ -159,7 +144,7 @@ TEST(cmd_inject, unformatted_trace) {
TEST(cmd_inject, multiple_input_files) {
std::string data;
- std::string perf_data = GetTestData(std::string("etm") + OS_PATH_SEPARATOR + "perf.data");
+ std::string perf_data = GetTestData(PERF_DATA_ETM_TEST_LOOP);
std::string perf_with_unformatted_trace =
GetTestData(std::string("etm") + OS_PATH_SEPARATOR + "perf_with_unformatted_trace.data");
@@ -195,7 +180,6 @@ TEST(cmd_inject, merge_branch_list_files) {
TEST(cmd_inject, report_warning_when_overflow) {
CapturedStderr capture;
- capture.Start();
std::vector<std::unique_ptr<TemporaryFile>> branch_list_files;
std::vector<std::unique_ptr<TemporaryFile>> input_files;
@@ -232,3 +216,11 @@ TEST(cmd_inject, report_warning_when_overflow) {
ASSERT_NE(capture.str().find(WARNING_MSG), std::string::npos);
ASSERT_NE(autofdo_data.find("106c->1074:18446744073709551615"), std::string::npos);
}
+
+TEST(cmd_inject, accept_missing_aux_data) {
+ // Recorded with "-e cs-etm:u --user-buffer-size 64k sleep 1".
+ std::string perf_data = GetTestData("etm/perf_with_missing_aux_data.data");
+ TemporaryFile tmpfile;
+ close(tmpfile.release());
+ ASSERT_TRUE(RunInjectCmd({"--output", "branch-list", "-i", perf_data, "-o", tmpfile.path}));
+}
diff --git a/simpleperf/cmd_kmem.cpp b/simpleperf/cmd_kmem.cpp
index c756eb3a..febc6518 100644
--- a/simpleperf/cmd_kmem.cpp
+++ b/simpleperf/cmd_kmem.cpp
@@ -314,7 +314,7 @@ class KmemCommand : public Command {
bool ReadFeaturesFromRecordFile();
bool ReadSampleTreeFromRecordFile();
bool ProcessRecord(std::unique_ptr<Record> record);
- void ProcessTracingData(const std::vector<char>& data);
+ bool ProcessTracingData(const std::vector<char>& data);
bool PrintReport();
void PrintReportContext(FILE* fp);
void PrintSlabReportContext(FILE* fp);
@@ -532,10 +532,9 @@ bool KmemCommand::PrepareToBuildSampleTree() {
}
void KmemCommand::ReadEventAttrsFromRecordFile() {
- std::vector<EventAttrWithId> attrs = record_file_reader_->AttrSection();
- for (const auto& attr_with_id : attrs) {
+ for (const EventAttrWithId& attr_with_id : record_file_reader_->AttrSection()) {
EventAttrWithName attr;
- attr.attr = *attr_with_id.attr;
+ attr.attr = attr_with_id.attr;
attr.event_ids = attr_with_id.ids;
attr.name = GetEventNameByAttr(attr.attr);
event_attrs_.push_back(attr);
@@ -543,7 +542,9 @@ void KmemCommand::ReadEventAttrsFromRecordFile() {
}
bool KmemCommand::ReadFeaturesFromRecordFile() {
- record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
+ if (!record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_)) {
+ return false;
+ }
std::string arch = record_file_reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH);
if (!arch.empty()) {
record_file_arch_ = GetArchType(arch);
@@ -588,18 +589,23 @@ bool KmemCommand::ProcessRecord(std::unique_ptr<Record> record) {
} else if (record->type() == PERF_RECORD_TRACING_DATA ||
record->type() == SIMPLE_PERF_RECORD_TRACING_DATA) {
const auto& r = *static_cast<TracingDataRecord*>(record.get());
- ProcessTracingData(std::vector<char>(r.data, r.data + r.data_size));
+ if (!ProcessTracingData(std::vector<char>(r.data, r.data + r.data_size))) {
+ return false;
+ }
}
return true;
}
-void KmemCommand::ProcessTracingData(const std::vector<char>& data) {
- Tracing tracing(data);
+bool KmemCommand::ProcessTracingData(const std::vector<char>& data) {
+ auto tracing = Tracing::Create(data);
+ if (!tracing) {
+ return false;
+ }
for (auto& attr : event_attrs_) {
if (attr.attr.type == PERF_TYPE_TRACEPOINT) {
uint64_t trace_event_id = attr.attr.config;
- attr.name = tracing.GetTracingEventNameHavingId(trace_event_id);
- TracingFormat format = tracing.GetTracingFormatHavingId(trace_event_id);
+ attr.name = tracing->GetTracingEventNameHavingId(trace_event_id);
+ TracingFormat format = tracing->GetTracingFormatHavingId(trace_event_id);
if (use_slab_) {
if (format.name == "kmalloc" || format.name == "kmem_cache_alloc" ||
format.name == "kmalloc_node" || format.name == "kmem_cache_alloc_node") {
@@ -621,6 +627,7 @@ void KmemCommand::ProcessTracingData(const std::vector<char>& data) {
}
}
}
+ return true;
}
bool KmemCommand::PrintReport() {
diff --git a/simpleperf/cmd_merge.cpp b/simpleperf/cmd_merge.cpp
index e80dd953..1bf9833f 100644
--- a/simpleperf/cmd_merge.cpp
+++ b/simpleperf/cmd_merge.cpp
@@ -255,16 +255,16 @@ class MergeCommand : public Command {
// Check attr sections to know if recorded event types are the same.
bool CheckAttrSection() {
- std::vector<EventAttrWithId> attrs0 = readers_[0]->AttrSection();
+ const EventAttrIds& attrs0 = readers_[0]->AttrSection();
for (size_t i = 1; i < readers_.size(); i++) {
- std::vector<EventAttrWithId> attrs = readers_[i]->AttrSection();
+ const EventAttrIds& attrs = readers_[i]->AttrSection();
if (attrs.size() != attrs0.size()) {
LOG(ERROR) << input_files_[0] << " and " << input_files_[i]
<< " are not mergeable for recording different event types";
return false;
}
for (size_t attr_id = 0; attr_id < attrs.size(); attr_id++) {
- if (memcmp(attrs[attr_id].attr, attrs0[attr_id].attr, sizeof(perf_event_attr)) != 0) {
+ if (attrs[attr_id].attr != attrs0[attr_id].attr) {
LOG(ERROR) << input_files_[0] << " and " << input_files_[i]
<< " are not mergeable for recording different event types";
return false;
@@ -300,7 +300,7 @@ class MergeCommand : public Command {
// map event_ids in readers_[next_read_id] to event attrs. The map info is put into an
// EventIdRecord.
const std::unordered_map<uint64_t, size_t>& cur_map = readers_[prev_reader_id]->EventIdMap();
- std::vector<EventAttrWithId> attrs = readers_[next_reader_id]->AttrSection();
+ const EventAttrIds& attrs = readers_[next_reader_id]->AttrSection();
std::vector<uint64_t> event_id_data;
for (size_t attr_id = 0; attr_id < attrs.size(); attr_id++) {
for (size_t event_id : attrs[attr_id].ids) {
@@ -381,8 +381,9 @@ class MergeCommand : public Command {
// Read file features.
for (auto& reader : readers_) {
FileFeature file;
- size_t read_pos = 0;
- while (reader->ReadFileFeature(read_pos, &file)) {
+ uint64_t read_pos = 0;
+ bool error = false;
+ while (reader->ReadFileFeature(read_pos, file, error)) {
if (files_to_drop.count(file.path) != 0) {
continue;
}
@@ -395,6 +396,9 @@ class MergeCommand : public Command {
files_to_drop.emplace(file.path);
}
}
+ if (error) {
+ return false;
+ }
}
// Write file features.
for (const auto& [file_path, file] : file_map) {
diff --git a/simpleperf/cmd_monitor.cpp b/simpleperf/cmd_monitor.cpp
index d81ccfea..2ef9cbca 100644
--- a/simpleperf/cmd_monitor.cpp
+++ b/simpleperf/cmd_monitor.cpp
@@ -80,8 +80,8 @@ constexpr size_t DESIRED_PAGES_IN_MAPPED_BUFFER = 1024;
// buffer size on a 8 core system. For system-wide recording, it is 8K pages *
// 4K page_size * 8 cores = 256MB. For non system-wide recording, it is 1K pages
// * 4K page_size * 8 cores = 64MB.
-static constexpr size_t kRecordBufferSize = 64 * 1024 * 1024;
-static constexpr size_t kSystemWideRecordBufferSize = 256 * 1024 * 1024;
+static constexpr size_t kRecordBufferSize = 64 * kMegabyte;
+static constexpr size_t kSystemWideRecordBufferSize = 256 * kMegabyte;
class MonitorCommand : public Command {
public:
@@ -252,7 +252,7 @@ bool MonitorCommand::PrepareMonitoring() {
// Use first perf_event_attr and first event id to dump mmap and comm records.
EventAttrWithId dumping_attr_id = event_selection_set_.GetEventAttrWithId()[0];
- map_record_reader_.emplace(*dumping_attr_id.attr, dumping_attr_id.ids[0],
+ map_record_reader_.emplace(dumping_attr_id.attr, dumping_attr_id.ids[0],
event_selection_set_.RecordNotExecutableMaps());
map_record_reader_->SetCallback([this](Record* r) { return ProcessRecord(r); });
diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp
index e69355d4..d60e2d03 100644
--- a/simpleperf/cmd_record.cpp
+++ b/simpleperf/cmd_record.cpp
@@ -49,6 +49,7 @@
#include <unwindstack/Error.h>
#include "CallChainJoiner.h"
+#include "ETMBranchListFile.h"
#include "ETMRecorder.h"
#include "IOEventLoop.h"
#include "JITDebugReader.h"
@@ -98,23 +99,17 @@ static std::unordered_map<std::string, int> clockid_map = {
// The max size of records dumped by kernel is 65535, and dump stack size
// should be a multiply of 8, so MAX_DUMP_STACK_SIZE is 65528.
-constexpr uint32_t MAX_DUMP_STACK_SIZE = 65528;
+static constexpr uint32_t MAX_DUMP_STACK_SIZE = 65528;
// The max allowed pages in mapped buffer is decided by rlimit(RLIMIT_MEMLOCK).
// Here 1024 is a desired value for pages in mapped buffer. If mapped
// successfully, the buffer size = 1024 * 4K (page size) = 4M.
-constexpr size_t DESIRED_PAGES_IN_MAPPED_BUFFER = 1024;
+static constexpr size_t DESIRED_PAGES_IN_MAPPED_BUFFER = 1024;
// Cache size used by CallChainJoiner to cache call chains in memory.
-constexpr size_t DEFAULT_CALL_CHAIN_JOINER_CACHE_SIZE = 8 * 1024 * 1024;
+static constexpr size_t DEFAULT_CALL_CHAIN_JOINER_CACHE_SIZE = 8 * kMegabyte;
-// Currently, the record buffer size in user-space is set to match the kernel buffer size on a
-// 8 core system. For system-wide recording, it is 8K pages * 4K page_size * 8 cores = 256MB.
-// For non system-wide recording, it is 1K pages * 4K page_size * 8 cores = 64MB.
-static constexpr size_t kRecordBufferSize = 64 * 1024 * 1024;
-static constexpr size_t kSystemWideRecordBufferSize = 256 * 1024 * 1024;
-
-static constexpr size_t kDefaultAuxBufferSize = 4 * 1024 * 1024;
+static constexpr size_t kDefaultAuxBufferSize = 4 * kMegabyte;
// On Pixel 3, it takes about 1ms to enable ETM, and 16-40ms to disable ETM and copy 4M ETM data.
// So make default period to 100ms.
@@ -128,6 +123,30 @@ struct TimeStat {
uint64_t post_process_time = 0;
};
+std::optional<size_t> GetDefaultRecordBufferSize(bool system_wide_recording) {
+ // Currently, the record buffer size in user-space is set to match the kernel buffer size on a
+ // 8 core system. For system-wide recording, it is 8K pages * 4K page_size * 8 cores = 256MB.
+ // For non system-wide recording, it is 1K pages * 4K page_size * 8 cores = 64MB.
+ // But on devices with memory >= 4GB, we increase buffer size to 256MB. This reduces the chance
+ // of cutting samples, which can cause broken callchains.
+ static constexpr size_t kLowMemoryRecordBufferSize = 64 * kMegabyte;
+ static constexpr size_t kHighMemoryRecordBufferSize = 256 * kMegabyte;
+ static constexpr size_t kSystemWideRecordBufferSize = 256 * kMegabyte;
+ // Ideally we can use >= 4GB here. But the memory size shown in /proc/meminfo is like to be 3.x GB
+ // on a device with 4GB memory. So we have to use <= 3GB.
+ static constexpr uint64_t kLowMemoryLimit = 3 * kGigabyte;
+
+ if (system_wide_recording) {
+ return kSystemWideRecordBufferSize;
+ }
+ auto device_memory = GetMemorySize();
+ if (!device_memory.has_value()) {
+ return std::nullopt;
+ }
+ return device_memory.value() <= kLowMemoryLimit ? kLowMemoryRecordBufferSize
+ : kHighMemoryRecordBufferSize;
+}
+
class RecordCommand : public Command {
public:
RecordCommand()
@@ -146,8 +165,9 @@ class RecordCommand : public Command {
" On non-rooted devices, the app must be debuggable,\n"
" because we use run-as to switch to the app's context.\n"
#endif
-"-p pid1,pid2,... Record events on existing processes. Mutually exclusive\n"
-" with -a.\n"
+"-p pid_or_process_name_regex1,pid_or_process_name_regex2,...\n"
+" Record events on existing processes. Processes are searched either by pid\n"
+" or process name regex. Mutually exclusive with -a.\n"
"-t tid1,tid2,... Record events on existing threads. Mutually exclusive with -a.\n"
"\n"
"Select monitored event types:\n"
@@ -215,32 +235,14 @@ class RecordCommand : public Command {
" This option requires at least one branch type among any, any_call,\n"
" any_ret, ind_call.\n"
"-b Enable taken branch stack sampling. Same as '-j any'.\n"
-"-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"
+"-m mmap_pages Set pages used in the kernel to cache sample data for each cpu.\n"
+" It should be a power of 2. If not set, the max possible value <= 1024\n"
+" will be used.\n"
+"--user-buffer-size <buffer_size> Set buffer size in userspace to cache sample data.\n"
+" By default, it is %s.\n"
"--no-inherit Don't record created child threads/processes.\n"
"--cpu-percent <percent> Set the max percent of cpu time used for recording.\n"
" percent is in range [1-100], default is 25.\n"
-"--addr-filter filter_str1,filter_str2,...\n"
-" Provide address filters for cs-etm instruction tracing.\n"
-" filter_str accepts below formats:\n"
-" 'filter <addr-range>' -- trace instructions in a range\n"
-" 'start <addr>' -- start tracing when ip is <addr>\n"
-" 'stop <addr>' -- stop tracing when ip is <addr>\n"
-" <addr-range> accepts below formats:\n"
-" <file_path> -- code sections in a binary file\n"
-" <vaddr_start>-<vaddr_end>@<file_path> -- part of a binary file\n"
-" <kernel_addr_start>-<kernel_addr_end> -- part of kernel space\n"
-" <addr> accepts below formats:\n"
-" <vaddr>@<file_path> -- virtual addr in a binary file\n"
-" <kernel_addr> -- a kernel address\n"
-" Examples:\n"
-" 'filter 0x456-0x480@/system/lib/libc.so'\n"
-" 'start 0x456@/system/lib/libc.so,stop 0x480@/system/lib/libc.so'\n"
"\n"
"--tp-filter filter_string Set filter_string for the previous tracepoint event.\n"
" Format is in Documentation/trace/events.rst in the kernel.\n"
@@ -287,6 +289,31 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING
" debug information, which are used for unwinding and dumping symbols.\n"
"--add-meta-info key=value Add extra meta info, which will be stored in the recording file.\n"
"\n"
+"ETM recording options:\n"
+"--addr-filter filter_str1,filter_str2,...\n"
+" Provide address filters for cs-etm instruction tracing.\n"
+" filter_str accepts below formats:\n"
+" 'filter <addr-range>' -- trace instructions in a range\n"
+" 'start <addr>' -- start tracing when ip is <addr>\n"
+" 'stop <addr>' -- stop tracing when ip is <addr>\n"
+" <addr-range> accepts below formats:\n"
+" <file_path> -- code sections in a binary file\n"
+" <vaddr_start>-<vaddr_end>@<file_path> -- part of a binary file\n"
+" <kernel_addr_start>-<kernel_addr_end> -- part of kernel space\n"
+" <addr> accepts below formats:\n"
+" <vaddr>@<file_path> -- virtual addr in a binary file\n"
+" <kernel_addr> -- a kernel address\n"
+" Examples:\n"
+" 'filter 0x456-0x480@/system/lib/libc.so'\n"
+" 'start 0x456@/system/lib/libc.so,stop 0x480@/system/lib/libc.so'\n"
+"--aux-buffer-size <buffer_size> Set aux buffer size, only used in cs-etm event type.\n"
+" Need to be power of 2 and page size aligned.\n"
+" Used memory size is (buffer_size * (cpu_count + 1).\n"
+" Default is 4M.\n"
+"--decode-etm Convert ETM data into branch lists while recording.\n"
+"--binary binary_name Used with --decode-etm to only generate data for binaries\n"
+" matching binary_name regex.\n"
+"\n"
"Other options:\n"
"--exit-with-parent Stop recording when the thread starting simpleperf dies.\n"
"--use-cmd-exit-code Exit with the same exit code as the monitored cmdline.\n"
@@ -319,7 +346,6 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING
mmap_page_range_(std::make_pair(1, DESIRED_PAGES_IN_MAPPED_BUFFER)),
record_filename_("perf.data"),
sample_record_count_(0),
- lost_record_count_(0),
in_app_context_(false),
trace_offcpu_(false),
exclude_kernel_callchain_(false),
@@ -334,6 +360,7 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING
signal(SIGPIPE, SIG_IGN);
}
+ std::string LongHelpString() const override;
void Run(const std::vector<std::string>& args, int* exit_code) override;
bool Run(const std::vector<std::string>& args) override {
int exit_code;
@@ -352,8 +379,8 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING
bool TraceOffCpu();
bool SetEventSelectionFlags();
bool CreateAndInitRecordFile();
- std::unique_ptr<RecordFileWriter> CreateRecordFile(
- const std::string& filename, const std::vector<EventAttrWithId>& override_attrs);
+ std::unique_ptr<RecordFileWriter> CreateRecordFile(const std::string& filename,
+ const EventAttrIds& attrs);
bool DumpKernelSymbol();
bool DumpTracingData();
bool DumpMaps();
@@ -384,6 +411,7 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING
bool DumpMetaInfoFeature(bool kernel_symbols_available);
bool DumpDebugUnwindFeature(const std::unordered_set<Dso*>& dso_set);
void CollectHitFileInfo(const SampleRecord& r, std::unordered_set<Dso*>* dso_set);
+ bool DumpETMBranchListFeature();
std::unique_ptr<SampleSpeed> sample_speed_;
bool system_wide_collection_;
@@ -405,6 +433,7 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING
EventSelectionSet event_selection_set_;
std::pair<size_t, size_t> mmap_page_range_;
+ std::optional<size_t> user_buffer_size_;
size_t aux_buffer_size_ = kDefaultAuxBufferSize;
ThreadTree thread_tree_;
@@ -414,7 +443,6 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING
android::base::unique_fd stop_signal_fd_;
uint64_t sample_record_count_;
- uint64_t lost_record_count_;
android::base::unique_fd start_profiling_fd_;
bool stdio_controls_profiling_ = false;
@@ -447,8 +475,32 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_RECORDING
std::unordered_map<std::string, std::string> extra_meta_info_;
bool use_cmd_exit_code_ = false;
std::vector<std::string> add_counters_;
+
+ std::unique_ptr<ETMBranchListGenerator> etm_branch_list_generator_;
+ std::unique_ptr<RegEx> binary_name_regex_;
};
+std::string RecordCommand::LongHelpString() const {
+ uint64_t process_buffer_size = 0;
+ uint64_t system_wide_buffer_size = 0;
+ if (auto size = GetDefaultRecordBufferSize(false); size) {
+ process_buffer_size = size.value() / kMegabyte;
+ }
+ if (auto size = GetDefaultRecordBufferSize(true); size) {
+ system_wide_buffer_size = size.value() / kMegabyte;
+ }
+ std::string buffer_size_str;
+ if (process_buffer_size == system_wide_buffer_size) {
+ buffer_size_str = android::base::StringPrintf("%" PRIu64 "M", process_buffer_size);
+ } else {
+ buffer_size_str =
+ android::base::StringPrintf("%" PRIu64 "M for process recording and %" PRIu64
+ "M\n for system wide recording",
+ process_buffer_size, system_wide_buffer_size);
+ }
+ return android::base::StringPrintf(long_help_string_.c_str(), buffer_size_str.c_str());
+}
+
void RecordCommand::Run(const std::vector<std::string>& args, int* exit_code) {
*exit_code = 1;
time_stat_.prepare_recording_time = GetSystemClock();
@@ -607,8 +659,16 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
if (!event_selection_set_.OpenEventFiles(cpus_)) {
return false;
}
- size_t record_buffer_size =
- system_wide_collection_ ? kSystemWideRecordBufferSize : kRecordBufferSize;
+ size_t record_buffer_size = 0;
+ if (user_buffer_size_.has_value()) {
+ record_buffer_size = user_buffer_size_.value();
+ } else {
+ auto default_size = GetDefaultRecordBufferSize(system_wide_collection_);
+ if (!default_size.has_value()) {
+ return false;
+ }
+ record_buffer_size = default_size.value();
+ }
if (!event_selection_set_.MmapEventFiles(mmap_page_range_.first, mmap_page_range_.second,
aux_buffer_size_, record_buffer_size,
allow_cutting_samples_, exclude_perf_)) {
@@ -697,6 +757,15 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
if (!loop->AddPeriodicEvent(SecondToTimeval(kDefaultEtmDataFlushPeriodInSec), etm_flush)) {
return false;
}
+
+ if (etm_branch_list_generator_) {
+ if (exclude_perf_) {
+ etm_branch_list_generator_->SetExcludePid(getpid());
+ }
+ if (binary_name_regex_) {
+ etm_branch_list_generator_->SetBinaryFilter(binary_name_regex_.get());
+ }
+ }
}
return true;
}
@@ -798,26 +867,59 @@ bool RecordCommand::PostProcessRecording(const std::vector<std::string>& args) {
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;
+ LOG(INFO) << "Aux data lost in user space: " << record_stat.lost_aux_data_size
+ << ", consider increasing userspace buffer size(--user-buffer-size).";
}
} else {
- std::string cut_samples;
- if (record_stat.cut_stack_samples > 0) {
- cut_samples = android::base::StringPrintf(" (cut %zu)", record_stat.cut_stack_samples);
- }
- lost_record_count_ += record_stat.lost_samples + record_stat.lost_non_samples;
- LOG(INFO) << "Samples recorded: " << sample_record_count_ << cut_samples
- << ". Samples lost: " << lost_record_count_ << ".";
- LOG(DEBUG) << "In user space, dropped " << record_stat.lost_samples << " samples, "
- << record_stat.lost_non_samples << " non samples, cut stack of "
- << record_stat.cut_stack_samples << " samples.";
- if (sample_record_count_ + lost_record_count_ != 0) {
- double lost_percent =
- static_cast<double>(lost_record_count_) / (lost_record_count_ + sample_record_count_);
- constexpr double LOST_PERCENT_WARNING_BAR = 0.1;
- if (lost_percent >= LOST_PERCENT_WARNING_BAR) {
- LOG(WARNING) << "Lost " << (lost_percent * 100) << "% of samples, "
- << "consider increasing mmap_pages(-m), "
+ // Here we report all lost records as samples. This isn't accurate. Because records like
+ // MmapRecords are not samples. But It's easier for users to understand.
+ size_t userspace_lost_samples =
+ record_stat.userspace_lost_samples + record_stat.userspace_lost_non_samples;
+ size_t lost_samples = record_stat.kernelspace_lost_records + userspace_lost_samples;
+
+ std::stringstream os;
+ os << "Samples recorded: " << sample_record_count_;
+ if (record_stat.userspace_cut_stack_samples > 0) {
+ os << " (cut " << record_stat.userspace_cut_stack_samples << ")";
+ }
+ os << ". Samples lost: " << lost_samples;
+ if (lost_samples != 0) {
+ os << " (kernelspace: " << record_stat.kernelspace_lost_records
+ << ", userspace: " << userspace_lost_samples << ")";
+ }
+ os << ".";
+ LOG(INFO) << os.str();
+
+ LOG(DEBUG) << "Record stat: kernelspace_lost_records=" << record_stat.kernelspace_lost_records
+ << ", userspace_lost_samples=" << record_stat.userspace_lost_samples
+ << ", userspace_lost_non_samples=" << record_stat.userspace_lost_non_samples
+ << ", userspace_cut_stack_samples=" << record_stat.userspace_cut_stack_samples;
+
+ if (sample_record_count_ + record_stat.kernelspace_lost_records != 0) {
+ double kernelspace_lost_percent =
+ static_cast<double>(record_stat.kernelspace_lost_records) /
+ (record_stat.kernelspace_lost_records + sample_record_count_);
+ constexpr double KERNELSPACE_LOST_PERCENT_WARNING_BAR = 0.1;
+ if (kernelspace_lost_percent >= KERNELSPACE_LOST_PERCENT_WARNING_BAR) {
+ LOG(WARNING) << "Lost " << (kernelspace_lost_percent * 100)
+ << "% of samples in kernel space, "
+ << "consider increasing kernel buffer size(-m), "
+ << "or decreasing sample frequency(-f), "
+ << "or increasing sample period(-c).";
+ }
+ }
+ size_t userspace_lost_cut_samples =
+ userspace_lost_samples + record_stat.userspace_cut_stack_samples;
+ size_t userspace_complete_samples =
+ sample_record_count_ - record_stat.userspace_cut_stack_samples;
+ if (userspace_complete_samples + userspace_lost_cut_samples != 0) {
+ double userspace_lost_percent = static_cast<double>(userspace_lost_cut_samples) /
+ (userspace_complete_samples + userspace_lost_cut_samples);
+ constexpr double USERSPACE_LOST_PERCENT_WARNING_BAR = 0.1;
+ if (userspace_lost_percent >= USERSPACE_LOST_PERCENT_WARNING_BAR) {
+ LOG(WARNING) << "Lost/Cut " << (userspace_lost_percent * 100)
+ << "% of samples in user space, "
+ << "consider increasing userspace buffer size(--user-buffer-size), "
<< "or decreasing sample frequency(-f), "
<< "or increasing sample period(-c).";
}
@@ -890,6 +992,13 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
branch_sampling_ = branch_sampling_type_map["any"];
}
+ if (auto value = options.PullValue("--binary"); value) {
+ binary_name_regex_ = RegEx::Create(*value->str_value);
+ if (binary_name_regex_ == nullptr) {
+ return false;
+ }
+ }
+
if (!options.PullUintValue("--callchain-joiner-min-matching-nodes",
&callchain_joiner_min_matching_nodes_, 1)) {
return false;
@@ -921,6 +1030,10 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
return false;
}
+ if (options.PullBoolValue("--decode-etm")) {
+ etm_branch_list_generator_ = ETMBranchListGenerator::Create(system_wide_collection_);
+ }
+
if (!options.PullDoubleValue("--duration", &duration_in_sec_, 1e-9)) {
return false;
}
@@ -992,8 +1105,8 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
out_fd_.reset(static_cast<int>(value->uint_value));
}
- for (const OptionValue& value : options.PullValues("-p")) {
- if (auto pids = GetTidsFromString(*value.str_value, true); pids) {
+ if (auto strs = options.PullStringValues("-p"); !strs.empty()) {
+ if (auto pids = GetPidsFromStrings(strs, true, true); pids) {
event_selection_set_.AddMonitoredProcesses(pids.value());
} else {
return false;
@@ -1011,6 +1124,15 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
post_unwind_ = false;
}
+ if (auto value = options.PullValue("--user-buffer-size"); value) {
+ uint64_t v = value->uint_value;
+ if (v > std::numeric_limits<size_t>::max() || v == 0) {
+ LOG(ERROR) << "invalid user buffer size: " << v;
+ return false;
+ }
+ user_buffer_size_ = static_cast<size_t>(v);
+ }
+
if (!options.PullUintValue("--size-limit", &size_limit_in_bytes_, 1)) {
return false;
}
@@ -1281,32 +1403,35 @@ bool RecordCommand::SetEventSelectionFlags() {
}
bool RecordCommand::CreateAndInitRecordFile() {
- record_file_writer_ =
- CreateRecordFile(record_filename_, event_selection_set_.GetEventAttrWithId());
+ EventAttrIds attrs = event_selection_set_.GetEventAttrWithId();
+ bool remove_regs_and_stacks = unwind_dwarf_callchain_ && !post_unwind_;
+ if (remove_regs_and_stacks) {
+ for (auto& attr : attrs) {
+ ReplaceRegAndStackWithCallChain(attr.attr);
+ }
+ }
+ record_file_writer_ = CreateRecordFile(record_filename_, attrs);
if (record_file_writer_ == nullptr) {
return false;
}
// Use first perf_event_attr and first event id to dump mmap and comm records.
- dumping_attr_id_ = event_selection_set_.GetEventAttrWithId()[0];
+ CHECK(!attrs.empty());
+ dumping_attr_id_ = attrs[0];
CHECK(!dumping_attr_id_.ids.empty());
- map_record_reader_.emplace(*dumping_attr_id_.attr, dumping_attr_id_.ids[0],
+ map_record_reader_.emplace(dumping_attr_id_.attr, dumping_attr_id_.ids[0],
event_selection_set_.RecordNotExecutableMaps());
map_record_reader_->SetCallback([this](Record* r) { return ProcessRecord(r); });
return DumpKernelSymbol() && DumpTracingData() && DumpMaps() && DumpAuxTraceInfo();
}
-std::unique_ptr<RecordFileWriter> RecordCommand::CreateRecordFile(
- const std::string& filename, const std::vector<EventAttrWithId>& attrs) {
+std::unique_ptr<RecordFileWriter> RecordCommand::CreateRecordFile(const std::string& filename,
+ const EventAttrIds& attrs) {
std::unique_ptr<RecordFileWriter> writer = RecordFileWriter::CreateInstance(filename);
- if (writer == nullptr) {
- return nullptr;
- }
-
- if (!writer->WriteAttrSection(attrs)) {
- return nullptr;
+ if (writer != nullptr && writer->WriteAttrSection(attrs)) {
+ return writer;
}
- return writer;
+ return nullptr;
}
bool RecordCommand::DumpKernelSymbol() {
@@ -1348,9 +1473,11 @@ bool RecordCommand::DumpMaps() {
// For system wide recording:
// If not aux tracing, only dump kernel maps. Maps of a process is dumped when needed (the
// first time a sample hits that process).
- // If aux tracing, we don't know which maps will be needed, so dump all process maps. To
- // reduce pre recording time, we dump process maps in map record thread while recording.
- if (event_selection_set_.HasAuxTrace()) {
+ // If aux tracing with decoding etm data, the maps are dumped by etm_branch_list_generator.
+ // If aux tracing without decoding etm data, we don't know which maps will be needed, so dump
+ // all process maps. To reduce pre recording time, we dump process maps in map record thread
+ // while recording.
+ if (event_selection_set_.HasAuxTrace() && !etm_branch_list_generator_) {
map_record_thread_.emplace(*map_record_reader_);
return true;
}
@@ -1409,6 +1536,15 @@ bool RecordCommand::ProcessRecord(Record* record) {
return true;
}
}
+ if (etm_branch_list_generator_) {
+ bool consumed = false;
+ if (!etm_branch_list_generator_->ProcessRecord(*record, consumed)) {
+ return false;
+ }
+ if (consumed) {
+ return true;
+ }
+ }
if (unwind_dwarf_callchain_) {
if (post_unwind_) {
return SaveRecordForPostUnwinding(record);
@@ -1487,8 +1623,6 @@ bool RecordCommand::SaveRecordAfterUnwinding(Record* record) {
return true;
}
sample_record_count_++;
- } else if (record->type() == PERF_RECORD_LOST) {
- lost_record_count_ += static_cast<LostRecord*>(record)->lost;
} else {
thread_tree_.Update(*record);
}
@@ -1506,8 +1640,6 @@ bool RecordCommand::SaveRecordWithoutUnwinding(Record* record) {
return true;
}
sample_record_count_++;
- } else if (record->type() == PERF_RECORD_LOST) {
- lost_record_count_ += static_cast<LostRecord*>(record)->lost;
}
return record_file_writer_->WriteRecord(*record);
}
@@ -1518,7 +1650,7 @@ bool RecordCommand::ProcessJITDebugInfo(const std::vector<JITDebugInfo>& debug_i
if (info.type == JITDebugInfo::JIT_DEBUG_JIT_CODE) {
uint64_t timestamp =
jit_debug_reader_->SyncWithRecords() ? info.timestamp : last_record_timestamp_;
- Mmap2Record record(*dumping_attr_id_.attr, false, info.pid, info.pid, info.jit_code_addr,
+ Mmap2Record record(dumping_attr_id_.attr, false, info.pid, info.pid, info.jit_code_addr,
info.jit_code_len, info.file_offset, map_flags::PROT_JIT_SYMFILE_MAP,
info.file_path, dumping_attr_id_.ids[0], timestamp);
if (!ProcessRecord(&record)) {
@@ -1529,7 +1661,7 @@ bool RecordCommand::ProcessJITDebugInfo(const std::vector<JITDebugInfo>& debug_i
ThreadMmap& map = *info.extracted_dex_file_map;
uint64_t timestamp =
jit_debug_reader_->SyncWithRecords() ? info.timestamp : last_record_timestamp_;
- Mmap2Record record(*dumping_attr_id_.attr, false, info.pid, info.pid, map.start_addr,
+ Mmap2Record record(dumping_attr_id_.attr, false, info.pid, info.pid, map.start_addr,
map.len, map.pgoff, map.prot, map.name, dumping_attr_id_.ids[0],
timestamp);
if (!ProcessRecord(&record)) {
@@ -1637,9 +1769,11 @@ void RecordCommand::UpdateRecord(Record* record) {
}
bool RecordCommand::UnwindRecord(SampleRecord& r) {
- if ((r.sample_type & PERF_SAMPLE_CALLCHAIN) && (r.sample_type & PERF_SAMPLE_REGS_USER) &&
- (r.regs_user_data.reg_mask != 0) && (r.sample_type & PERF_SAMPLE_STACK_USER) &&
- (r.GetValidStackSize() > 0)) {
+ if (!(r.sample_type & PERF_SAMPLE_CALLCHAIN) && (r.sample_type & PERF_SAMPLE_REGS_USER) &&
+ (r.regs_user_data.reg_mask != 0) && (r.sample_type & PERF_SAMPLE_STACK_USER)) {
+ return true;
+ }
+ if (r.GetValidStackSize() > 0) {
ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
RegSet regs(r.regs_user_data.abi, r.regs_user_data.reg_mask, r.regs_user_data.regs);
std::vector<uint64_t> ips;
@@ -1668,6 +1802,9 @@ bool RecordCommand::UnwindRecord(SampleRecord& r) {
CallChainJoiner::ORIGINAL_OFFLINE, ips, sps)) {
return false;
}
+ } else {
+ // For kernel samples, we still need to remove user stack and register fields.
+ r.ReplaceRegAndStackWithCallChain({});
}
return true;
}
@@ -1692,11 +1829,15 @@ std::unique_ptr<RecordFileReader> RecordCommand::MoveRecordFile(const std::strin
return nullptr;
}
record_file_writer_.reset();
- {
- std::error_code ec;
- std::filesystem::rename(record_filename_, old_filename, ec);
- if (ec) {
- LOG(ERROR) << "Failed to rename: " << ec.message();
+ std::error_code ec;
+ std::filesystem::rename(record_filename_, old_filename, ec);
+ if (ec) {
+ LOG(DEBUG) << "Failed to rename: " << ec.message();
+ // rename() fails on Android N x86 emulator, which uses kernel 3.10. Because rename() in bionic
+ // uses renameat2 syscall, which isn't support on kernel < 3.15. So add a fallback to mv
+ // command. The mv command can also work with other situations when rename() doesn't work.
+ // So we'd like to keep it as a fallback to rename().
+ if (!Workload::RunCmd({"mv", record_filename_, old_filename})) {
return nullptr;
}
}
@@ -1755,8 +1896,16 @@ bool RecordCommand::PostUnwindRecords() {
if (!reader) {
return false;
}
+ // Write new event attrs without regs and stacks fields.
+ EventAttrIds attrs = reader->AttrSection();
+ for (auto& attr : attrs) {
+ ReplaceRegAndStackWithCallChain(attr.attr);
+ }
+ if (!record_file_writer_->WriteAttrSection(attrs)) {
+ return false;
+ }
+
sample_record_count_ = 0;
- lost_record_count_ = 0;
auto callback = [this](std::unique_ptr<Record> record) {
return SaveRecordAfterUnwinding(record.get());
};
@@ -1867,6 +2016,9 @@ bool RecordCommand::DumpAdditionalFeatures(const std::vector<std::string>& args)
if (keep_failed_unwinding_debug_info_) {
feature_count += 2;
}
+ if (etm_branch_list_generator_) {
+ feature_count++;
+ }
if (!record_file_writer_->BeginWriteFeatures(feature_count)) {
return false;
}
@@ -1909,6 +2061,9 @@ bool RecordCommand::DumpAdditionalFeatures(const std::vector<std::string>& args)
if (keep_failed_unwinding_debug_info_ && !DumpDebugUnwindFeature(debug_unwinding_files)) {
return false;
}
+ if (etm_branch_list_generator_ && !DumpETMBranchListFeature()) {
+ return false;
+ }
if (!record_file_writer_->EndWriteFeatures()) {
return false;
@@ -1926,34 +2081,9 @@ bool RecordCommand::DumpBuildIdFeature() {
if (!dso->HasDumpId() && !event_selection_set_.HasAuxTrace()) {
continue;
}
- if (dso->type() == DSO_KERNEL) {
- if (!GetKernelBuildId(&build_id)) {
- continue;
- }
- build_id_records.push_back(BuildIdRecord(true, UINT_MAX, build_id, dso->Path()));
- } else if (dso->type() == DSO_KERNEL_MODULE) {
- bool has_build_id = false;
- if (android::base::EndsWith(dso->Path(), ".ko")) {
- has_build_id = GetBuildIdFromDsoPath(dso->Path(), &build_id);
- } else if (const std::string& path = dso->Path();
- path.size() > 2 && path[0] == '[' && path.back() == ']') {
- // For kernel modules that we can't find the corresponding file, read build id from /sysfs.
- has_build_id = GetModuleBuildId(path.substr(1, path.size() - 2), &build_id);
- }
- if (has_build_id) {
- build_id_records.push_back(BuildIdRecord(true, UINT_MAX, build_id, dso->Path()));
- } else {
- LOG(DEBUG) << "Can't read build_id for module " << dso->Path();
- }
- } else if (dso->type() == DSO_ELF_FILE) {
- if (dso->Path() == DEFAULT_EXECNAME_FOR_THREAD_MMAP || dso->IsForJavaMethod()) {
- continue;
- }
- if (!GetBuildIdFromDsoPath(dso->Path(), &build_id)) {
- LOG(DEBUG) << "Can't read build_id from file " << dso->Path();
- continue;
- }
- build_id_records.push_back(BuildIdRecord(false, UINT_MAX, build_id, dso->Path()));
+ if (GetBuildId(*dso, build_id)) {
+ bool in_kernel = dso->type() == DSO_KERNEL || dso->type() == DSO_KERNEL_MODULE;
+ build_id_records.emplace_back(in_kernel, UINT_MAX, build_id, dso->Path());
}
}
if (!record_file_writer_->WriteBuildIdFeature(build_id_records)) {
@@ -2013,6 +2143,15 @@ bool RecordCommand::DumpMetaInfoFeature(bool kernel_symbols_available) {
if (dwarf_callchain_sampling_ && !unwind_dwarf_callchain_) {
OfflineUnwinder::CollectMetaInfo(&info_map);
}
+ auto record_stat = event_selection_set_.GetRecordStat();
+ info_map["record_stat"] = android::base::StringPrintf(
+ "sample_record_count=%" PRIu64
+ ",kernelspace_lost_records=%zu,userspace_lost_samples=%zu,"
+ "userspace_lost_non_samples=%zu,userspace_cut_stack_samples=%zu",
+ sample_record_count_, record_stat.kernelspace_lost_records,
+ record_stat.userspace_lost_samples, record_stat.userspace_lost_non_samples,
+ record_stat.userspace_cut_stack_samples);
+
return record_file_writer_->WriteMetaInfoFeature(info_map);
}
@@ -2064,6 +2203,16 @@ void RecordCommand::CollectHitFileInfo(const SampleRecord& r, std::unordered_set
}
}
+bool RecordCommand::DumpETMBranchListFeature() {
+ BranchListBinaryMap binary_map = etm_branch_list_generator_->GetBranchListBinaryMap();
+ std::string s;
+ if (!BranchListBinaryMapToString(binary_map, s)) {
+ return false;
+ }
+ return record_file_writer_->WriteFeature(PerfFileFormat::FEAT_ETM_BRANCH_LIST, s.data(),
+ s.size());
+}
+
} // namespace
static bool ConsumeStr(const char*& p, const char* s) {
diff --git a/simpleperf/cmd_record_impl.h b/simpleperf/cmd_record_impl.h
index 2ef407bf..3f277910 100644
--- a/simpleperf/cmd_record_impl.h
+++ b/simpleperf/cmd_record_impl.h
@@ -40,6 +40,7 @@ inline const OptionFormatMap& GetRecordCmdOptionFormats() {
{"--app", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::NOT_ALLOWED}},
{"--aux-buffer-size", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::ALLOWED}},
{"-b", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--binary", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::ALLOWED}},
{"-c", {OptionValueType::UINT, OptionType::ORDERED, AppRunnerType::ALLOWED}},
{"--call-graph", {OptionValueType::STRING, OptionType::ORDERED, AppRunnerType::ALLOWED}},
{"--callchain-joiner-min-matching-nodes",
@@ -47,6 +48,7 @@ inline const OptionFormatMap& GetRecordCmdOptionFormats() {
{"--clockid", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::ALLOWED}},
{"--cpu", {OptionValueType::STRING, OptionType::SINGLE, AppRunnerType::ALLOWED}},
{"--cpu-percent", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--decode-etm", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
{"--duration", {OptionValueType::DOUBLE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
{"-e", {OptionValueType::STRING, OptionType::ORDERED, AppRunnerType::ALLOWED}},
{"--exclude-perf", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
@@ -76,6 +78,7 @@ inline const OptionFormatMap& GetRecordCmdOptionFormats() {
{"--post-unwind", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
{"--post-unwind=no", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
{"--post-unwind=yes", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}},
+ {"--user-buffer-size", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::ALLOWED}},
{"--size-limit", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::ALLOWED}},
{"--start_profiling_fd",
{OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::CHECK_FD}},
diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp
index 01861c60..b8300d7f 100644
--- a/simpleperf/cmd_record_test.cpp
+++ b/simpleperf/cmd_record_test.cpp
@@ -98,15 +98,15 @@ static void CheckEventType(const std::string& record_file, const std::string& ev
ASSERT_TRUE(type != nullptr);
std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(record_file);
ASSERT_TRUE(reader);
- std::vector<EventAttrWithId> attrs = reader->AttrSection();
- for (auto& attr : attrs) {
- if (attr.attr->type == type->type && attr.attr->config == type->config) {
- if (attr.attr->freq == 0) {
- ASSERT_EQ(sample_period, attr.attr->sample_period);
+ for (const auto& attr_with_id : reader->AttrSection()) {
+ const perf_event_attr& attr = attr_with_id.attr;
+ if (attr.type == type->type && attr.config == type->config) {
+ if (attr.freq == 0) {
+ ASSERT_EQ(sample_period, attr.sample_period);
ASSERT_EQ(sample_freq, 0u);
} else {
ASSERT_EQ(sample_period, 0u);
- ASSERT_EQ(sample_freq, attr.attr->sample_freq);
+ ASSERT_EQ(sample_freq, attr.sample_freq);
}
return;
}
@@ -192,6 +192,9 @@ TEST(record_cmd, rN_event) {
} else if (GetTargetArch() == ARCH_X86_32 || GetTargetArch() == ARCH_X86_64) {
// As in volume 3 chapter 19 of the Intel manual, 0x00c0 is the event number for instruction.
event_number = 0x00c0;
+ } else if (GetTargetArch() == ARCH_RISCV64) {
+ // RISCV_PMU_INSTRET = 1
+ event_number = 0x1;
} else {
GTEST_LOG_(INFO) << "Omit arch " << GetTargetArch();
return;
@@ -201,10 +204,10 @@ TEST(record_cmd, rN_event) {
ASSERT_TRUE(RunRecordCmd({"-e", event_name}, tmpfile.path));
std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
ASSERT_TRUE(reader);
- std::vector<EventAttrWithId> attrs = reader->AttrSection();
+ const EventAttrIds& attrs = reader->AttrSection();
ASSERT_EQ(1u, attrs.size());
- ASSERT_EQ(PERF_TYPE_RAW, attrs[0].attr->type);
- ASSERT_EQ(event_number, attrs[0].attr->config);
+ ASSERT_EQ(PERF_TYPE_RAW, attrs[0].attr.type);
+ ASSERT_EQ(event_number, attrs[0].attr.config);
}
TEST(record_cmd, branch_sampling) {
@@ -254,7 +257,16 @@ TEST(record_cmd, dwarf_callchain_sampling) {
ASSERT_TRUE(RunRecordCmd({"-p", pid, "--call-graph", "dwarf"}));
ASSERT_TRUE(RunRecordCmd({"-p", pid, "--call-graph", "dwarf,16384"}));
ASSERT_FALSE(RunRecordCmd({"-p", pid, "--call-graph", "dwarf,65536"}));
- ASSERT_TRUE(RunRecordCmd({"-p", pid, "-g"}));
+ TemporaryFile tmpfile;
+ ASSERT_TRUE(RunRecordCmd({"-p", pid, "-g"}, tmpfile.path));
+ auto reader = RecordFileReader::CreateInstance(tmpfile.path);
+ ASSERT_TRUE(reader);
+ const EventAttrIds& attrs = reader->AttrSection();
+ ASSERT_GT(attrs.size(), 0);
+ // Check that reg and stack fields are removed after unwinding.
+ for (const auto& attr : attrs) {
+ ASSERT_EQ(attr.attr.sample_type & (PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER), 0);
+ }
}
TEST(record_cmd, system_wide_dwarf_callchain_sampling) {
@@ -349,14 +361,16 @@ static void ProcessSymbolsInPerfDataFile(
auto reader = RecordFileReader::CreateInstance(perf_data_file);
ASSERT_TRUE(reader);
FileFeature file;
- size_t read_pos = 0;
- while (reader->ReadFileFeature(read_pos, &file)) {
+ uint64_t read_pos = 0;
+ bool error = false;
+ while (reader->ReadFileFeature(read_pos, file, error)) {
for (const auto& symbol : file.symbols) {
if (callback(symbol, file.type)) {
return;
}
}
}
+ ASSERT_FALSE(error);
}
// Check if dumped symbols in perf.data matches our expectation.
@@ -507,6 +521,7 @@ TEST(record_cmd, record_meta_info_feature) {
auto& info_map = reader->GetMetaInfoFeature();
ASSERT_NE(info_map.find("simpleperf_version"), info_map.end());
ASSERT_NE(info_map.find("timestamp"), info_map.end());
+ ASSERT_NE(info_map.find("record_stat"), info_map.end());
#if defined(__ANDROID__)
ASSERT_NE(info_map.find("product_props"), info_map.end());
ASSERT_NE(info_map.find("android_version"), info_map.end());
@@ -546,7 +561,7 @@ TEST(record_cmd, trace_offcpu_option) {
auto info_map = reader->GetMetaInfoFeature();
ASSERT_EQ(info_map["trace_offcpu"], "true");
if (IsSwitchRecordSupported()) {
- ASSERT_EQ(reader->AttrSection()[0].attr->context_switch, 1);
+ ASSERT_EQ(reader->AttrSection()[0].attr.context_switch, 1);
}
// Release recording environment in perf.data, to avoid affecting tests below.
reader.reset();
@@ -709,8 +724,9 @@ class RecordingAppHelper {
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);
+ // record_cmd may end with child command. We should put output options before it.
+ args.emplace(args.begin(), "-o");
+ args.emplace(args.begin() + 1, GetDataPath());
return RecordCmd()->Run(args);
}
@@ -722,10 +738,15 @@ class RecordingAppHelper {
}
return success;
};
- ProcessSymbolsInPerfDataFile(perf_data_file_.path, callback);
+ ProcessSymbolsInPerfDataFile(GetDataPath(), callback);
+ if (!success) {
+ DumpData();
+ }
return success;
}
+ void DumpData() { CreateCommandInstance("report")->Run({"-i", GetDataPath()}); }
+
std::string GetDataPath() const { return perf_data_file_.path; }
private:
@@ -763,7 +784,9 @@ static void TestRecordingApps(const std::string& app_name, const std::string& ap
reader.reset(nullptr);
// Check that simpleperf can't execute child command in app uid.
- ASSERT_FALSE(helper.RecordData("--app " + app_name + " -e " + GetDefaultEvent() + " sleep 1"));
+ if (!IsRoot()) {
+ ASSERT_FALSE(helper.RecordData("--app " + app_name + " -e " + GetDefaultEvent() + " sleep 1"));
+ }
}
TEST(record_cmd, app_option_for_debuggable_app) {
@@ -812,15 +835,18 @@ TEST(record_cmd, record_java_app) {
}
// Check perf.data by looking for java symbols.
+ const char* java_symbols[] = {
+ "androidx.test.runner",
+ "androidx.test.espresso",
+ "android.app.ActivityThread.main",
+ };
auto process_symbol = [&](const char* name) {
-#if !defined(IN_CTS_TEST)
- const char* expected_name_with_keyguard = "androidx.test.runner"; // when screen is locked
- if (strstr(name, expected_name_with_keyguard) != nullptr) {
- return true;
+ for (const char* java_symbol : java_symbols) {
+ if (strstr(name, java_symbol) != nullptr) {
+ return true;
+ }
}
-#endif
- const char* expected_name = "androidx.test.espresso"; // when screen stays awake
- return strstr(name, expected_name) != nullptr;
+ return false;
};
ASSERT_TRUE(helper.CheckData(process_symbol));
#else
@@ -930,9 +956,9 @@ TEST(record_cmd, cs_etm_event) {
// cs-etm uses sample period instead of sample freq.
ASSERT_EQ(reader->AttrSection().size(), 1u);
- const perf_event_attr* attr = reader->AttrSection()[0].attr;
- ASSERT_EQ(attr->freq, 0);
- ASSERT_EQ(attr->sample_period, 1);
+ const perf_event_attr& attr = reader->AttrSection()[0].attr;
+ ASSERT_EQ(attr.freq, 0);
+ ASSERT_EQ(attr.sample_period, 1);
bool has_auxtrace_info = false;
bool has_auxtrace = false;
@@ -1033,6 +1059,23 @@ TEST(record_cmd, addr_filter_option) {
ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--addr-filter", filter}));
}
+TEST(record_cmd, decode_etm_option) {
+ if (!ETMRecorder::GetInstance().CheckEtmSupport().ok()) {
+ GTEST_LOG_(INFO) << "Omit this test since etm isn't supported on this device";
+ return;
+ }
+ ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--decode-etm"}));
+ ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--decode-etm", "--exclude-perf"}));
+}
+
+TEST(record_cmd, binary_option) {
+ if (!ETMRecorder::GetInstance().CheckEtmSupport().ok()) {
+ GTEST_LOG_(INFO) << "Omit this test since etm isn't supported on this device";
+ return;
+ }
+ ASSERT_TRUE(RunRecordCmd({"-e", "cs-etm", "--decode-etm", "--binary", ".*"}));
+}
+
TEST(record_cmd, pmu_event_option) {
TEST_REQUIRE_PMU_COUNTER();
TEST_REQUIRE_HW_COUNTER();
@@ -1197,6 +1240,7 @@ TEST(record_cmd, add_meta_info_option) {
}
TEST(record_cmd, device_meta_info) {
+#if defined(__ANDROID__)
TemporaryFile tmpfile;
ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
auto reader = RecordFileReader::CreateInstance(tmpfile.path);
@@ -1209,6 +1253,9 @@ TEST(record_cmd, device_meta_info) {
it = meta_info.find("android_build_type");
ASSERT_NE(it, meta_info.end());
ASSERT_FALSE(it->second.empty());
+#else
+ GTEST_LOG_(INFO) << "This test tests a function only available on Android.";
+#endif
}
TEST(record_cmd, add_counter_option) {
@@ -1231,3 +1278,25 @@ TEST(record_cmd, add_counter_option) {
}));
ASSERT_TRUE(has_sample);
}
+
+TEST(record_cmd, user_buffer_size_option) {
+ ASSERT_TRUE(RunRecordCmd({"--user-buffer-size", "256M"}));
+}
+
+TEST(record_cmd, record_process_name) {
+ TemporaryFile tmpfile;
+ ASSERT_TRUE(RecordCmd()->Run({"-e", GetDefaultEvent(), "-o", tmpfile.path, "sleep", SLEEP_SEC}));
+ std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
+ ASSERT_TRUE(reader);
+ bool has_comm = false;
+ ASSERT_TRUE(reader->ReadDataSection([&](std::unique_ptr<Record> r) {
+ if (r->type() == PERF_RECORD_COMM) {
+ CommRecord* cr = static_cast<CommRecord*>(r.get());
+ if (strcmp(cr->comm, "sleep") == 0) {
+ has_comm = true;
+ }
+ }
+ return true;
+ }));
+ ASSERT_TRUE(has_comm);
+}
diff --git a/simpleperf/cmd_report.cpp b/simpleperf/cmd_report.cpp
index 8457df2f..2ab98548 100644
--- a/simpleperf/cmd_report.cpp
+++ b/simpleperf/cmd_report.cpp
@@ -662,8 +662,8 @@ bool ReportCommand::ParseOptions(const std::vector<std::string>& args) {
return false;
}
- for (const OptionValue& value : options.PullValues("--pids")) {
- if (auto pids = GetTidsFromString(*value.str_value, false); pids) {
+ if (auto strs = options.PullStringValues("--pids"); !strs.empty()) {
+ if (auto pids = GetPidsFromStrings(strs, false, false); pids) {
record_filter_.AddPids(pids.value(), false);
} else {
return false;
@@ -856,9 +856,8 @@ bool ReportCommand::ReadMetaInfoFromRecordFile() {
}
bool ReportCommand::ReadEventAttrFromRecordFile() {
- std::vector<EventAttrWithId> attrs = record_file_reader_->AttrSection();
- for (const auto& attr_with_id : attrs) {
- const perf_event_attr& attr = *attr_with_id.attr;
+ for (const EventAttrWithId& attr_with_id : record_file_reader_->AttrSection()) {
+ const perf_event_attr& attr = attr_with_id.attr;
attr_names_.emplace_back(GetEventNameByAttr(attr));
// There are no samples for events added by --add-counter. So skip them.
@@ -895,7 +894,9 @@ bool ReportCommand::ReadEventAttrFromRecordFile() {
}
bool ReportCommand::ReadFeaturesFromRecordFile() {
- record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
+ if (!record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_)) {
+ return false;
+ }
std::string arch = record_file_reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH);
if (!arch.empty()) {
@@ -991,11 +992,14 @@ void ReportCommand::ProcessSampleRecordInTraceOffCpuMode(std::unique_ptr<Record>
}
bool ReportCommand::ProcessTracingData(const std::vector<char>& data) {
- Tracing tracing(data);
+ auto tracing = Tracing::Create(data);
+ if (!tracing) {
+ return false;
+ }
for (size_t i = 0; i < event_attrs_.size(); i++) {
if (event_attrs_[i].type == PERF_TYPE_TRACEPOINT) {
uint64_t trace_event_id = event_attrs_[i].config;
- attr_names_[i] = tracing.GetTracingEventNameHavingId(trace_event_id);
+ attr_names_[i] = tracing->GetTracingEventNameHavingId(trace_event_id);
}
}
return true;
diff --git a/simpleperf/cmd_report_sample.cpp b/simpleperf/cmd_report_sample.cpp
index dc2a3d44..12e3ed61 100644
--- a/simpleperf/cmd_report_sample.cpp
+++ b/simpleperf/cmd_report_sample.cpp
@@ -372,8 +372,7 @@ bool ReportSampleCommand::DumpProtobufReport(const std::string& filename) {
// files[file_id] is the number of symbols in the file.
std::vector<uint32_t> files;
uint32_t max_message_size = 64 * (1 << 20);
- uint32_t warning_message_size = 512 * (1 << 20);
- coded_is.SetTotalBytesLimit(max_message_size, warning_message_size);
+ coded_is.SetTotalBytesLimit(max_message_size);
while (true) {
uint32_t size;
if (!coded_is.ReadLittleEndian32(&size)) {
@@ -386,7 +385,7 @@ bool ReportSampleCommand::DumpProtobufReport(const std::string& filename) {
// Handle files having large symbol table.
if (size > max_message_size) {
max_message_size = size;
- coded_is.SetTotalBytesLimit(max_message_size, warning_message_size);
+ coded_is.SetTotalBytesLimit(max_message_size);
}
auto limit = coded_is.PushLimit(size);
proto::Record proto_record;
@@ -516,12 +515,14 @@ bool ReportSampleCommand::OpenRecordFile() {
if (record_file_reader_ == nullptr) {
return false;
}
- record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
+ if (!record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_)) {
+ return false;
+ }
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 (trace_offcpu_) {
- std::string event_name = GetEventNameByAttr(*record_file_reader_->AttrSection()[0].attr);
+ std::string event_name = GetEventNameByAttr(record_file_reader_->AttrSection()[0].attr);
if (!android::base::StartsWith(event_name, "cpu-clock") &&
!android::base::StartsWith(event_name, "task-clock")) {
LOG(ERROR) << "Recording file " << record_filename_ << " is no longer supported. "
@@ -536,8 +537,8 @@ bool ReportSampleCommand::OpenRecordFile() {
if (!record_filter_.CheckClock(record_file_reader_->GetClockId())) {
return false;
}
- for (EventAttrWithId& attr : record_file_reader_->AttrSection()) {
- event_types_.push_back(GetEventNameByAttr(*attr.attr));
+ for (const EventAttrWithId& attr : record_file_reader_->AttrSection()) {
+ event_types_.push_back(GetEventNameByAttr(attr.attr));
}
return true;
}
@@ -782,7 +783,7 @@ bool ReportSampleCommand::ProcessSwitchRecord(Record* r) {
}
bool ReportSampleCommand::WriteRecordInProtobuf(proto::Record& proto_record) {
- coded_os_->WriteLittleEndian32(proto_record.ByteSize());
+ coded_os_->WriteLittleEndian32(static_cast<uint32_t>(proto_record.ByteSizeLong()));
if (!proto_record.SerializeToCodedStream(coded_os_)) {
LOG(ERROR) << "failed to write record to protobuf";
return false;
diff --git a/simpleperf/cmd_report_sample.proto b/simpleperf/cmd_report_sample.proto
index d5a6b867..8944fbef 100644
--- a/simpleperf/cmd_report_sample.proto
+++ b/simpleperf/cmd_report_sample.proto
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
// The file format generated by cmd_report_sample.proto is as below:
// char magic[10] = "SIMPLEPERF";
// LittleEndian16(version) = 1;
diff --git a/simpleperf/cmd_report_sample_test.cpp b/simpleperf/cmd_report_sample_test.cpp
index ca6db2f4..fc2f9f97 100644
--- a/simpleperf/cmd_report_sample_test.cpp
+++ b/simpleperf/cmd_report_sample_test.cpp
@@ -18,8 +18,8 @@
#include <android-base/file.h>
#include <android-base/strings.h>
-#include <regex>
+#include "RegEx.h"
#include "command.h"
#include "get_test_data.h"
@@ -96,8 +96,8 @@ TEST(cmd_report_sample, trace_offcpu) {
{"context_switch:", "switch_on: false", "time: 676374953363850", "thread_id: 6525"},
};
for (auto& test_case : cases) {
- auto pattern = std::regex(android::base::Join(test_case, R"((\s|\n|\r)+)"));
- ASSERT_TRUE(std::regex_search(data, pattern));
+ auto regex = RegEx::Create(android::base::Join(test_case, R"((\s|\n|\r)+)"));
+ ASSERT_TRUE(regex->Search(data));
}
}
diff --git a/simpleperf/cmd_report_test.cpp b/simpleperf/cmd_report_test.cpp
index e126b52e..6dfbae5c 100644
--- a/simpleperf/cmd_report_test.cpp
+++ b/simpleperf/cmd_report_test.cpp
@@ -16,7 +16,6 @@
#include <gtest/gtest.h>
-#include <regex>
#include <set>
#include <unordered_map>
@@ -25,6 +24,7 @@
#include <android-base/strings.h>
#include <android-base/test_utils.h>
+#include "RegEx.h"
#include "command.h"
#include "get_test_data.h"
#include "perf_regs.h"
@@ -67,10 +67,11 @@ class ReportCommandTest : public ::testing::Test {
}
size_t GetSampleCount() {
- std::smatch m;
- if (std::regex_search(content, m, std::regex(R"(Samples: (\d+))"))) {
+ auto regex = RegEx::Create(R"(Samples: (\d+))");
+ auto match = regex->SearchAll(content);
+ if (match->IsValid()) {
size_t count;
- if (android::base::ParseUint(m[1], &count)) {
+ if (android::base::ParseUint(match->GetField(1), &count)) {
return count;
}
}
@@ -226,7 +227,7 @@ TEST_F(ReportCommandTest, wrong_pid_filter_option) {
Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--pids", "2,bogus"});
exit(success ? 0 : 1);
},
- testing::ExitedWithCode(1), "Invalid tid 'bogus'");
+ testing::ExitedWithCode(1), "invalid pid: bogus");
}
TEST_F(ReportCommandTest, tid_filter_option) {
@@ -403,21 +404,6 @@ TEST_F(ReportCommandTest, no_show_ip_option) {
ASSERT_NE(content.find("unknown"), std::string::npos);
}
-TEST_F(ReportCommandTest, no_symbol_table_warning) {
- ASSERT_EXIT(
- {
- Report(PERF_DATA, {"--symfs", GetTestData(SYMFS_FOR_NO_SYMBOL_TABLE_WARNING)});
- if (!success) {
- exit(1);
- }
- if (content.find("GlobalFunc") != std::string::npos) {
- exit(2);
- }
- exit(0);
- },
- testing::ExitedWithCode(0), "elf doesn't contain symbol table");
-}
-
TEST_F(ReportCommandTest, read_elf_file_warning) {
ASSERT_EXIT(
{
@@ -570,32 +556,31 @@ TEST_F(ReportCommandTest, print_event_count_option) {
Report("perf.data", {"--print-event-count"});
ASSERT_TRUE(success);
ASSERT_NE(content.find("EventCount"), std::string::npos);
- ASSERT_TRUE(std::regex_search(
- content, std::regex(R"(325005586\s+elf\s+26083\s+26083\s+/elf\s+GlobalFunc)")));
+ ASSERT_TRUE(
+ RegEx::Create(R"(325005586\s+elf\s+26083\s+26083\s+/elf\s+GlobalFunc)")->Search(content));
// Report record file recorded with --add-counter.
const std::string record_file = "perf_with_add_counter.data";
Report(record_file, {"--print-event-count"});
ASSERT_TRUE(success);
+ ASSERT_TRUE(RegEx::Create(R"(EventCount_cpu-cycles\s+EventCount_instructions)")->Search(content));
ASSERT_TRUE(
- std::regex_search(content, std::regex(R"(EventCount_cpu-cycles\s+EventCount_instructions)")));
- ASSERT_TRUE(std::regex_search(
- content, std::regex(R"(175099\s+140443\s+sleep\s+689664\s+689664.+_dl_addr)")));
+ RegEx::Create(R"(175099\s+140443\s+sleep\s+689664\s+689664.+_dl_addr)")->Search(content));
// Report accumulated event counts.
Report(record_file, {"--print-event-count", "--children"});
ASSERT_TRUE(success);
- ASSERT_TRUE(std::regex_search(
- content,
- std::regex(
+ ASSERT_TRUE(
+ RegEx::Create(
R"(AccEventCount_cpu-cycles\s+SelfEventCount_cpu-cycles\s+AccEventCount_instructions\s+)"
- R"(SelfEventCount_instructions)")));
- ASSERT_TRUE(std::regex_search(
- content,
- std::regex(R"(175099\s+175099\s+140443\s+140443\s+sleep\s+689664\s+689664.+_dl_addr)")));
- ASSERT_TRUE(std::regex_search(
- content,
- std::regex(R"(366116\s+0\s+297474\s+0\s+sleep\s+689664\s+689664.+__libc_start_main)")));
+ R"(SelfEventCount_instructions)")
+ ->Search(content));
+ ASSERT_TRUE(
+ RegEx::Create(R"(175099\s+175099\s+140443\s+140443\s+sleep\s+689664\s+689664.+_dl_addr)")
+ ->Search(content));
+ ASSERT_TRUE(
+ RegEx::Create(R"(366116\s+0\s+297474\s+0\s+sleep\s+689664\s+689664.+__libc_start_main)")
+ ->Search(content));
}
TEST_F(ReportCommandTest, exclude_include_pid_options) {
diff --git a/simpleperf/cmd_stat.cpp b/simpleperf/cmd_stat.cpp
index bd1bffba..009afc3c 100644
--- a/simpleperf/cmd_stat.cpp
+++ b/simpleperf/cmd_stat.cpp
@@ -380,8 +380,10 @@ class StatCommand : public Command {
"-o output_filename Write report to output_filename instead of standard output.\n"
"--per-core Print counters for each cpu core.\n"
"--per-thread Print counters for each thread.\n"
-"-p pid1,pid2,... Stat events on existing processes. Mutually exclusive with -a.\n"
-"-t tid1,tid2,... Stat events on existing threads. Mutually exclusive with -a.\n"
+"-p pid_or_process_name_regex1,pid_or_process_name_regex2,...\n"
+" Stat events on existing processes. Processes are searched either by pid\n"
+" or process name regex. Mutually exclusive with -a.\n"
+"-t tid1,tid2,... Stat events on existing threads. Mutually exclusive with -a.\n"
"--print-hw-counter Test and print CPU PMU hardware counters available on the device.\n"
"--sort key1,key2,... Select keys used to sort the report, used when --per-thread\n"
" or --per-core appears. The appearance order of keys decides\n"
@@ -440,6 +442,7 @@ class StatCommand : public Command {
void AdjustToIntervalOnlyValues(std::vector<CountersInfo>& counters);
bool ShowCounters(const std::vector<CountersInfo>& counters, double duration_in_sec, FILE* fp);
void CheckHardwareCounterMultiplexing();
+ void PrintWarningForInaccurateEvents();
bool verbose_mode_;
bool system_wide_collection_;
@@ -620,9 +623,10 @@ bool StatCommand::Run(const std::vector<std::string>& args) {
}
}
- // 7. Print hardware counter multiplexing warning when needed.
+ // 7. Print warnings when needed.
event_selection_set_.CloseEventFiles();
CheckHardwareCounterMultiplexing();
+ PrintWarningForInaccurateEvents();
return true;
}
@@ -688,8 +692,8 @@ bool StatCommand::ParseOptions(const std::vector<std::string>& args,
report_per_core_ = options.PullBoolValue("--per-core");
report_per_thread_ = options.PullBoolValue("--per-thread");
- for (const OptionValue& value : options.PullValues("-p")) {
- if (auto pids = GetTidsFromString(*value.str_value, true); pids) {
+ if (auto strs = options.PullStringValues("-p"); !strs.empty()) {
+ if (auto pids = GetPidsFromStrings(strs, true, true); pids) {
event_selection_set_.AddMonitoredProcesses(pids.value());
} else {
return false;
@@ -955,6 +959,16 @@ void StatCommand::CheckHardwareCounterMultiplexing() {
}
}
+void StatCommand::PrintWarningForInaccurateEvents() {
+ for (const EventType* event : event_selection_set_.GetEvents()) {
+ if (event->name == "raw-l3d-cache-lmiss-rd") {
+ LOG(WARNING) << "PMU event L3D_CACHE_LMISS_RD might undercount on A510. Please use "
+ "L3D_CACHE_REFILL_RD instead.";
+ break;
+ }
+ }
+}
+
} // namespace
void RegisterStatCommand() {
diff --git a/simpleperf/cmd_stat_test.cpp b/simpleperf/cmd_stat_test.cpp
index 78eac57e..53ff0d48 100644
--- a/simpleperf/cmd_stat_test.cpp
+++ b/simpleperf/cmd_stat_test.cpp
@@ -67,6 +67,9 @@ TEST(stat_cmd, rN_event) {
} else if (GetTargetArch() == ARCH_X86_32 || GetTargetArch() == ARCH_X86_64) {
// As in volume 3 chapter 19 of the Intel manual, 0x00c0 is the event number for instruction.
event_number = 0x00c0;
+ } else if (GetTargetArch() == ARCH_RISCV64) {
+ // RISCV_PMU_INSTRET = 1
+ event_number = 0x1;
} else {
GTEST_LOG_(INFO) << "Omit arch " << GetTargetArch();
return;
@@ -83,6 +86,8 @@ TEST(stat_cmd, pmu_event) {
event_string = "cpu/instructions/";
} else if (GetTargetArch() == ARCH_ARM64) {
event_string = "armv8_pmuv3/inst_retired/";
+ } else if (GetTargetArch() == ARCH_RISCV64) {
+ event_string = "cpu/instructions/";
} else {
GTEST_LOG_(INFO) << "Omit arch " << GetTargetArch();
return;
@@ -228,12 +233,12 @@ TEST(stat_cmd, sample_speed_should_be_zero) {
ASSERT_TRUE(set.AddEventType("cpu-cycles"));
set.AddMonitoredProcesses({getpid()});
ASSERT_TRUE(set.OpenEventFiles({-1}));
- std::vector<EventAttrWithId> attrs = set.GetEventAttrWithId();
+ const EventAttrIds& attrs = set.GetEventAttrWithId();
ASSERT_GT(attrs.size(), 0u);
for (auto& attr : attrs) {
- ASSERT_EQ(attr.attr->sample_period, 0u);
- ASSERT_EQ(attr.attr->sample_freq, 0u);
- ASSERT_EQ(attr.attr->freq, 0u);
+ ASSERT_EQ(attr.attr.sample_period, 0u);
+ ASSERT_EQ(attr.attr.sample_freq, 0u);
+ ASSERT_EQ(attr.attr.freq, 0u);
}
}
@@ -611,4 +616,4 @@ TEST_F(StatCmdSummariesTest, rate_comment) {
ASSERT_EQ(*GetComment(1), "1.000 M/sec");
ASSERT_EQ(*GetComment(2), "1.000 K/sec");
ASSERT_EQ(*GetComment(3), "1.000 /sec");
-} \ No newline at end of file
+}
diff --git a/simpleperf/cmd_trace_sched.cpp b/simpleperf/cmd_trace_sched.cpp
index 0fcabd28..d02495a9 100644
--- a/simpleperf/cmd_trace_sched.cpp
+++ b/simpleperf/cmd_trace_sched.cpp
@@ -100,7 +100,7 @@ class TraceSchedCommand : public Command {
bool ParseOptions(const std::vector<std::string>& args);
bool RecordSchedEvents(const std::string& record_file_path);
bool ParseSchedEvents(const std::string& record_file_path);
- void ProcessRecord(Record& record);
+ bool ProcessRecord(Record& record);
void ProcessSampleRecord(const SampleRecord& record);
std::vector<ProcessInfo> BuildProcessInfo();
void ReportProcessInfo(const std::vector<ProcessInfo>& processes);
@@ -192,21 +192,18 @@ bool TraceSchedCommand::ParseSchedEvents(const std::string& record_file_path) {
return false;
}
const EventType* event = FindEventTypeByName("sched:sched_stat_runtime");
- std::vector<EventAttrWithId> attrs = reader->AttrSection();
- if (attrs.size() != 1u || attrs[0].attr->type != event->type ||
- attrs[0].attr->config != event->config) {
+ const EventAttrIds& attrs = reader->AttrSection();
+ if (attrs.size() != 1u || attrs[0].attr.type != event->type ||
+ attrs[0].attr.config != event->config) {
LOG(ERROR) << "sched:sched_stat_runtime isn't recorded in " << record_file_path;
return false;
}
- auto callback = [this](std::unique_ptr<Record> record) {
- ProcessRecord(*record);
- return true;
- };
+ auto callback = [this](std::unique_ptr<Record> record) { return ProcessRecord(*record); };
return reader->ReadDataSection(callback);
}
-void TraceSchedCommand::ProcessRecord(Record& record) {
+bool TraceSchedCommand::ProcessRecord(Record& record) {
switch (record.type()) {
case PERF_RECORD_SAMPLE: {
ProcessSampleRecord(*static_cast<SampleRecord*>(&record));
@@ -234,15 +231,19 @@ void TraceSchedCommand::ProcessRecord(Record& record) {
case PERF_RECORD_TRACING_DATA:
case SIMPLE_PERF_RECORD_TRACING_DATA: {
const TracingDataRecord& r = *static_cast<const TracingDataRecord*>(&record);
- Tracing tracing(std::vector<char>(r.data, r.data + r.data_size));
+ auto tracing = Tracing::Create(std::vector<char>(r.data, r.data + r.data_size));
+ if (!tracing) {
+ return false;
+ }
const EventType* event = FindEventTypeByName("sched:sched_stat_runtime");
CHECK(event != nullptr);
- TracingFormat format = tracing.GetTracingFormatHavingId(event->config);
+ TracingFormat format = tracing->GetTracingFormatHavingId(event->config);
format.GetField("comm", tracing_field_comm_);
format.GetField("runtime", tracing_field_runtime_);
break;
}
}
+ return true;
}
void TraceSchedCommand::ProcessSampleRecord(const SampleRecord& record) {
diff --git a/simpleperf/command.h b/simpleperf/command.h
index a46da6f5..35db8469 100644
--- a/simpleperf/command.h
+++ b/simpleperf/command.h
@@ -117,6 +117,14 @@ struct OptionValueMap {
return res;
}
+ std::vector<std::string> PullStringValues(const OptionName& name) {
+ std::vector<std::string> res;
+ for (const auto& value : PullValues(name)) {
+ res.emplace_back(*value.str_value);
+ }
+ return res;
+ }
+
std::vector<OptionValue> PullValues(const OptionName& name) {
auto pair = values.equal_range(name);
if (pair.first != pair.second) {
@@ -161,7 +169,7 @@ class Command {
const std::string& ShortHelpString() const { return short_help_string_; }
- const std::string LongHelpString() const { return long_help_string_; }
+ virtual std::string LongHelpString() const { return long_help_string_; }
virtual bool Run(const std::vector<std::string>&) { return false; }
virtual void Run(const std::vector<std::string>& args, int* exit_code) {
@@ -195,7 +203,6 @@ class Command {
bool NextArgumentOrError(const std::vector<std::string>& args, size_t* pi);
void ReportUnknownOption(const std::vector<std::string>& args, size_t i);
- private:
const std::string name_;
const std::string short_help_string_;
const std::string long_help_string_;
diff --git a/simpleperf/cpu_hotplug_test.cpp b/simpleperf/cpu_hotplug_test.cpp
index 9b78c644..526ab87a 100644
--- a/simpleperf/cpu_hotplug_test.cpp
+++ b/simpleperf/cpu_hotplug_test.cpp
@@ -497,7 +497,7 @@ int main(int argc, char** argv) {
verbose_mode = true;
}
}
- android::base::InitLogging(argv, android::base::StderrLogger);
testing::InitGoogleTest(&argc, argv);
+ android::base::InitLogging(argv, android::base::StderrLogger);
return RUN_ALL_TESTS();
}
diff --git a/simpleperf/demo/README.md b/simpleperf/demo/README.md
index c1ef30a3..8e554a80 100644
--- a/simpleperf/demo/README.md
+++ b/simpleperf/demo/README.md
@@ -19,9 +19,9 @@ This directory is to show examples of using simpleperf to profile Android applic
meaning of each directory is as below:
../scripts/ -- contain simpleperf binaries and scripts.
- SimpleperfExamplePureJava/ -- contains an Android Studio project using only Java code.
+ SimpleperfExampleJava/ -- contains an Android Studio project using only Java code.
SimpleperfExampleCpp/ -- contains an Android Studio project using both Java and C++ code.
- SimpleperfExampleOfKotlin/ -- contains an Android Studio project using Kotlin code.
+ SimpleperfExampleKotlin/ -- contains an Android Studio project using Kotlin code.
CppApi/ -- contains an Android Studio project using c++ app_api to record.
JavaApi/ -- contains an Android Studio project using Java app_api to record.
@@ -43,19 +43,20 @@ Please make sure your device having Android version >= N.
## Profile a Java application
-Android Studio project: SimpleExamplePureJava
+Android Studio project: SimpleExampleJava
steps:
1. Build and install the application:
```sh
-# Open SimpleperfExamplePureJava project with Android Studio,
+# Open SimpleperfExampleJava project with Android Studio,
# and build this project successfully, otherwise the `./gradlew` command below will fail.
-$ cd SimpleperfExamplePureJava
+$ cd SimpleperfExampleJava
+# Build and install a debuggable app. We can also build and install a released app on Android >= Q.
# On windows, use "gradlew" instead.
$ ./gradlew clean assemble
-$ adb install -r app/build/outputs/apk/app-profiling.apk
+$ adb install -r app/build/outputs/apk/debug/app-debug.apk
```
2. Record profiling data:
@@ -63,7 +64,7 @@ $ adb install -r app/build/outputs/apk/app-profiling.apk
```sh
$ cd ../../scripts/
# app_profiler.py collects profiling data in perf.data, and binaries on device in binary_cache/.
-$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplepurejava
+$ python app_profiler.py -p simpleperf.example.java
```
3. Show profiling data:
@@ -107,19 +108,20 @@ $ python report_html.py --add_source_code --source_dirs ../demo --add_disassembl
## Profile a Kotlin application
-Android Studio project: SimpleExampleOfKotlin
+Android Studio project: SimpleExampleKotlin
steps:
1. Build and install the application:
```sh
-# Open SimpleperfExampleOfKotlin project with Android Studio,
+# Open SimpleperfExampleKotlin project with Android Studio,
# and build this project sucessfully, otherwise the `./gradlew` command below will fail.
-$ cd SimpleperfExampleOfKotlin
+$ cd SimpleperfExampleKotlin
+# Build and install a debuggable app. We can also build and install a released app on Android >= Q.
# On windows, use "gradlew" instead.
$ ./gradlew clean assemble
-$ adb install -r app/build/outputs/apk/profiling/app-profiling.apk
+$ adb install -r app/build/outputs/apk/debug/app-debug.apk
```
2. Record profiling data:
@@ -127,7 +129,7 @@ $ adb install -r app/build/outputs/apk/profiling/app-profiling.apk
```sh
$ cd ../../scripts/
# app_profiler.py collects profiling data in perf.data, and binaries on device in binary_cache/.
-$ python app_profiler.py -p com.example.simpleperf.simpleperfexampleofkotlin
+$ python app_profiler.py -p simpleperf.example.kotlin
```
3. Show profiling data:
diff --git a/simpleperf/demo/SimpleperfExampleCpp/app/build.gradle b/simpleperf/demo/SimpleperfExampleCpp/app/build.gradle
index f327dbf4..8cbdf272 100644
--- a/simpleperf/demo/SimpleperfExampleCpp/app/build.gradle
+++ b/simpleperf/demo/SimpleperfExampleCpp/app/build.gradle
@@ -3,12 +3,12 @@ plugins {
}
android {
- compileSdkVersion 30
+ compileSdkVersion 32
defaultConfig {
applicationId "simpleperf.example.cpp"
minSdkVersion 24
- targetSdkVersion 30
+ targetSdkVersion 32
versionCode 1
versionName "1.0"
@@ -35,7 +35,7 @@ android {
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
- version '3.10.2'
+ version '3.18.1'
}
}
buildFeatures {
diff --git a/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/arm64-v8a/libnative-lib.so b/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/arm64-v8a/libnative-lib.so
index ad974c80..6c366c0d 100755
--- a/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/arm64-v8a/libnative-lib.so
+++ b/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/arm64-v8a/libnative-lib.so
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/armeabi-v7a/libnative-lib.so b/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/armeabi-v7a/libnative-lib.so
index afacd9fd..d8ec66de 100755
--- a/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/armeabi-v7a/libnative-lib.so
+++ b/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/armeabi-v7a/libnative-lib.so
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/x86/libnative-lib.so b/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/x86/libnative-lib.so
index 883b6430..a1505db0 100755
--- a/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/x86/libnative-lib.so
+++ b/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/x86/libnative-lib.so
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/x86_64/libnative-lib.so b/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/x86_64/libnative-lib.so
index 103ed972..93a68ca1 100755
--- a/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/x86_64/libnative-lib.so
+++ b/simpleperf/demo/SimpleperfExampleCpp/app/build/intermediates/cmake/debug/obj/x86_64/libnative-lib.so
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleCpp/app/build/outputs/apk/debug/app-debug.apk b/simpleperf/demo/SimpleperfExampleCpp/app/build/outputs/apk/debug/app-debug.apk
index ba6ad0c7..b1aa5c4e 100644
--- a/simpleperf/demo/SimpleperfExampleCpp/app/build/outputs/apk/debug/app-debug.apk
+++ b/simpleperf/demo/SimpleperfExampleCpp/app/build/outputs/apk/debug/app-debug.apk
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleCpp/app/release/app-release.apk b/simpleperf/demo/SimpleperfExampleCpp/app/release/app-release.apk
new file mode 100644
index 00000000..01e0e5a7
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleCpp/app/release/app-release.apk
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleCpp/app/src/main/AndroidManifest.xml b/simpleperf/demo/SimpleperfExampleCpp/app/src/main/AndroidManifest.xml
index 18ffc0c7..7bb592da 100644
--- a/simpleperf/demo/SimpleperfExampleCpp/app/src/main/AndroidManifest.xml
+++ b/simpleperf/demo/SimpleperfExampleCpp/app/src/main/AndroidManifest.xml
@@ -9,7 +9,9 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.SimpleperfExampleCpp">
- <activity android:name=".MainActivity">
+ <profileable android:shell="true" />
+ <activity android:name=".MainActivity"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/simpleperf/demo/SimpleperfExampleCpp/app/src/main/cpp/CMakeLists.txt b/simpleperf/demo/SimpleperfExampleCpp/app/src/main/cpp/CMakeLists.txt
index c5b83ac0..9c11f078 100644
--- a/simpleperf/demo/SimpleperfExampleCpp/app/src/main/cpp/CMakeLists.txt
+++ b/simpleperf/demo/SimpleperfExampleCpp/app/src/main/cpp/CMakeLists.txt
@@ -3,7 +3,7 @@
# Sets the minimum version of CMake required to build the native library.
-cmake_minimum_required(VERSION 3.10.2)
+cmake_minimum_required(VERSION 3.18.1)
# Declares and names the project.
diff --git a/simpleperf/demo/SimpleperfExampleCpp/build.gradle b/simpleperf/demo/SimpleperfExampleCpp/build.gradle
index 5a7c7fc5..71b99f4c 100644
--- a/simpleperf/demo/SimpleperfExampleCpp/build.gradle
+++ b/simpleperf/demo/SimpleperfExampleCpp/build.gradle
@@ -1,25 +1,5 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
-buildscript {
- repositories {
- google()
- mavenCentral()
- }
- dependencies {
- classpath "com.android.tools.build:gradle:4.2.2"
-
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
- }
-}
-
-allprojects {
- repositories {
- google()
- mavenCentral()
- jcenter() // Warning: this repository is going to shut down soon
- }
-}
-
-task clean(type: Delete) {
- delete rootProject.buildDir
+plugins {
+ id 'com.android.application' version '7.3.1' apply false
+ id 'com.android.library' version '7.3.1' apply false
} \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleCpp/gradle/wrapper/gradle-wrapper.properties b/simpleperf/demo/SimpleperfExampleCpp/gradle/wrapper/gradle-wrapper.properties
index f793a34d..6903aadd 100644
--- a/simpleperf/demo/SimpleperfExampleCpp/gradle/wrapper/gradle-wrapper.properties
+++ b/simpleperf/demo/SimpleperfExampleCpp/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Fri Oct 15 16:48:24 PDT 2021
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
diff --git a/simpleperf/demo/SimpleperfExampleCpp/settings.gradle b/simpleperf/demo/SimpleperfExampleCpp/settings.gradle
index a3893159..15bae728 100644
--- a/simpleperf/demo/SimpleperfExampleCpp/settings.gradle
+++ b/simpleperf/demo/SimpleperfExampleCpp/settings.gradle
@@ -1,2 +1,16 @@
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ google()
+ mavenCentral()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
rootProject.name = "SimpleperfExampleCpp"
include ':app'
diff --git a/simpleperf/demo/SimpleperfExampleJava/.idea/compiler.xml b/simpleperf/demo/SimpleperfExampleJava/.idea/compiler.xml
new file mode 100644
index 00000000..fb7f4a8a
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/.idea/compiler.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="CompilerConfiguration">
+ <bytecodeTargetLevel target="11" />
+ </component>
+</project> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/gradle.xml b/simpleperf/demo/SimpleperfExampleJava/.idea/gradle.xml
index 7ac24c77..a9f4e522 100644
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/gradle.xml
+++ b/simpleperf/demo/SimpleperfExampleJava/.idea/gradle.xml
@@ -3,6 +3,7 @@
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
+ <option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
@@ -11,7 +12,6 @@
<option value="$PROJECT_DIR$/app" />
</set>
</option>
- <option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
diff --git a/simpleperf/demo/SimpleperfExampleJava/.idea/misc.xml b/simpleperf/demo/SimpleperfExampleJava/.idea/misc.xml
new file mode 100644
index 00000000..bdd92780
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/.idea/misc.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ExternalStorageConfigurationManager" enabled="true" />
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
+ <output url="file://$PROJECT_DIR$/build/classes" />
+ </component>
+ <component name="ProjectType">
+ <option name="id" value="Android" />
+ </component>
+</project> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/.idea/vcs.xml b/simpleperf/demo/SimpleperfExampleJava/.idea/vcs.xml
new file mode 100644
index 00000000..c2365ab1
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="$PROJECT_DIR$/../../.." vcs="Git" />
+ </component>
+</project> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/build.gradle b/simpleperf/demo/SimpleperfExampleJava/app/build.gradle
new file mode 100644
index 00000000..c2c1fe04
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/build.gradle
@@ -0,0 +1,39 @@
+plugins {
+ id 'com.android.application'
+}
+
+android {
+ namespace 'simpleperf.example.java'
+ compileSdk 32
+
+ defaultConfig {
+ applicationId "simpleperf.example.java"
+ minSdk 24
+ targetSdk 32
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+dependencies {
+
+ implementation 'androidx.appcompat:appcompat:1.4.1'
+ implementation 'com.google.android.material:material:1.5.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+} \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/build/outputs/apk/debug/app-debug.apk b/simpleperf/demo/SimpleperfExampleJava/app/build/outputs/apk/debug/app-debug.apk
new file mode 100644
index 00000000..dcb1a6d8
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/build/outputs/apk/debug/app-debug.apk
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/proguard-rules.pro b/simpleperf/demo/SimpleperfExampleJava/app/proguard-rules.pro
index b9d149a6..481bb434 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/proguard-rules.pro
+++ b/simpleperf/demo/SimpleperfExampleJava/app/proguard-rules.pro
@@ -1,14 +1,10 @@
# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in /home/yabinc/Android/Sdk/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the proguardFiles
-# directive in build.gradle.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
-# Add any project specific keep options here:
-
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
@@ -22,4 +18,4 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
-#-renamesourcefileattribute SourceFile
+#-renamesourcefileattribute SourceFile \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/release/app-release.apk b/simpleperf/demo/SimpleperfExampleJava/app/release/app-release.apk
new file mode 100644
index 00000000..87b46c27
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/release/app-release.apk
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/release/output-metadata.json b/simpleperf/demo/SimpleperfExampleJava/app/release/output-metadata.json
new file mode 100644
index 00000000..d95c5c64
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/release/output-metadata.json
@@ -0,0 +1,20 @@
+{
+ "version": 3,
+ "artifactType": {
+ "type": "APK",
+ "kind": "Directory"
+ },
+ "applicationId": "simpleperf.example.java",
+ "variantName": "release",
+ "elements": [
+ {
+ "type": "SINGLE",
+ "filters": [],
+ "attributes": [],
+ "versionCode": 1,
+ "versionName": "1.0",
+ "outputFile": "app-release.apk"
+ }
+ ],
+ "elementType": "File"
+} \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/androidTest/java/simpleperf/example/java/ExampleInstrumentedTest.java b/simpleperf/demo/SimpleperfExampleJava/app/src/androidTest/java/simpleperf/example/java/ExampleInstrumentedTest.java
new file mode 100644
index 00000000..46af4871
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/androidTest/java/simpleperf/example/java/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package simpleperf.example.java;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ assertEquals("simpleperf.example.java", appContext.getPackageName());
+ }
+} \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/AndroidManifest.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..5baf7258
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools">
+
+ <application
+ android:allowBackup="true"
+ android:dataExtractionRules="@xml/data_extraction_rules"
+ android:fullBackupContent="@xml/backup_rules"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/Theme.SimpleperfExampleJava"
+ tools:targetApi="31">
+ <profileable android:shell="true" />
+ <activity
+ android:name=".MainActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".SleepActivity"
+ android:exported="true"/>
+ <activity
+ android:name=".MultiProcessActivity"
+ android:exported="true"/>
+ <service
+ android:name=".MultiProcessService"
+ android:process=":multiprocess_service"/>
+ </application>
+
+</manifest> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java b/simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/MainActivity.java
index 2d6196b7..7296dba8 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/MainActivity.java
@@ -1,6 +1,7 @@
-package com.example.simpleperf.simpleperfexamplepurejava;
+package simpleperf.example.java;
+
+import androidx.appcompat.app.AppCompatActivity;
-import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@@ -9,7 +10,6 @@ public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
-
createBusyThread();
}
@@ -29,4 +29,4 @@ public class MainActivity extends AppCompatActivity {
}
}, "BusyThread").start();
}
-}
+} \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessActivity.java b/simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/MultiProcessActivity.java
index de698ec6..25dc7965 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessActivity.java
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/MultiProcessActivity.java
@@ -1,20 +1,20 @@
-package com.example.simpleperf.simpleperfexamplepurejava;
+package simpleperf.example.java;
+
+import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
-import android.support.v7.app.AppCompatActivity;
-import android.os.Bundle;
import android.util.Log;
public class MultiProcessActivity extends AppCompatActivity {
public static final String TAG = "MultiProcessActivity";
-
Messenger mService = null;
boolean mBound;
@@ -22,7 +22,6 @@ public class MultiProcessActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_multi_process);
-
bindService(new Intent(this, MultiProcessService.class), mConnection,
Context.BIND_AUTO_CREATE);
createBusyThread();
@@ -75,4 +74,4 @@ public class MultiProcessActivity extends AppCompatActivity {
}
}, "BusyThread").start();
}
-}
+} \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessService.java b/simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/MultiProcessService.java
index 2fd4d571..3e85c243 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessService.java
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/MultiProcessService.java
@@ -1,9 +1,10 @@
-package com.example.simpleperf.simpleperfexamplepurejava;
+package simpleperf.example.java;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
@@ -47,4 +48,4 @@ public class MultiProcessService extends Service {
}
}, "BusyService").start();
}
-}
+} \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/SleepActivity.java b/simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/SleepActivity.java
index 25dc7e3e..4c310df4 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/SleepActivity.java
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/java/simpleperf/example/java/SleepActivity.java
@@ -1,6 +1,7 @@
-package com.example.simpleperf.simpleperfexamplepurejava;
+package simpleperf.example.java;
+
+import androidx.appcompat.app.AppCompatActivity;
-import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class SleepActivity extends AppCompatActivity {
@@ -51,5 +52,4 @@ public class SleepActivity extends AppCompatActivity {
}
}, "RunSleepThread").start();
}
-
-}
+} \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 00000000..2b068d11
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:endX="85.84757"
+ android:endY="92.4963"
+ android:startX="42.9492"
+ android:startY="49.59793"
+ android:type="linear">
+ <item
+ android:color="#44000000"
+ android:offset="0.0" />
+ <item
+ android:color="#00000000"
+ android:offset="1.0" />
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:fillColor="#FFFFFF"
+ android:fillType="nonZero"
+ android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+ android:strokeWidth="1"
+ android:strokeColor="#00000000" />
+</vector> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/drawable/ic_launcher_background.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 00000000..07d5da9c
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:fillColor="#3DDC84"
+ android:pathData="M0,0h108v108h-108z" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M9,0L9,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,0L19,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,0L29,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,0L39,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,0L49,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,0L59,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,0L69,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,0L79,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M89,0L89,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M99,0L99,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,9L108,9"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,19L108,19"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,29L108,29"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,39L108,39"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,49L108,49"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,59L108,59"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,69L108,69"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,79L108,79"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,89L108,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,99L108,99"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,29L89,29"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,39L89,39"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,49L89,49"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,59L89,59"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,69L89,69"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,79L89,79"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,19L29,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,19L39,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,19L49,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,19L59,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,19L69,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,19L79,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+</vector>
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/layout/activity_main.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/layout/activity_main.xml
index 4c7edcf2..c937b699 100644
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/layout/activity_main.xml
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/layout/activity_main.xml
@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
-<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context="com.example.simpleperf.simpleperfexampleofkotlin.MainActivity">
+ tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="Hello World!"
+ android:text="Running a busy thread!"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
-</android.support.constraint.ConstraintLayout>
+</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_multi_process.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/layout/activity_multi_process.xml
index f97b72e0..8b23ff4e 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_multi_process.xml
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/layout/activity_multi_process.xml
@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
-<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context="com.example.simpleperf.simpleperfexamplepurejava.MultiProcessActivity">
+ tools:context=".MultiProcessActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="MultiProcessActivity"
+ android:text="Running a busy thread and a busy service thread!"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
-</android.support.constraint.ConstraintLayout>
+</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_sleep.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/layout/activity_sleep.xml
index f732f777..fd8ce422 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_sleep.xml
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/layout/activity_sleep.xml
@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
-<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context="com.example.simpleperf.simpleperfexamplepurejava.SleepActivity">
+ tools:context=".SleepActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="SleepActivity"
+ android:text="Running a half busy half sleep thread!"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
-</android.support.constraint.ConstraintLayout>
+</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 00000000..c209e78e
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..b2dfe3d1
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 00000000..4f0f1d64
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..62b611da
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 00000000..948a3070
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..1b9a6956
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 00000000..28d4b77f
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..9287f508
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 00000000..aa7d6427
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..9126ae37
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values-night/themes.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values-night/themes.xml
new file mode 100644
index 00000000..6327fb79
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!-- Base application theme. -->
+ <style name="Theme.SimpleperfExampleJava" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+ <!-- Primary brand color. -->
+ <item name="colorPrimary">@color/purple_200</item>
+ <item name="colorPrimaryVariant">@color/purple_700</item>
+ <item name="colorOnPrimary">@color/black</item>
+ <!-- Secondary brand color. -->
+ <item name="colorSecondary">@color/teal_200</item>
+ <item name="colorSecondaryVariant">@color/teal_200</item>
+ <item name="colorOnSecondary">@color/black</item>
+ <!-- Status bar color. -->
+ <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
+ <!-- Customize your theme here. -->
+ </style>
+</resources> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values/colors.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values/colors.xml
new file mode 100644
index 00000000..f8c6127d
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="purple_200">#FFBB86FC</color>
+ <color name="purple_500">#FF6200EE</color>
+ <color name="purple_700">#FF3700B3</color>
+ <color name="teal_200">#FF03DAC5</color>
+ <color name="teal_700">#FF018786</color>
+ <color name="black">#FF000000</color>
+ <color name="white">#FFFFFFFF</color>
+</resources> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values/strings.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000..069da5ce
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+ <string name="app_name">SimpleperfExampleJava</string>
+</resources> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values/themes.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values/themes.xml
new file mode 100644
index 00000000..5d8a48d1
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!-- Base application theme. -->
+ <style name="Theme.SimpleperfExampleJava" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+ <!-- Primary brand color. -->
+ <item name="colorPrimary">@color/purple_500</item>
+ <item name="colorPrimaryVariant">@color/purple_700</item>
+ <item name="colorOnPrimary">@color/white</item>
+ <!-- Secondary brand color. -->
+ <item name="colorSecondary">@color/teal_200</item>
+ <item name="colorSecondaryVariant">@color/teal_700</item>
+ <item name="colorOnSecondary">@color/black</item>
+ <!-- Status bar color. -->
+ <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
+ <!-- Customize your theme here. -->
+ </style>
+</resources> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/xml/backup_rules.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 00000000..fa0f996d
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Sample backup rules file; uncomment and customize as necessary.
+ See https://developer.android.com/guide/topics/data/autobackup
+ for details.
+ Note: This file is ignored for devices older that API 31
+ See https://developer.android.com/about/versions/12/backup-restore
+-->
+<full-backup-content>
+ <!--
+ <include domain="sharedpref" path="."/>
+ <exclude domain="sharedpref" path="device.xml"/>
+-->
+</full-backup-content> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/xml/data_extraction_rules.xml b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 00000000..9ee9997b
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Sample data extraction rules file; uncomment and customize as necessary.
+ See https://developer.android.com/about/versions/12/backup-restore#xml-changes
+ for details.
+-->
+<data-extraction-rules>
+ <cloud-backup>
+ <!-- TODO: Use <include> and <exclude> to control what is backed up.
+ <include .../>
+ <exclude .../>
+ -->
+ </cloud-backup>
+ <!--
+ <device-transfer>
+ <include .../>
+ <exclude .../>
+ </device-transfer>
+ -->
+</data-extraction-rules> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/test/java/com/example/simpleperf/simpleperfexamplepurejava/ExampleUnitTest.java b/simpleperf/demo/SimpleperfExampleJava/app/src/test/java/simpleperf/example/java/ExampleUnitTest.java
index 0f3813df..8bcec03f 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/test/java/com/example/simpleperf/simpleperfexamplepurejava/ExampleUnitTest.java
+++ b/simpleperf/demo/SimpleperfExampleJava/app/src/test/java/simpleperf/example/java/ExampleUnitTest.java
@@ -1,4 +1,4 @@
-package com.example.simpleperf.simpleperfexamplepurejava;
+package simpleperf.example.java;
import org.junit.Test;
@@ -11,7 +11,7 @@ import static org.junit.Assert.*;
*/
public class ExampleUnitTest {
@Test
- public void addition_isCorrect() throws Exception {
+ public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
} \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/build.gradle b/simpleperf/demo/SimpleperfExampleJava/build.gradle
new file mode 100644
index 00000000..71b99f4c
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/build.gradle
@@ -0,0 +1,5 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ id 'com.android.application' version '7.3.1' apply false
+ id 'com.android.library' version '7.3.1' apply false
+} \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/gradle.properties b/simpleperf/demo/SimpleperfExampleJava/gradle.properties
index aac7c9b4..3e927b11 100644
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/gradle.properties
+++ b/simpleperf/demo/SimpleperfExampleJava/gradle.properties
@@ -1,17 +1,21 @@
# Project-wide Gradle settings.
-
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
-
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
-
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx1536m
-
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleJava/gradle/wrapper/gradle-wrapper.jar b/simpleperf/demo/SimpleperfExampleJava/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..e708b1c0
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/gradle/wrapper/gradle-wrapper.properties b/simpleperf/demo/SimpleperfExampleJava/gradle/wrapper/gradle-wrapper.properties
index be54b8d6..51d529e7 100644
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/gradle/wrapper/gradle-wrapper.properties
+++ b/simpleperf/demo/SimpleperfExampleJava/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Tue Apr 10 15:23:21 PDT 2018
+#Wed Nov 09 13:48:30 PST 2022
distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
+zipStoreBase=GRADLE_USER_HOME
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/gradlew b/simpleperf/demo/SimpleperfExampleJava/gradlew
index 9d82f789..4f906e0c 100755
--- a/simpleperf/demo/SimpleperfExamplePureJava/gradlew
+++ b/simpleperf/demo/SimpleperfExampleJava/gradlew
@@ -1,4 +1,20 @@
-#!/usr/bin/env bash
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# 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
+#
+# https://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.
+#
##############################################################################
##
@@ -6,20 +22,38 @@
##
##############################################################################
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
-warn ( ) {
+warn () {
echo "$*"
}
-die ( ) {
+die () {
echo
echo "$*"
echo
@@ -30,6 +64,7 @@ die ( ) {
cygwin=false
msys=false
darwin=false
+nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
@@ -40,28 +75,14 @@ case "`uname`" in
MINGW* )
msys=true
;;
+ NONSTOP* )
+ nonstop=true
+ ;;
esac
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -85,7 +106,7 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -105,10 +126,11 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
@@ -134,27 +156,30 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
- i=$((i+1))
+ i=`expr $i + 1`
done
case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
- JVM_OPTS=("$@")
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
+exec "$JAVACMD" "$@"
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/gradlew.bat b/simpleperf/demo/SimpleperfExampleJava/gradlew.bat
index aec99730..ac1b06f9 100644
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/gradlew.bat
+++ b/simpleperf/demo/SimpleperfExampleJava/gradlew.bat
@@ -1,3 +1,19 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -8,20 +24,23 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
+if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -35,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-if exist "%JAVA_EXE%" goto init
+if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -45,34 +64,14 @@ echo location of your Java installation.
goto fail
-:init
-@rem Get command-line arguments, handling Windowz variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
-
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
diff --git a/simpleperf/demo/SimpleperfExampleJava/settings.gradle b/simpleperf/demo/SimpleperfExampleJava/settings.gradle
new file mode 100644
index 00000000..75cefd9e
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleJava/settings.gradle
@@ -0,0 +1,16 @@
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ google()
+ mavenCentral()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+rootProject.name = "SimpleperfExampleJava"
+include ':app'
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/.idea/compiler.xml b/simpleperf/demo/SimpleperfExampleKotlin/.idea/compiler.xml
new file mode 100644
index 00000000..fb7f4a8a
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/.idea/compiler.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="CompilerConfiguration">
+ <bytecodeTargetLevel target="11" />
+ </component>
+</project> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/.idea/gradle.xml b/simpleperf/demo/SimpleperfExampleKotlin/.idea/gradle.xml
index 7ac24c77..a9f4e522 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/.idea/gradle.xml
+++ b/simpleperf/demo/SimpleperfExampleKotlin/.idea/gradle.xml
@@ -3,6 +3,7 @@
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
+ <option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
@@ -11,7 +12,6 @@
<option value="$PROJECT_DIR$/app" />
</set>
</option>
- <option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/.idea/misc.xml b/simpleperf/demo/SimpleperfExampleKotlin/.idea/misc.xml
new file mode 100644
index 00000000..bdd92780
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/.idea/misc.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ExternalStorageConfigurationManager" enabled="true" />
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
+ <output url="file://$PROJECT_DIR$/build/classes" />
+ </component>
+ <component name="ProjectType">
+ <option name="id" value="Android" />
+ </component>
+</project> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/.idea/vcs.xml b/simpleperf/demo/SimpleperfExampleKotlin/.idea/vcs.xml
new file mode 100644
index 00000000..c2365ab1
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="$PROJECT_DIR$/../../.." vcs="Git" />
+ </component>
+</project> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/build.gradle b/simpleperf/demo/SimpleperfExampleKotlin/app/build.gradle
new file mode 100644
index 00000000..0b9f2d53
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/build.gradle
@@ -0,0 +1,44 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ namespace 'simpleperf.example.kotlin'
+ compileSdk 32
+
+ defaultConfig {
+ applicationId "simpleperf.example.kotlin"
+ minSdk 24
+ targetSdk 32
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.4.1'
+ implementation 'com.google.android.material:material:1.5.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+} \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/build/outputs/apk/debug/app-debug.apk b/simpleperf/demo/SimpleperfExampleKotlin/app/build/outputs/apk/debug/app-debug.apk
new file mode 100644
index 00000000..ffb872ed
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/build/outputs/apk/debug/app-debug.apk
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/proguard-rules.pro b/simpleperf/demo/SimpleperfExampleKotlin/app/proguard-rules.pro
index bd138859..481bb434 100644
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/proguard-rules.pro
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/proguard-rules.pro
@@ -1,14 +1,10 @@
# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in /usr/local/google/home/yabinc/Android/Sdk/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the proguardFiles
-# directive in build.gradle.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
-# Add any project specific keep options here:
-
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
@@ -22,4 +18,4 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
-#-renamesourcefileattribute SourceFile
+#-renamesourcefileattribute SourceFile \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/release/app-release.apk b/simpleperf/demo/SimpleperfExampleKotlin/app/release/app-release.apk
new file mode 100644
index 00000000..517a4420
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/release/app-release.apk
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/release/output-metadata.json b/simpleperf/demo/SimpleperfExampleKotlin/app/release/output-metadata.json
new file mode 100644
index 00000000..16a114c2
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/release/output-metadata.json
@@ -0,0 +1,20 @@
+{
+ "version": 3,
+ "artifactType": {
+ "type": "APK",
+ "kind": "Directory"
+ },
+ "applicationId": "simpleperf.example.kotlin",
+ "variantName": "release",
+ "elements": [
+ {
+ "type": "SINGLE",
+ "filters": [],
+ "attributes": [],
+ "versionCode": 1,
+ "versionName": "1.0",
+ "outputFile": "app-release.apk"
+ }
+ ],
+ "elementType": "File"
+} \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/androidTest/java/com/example/simpleperf/simpleperfexampleofkotlin/ExampleInstrumentedTest.kt b/simpleperf/demo/SimpleperfExampleKotlin/app/src/androidTest/java/simpleperf/example/kotlin/ExampleInstrumentedTest.kt
index 8946158e..01e4c342 100644
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/androidTest/java/com/example/simpleperf/simpleperfexampleofkotlin/ExampleInstrumentedTest.kt
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/androidTest/java/simpleperf/example/kotlin/ExampleInstrumentedTest.kt
@@ -1,7 +1,7 @@
-package com.example.simpleperf.simpleperfexampleofkotlin
+package simpleperf.example.kotlin
-import android.support.test.InstrumentationRegistry
-import android.support.test.runner.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
@@ -18,7 +18,7 @@ class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
- val appContext = InstrumentationRegistry.getTargetContext()
- assertEquals("com.example.simpleperf.simpleperfexampleofkotlin", appContext.packageName)
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("simpleperf.example.kotlin", appContext.packageName)
}
-}
+} \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/AndroidManifest.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..4f6de7ae
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools">
+
+ <application
+ android:allowBackup="true"
+ android:dataExtractionRules="@xml/data_extraction_rules"
+ android:fullBackupContent="@xml/backup_rules"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/Theme.SimpleperfExampleKotlin"
+ tools:targetApi="31">
+ <profileable android:shell="true" />
+ <activity
+ android:name=".MainActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".SleepActivity"
+ android:exported="true">
+ </activity>
+ </application>
+
+</manifest> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/java/com/example/simpleperf/simpleperfexampleofkotlin/MainActivity.kt b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/java/simpleperf/example/kotlin/MainActivity.kt
index 55d47aa3..785ba3c1 100644
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/java/com/example/simpleperf/simpleperfexampleofkotlin/MainActivity.kt
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/java/simpleperf/example/kotlin/MainActivity.kt
@@ -1,10 +1,9 @@
-package com.example.simpleperf.simpleperfexampleofkotlin
+package simpleperf.example.kotlin
-import android.support.v7.app.AppCompatActivity
+import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
@@ -26,4 +25,4 @@ class MainActivity : AppCompatActivity() {
}
}.start()
}
-}
+} \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/java/com/example/simpleperf/simpleperfexampleofkotlin/SleepActivity.kt b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/java/simpleperf/example/kotlin/SleepActivity.kt
index 2ed5afe5..cc744e68 100644
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/java/com/example/simpleperf/simpleperfexampleofkotlin/SleepActivity.kt
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/java/simpleperf/example/kotlin/SleepActivity.kt
@@ -1,14 +1,13 @@
-package com.example.simpleperf.simpleperfexampleofkotlin
+package simpleperf.example.kotlin
-import android.support.v7.app.AppCompatActivity
+import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class SleepActivity : AppCompatActivity() {
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sleep)
- createRunSleepThread();
+ createRunSleepThread()
}
fun createRunSleepThread() {
@@ -47,4 +46,4 @@ class SleepActivity : AppCompatActivity() {
}
}.start()
}
-}
+} \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 00000000..2b068d11
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:endX="85.84757"
+ android:endY="92.4963"
+ android:startX="42.9492"
+ android:startY="49.59793"
+ android:type="linear">
+ <item
+ android:color="#44000000"
+ android:offset="0.0" />
+ <item
+ android:color="#00000000"
+ android:offset="1.0" />
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:fillColor="#FFFFFF"
+ android:fillType="nonZero"
+ android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+ android:strokeWidth="1"
+ android:strokeColor="#00000000" />
+</vector> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/drawable/ic_launcher_background.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 00000000..07d5da9c
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:fillColor="#3DDC84"
+ android:pathData="M0,0h108v108h-108z" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M9,0L9,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,0L19,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,0L29,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,0L39,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,0L49,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,0L59,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,0L69,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,0L79,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M89,0L89,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M99,0L99,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,9L108,9"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,19L108,19"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,29L108,29"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,39L108,39"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,49L108,49"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,59L108,59"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,69L108,69"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,79L108,79"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,89L108,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,99L108,99"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,29L89,29"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,39L89,39"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,49L89,49"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,59L89,59"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,69L89,69"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,79L89,79"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,19L29,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,19L39,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,19L49,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,19L59,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,19L69,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,19L79,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+</vector>
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_main.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/layout/activity_main.xml
index 4a09b1ae..c937b699 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_main.xml
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/layout/activity_main.xml
@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
-<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context="com.example.simpleperf.simpleperfexamplepurejava.MainActivity">
+ tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="MainActivity"
+ android:text="Running a busy thread!"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
-</android.support.constraint.ConstraintLayout>
+</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/layout/activity_sleep.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/layout/activity_sleep.xml
new file mode 100644
index 00000000..fd8ce422
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/layout/activity_sleep.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".SleepActivity">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Running a half busy half sleep thread!"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 00000000..c209e78e
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..b2dfe3d1
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 00000000..4f0f1d64
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..62b611da
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 00000000..948a3070
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..1b9a6956
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 00000000..28d4b77f
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..9287f508
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 00000000..aa7d6427
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..9126ae37
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values-night/themes.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values-night/themes.xml
new file mode 100644
index 00000000..812d0835
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!-- Base application theme. -->
+ <style name="Theme.SimpleperfExampleKotlin" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+ <!-- Primary brand color. -->
+ <item name="colorPrimary">@color/purple_200</item>
+ <item name="colorPrimaryVariant">@color/purple_700</item>
+ <item name="colorOnPrimary">@color/black</item>
+ <!-- Secondary brand color. -->
+ <item name="colorSecondary">@color/teal_200</item>
+ <item name="colorSecondaryVariant">@color/teal_200</item>
+ <item name="colorOnSecondary">@color/black</item>
+ <!-- Status bar color. -->
+ <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
+ <!-- Customize your theme here. -->
+ </style>
+</resources> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values/colors.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values/colors.xml
new file mode 100644
index 00000000..f8c6127d
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="purple_200">#FFBB86FC</color>
+ <color name="purple_500">#FF6200EE</color>
+ <color name="purple_700">#FF3700B3</color>
+ <color name="teal_200">#FF03DAC5</color>
+ <color name="teal_700">#FF018786</color>
+ <color name="black">#FF000000</color>
+ <color name="white">#FFFFFFFF</color>
+</resources> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values/strings.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000..af60980a
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+ <string name="app_name">SimpleperfExampleKotlin</string>
+</resources> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values/themes.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values/themes.xml
new file mode 100644
index 00000000..7568d49e
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!-- Base application theme. -->
+ <style name="Theme.SimpleperfExampleKotlin" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+ <!-- Primary brand color. -->
+ <item name="colorPrimary">@color/purple_500</item>
+ <item name="colorPrimaryVariant">@color/purple_700</item>
+ <item name="colorOnPrimary">@color/white</item>
+ <!-- Secondary brand color. -->
+ <item name="colorSecondary">@color/teal_200</item>
+ <item name="colorSecondaryVariant">@color/teal_700</item>
+ <item name="colorOnSecondary">@color/black</item>
+ <!-- Status bar color. -->
+ <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
+ <!-- Customize your theme here. -->
+ </style>
+</resources> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/xml/backup_rules.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 00000000..fa0f996d
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Sample backup rules file; uncomment and customize as necessary.
+ See https://developer.android.com/guide/topics/data/autobackup
+ for details.
+ Note: This file is ignored for devices older that API 31
+ See https://developer.android.com/about/versions/12/backup-restore
+-->
+<full-backup-content>
+ <!--
+ <include domain="sharedpref" path="."/>
+ <exclude domain="sharedpref" path="device.xml"/>
+-->
+</full-backup-content> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/xml/data_extraction_rules.xml b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 00000000..9ee9997b
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Sample data extraction rules file; uncomment and customize as necessary.
+ See https://developer.android.com/about/versions/12/backup-restore#xml-changes
+ for details.
+-->
+<data-extraction-rules>
+ <cloud-backup>
+ <!-- TODO: Use <include> and <exclude> to control what is backed up.
+ <include .../>
+ <exclude .../>
+ -->
+ </cloud-backup>
+ <!--
+ <device-transfer>
+ <include .../>
+ <exclude .../>
+ </device-transfer>
+ -->
+</data-extraction-rules> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/test/java/com/example/simpleperf/simpleperfexampleofkotlin/ExampleUnitTest.kt b/simpleperf/demo/SimpleperfExampleKotlin/app/src/test/java/simpleperf/example/kotlin/ExampleUnitTest.kt
index 39fbbf85..09e76f42 100644
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/test/java/com/example/simpleperf/simpleperfexampleofkotlin/ExampleUnitTest.kt
+++ b/simpleperf/demo/SimpleperfExampleKotlin/app/src/test/java/simpleperf/example/kotlin/ExampleUnitTest.kt
@@ -1,4 +1,4 @@
-package com.example.simpleperf.simpleperfexampleofkotlin
+package simpleperf.example.kotlin
import org.junit.Test
@@ -14,4 +14,4 @@ class ExampleUnitTest {
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
-}
+} \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/build.gradle b/simpleperf/demo/SimpleperfExampleKotlin/build.gradle
new file mode 100644
index 00000000..25369742
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/build.gradle
@@ -0,0 +1,6 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ id 'com.android.application' version '7.3.1' apply false
+ id 'com.android.library' version '7.3.1' apply false
+ id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
+} \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/gradle.properties b/simpleperf/demo/SimpleperfExampleKotlin/gradle.properties
index aac7c9b4..3c5031eb 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/gradle.properties
+++ b/simpleperf/demo/SimpleperfExampleKotlin/gradle.properties
@@ -1,17 +1,23 @@
# Project-wide Gradle settings.
-
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
-
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
-
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx1536m
-
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/gradle/wrapper/gradle-wrapper.jar b/simpleperf/demo/SimpleperfExampleKotlin/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..e708b1c0
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/gradle/wrapper/gradle-wrapper.properties b/simpleperf/demo/SimpleperfExampleKotlin/gradle/wrapper/gradle-wrapper.properties
index 275de5d6..f656d2fc 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/gradle/wrapper/gradle-wrapper.properties
+++ b/simpleperf/demo/SimpleperfExampleKotlin/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Tue Apr 10 14:48:08 PDT 2018
+#Wed Nov 09 11:27:42 PST 2022
distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
+zipStoreBase=GRADLE_USER_HOME
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/gradlew b/simpleperf/demo/SimpleperfExampleKotlin/gradlew
index 9d82f789..4f906e0c 100755
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/gradlew
+++ b/simpleperf/demo/SimpleperfExampleKotlin/gradlew
@@ -1,4 +1,20 @@
-#!/usr/bin/env bash
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# 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
+#
+# https://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.
+#
##############################################################################
##
@@ -6,20 +22,38 @@
##
##############################################################################
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
-warn ( ) {
+warn () {
echo "$*"
}
-die ( ) {
+die () {
echo
echo "$*"
echo
@@ -30,6 +64,7 @@ die ( ) {
cygwin=false
msys=false
darwin=false
+nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
@@ -40,28 +75,14 @@ case "`uname`" in
MINGW* )
msys=true
;;
+ NONSTOP* )
+ nonstop=true
+ ;;
esac
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -85,7 +106,7 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -105,10 +126,11 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
@@ -134,27 +156,30 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
- i=$((i+1))
+ i=`expr $i + 1`
done
case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
- JVM_OPTS=("$@")
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
+exec "$JAVACMD" "$@"
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/gradlew.bat b/simpleperf/demo/SimpleperfExampleKotlin/gradlew.bat
index aec99730..ac1b06f9 100644
--- a/simpleperf/demo/SimpleperfExamplePureJava/gradlew.bat
+++ b/simpleperf/demo/SimpleperfExampleKotlin/gradlew.bat
@@ -1,3 +1,19 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -8,20 +24,23 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
+if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -35,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-if exist "%JAVA_EXE%" goto init
+if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -45,34 +64,14 @@ echo location of your Java installation.
goto fail
-:init
-@rem Get command-line arguments, handling Windowz variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
-
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
diff --git a/simpleperf/demo/SimpleperfExampleKotlin/settings.gradle b/simpleperf/demo/SimpleperfExampleKotlin/settings.gradle
new file mode 100644
index 00000000..82a9613c
--- /dev/null
+++ b/simpleperf/demo/SimpleperfExampleKotlin/settings.gradle
@@ -0,0 +1,16 @@
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ google()
+ mavenCentral()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+rootProject.name = "SimpleperfExampleKotlin"
+include ':app'
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/.gitignore b/simpleperf/demo/SimpleperfExampleOfKotlin/.gitignore
deleted file mode 100644
index 39fb081a..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/.gitignore
+++ /dev/null
@@ -1,9 +0,0 @@
-*.iml
-.gradle
-/local.properties
-/.idea/workspace.xml
-/.idea/libraries
-.DS_Store
-/build
-/captures
-.externalNativeBuild
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/caches/build_file_checksums.ser b/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/caches/build_file_checksums.ser
deleted file mode 100644
index 6468d99b..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/caches/build_file_checksums.ser
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/codeStyles/Project.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/codeStyles/Project.xml
deleted file mode 100644
index 30aa626c..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<component name="ProjectCodeStyleConfiguration">
- <code_scheme name="Project" version="173">
- <Objective-C-extensions>
- <file>
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
- </file>
- <class>
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
- </class>
- <extensions>
- <pair source="cpp" header="h" fileNamingConvention="NONE" />
- <pair source="c" header="h" fileNamingConvention="NONE" />
- </extensions>
- </Objective-C-extensions>
- </code_scheme>
-</component> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/kotlinc.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/kotlinc.xml
deleted file mode 100644
index 1d2cf4e1..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/kotlinc.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="KotlinCommonCompilerArguments">
- <option name="errors">
- <ArgumentParseErrors />
- </option>
- </component>
-</project> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/misc.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/misc.xml
deleted file mode 100644
index 99202cc2..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/misc.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="NullableNotNullManager">
- <option name="myDefaultNullable" value="android.support.annotation.Nullable" />
- <option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
- <option name="myNullables">
- <value>
- <list size="5">
- <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
- <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
- <item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
- <item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
- <item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
- </list>
- </value>
- </option>
- <option name="myNotNulls">
- <value>
- <list size="4">
- <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
- <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
- <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
- <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
- </list>
- </value>
- </option>
- </component>
- <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
- <output url="file://$PROJECT_DIR$/build/classes" />
- </component>
- <component name="ProjectType">
- <option name="id" value="Android" />
- </component>
-</project> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/modules.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/modules.xml
deleted file mode 100644
index 2bb601f7..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/modules.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="ProjectModuleManager">
- <modules>
- <module fileurl="file://$PROJECT_DIR$/SimpleperfExampleOfKotlin.iml" filepath="$PROJECT_DIR$/SimpleperfExampleOfKotlin.iml" />
- <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
- </modules>
- </component>
-</project> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/runConfigurations.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/runConfigurations.xml
deleted file mode 100644
index 7f68460d..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/.idea/runConfigurations.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="RunConfigurationProducerService">
- <option name="ignoredProducers">
- <set>
- <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
- <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
- <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
- </set>
- </option>
- </component>
-</project> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/.gitignore b/simpleperf/demo/SimpleperfExampleOfKotlin/app/.gitignore
deleted file mode 100644
index 796b96d1..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/build.gradle b/simpleperf/demo/SimpleperfExampleOfKotlin/app/build.gradle
deleted file mode 100644
index 132e64bd..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/build.gradle
+++ /dev/null
@@ -1,37 +0,0 @@
-apply plugin: 'com.android.application'
-
-apply plugin: 'kotlin-android'
-
-apply plugin: 'kotlin-android-extensions'
-
-apply from: 'profiling.gradle'
-
-android {
- compileSdkVersion 25
- buildToolsVersion '27.0.3'
- defaultConfig {
- applicationId "com.example.simpleperf.simpleperfexampleofkotlin"
- minSdkVersion 15
- targetSdkVersion 26
- versionCode 1
- versionName "1.0"
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- }
-}
-
-dependencies {
- implementation fileTree(dir: 'libs', include: ['*.jar'])
- androidTestImplementation('androidx.test.espresso:espresso-core:3.1.1', {
- exclude group: 'com.android.support', module: 'support-annotations'
- })
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
- implementation 'com.android.support:appcompat-v7:25.4.0'
- testImplementation 'junit:junit:4.12'
- implementation 'com.android.support.constraint:constraint-layout:1.0.2'
-}
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/build/outputs/apk/profiling/app-profiling.apk b/simpleperf/demo/SimpleperfExampleOfKotlin/app/build/outputs/apk/profiling/app-profiling.apk
deleted file mode 100644
index 8b69bd4c..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/build/outputs/apk/profiling/app-profiling.apk
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling.gradle b/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling.gradle
deleted file mode 100644
index aa23c8d2..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling.gradle
+++ /dev/null
@@ -1,58 +0,0 @@
-
-// Set when building only part of the abis in the apk.
-def abiFiltersForWrapScript = []
-
-android {
- buildTypes {
- profiling {
- initWith debug
- externalNativeBuild {
- cmake {
- // cmake Debug build type uses -O0, which makes the code slow.
- arguments "-DCMAKE_BUILD_TYPE=Release"
- }
- }
- packagingOptions {
-
- // Exclude wrap.sh for architectures not built.
- if (abiFiltersForWrapScript) {
- def exclude_abis = ["armeabi", "armeabi-v7a", "arm64-v8a",
- "x86", "x86_64", "mips", "mips64"]
- .findAll{ !(it in abiFiltersForWrapScript) }
- .collect{ "**/" + it + "/wrap.sh" }
- excludes += exclude_abis
- }
- }
-
- // Add lib/xxx/wrap.sh in the apk. This is to enable java profiling on Android O
- // devices.
- sourceSets {
- profiling {
- resources {
- srcDir {
- "profiling_apk_add_dir"
- }
- }
- }
- }
- }
- }
-}
-
-def writeWrapScriptToFullyCompileJavaApp(wrapFile) {
- wrapFile.withWriter { writer ->
- writer.write('#!/system/bin/sh\n')
- writer.write('\$@\n')
- }
-}
-
-task createProfilingApkAddDir {
- for (String abi : ["armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64", "mips", "mips64"]) {
- def dir = new File("app/profiling_apk_add_dir/lib/" + abi)
- dir.mkdirs()
- def wrapFile = new File(dir, "wrap.sh")
- writeWrapScriptToFullyCompileJavaApp(wrapFile)
- println "write file " + wrapFile.path
- }
-}
-
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/arm64-v8a/wrap.sh b/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/arm64-v8a/wrap.sh
deleted file mode 100644
index 047ea6fa..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/arm64-v8a/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/armeabi-v7a/wrap.sh b/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/armeabi-v7a/wrap.sh
deleted file mode 100644
index 047ea6fa..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/armeabi-v7a/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/armeabi/wrap.sh b/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/armeabi/wrap.sh
deleted file mode 100644
index 047ea6fa..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/armeabi/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/mips/wrap.sh b/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/mips/wrap.sh
deleted file mode 100644
index 047ea6fa..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/mips/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/mips64/wrap.sh b/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/mips64/wrap.sh
deleted file mode 100644
index 047ea6fa..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/mips64/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/x86/wrap.sh b/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/x86/wrap.sh
deleted file mode 100644
index 047ea6fa..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/x86/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/x86_64/wrap.sh b/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/x86_64/wrap.sh
deleted file mode 100644
index 047ea6fa..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/profiling_apk_add_dir/lib/x86_64/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/AndroidManifest.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/AndroidManifest.xml
deleted file mode 100644
index 8cb40742..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.simpleperf.simpleperfexampleofkotlin">
-
- <application android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/AppTheme">
- <activity android:name=".MainActivity"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
-
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- <activity android:name=".SleepActivity"
- android:exported="true">
-
- </activity>
- </application>
-
-</manifest>
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/layout/activity_sleep.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/layout/activity_sleep.xml
deleted file mode 100644
index 7043eb11..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/layout/activity_sleep.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context="com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity">
-
-</android.support.constraint.ConstraintLayout>
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-hdpi/ic_launcher.png b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index 55073031..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-hdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
deleted file mode 100644
index 8fab6a3a..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-mdpi/ic_launcher.png b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index 6bc7fcd6..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-mdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
deleted file mode 100644
index 1eecc0e7..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index ec87dceb..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
deleted file mode 100644
index 05ca079c..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 6f67f21b..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
deleted file mode 100644
index 8bac0f27..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index 0327e13f..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
deleted file mode 100644
index bacd3e75..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/values/colors.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/values/colors.xml
deleted file mode 100644
index 3ab3e9cb..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <color name="colorPrimary">#3F51B5</color>
- <color name="colorPrimaryDark">#303F9F</color>
- <color name="colorAccent">#FF4081</color>
-</resources>
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/values/strings.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/values/strings.xml
deleted file mode 100644
index 7972edf9..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<resources>
- <string name="app_name">SimpleperfExampleOfKotlin</string>
-</resources>
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/values/styles.xml b/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/values/styles.xml
deleted file mode 100644
index 5885930d..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/app/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<resources>
-
- <!-- Base application theme. -->
- <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
- <!-- Customize your theme here. -->
- <item name="colorPrimary">@color/colorPrimary</item>
- <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
- <item name="colorAccent">@color/colorAccent</item>
- </style>
-
-</resources>
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/build.gradle b/simpleperf/demo/SimpleperfExampleOfKotlin/build.gradle
deleted file mode 100644
index ba06bb9f..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/build.gradle
+++ /dev/null
@@ -1,28 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-
-buildscript {
- ext.kotlin_version = '1.1.2-4'
- repositories {
- google()
- jcenter()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:3.2.0-alpha09'
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
-
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
- }
-}
-
-allprojects {
- repositories {
- google()
- jcenter()
- mavenCentral()
- }
-}
-
-task clean(type: Delete) {
- delete rootProject.buildDir
-}
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/gradle/wrapper/gradle-wrapper.jar b/simpleperf/demo/SimpleperfExampleOfKotlin/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 13372aef..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/gradle/wrapper/gradle-wrapper.jar
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExampleOfKotlin/settings.gradle b/simpleperf/demo/SimpleperfExampleOfKotlin/settings.gradle
deleted file mode 100644
index e7b4def4..00000000
--- a/simpleperf/demo/SimpleperfExampleOfKotlin/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-include ':app'
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/.gitignore b/simpleperf/demo/SimpleperfExamplePureJava/.gitignore
deleted file mode 100644
index 39fb081a..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/.gitignore
+++ /dev/null
@@ -1,9 +0,0 @@
-*.iml
-.gradle
-/local.properties
-/.idea/workspace.xml
-/.idea/libraries
-.DS_Store
-/build
-/captures
-.externalNativeBuild
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/.idea/caches/build_file_checksums.ser b/simpleperf/demo/SimpleperfExamplePureJava/.idea/caches/build_file_checksums.ser
deleted file mode 100644
index 5ad3dafd..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/.idea/caches/build_file_checksums.ser
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/.idea/codeStyles/Project.xml b/simpleperf/demo/SimpleperfExamplePureJava/.idea/codeStyles/Project.xml
deleted file mode 100644
index 30aa626c..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<component name="ProjectCodeStyleConfiguration">
- <code_scheme name="Project" version="173">
- <Objective-C-extensions>
- <file>
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
- </file>
- <class>
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
- <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
- </class>
- <extensions>
- <pair source="cpp" header="h" fileNamingConvention="NONE" />
- <pair source="c" header="h" fileNamingConvention="NONE" />
- </extensions>
- </Objective-C-extensions>
- </code_scheme>
-</component> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/.idea/compiler.xml b/simpleperf/demo/SimpleperfExamplePureJava/.idea/compiler.xml
deleted file mode 100644
index 96cc43ef..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/.idea/compiler.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="CompilerConfiguration">
- <resourceExtensions />
- <wildcardResourcePatterns>
- <entry name="!?*.java" />
- <entry name="!?*.form" />
- <entry name="!?*.class" />
- <entry name="!?*.groovy" />
- <entry name="!?*.scala" />
- <entry name="!?*.flex" />
- <entry name="!?*.kt" />
- <entry name="!?*.clj" />
- <entry name="!?*.aj" />
- </wildcardResourcePatterns>
- <annotationProcessing>
- <profile default="true" name="Default" enabled="false">
- <processorPath useClasspath="true" />
- </profile>
- </annotationProcessing>
- </component>
-</project> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/.idea/copyright/profiles_settings.xml b/simpleperf/demo/SimpleperfExamplePureJava/.idea/copyright/profiles_settings.xml
deleted file mode 100644
index e7bedf33..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/.idea/copyright/profiles_settings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<component name="CopyrightManager">
- <settings default="" />
-</component> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/.idea/misc.xml b/simpleperf/demo/SimpleperfExamplePureJava/.idea/misc.xml
deleted file mode 100644
index ba7052b8..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/.idea/misc.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="NullableNotNullManager">
- <option name="myDefaultNullable" value="android.support.annotation.Nullable" />
- <option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
- <option name="myNullables">
- <value>
- <list size="4">
- <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
- <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
- <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
- <item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
- </list>
- </value>
- </option>
- <option name="myNotNulls">
- <value>
- <list size="4">
- <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
- <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
- <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
- <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
- </list>
- </value>
- </option>
- </component>
- <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
- <output url="file://$PROJECT_DIR$/build/classes" />
- </component>
- <component name="ProjectType">
- <option name="id" value="Android" />
- </component>
-</project> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/.idea/modules.xml b/simpleperf/demo/SimpleperfExamplePureJava/.idea/modules.xml
deleted file mode 100644
index 5b6be612..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/.idea/modules.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="ProjectModuleManager">
- <modules>
- <module fileurl="file://$PROJECT_DIR$/SimpleperfExamplePureJava.iml" filepath="$PROJECT_DIR$/SimpleperfExamplePureJava.iml" />
- <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
- </modules>
- </component>
-</project> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/.idea/runConfigurations.xml b/simpleperf/demo/SimpleperfExamplePureJava/.idea/runConfigurations.xml
deleted file mode 100644
index 7f68460d..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/.idea/runConfigurations.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="RunConfigurationProducerService">
- <option name="ignoredProducers">
- <set>
- <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
- <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
- <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
- </set>
- </option>
- </component>
-</project> \ No newline at end of file
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/.gitignore b/simpleperf/demo/SimpleperfExamplePureJava/app/.gitignore
deleted file mode 100644
index 796b96d1..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/build.gradle b/simpleperf/demo/SimpleperfExamplePureJava/app/build.gradle
deleted file mode 100644
index 0dcc1991..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/build.gradle
+++ /dev/null
@@ -1,32 +0,0 @@
-apply plugin: 'com.android.application'
-
-apply from: 'profiling.gradle'
-
-android {
- compileSdkVersion 25
- buildToolsVersion '27.0.3'
- defaultConfig {
- applicationId "com.example.simpleperf.simpleperfexamplepurejava"
- minSdkVersion 15
- targetSdkVersion 25
- versionCode 1
- versionName "1.0"
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- }
-}
-
-dependencies {
- implementation fileTree(dir: 'libs', include: ['*.jar'])
- androidTestImplementation('androidx.test.espresso:espresso-core:3.1.1', {
- exclude group: 'com.android.support', module: 'support-annotations'
- })
- implementation 'com.android.support:appcompat-v7:25.3.1'
- implementation 'com.android.support.constraint:constraint-layout:1.0.2'
- testImplementation 'junit:junit:4.12'
-}
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/profiling/app-profiling.apk b/simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/profiling/app-profiling.apk
deleted file mode 100644
index f7ead366..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/profiling/app-profiling.apk
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling.gradle b/simpleperf/demo/SimpleperfExamplePureJava/app/profiling.gradle
deleted file mode 100644
index aa23c8d2..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling.gradle
+++ /dev/null
@@ -1,58 +0,0 @@
-
-// Set when building only part of the abis in the apk.
-def abiFiltersForWrapScript = []
-
-android {
- buildTypes {
- profiling {
- initWith debug
- externalNativeBuild {
- cmake {
- // cmake Debug build type uses -O0, which makes the code slow.
- arguments "-DCMAKE_BUILD_TYPE=Release"
- }
- }
- packagingOptions {
-
- // Exclude wrap.sh for architectures not built.
- if (abiFiltersForWrapScript) {
- def exclude_abis = ["armeabi", "armeabi-v7a", "arm64-v8a",
- "x86", "x86_64", "mips", "mips64"]
- .findAll{ !(it in abiFiltersForWrapScript) }
- .collect{ "**/" + it + "/wrap.sh" }
- excludes += exclude_abis
- }
- }
-
- // Add lib/xxx/wrap.sh in the apk. This is to enable java profiling on Android O
- // devices.
- sourceSets {
- profiling {
- resources {
- srcDir {
- "profiling_apk_add_dir"
- }
- }
- }
- }
- }
- }
-}
-
-def writeWrapScriptToFullyCompileJavaApp(wrapFile) {
- wrapFile.withWriter { writer ->
- writer.write('#!/system/bin/sh\n')
- writer.write('\$@\n')
- }
-}
-
-task createProfilingApkAddDir {
- for (String abi : ["armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64", "mips", "mips64"]) {
- def dir = new File("app/profiling_apk_add_dir/lib/" + abi)
- dir.mkdirs()
- def wrapFile = new File(dir, "wrap.sh")
- writeWrapScriptToFullyCompileJavaApp(wrapFile)
- println "write file " + wrapFile.path
- }
-}
-
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/arm64-v8a/wrap.sh b/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/arm64-v8a/wrap.sh
deleted file mode 100644
index 047ea6fa..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/arm64-v8a/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/armeabi-v7a/wrap.sh b/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/armeabi-v7a/wrap.sh
deleted file mode 100644
index 047ea6fa..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/armeabi-v7a/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/armeabi/wrap.sh b/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/armeabi/wrap.sh
deleted file mode 100644
index 047ea6fa..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/armeabi/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/mips/wrap.sh b/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/mips/wrap.sh
deleted file mode 100644
index 047ea6fa..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/mips/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/mips64/wrap.sh b/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/mips64/wrap.sh
deleted file mode 100644
index 047ea6fa..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/mips64/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/x86/wrap.sh b/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/x86/wrap.sh
deleted file mode 100644
index 047ea6fa..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/x86/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/x86_64/wrap.sh b/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/x86_64/wrap.sh
deleted file mode 100644
index 047ea6fa..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/profiling_apk_add_dir/lib/x86_64/wrap.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/system/bin/sh
-$@
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/androidTest/java/com/example/simpleperf/simpleperfexamplepurejava/ExampleInstrumentedTest.java b/simpleperf/demo/SimpleperfExamplePureJava/app/src/androidTest/java/com/example/simpleperf/simpleperfexamplepurejava/ExampleInstrumentedTest.java
deleted file mode 100644
index cb918063..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/androidTest/java/com/example/simpleperf/simpleperfexamplepurejava/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.example.simpleperf.simpleperfexamplepurejava;
-
-import static org.junit.Assert.*;
-
-import android.content.Context;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Instrumentation test, which will execute on an Android device.
- *
- * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() throws Exception {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
-
- assertEquals("com.example.simpleperf.simpleperfexamplepurejava", appContext.getPackageName());
- }
-}
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml
deleted file mode 100644
index ee42fead..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.simpleperf.simpleperfexamplepurejava">
-
- <application android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/AppTheme">
- <activity android:name=".MainActivity"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
-
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- <activity android:name=".SleepActivity"
- android:exported="true"/>
- <activity android:name=".MultiProcessActivity"
- android:exported="true"/>
-
- <service android:name=".MultiProcessService"
- android:process=":multiprocess_service"/>
- </application>
-
-</manifest>
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-hdpi/ic_launcher.png b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index cde69bcc..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-hdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
deleted file mode 100644
index 9a078e3e..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-mdpi/ic_launcher.png b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index c133a0cb..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-mdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
deleted file mode 100644
index efc028a6..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index bfa42f0e..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
deleted file mode 100644
index 3af2608a..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 324e72cd..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
deleted file mode 100644
index 9bec2e62..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index aee44e13..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
deleted file mode 100644
index 34947cd6..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/values/colors.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/values/colors.xml
deleted file mode 100644
index 3ab3e9cb..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <color name="colorPrimary">#3F51B5</color>
- <color name="colorPrimaryDark">#303F9F</color>
- <color name="colorAccent">#FF4081</color>
-</resources>
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/values/strings.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/values/strings.xml
deleted file mode 100644
index 9cb14df5..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<resources>
- <string name="app_name">SimpleperfExamplePureJava</string>
-</resources>
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/values/styles.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/values/styles.xml
deleted file mode 100644
index 5885930d..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<resources>
-
- <!-- Base application theme. -->
- <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
- <!-- Customize your theme here. -->
- <item name="colorPrimary">@color/colorPrimary</item>
- <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
- <item name="colorAccent">@color/colorAccent</item>
- </style>
-
-</resources>
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/build.gradle b/simpleperf/demo/SimpleperfExamplePureJava/build.gradle
deleted file mode 100644
index 5af83b42..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/build.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-
-buildscript {
- repositories {
- jcenter()
- google()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:3.2.0-alpha09'
-
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
- }
-}
-
-allprojects {
- repositories {
- google()
- jcenter()
- }
-}
-
-task clean(type: Delete) {
- delete rootProject.buildDir
-}
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/gradle/wrapper/gradle-wrapper.jar b/simpleperf/demo/SimpleperfExamplePureJava/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 13372aef..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/gradle/wrapper/gradle-wrapper.jar
+++ /dev/null
Binary files differ
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/settings.gradle b/simpleperf/demo/SimpleperfExamplePureJava/settings.gradle
deleted file mode 100644
index e7b4def4..00000000
--- a/simpleperf/demo/SimpleperfExamplePureJava/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-include ':app'
diff --git a/simpleperf/doc/README.md b/simpleperf/doc/README.md
index 51ffc839..d1ecbcce 100644
--- a/simpleperf/doc/README.md
+++ b/simpleperf/doc/README.md
@@ -1,5 +1,9 @@
# Simpleperf
+Android Studio includes a graphical front end to Simpleperf, documented in
+[Inspect CPU activity with CPU Profiler](https://developer.android.com/studio/profile/cpu-profiler).
+Most users will prefer to use that instead of using Simpleperf directly.
+
Simpleperf is a native CPU 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,
@@ -57,6 +61,7 @@ Python scripts are split into three parts according to their functions:
3. Scripts used for parsing profiling data, like simpleperf_report_lib.py.
+The python scripts are tested on Python >= 3.9. Older versions may not be supported.
Detailed documentation for the Python scripts is [here](#scripts-reference).
@@ -101,83 +106,100 @@ See [view_the_profile.md](./view_the_profile.md).
## Answers to common issues
-### Why we suggest profiling on Android >= N devices?
-
-1. Running on a device reflects a real running situation, so we suggest
- profiling on real devices instead of emulators.
-2. To profile Java code, we need ART running in oat mode, which is only
- available >= L for rooted devices, and >= N for non-rooted devices.
-3. Old Android versions are likely to be shipped with old kernels (< 3.18),
- which may not support profiling features like recording dwarf based call graphs.
-4. Old Android versions are likely to be shipped with Arm32 chips. In Arm32
- mode, recording stack frame based call graphs doesn't work well.
-
-### Suggestions about recording call graphs
-
-Below is our experiences of dwarf based call graphs and stack frame based call graphs.
-
-dwarf based call graphs:
-1. Need support of debug information in binaries.
-2. Behave normally well on both ARM and ARM64, for both fully compiled Java code and C++ code.
-3. Can only unwind 64K stack for each sample. So usually can't show complete flamegraph. But
- probably is enough for users to identify hot places.
-4. Take more CPU time than stack frame based call graphs. So the sample frequency is suggested
- to be 1000 Hz. Thus at most 1000 samples per second.
-
-stack frame based call graphs:
-1. Need support of stack frame registers.
-2. Don't work well on ARM. Because ARM is short of registers, and ARM and THUMB code have different
- stack frame registers. So the kernel can't unwind user stack containing both ARM/THUMB code.
-3. Also don't work well on fully compiled Java code on ARM64. Because the ART compiler doesn't
- reserve stack frame registers.
-4. Work well when profiling native programs on ARM64. One example is profiling surfacelinger. And
+### Support on different Android versions
+
+On Android < N, the kernel may be too old (< 3.18) to support features like recording DWARF
+based call graphs.
+On Android M - O, we can only profile C++ code and fully compiled Java code.
+On Android >= P, the ART interpreter supports DWARF based unwinding. So we can profile Java code.
+On Android >= Q, we can used simpleperf shipped on device to profile released Android apps, with
+ `<profileable android:shell="true" />`.
+
+
+### Comparing DWARF based and stack frame based call graphs
+
+Simpleperf supports two ways recording call stacks with samples. One is DWARF based call graph,
+the other is stack frame based call graph. Below is their comparison:
+
+Recording DWARF based call graph:
+1. Needs support of debug information in binaries.
+2. Behaves normally well on both ARM and ARM64, for both Java code and C++ code.
+3. Can only unwind 64K stack for each sample. So it isn't always possible to unwind to the bottom.
+ However, this is alleviated in simpleperf, as explained in the next section.
+4. Takes more CPU time than stack frame based call graphs. So it has higher overhead, and can't
+ sample at very high frequency (usually <= 4000 Hz).
+
+Recording stack frame based call graph:
+1. Needs support of stack frame registers.
+2. Doesn't work well on ARM. Because ARM is short of registers, and ARM and THUMB code have
+ different stack frame registers. So the kernel can't unwind user stack containing both ARM and
+ THUMB code.
+3. Also doesn't work well on Java code. Because the ART compiler doesn't reserve stack frame
+ registers. And it can't get frames for interpreted Java code.
+4. Works well when profiling native programs on ARM64. One example is profiling surfacelinger. And
usually shows complete flamegraph when it works well.
-5. Take less CPU time than dwarf based call graphs. So the sample frequency can be 4000 Hz or
+5. Takes much less CPU time than DWARF based call graphs. So the sample frequency can be 10000 Hz or
higher.
-So if you need to profile code on ARM or profile fully compiled Java code, dwarf based call graphs
-may be better. If you need to profile C++ code on ARM64, stack frame based call graphs may be
-better. After all, you can always try dwarf based call graph first, because it always produces
-reasonable results when given unstripped binaries properly. If it doesn't work well enough, then
-try stack frame based call graphs instead.
+So if you need to profile code on ARM or profile Java code, DWARF based call graph is better. If you
+need to profile C++ code on ARM64, stack frame based call graphs may be better. After all, you can
+fisrt try DWARF based call graph, which is also the default option when `-g` is used. Because it
+always produces reasonable results. If it doesn't work well enough, then try stack frame based call
+graph instead.
-Simpleperf may need unstripped native binaries on the device to generate good dwarf based call
-graphs. It can be supported by downloading unstripped native libraries on device, as [here](#fix-broken-callchain-stopped-at-c-functions).
-### Why we can't always get complete DWARF-based call graphs?
+### Fix broken DWARF based call graph
-DWARF-based call graphs are generated by unwinding thread stacks. When a sample is generated, up to
-64KB stack data is dumped by the kernel. By unwinding the stack based on dwarf information, we get
-a callchain. But the thread stack can be much longer than 64KB. In that case, we can't unwind to
-the thread start point.
+A DWARF-based call graph is generated by unwinding thread stacks. When a sample is recorded, a
+kernel dumps up to 64 kilobytes of stack data. By unwinding the stack based on DWARF information,
+we can get a call stack.
-To alleviate the problem, simpleperf joins callchains after recording them. If two callchains of
-a thread have an entry containing the same ip and sp address, then simpleperf tries to join them to
-make the callchains longer. In that case, the longer we run, the more samples we get. This makes it
-more likely to get complete callchains, but it's still not guaranteed to get complete call graphs.
+Two reasons may cause a broken call stack:
+1. The kernel can only dump up to 64 kilobytes of stack data for each sample, but a thread can have
+ much larger stack. In this case, we can't unwind to the thread start point.
-### How to solve missing symbols in report?
+2. We need binaries containing DWARF call frame information to unwind stack frames. The binary
+ should have one of the following sections: .eh_frame, .debug_frame, .ARM.exidx or .gnu_debugdata.
-The simpleperf record command collects symbols on device in perf.data. But if the native libraries
-you use on device are stripped, this will result in a lot of unknown symbols in the report. A
-solution is to build binary_cache on host.
+To mitigate these problems,
+
+
+For the missing stack data problem:
+1. To alleviate it, simpleperf joins callchains (call stacks) after recording. If two callchains of
+ a thread have an entry containing the same ip and sp address, then simpleperf tries to join them
+ to make the callchains longer. So we can get more complete callchains by recording longer and
+ joining more samples. This doesn't guarantee to get complete call graphs. But it usually works
+ well.
+
+2. Simpleperf stores samples in a buffer before unwinding them. If the bufer is low in free space,
+ simpleperf may decide to cut stack data for a sample to 1K. Hopefully, this can be recovered by
+ callchain joiner. But when a high percentage of samples are cut, many callchains can be broken.
+ We can tell if many samples are cut in the record command output, like:
```sh
-# Collect binaries needed by perf.data in binary_cache/.
-$ ./binary_cache_builder.py -lib NATIVE_LIB_DIR,...
+$ simpleperf record ...
+simpleperf I cmd_record.cpp:809] Samples recorded: 105584 (cut 86291). Samples lost: 6501.
```
-The NATIVE_LIB_DIRs passed in -lib option are the directories containing unstripped native
-libraries on host. After running it, the native libraries containing symbol tables are collected
-in binary_cache/ for use when reporting.
+ There are two ways to avoid cutting samples. One is increasing the buffer size, like
+ `--user-buffer-size 1G`. But `--user-buffer-size` is only available on latest simpleperf. If that
+ option isn't available, we can use `--no-cut-samples` to disable cutting samples.
-```sh
-$ ./report.py --symfs binary_cache
+For the missing DWARF call frame info problem:
+1. Most C++ code generates binaries containing call frame info, in .eh_frame or .ARM.exidx sections.
+ These sections are not stripped, and are usually enough for stack unwinding.
+
+2. For C code and a small percentage of C++ code that the compiler is sure will not generate
+ exceptions, the call frame info is generated in .debug_frame section. .debug_frame section is
+ usually stripped with other debug sections. One way to fix it, is to download unstripped binaries
+ on device, as [here](#fix-broken-callchain-stopped-at-c-functions).
+
+3. The compiler doesn't generate unwind instructions for function prologue and epilogue. Because
+ they operates stack frames and will not generate exceptions. But profiling may hit these
+ instructions, and fails to unwind them. This usually doesn't matter in a frame graph. But in a
+ time based Stack Chart (like in Android Studio and Firefox profiler), this causes stack gaps once
+ in a while. We can remove stack gaps via `--remove-gaps`, which is already enabled by default.
-# report_html.py searches binary_cache/ automatically, so you don't need to
-# pass it any argument.
-$ ./report_html.py
-```
### Fix broken callchain stopped at C functions
@@ -202,6 +224,31 @@ To use app_profiler.py:
$ ./app_profiler.py -lib <unstripped_dir>
```
+
+### How to solve missing symbols in report?
+
+The simpleperf record command collects symbols on device in perf.data. But if the native libraries
+you use on device are stripped, this will result in a lot of unknown symbols in the report. A
+solution is to build binary_cache on host.
+
+```sh
+# Collect binaries needed by perf.data in binary_cache/.
+$ ./binary_cache_builder.py -lib NATIVE_LIB_DIR,...
+```
+
+The NATIVE_LIB_DIRs passed in -lib option are the directories containing unstripped native
+libraries on host. After running it, the native libraries containing symbol tables are collected
+in binary_cache/ for use when reporting.
+
+```sh
+$ ./report.py --symfs binary_cache
+
+# report_html.py searches binary_cache/ automatically, so you don't need to
+# pass it any argument.
+$ ./report_html.py
+```
+
+
### Show annotated source code and disassembly
To show hot places at source code and instruction level, we need to show source code and
@@ -238,3 +285,13 @@ $ mmma system/extras/simpleperf -j30
If built successfully, out/target/product/generic_arm64/system/bin/simpleperf is for ARM64, and
out/target/product/generic_arm64/system/bin/simpleperf32 is for ARM.
+
+The source code of simpleperf python scripts is in [system/extras/simpleperf/scripts](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/scripts/).
+Most scripts rely on simpleperf binaries to work. To update binaries for scripts (using linux
+x86_64 host and android arm64 target as an example):
+```sh
+$ cp out/host/linux-x86/lib64/libsimpleperf_report.so system/extras/simpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.so
+$ cp out/target/product/generic_arm64/system/bin/simpleperf_ndk64 system/extras/simpleperf/scripts/bin/android/arm64/simpleperf
+```
+
+Then you can try the latest simpleperf scripts and binaries in system/extras/simpleperf/scripts.
diff --git a/simpleperf/doc/android_application_profiling.md b/simpleperf/doc/android_application_profiling.md
index 65fae751..5ee46a51 100644
--- a/simpleperf/doc/android_application_profiling.md
+++ b/simpleperf/doc/android_application_profiling.md
@@ -70,7 +70,7 @@ android {
}
task createWrapShLibDir
- for (String abi : ["armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64"]) {
+ for (String abi : ["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")
diff --git a/simpleperf/doc/collect_etm_data_for_autofdo.md b/simpleperf/doc/collect_etm_data_for_autofdo.md
index 145c0adf..2c001016 100644
--- a/simpleperf/doc/collect_etm_data_for_autofdo.md
+++ b/simpleperf/doc/collect_etm_data_for_autofdo.md
@@ -81,9 +81,18 @@ branch with count info for binary2
We need to split perf_inject.data, and make sure one file only contains info for one binary.
-Then we can use [AutoFDO](https://github.com/google/autofdo) to create profile like below:
+Then we can use [AutoFDO](https://github.com/google/autofdo) to create profile. AutoFDO only works
+for binaries having an executable segment as its first loadable segment. But binaries built in
+Android may not follow this rule. Simpleperf inject command knows how to work around this problem.
+But there is a check in AutoFDO forcing binaries to start with an executable segment. We need to
+disable the check in AutoFDO, by commenting out L127-L136 in
+https://github.com/google/autofdo/commit/188db2834ce74762ed17108ca344916994640708#diff-2d132ecbb5e4f13e0da65419f6d1759dd27d6b696786dd7096c0c34d499b1710R127-R136.
+Then we can use `create_llvm_prof` in AutoFDO to create profiles used by clang.
```sh
+# perf_inject_binary1.data is split from perf_inject.data, and only contains branch info for binary1.
+host $ autofdo/create_llvm_prof -profile perf_inject_binary1.data -profiler text -binary path_of_binary1 -out a.prof -format binary
+
# perf_inject_kernel.data is split from perf_inject.data, and only contains branch info for [kernel.kallsyms].
host $ autofdo/create_llvm_prof -profile perf_inject_kernel.data -profiler text -binary vmlinux -out a.prof -format binary
```
@@ -91,6 +100,96 @@ host $ autofdo/create_llvm_prof -profile perf_inject_kernel.data -profiler text
Then we can use a.prof for PGO during compilation, via `-fprofile-sample-use=a.prof`.
[Here](https://clang.llvm.org/docs/UsersManual.html#using-sampling-profilers) are more details.
+### A complete example: etm_test_loop.cpp
+
+`etm_test_loop.cpp` is an example to show the complete process.
+The source code is in [etm_test_loop.cpp](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/runtest/etm_test_loop.cpp).
+The build script is in [Android.bp](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/runtest/Android.bp).
+It builds an executable called `etm_test_loop`, which runs on device.
+
+Step 1: Build `etm_test_loop` binary.
+
+```sh
+(host) <AOSP>$ . build/envsetup.sh
+(host) <AOSP>$ lunch aosp_arm64-userdebug
+(host) <AOSP>$ make etm_test_loop
+```
+
+Step 2: Run `etm_test_loop` on device, and collect ETM data for its running.
+
+```sh
+(host) <AOSP>$ adb push out/target/product/generic_arm64/system/bin/etm_test_loop /data/local/tmp
+(host) <AOSP>$ adb root
+(host) <AOSP>$ adb shell
+(device) / # cd /data/local/tmp
+(device) /data/local/tmp # chmod a+x etm_test_loop
+(device) /data/local/tmp # simpleperf record -e cs-etm:u ./etm_test_loop
+simpleperf I cmd_record.cpp:729] Recorded for 0.0370068 seconds. Start post processing.
+simpleperf I cmd_record.cpp:799] Aux data traced: 1689136
+(device) /data/local/tmp # simpleperf inject -i perf.data --output branch-list -o branch_list.data
+simpleperf W dso.cpp:557] failed to read min virtual address of [vdso]: File not found
+(device) /data/local/tmp # exit
+(host) <AOSP>$ adb pull /data/local/tmp/branch_list.data
+```
+
+Step 3: Convert ETM data to AutoFDO data.
+
+```sh
+# Build simpleperf tool on host.
+(host) <AOSP>$ make simpleperf_ndk
+(host) <AOSP>$ simpleperf_ndk64 inject -i branch_list.data -o perf_inject_etm_test_loop.data --symdir out/target/product/generic_arm64/symbols/system/bin
+simpleperf W cmd_inject.cpp:505] failed to build instr ranges for binary [vdso]: File not found
+(host) <AOSP>$ cat perf_inject_etm_test_loop.data
+13
+1000-1010:1
+1014-1050:1
+...
+112c->0:1
+// /data/local/tmp/etm_test_loop
+
+(host) <AOSP>$ create_llvm_prof -profile perf_inject_etm_test_loop.data -profiler text -binary out/target/product/generic_arm64/symbols/system/bin/etm_test_loop -out etm_test_loop.afdo -format binary
+(host) <AOSP>$ ls -lh etm_test_loop.afdo
+rw-r--r-- 1 user group 241 Aug 29 16:04 etm_test_loop.afdo
+```
+
+Step 4: Use AutoFDO data to build optimized binary.
+
+```sh
+(host) <AOSP>$ mkdir toolchain/pgo-profiles/sampling/
+(host) <AOSP>$ cp etm_test_loop.afdo toolchain/pgo-profiles/sampling/
+(host) <AOSP>$ vi toolchain/pgo-profiles/sampling/Android.bp
+# edit Android.bp to add a fdo_profile module
+# soong_namespace {}
+#
+# fdo_profile {
+# name: "etm_test_loop_afdo",
+# profile: ["etm_test_loop.afdo"],
+# }
+```
+
+`soong_namespace` is added to support fdo_profile modules with the same name
+
+In a product config mk file, update `PRODUCT_AFDO_PROFILES` with
+
+```make
+PRODUCT_AFDO_PROFILES += etm_test_loop://toolchain/pgo-profiles/sampling:etm_test_loop_afdo
+```
+
+```sh
+(host) <AOSP>$ vi system/extras/simpleperf/runtest/Android.bp
+# edit Android.bp to enable afdo for etm_test_loop.
+# cc_binary {
+# name: "etm_test_loop",
+# srcs: ["etm_test_loop.cpp"],
+# afdo: true,
+# }
+(host) <AOSP>$ make etm_test_loop
+```
+
+If comparing the disassembly of `out/target/product/generic_arm64/symbols/system/bin/etm_test_loop`
+before and after optimizing with AutoFDO data, we can see different preferences when branching.
+
+
## Collect ETM data with a daemon
Android also has a daemon collecting ETM data periodically. It only runs on userdebug and eng
@@ -138,6 +237,15 @@ for ETR to store ETM data. Without IOMMU, the memory needs to be contiguous. If
fulfill the request, simpleperf will report out of memory error. Fortunately, we can use
"arm,scatter-gather" flag to let ETR run in scatter gather mode, which uses non-contiguous memory.
+
+### A possible problem: trace_id mismatch
+
+Each CPU has an ETM device, which has a unique trace_id assigned from the kernel.
+The formula is: `trace_id = 0x10 + cpu * 2`, as in https://github.com/torvalds/linux/blob/master/include/linux/coresight-pmu.h#L37.
+If the formula is modified by local patches, then simpleperf inject command can't parse ETM data
+properly and is likely to give empty output.
+
+
## Enable ETM in the bootloader
Unless ARMv8.4 Self-hosted Trace extension is implemented, ETM is considered as an external debug
diff --git a/simpleperf/doc/executable_commands_reference.md b/simpleperf/doc/executable_commands_reference.md
index ccff5ced..0e29fbf7 100644
--- a/simpleperf/doc/executable_commands_reference.md
+++ b/simpleperf/doc/executable_commands_reference.md
@@ -98,14 +98,17 @@ print interval.
$ 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%)
+# count event_name # count / runtime
+ 16,513,564 cpu-cycles # 1.612904 GHz
+ 4,564,133 stalled-cycles-frontend # 341.490 M/sec
+ 6,520,383 stalled-cycles-backend # 591.666 M/sec
+ 4,900,403 instructions # 612.859 M/sec
+ 47,821 branch-misses # 6.085 M/sec
+ 25.274251(ms) task-clock # 0.002520 cpus used
+ 4 context-switches # 158.264 /sec
+ 466 page-faults # 18.438 K/sec
-Total test time: 10.023829 seconds.
+Total test time: 10.027923 seconds.
```
### Select events to stat
@@ -122,9 +125,8 @@ $ 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.
+event is only monitored for part of the total time. As a result, the number of events shown is
+smaller than the number of events that actually happened. The following is an example.
```sh
# Stat using event cache-references, cache-references:u,....
@@ -132,37 +134,66 @@ $ simpleperf stat -p 7394 -e cache-references,cache-references:u,cache-reference
-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%)
+# count event_name # count / runtime
+ 490,713 cache-references # 151.682 M/sec
+ 899,652 cache-references:u # 130.152 M/sec
+ 855,218 cache-references:k # 111.356 M/sec
+ 61,602 cache-misses # 7.710 M/sec
+ 33,282 cache-misses:u # 5.050 M/sec
+ 11,662 cache-misses:k # 4.478 M/sec
+ 0 instructions #
-Total test time: 1.026802 seconds.
+Total test time: 1.000867 seconds.
+simpleperf W cmd_stat.cpp:946] It seems the number of hardware events are more than the number of
+available CPU PMU hardware counters. That will trigger hardware counter
+multiplexing. As a result, events are not counted all the time processes
+running, and event counts are smaller than what really happens.
+Use --print-hw-counter to show available hardware counters.
```
-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.
+In the example above, we monitor 7 events. Each event is only monitored part of the total time.
+Because the number of cache-references is smaller than the number of cache-references:u
+(cache-references only in userspace) and cache-references:k (cache-references only in kernel).
+The number of instructions is zero. After printing the result, simpleperf checks if CPUs have
+enough hardware counters to count hardware events at the same time. If not, it prints a warning.
+
+To avoid hardware counter multiplexing, we can use `simpleperf stat --print-hw-counter` to show
+available counters on each CPU. Then don't monitor more hardware events than counters available.
+
+```sh
+$ simpleperf stat --print-hw-counter
+There are 2 CPU PMU hardware counters available on cpu 0.
+There are 2 CPU PMU hardware counters available on cpu 1.
+There are 2 CPU PMU hardware counters available on cpu 2.
+There are 2 CPU PMU hardware counters available on cpu 3.
+There are 2 CPU PMU hardware counters available on cpu 4.
+There are 2 CPU PMU hardware counters available on cpu 5.
+There are 2 CPU PMU hardware counters available on cpu 6.
+There are 2 CPU PMU hardware counters available on cpu 7.
+```
+
+When counter multiplexing happens, there is no guarantee of which events will be monitored at
+which time. If we want to ensure some events are always 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
+ --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%)
+# count event_name # count / runtime
+ 2,088,463 cache-references # 181.360 M/sec
+ 47,871 cache-misses # 2.292164% miss rate
+ 1,277,600 cache-references:u # 136.419 M/sec
+ 25,977 cache-misses:u # 2.033265% miss rate
+ 326,305 cache-references:k # 74.724 M/sec
+ 13,596 cache-misses:k # 4.166654% miss rate
-Total test time: 1.029843 seconds.
+Total test time: 1.029729 seconds.
+simpleperf W cmd_stat.cpp:946] It seems the number of hardware events are more than the number of
+...
```
### Select target to stat
@@ -175,15 +206,23 @@ process to run the new command and then monitor the child process.
# Stat process 11904 and 11905.
$ simpleperf stat -p 11904,11905 --duration 10
+# Stat processes with name containing "chrome".
+$ simpleperf stat -p chrome --duration 10
+# Stat processes with name containing part matching regex "chrome:(privileged|sandboxed)".
+$ simpleperf stat -p "chrome:(privileged|sandboxed)" --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 simpleperf.example.cpp
+# Stat the process of an Android application. On non-root devices, this only works for debuggable
+# or profileable from shell apps.
+$ simpleperf stat --app simpleperf.example.cpp --duration 10
+
+# Stat only selected thread 11904 in an app.
+$ simpleperf stat --app simpleperf.example.cpp -t 11904 --duration 10
# Stat system wide using -a.
$ simpleperf stat -a --duration 10
@@ -274,17 +313,18 @@ affected by hardware counter multiplexing. Check simpleperf log output for ways
```sh
# Print event counts for each cpu running threads in process 11904.
# A percentage shows runtime_on_a_cpu / runtime_on_all_cpus.
-$ simpleperf stat --per-core -p 11904 --duration 1
+$ simpleperf stat -e cpu-cycles --per-core -p 1057 --duration 3
Performance counter statistics:
-# cpu count event_name # percentage = event_run_time / enabled_time
- 7 56,552,838 cpu-cycles # (60%)
- 3 25,958,605 cpu-cycles # (20%)
- 0 22,822,698 cpu-cycles # (15%)
- 1 6,661,495 cpu-cycles # (5%)
- 4 1,519,093 cpu-cycles # (0%)
+# cpu count event_name # count / runtime
+ 0 1,667,660 cpu-cycles # 1.571565 GHz
+ 1 3,850,440 cpu-cycles # 1.736958 GHz
+ 2 2,463,792 cpu-cycles # 1.701367 GHz
+ 3 2,350,528 cpu-cycles # 1.700841 GHz
+ 5 7,919,520 cpu-cycles # 2.377081 GHz
+ 6 105,622,673 cpu-cycles # 2.381331 GHz
-Total test time: 1.001082 seconds.
+Total test time: 3.002703 seconds.
# Print event counts for each cpu system wide.
$ su 0 simpleperf stat --per-core -a --duration 1
@@ -330,15 +370,23 @@ The way to select target in record command is similar to that in the stat comman
# Record process 11904 and 11905.
$ simpleperf record -p 11904,11905 --duration 10
+# Record processes with name containing "chrome".
+$ simpleperf record -p chrome --duration 10
+# Record processes with name containing part matching regex "chrome:(privileged|sandboxed)".
+$ simpleperf record -p "chrome:(privileged|sandboxed)" --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 simpleperf.example.cpp
+# Record the process of an Android application. On non-root devices, this only works for debuggable
+# or profileable from shell apps.
+$ simpleperf record --app simpleperf.example.cpp --duration 10
+
+# Record only selected thread 11904 in an app.
+$ simpleperf record --app simpleperf.example.cpp -t 11904 --duration 10
# Record system wide.
$ simpleperf record -a --duration 10
diff --git a/simpleperf/dso.cpp b/simpleperf/dso.cpp
index 6d559770..288f75ed 100644
--- a/simpleperf/dso.cpp
+++ b/simpleperf/dso.cpp
@@ -267,6 +267,9 @@ void Dso::SetDemangle(bool demangle) {
}
extern "C" char* __cxa_demangle(const char* mangled_name, char* buf, size_t* n, int* status);
+#if defined(__linux__) || defined(__darwin__)
+extern "C" char* rustc_demangle(const char* mangled, char* out, size_t* len, int* status);
+#endif
std::string Dso::Demangle(const std::string& name) {
if (!demangle_) {
@@ -278,19 +281,35 @@ std::string Dso::Demangle(const std::string& name) {
if (is_linker_symbol) {
mangled_str += linker_prefix.size();
}
- std::string result = name;
- char* demangled_name = __cxa_demangle(mangled_str, nullptr, nullptr, &status);
- if (status == 0) {
- if (is_linker_symbol) {
- result = std::string("[linker]") + demangled_name;
- } else {
- result = demangled_name;
+
+ if (mangled_str[0] == '_') {
+ char* demangled_name = nullptr;
+ int status = -2; // -2 means name didn't demangle.
+ if (mangled_str[1] == 'Z') {
+ demangled_name = __cxa_demangle(mangled_str, nullptr, nullptr, &status);
+#if defined(__linux__) || defined(__darwin__)
+ } else if (mangled_str[1] == 'R') {
+ demangled_name = rustc_demangle(mangled_str, nullptr, nullptr, &status);
+#endif
+ }
+ if (status == 0) {
+ // demangled successfully
+ std::string result;
+ if (is_linker_symbol) {
+ result = std::string("[linker]") + demangled_name;
+ } else {
+ result = demangled_name;
+ }
+ free(demangled_name);
+ return result;
}
- free(demangled_name);
- } else if (is_linker_symbol) {
- result = std::string("[linker]") + mangled_str;
}
- return result;
+
+ // failed to demangle
+ if (is_linker_symbol) {
+ return std::string("[linker]") + mangled_str;
+ }
+ return name;
}
bool Dso::SetSymFsDir(const std::string& symfs_dir) {
@@ -554,8 +573,10 @@ class ElfDso : public Dso {
if (elf) {
min_vaddr_ = elf->ReadMinExecutableVaddr(&file_offset_of_min_vaddr_);
} else {
- LOG(WARNING) << "failed to read min virtual address of " << GetDebugFilePath() << ": "
- << status;
+ // This is likely to be a file wrongly thought of as an ELF file, due to stack unwinding.
+ // No need to report it by default.
+ LOG(DEBUG) << "failed to read min virtual address of " << GetDebugFilePath() << ": "
+ << status;
}
}
*min_vaddr = min_vaddr_;
@@ -617,8 +638,17 @@ class ElfDso : public Dso {
if (elf) {
status = elf->ParseSymbols(symbol_callback);
}
- ReportReadElfSymbolResult(status, path_, GetDebugFilePath(),
- symbols_.empty() ? android::base::WARNING : android::base::DEBUG);
+ android::base::LogSeverity log_level = android::base::WARNING;
+ if (!symbols_.empty() || !symbols.empty()) {
+ // We already have some symbols when recording.
+ log_level = android::base::DEBUG;
+ }
+ if ((status == ElfStatus::FILE_NOT_FOUND || status == ElfStatus::FILE_MALFORMED) &&
+ build_id.IsEmpty()) {
+ // This is likely to be a file wrongly thought of as an ELF file, due to stack unwinding.
+ log_level = android::base::DEBUG;
+ }
+ ReportReadElfSymbolResult(status, path_, GetDebugFilePath(), log_level);
SortAndFixSymbols(symbols);
return symbols;
}
@@ -642,10 +672,7 @@ class KernelDso : public Dso {
ElfStatus status;
if (ElfFile::Open(vmlinux_, &build_id, &status)) {
debug_file_path_ = vmlinux_;
- has_debug_file_ = true;
}
- } else if (IsRegularFile(GetDebugFilePath())) {
- has_debug_file_ = true;
}
}
@@ -675,9 +702,7 @@ class KernelDso : public Dso {
std::vector<Symbol> LoadSymbolsImpl() override {
std::vector<Symbol> symbols;
- if (has_debug_file_) {
- ReadSymbolsFromDebugFile(&symbols);
- }
+ ReadSymbolsFromDebugFile(&symbols);
if (symbols.empty() && !kallsyms_.empty()) {
ReadSymbolsFromKallsyms(kallsyms_, &symbols);
@@ -696,6 +721,12 @@ class KernelDso : public Dso {
private:
void ReadSymbolsFromDebugFile(std::vector<Symbol>* symbols) {
+ ElfStatus status;
+ auto elf = ElfFile::Open(GetDebugFilePath(), &status);
+ if (!elf) {
+ return;
+ }
+
if (!fix_kernel_address_randomization_) {
LOG(WARNING) << "Don't know how to fix addresses changed by kernel address randomization. So "
"symbols in "
@@ -712,10 +743,7 @@ class KernelDso : public Dso {
symbols->emplace_back(symbol.name, symbol.vaddr, symbol.len);
}
};
- ElfStatus status;
- if (auto elf = ElfFile::Open(GetDebugFilePath(), &status); elf) {
- status = elf->ParseSymbols(symbol_callback);
- }
+ status = elf->ParseSymbols(symbol_callback);
ReportReadElfSymbolResult(status, path_, GetDebugFilePath());
}
@@ -779,21 +807,18 @@ class KernelDso : public Dso {
void ParseKernelStartAddr() {
kernel_start_addr_ = 0;
kernel_start_file_offset_ = 0;
- if (has_debug_file_) {
- ElfStatus status;
- if (auto elf = ElfFile::Open(GetDebugFilePath(), &status); elf) {
- for (const auto& section : elf->GetSectionHeader()) {
- if (section.name == ".text") {
- kernel_start_addr_ = section.vaddr;
- kernel_start_file_offset_ = section.file_offset;
- break;
- }
+ ElfStatus status;
+ if (auto elf = ElfFile::Open(GetDebugFilePath(), &status); elf) {
+ for (const auto& section : elf->GetSectionHeader()) {
+ if (section.name == ".text") {
+ kernel_start_addr_ = section.vaddr;
+ kernel_start_file_offset_ = section.file_offset;
+ break;
}
}
}
}
- bool has_debug_file_ = false;
bool fix_kernel_address_randomization_ = false;
std::optional<uint64_t> kernel_start_addr_;
std::optional<uint64_t> kernel_start_file_offset_;
@@ -866,10 +891,9 @@ class KernelModuleDso : public Dso {
// need to know its relative position in the module memory. There are two ways:
// 1. Read the kernel module file to calculate the relative position of .text section. It
// is relatively complex and depends on both PLT entries and the kernel version.
- // 2. Find a module symbol in .text section, get its address in memory from /proc/kallsyms, and
- // its vaddr_in_file from the kernel module file. Then other symbols in .text section can be
- // mapped in the same way.
- // Below we use the second method.
+ // 2. Find a module symbol in .text section, get its address in memory from /proc/kallsyms,
+ // and its vaddr_in_file from the kernel module file. Then other symbols in .text section can
+ // be mapped in the same way. Below we use the second method.
// 1. Select a module symbol in /proc/kallsyms.
kernel_dso_->LoadSymbols();
@@ -944,9 +968,9 @@ std::unique_ptr<Dso> Dso::CreateDso(DsoType dso_type, const std::string& dso_pat
case DSO_UNKNOWN_FILE:
return std::unique_ptr<Dso>(new UnknownDso(dso_path));
default:
- LOG(FATAL) << "Unexpected dso_type " << static_cast<int>(dso_type);
+ LOG(ERROR) << "Unexpected dso_type " << static_cast<int>(dso_type);
+ return nullptr;
}
- return nullptr;
}
std::unique_ptr<Dso> Dso::CreateDsoWithBuildId(DsoType dso_type, const std::string& dso_path,
@@ -963,7 +987,7 @@ std::unique_ptr<Dso> Dso::CreateDsoWithBuildId(DsoType dso_type, const std::stri
dso.reset(new KernelModuleDso(dso_path, 0, 0, nullptr));
break;
default:
- LOG(FATAL) << "Unexpected dso_type " << static_cast<int>(dso_type);
+ LOG(ERROR) << "Unexpected dso_type " << static_cast<int>(dso_type);
return nullptr;
}
dso->debug_file_path_ = debug_elf_file_finder_.FindDebugFile(dso_path, false, build_id);
@@ -1001,4 +1025,30 @@ bool GetBuildIdFromDsoPath(const std::string& dso_path, BuildId* build_id) {
return false;
}
+bool GetBuildId(const Dso& dso, BuildId& build_id) {
+ if (dso.type() == DSO_KERNEL) {
+ if (GetKernelBuildId(&build_id)) {
+ return true;
+ }
+ } else if (dso.type() == DSO_KERNEL_MODULE) {
+ bool has_build_id = false;
+ if (android::base::EndsWith(dso.Path(), ".ko")) {
+ return GetBuildIdFromDsoPath(dso.Path(), &build_id);
+ }
+ if (const std::string& path = dso.Path();
+ path.size() > 2 && path[0] == '[' && path.back() == ']') {
+ // For kernel modules that we can't find the corresponding file, read build id from /sysfs.
+ return GetModuleBuildId(path.substr(1, path.size() - 2), &build_id);
+ }
+ } else if (dso.type() == DSO_ELF_FILE) {
+ if (dso.Path() == DEFAULT_EXECNAME_FOR_THREAD_MMAP || dso.IsForJavaMethod()) {
+ return false;
+ }
+ if (GetBuildIdFromDsoPath(dso.Path(), &build_id)) {
+ return true;
+ }
+ }
+ return false;
+}
+
} // namespace simpleperf
diff --git a/simpleperf/dso.h b/simpleperf/dso.h
index 30427766..41cab757 100644
--- a/simpleperf/dso.h
+++ b/simpleperf/dso.h
@@ -228,6 +228,7 @@ class Dso {
const char* DsoTypeToString(DsoType dso_type);
bool GetBuildIdFromDsoPath(const std::string& dso_path, BuildId* build_id);
+bool GetBuildId(const Dso& dso, BuildId& build_id);
} // namespace simpleperf
diff --git a/simpleperf/dso_test.cpp b/simpleperf/dso_test.cpp
index 53f62a57..11bcde19 100644
--- a/simpleperf/dso_test.cpp
+++ b/simpleperf/dso_test.cpp
@@ -140,7 +140,6 @@ TEST(DebugElfFileFinder, build_id_mismatch) {
DebugElfFileFinder finder;
finder.SetSymFsDir(GetTestDataDir());
CapturedStderr capture;
- capture.Start();
BuildId mismatch_build_id("0c12a384a9f4a3f3659b7171ca615dbec3a81f71");
std::string debug_file = finder.FindDebugFile(ELF_FILE, false, mismatch_build_id);
capture.Stop();
@@ -250,6 +249,15 @@ TEST(dso, find_vmlinux_in_symdirs) {
std::unique_ptr<Dso> dso = Dso::CreateDso(DSO_KERNEL, DEFAULT_KERNEL_MMAP_NAME);
ASSERT_TRUE(dso);
ASSERT_EQ(dso->GetDebugFilePath(), vmlinux_path);
+ ASSERT_EQ(0x400927, dso->IpToVaddrInFile(0x800527, 0x800000, 0));
+
+ // Find vmlinux by CreateDsoWithBuildId.
+ Dso::SetBuildIds({});
+ BuildId build_id(ELF_FILE_BUILD_ID);
+ dso = Dso::CreateDsoWithBuildId(DSO_KERNEL, DEFAULT_KERNEL_MMAP_NAME, build_id);
+ ASSERT_TRUE(dso);
+ ASSERT_EQ(dso->GetDebugFilePath(), vmlinux_path);
+ ASSERT_EQ(0x400927, dso->IpToVaddrInFile(0x800527, 0x800000, 0));
}
TEST(dso, kernel_module) {
@@ -315,10 +323,58 @@ TEST(dso, search_debug_file_only_when_needed) {
Dso::SetBuildIds({std::make_pair("/elf", BuildId("1b12a384a9f4a3f3659b7171ca615dbec3a81f71"))});
Dso::SetSymFsDir(GetTestDataDir());
CapturedStderr capture;
- capture.Start();
auto dso = Dso::CreateDso(DSO_ELF_FILE, "/elf");
ASSERT_EQ(capture.str().find("build id mismatch"), std::string::npos);
ASSERT_EQ(dso->GetDebugFilePath(), "/elf");
ASSERT_NE(capture.str().find("build id mismatch"), std::string::npos);
capture.Stop();
}
+
+TEST(dso, read_symbol_warning) {
+ {
+ // Don't warn when the file may not be an ELF file.
+ auto dso = Dso::CreateDso(DSO_ELF_FILE, GetTestData("not_exist_file"));
+ CapturedStderr capture;
+ dso->LoadSymbols();
+ ASSERT_EQ(capture.str().find("failed to read symbols"), std::string::npos);
+ }
+ {
+ // Don't warn when the file may not be an ELF file.
+ auto dso = Dso::CreateDso(DSO_ELF_FILE, GetTestData("base.vdex"));
+ CapturedStderr capture;
+ dso->LoadSymbols();
+ ASSERT_EQ(capture.str().find("failed to read symbols"), std::string::npos);
+ }
+ {
+ // Warn when the file is an ELF file (having a build id).
+ std::string file_path = GetTestData("not_exist_file");
+ Dso::SetBuildIds(
+ {std::make_pair(file_path, BuildId("1b12a384a9f4a3f3659b7171ca615dbec3a81f71"))});
+ auto dso = Dso::CreateDso(DSO_ELF_FILE, file_path);
+ CapturedStderr capture;
+ dso->LoadSymbols();
+ ASSERT_NE(capture.str().find("failed to read symbols"), std::string::npos);
+ }
+ {
+ // Don't warn when we already have symbols.
+ std::string file_path = GetTestData("not_exist_file");
+ Dso::SetBuildIds(
+ {std::make_pair(file_path, BuildId("1b12a384a9f4a3f3659b7171ca615dbec3a81f71"))});
+ auto dso = Dso::CreateDso(DSO_ELF_FILE, file_path);
+ std::vector<Symbol> symbols;
+ symbols.emplace_back("fake_symbol", 0x1234, 0x60);
+ dso->SetSymbols(&symbols);
+ CapturedStderr capture;
+ dso->LoadSymbols();
+ ASSERT_EQ(capture.str().find("failed to read symbols"), std::string::npos);
+ }
+}
+
+TEST(dso, demangle) {
+ ASSERT_EQ(Dso::Demangle("main"), "main");
+ ASSERT_EQ(Dso::Demangle("_ZN4main4main17h2a68d4d833d7495aE"), "main::main::h2a68d4d833d7495a");
+#if defined(__linux__) || defined(__darwin__)
+ // Demangling rust symbol is only supported on linux and darwin.
+ ASSERT_EQ(Dso::Demangle("_RNvC6_123foo3bar"), "123foo::bar");
+#endif
+}
diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp
index 8e95d449..5ff09160 100644
--- a/simpleperf/environment.cpp
+++ b/simpleperf/environment.cpp
@@ -241,6 +241,16 @@ bool CanRecordRawData() {
#endif
}
+std::optional<uint64_t> GetMemorySize() {
+ std::unique_ptr<FILE, decltype(&fclose)> fp(fopen("/proc/meminfo", "r"), fclose);
+ uint64_t size;
+ if (fp && fscanf(fp.get(), "MemTotal:%" PRIu64 " k", &size) == 1) {
+ return size * kKilobyte;
+ }
+ PLOG(ERROR) << "failed to get memory size";
+ return std::nullopt;
+}
+
static const char* GetLimitLevelDescription(int limit_level) {
switch (limit_level) {
case -1:
@@ -299,15 +309,16 @@ bool CheckPerfEventLimit() {
}
}
if (can_read_allow_file) {
- LOG(WARNING) << perf_event_allow_path << " is " << limit_level << ", "
- << GetLimitLevelDescription(limit_level) << ".";
+ LOG(ERROR) << perf_event_allow_path << " is " << limit_level << ", "
+ << GetLimitLevelDescription(limit_level) << ".";
}
- LOG(WARNING) << "Try using `adb shell setprop security.perf_harden 0` to allow profiling.";
+ LOG(ERROR) << "Try using `adb shell setprop security.perf_harden 0` to allow profiling.";
return false;
#else
if (can_read_allow_file) {
- LOG(WARNING) << perf_event_allow_path << " is " << limit_level << ", "
- << GetLimitLevelDescription(limit_level) << ".";
+ LOG(ERROR) << perf_event_allow_path << " is " << limit_level << ", "
+ << GetLimitLevelDescription(limit_level) << ". Try using `echo -1 >"
+ << perf_event_allow_path << "` to enable profiling.";
return false;
}
#endif
@@ -332,9 +343,10 @@ bool SetPerfEventLimits(uint64_t sample_freq, size_t cpu_percent, uint64_t mlock
}
// Wait for init process to change perf event limits based on properties.
const size_t max_wait_us = 3 * 1000000;
+ const size_t interval_us = 10000;
int finish_mask = 0;
- for (size_t i = 0; i < max_wait_us && finish_mask != 7; ++i) {
- usleep(1); // Wait 1us to avoid busy loop.
+ for (size_t i = 0; i < max_wait_us && finish_mask != 7; i += interval_us) {
+ usleep(interval_us); // Wait 10ms to avoid busy loop.
if ((finish_mask & 1) == 0) {
uint64_t freq;
if (!GetMaxSampleFrequency(&freq) || freq == sample_freq) {
@@ -486,12 +498,10 @@ std::set<pid_t> WaitForAppProcesses(const std::string& package_name) {
while (true) {
std::vector<pid_t> pids = GetAllProcesses();
for (pid_t pid : pids) {
- std::string cmdline;
- if (!android::base::ReadFileToString("/proc/" + std::to_string(pid) + "/cmdline", &cmdline)) {
- // Maybe we don't have permission to read it.
+ std::string process_name = GetCompleteProcessName(pid);
+ if (process_name.empty()) {
continue;
}
- std::string process_name = android::base::Basename(cmdline);
// The app may have multiple processes, with process name like
// com.google.android.googlequicksearchbox:search.
size_t split_pos = process_name.find(':');
@@ -824,9 +834,22 @@ void AllowMoreOpenedFiles() {
// On Android <= O, the hard limit is 4096, and the soft limit is 1024.
// On Android >= P, both the hard and soft limit are 32768.
rlimit limit;
- if (getrlimit(RLIMIT_NOFILE, &limit) == 0) {
- limit.rlim_cur = limit.rlim_max;
- setrlimit(RLIMIT_NOFILE, &limit);
+ if (getrlimit(RLIMIT_NOFILE, &limit) != 0) {
+ return;
+ }
+ rlim_t new_limit = limit.rlim_max;
+ if (IsRoot()) {
+ rlim_t sysctl_nr_open = 0;
+ if (ReadUintFromProcFile("/proc/sys/fs/nr_open", &sysctl_nr_open) &&
+ sysctl_nr_open > new_limit) {
+ new_limit = sysctl_nr_open;
+ }
+ }
+ if (limit.rlim_cur < new_limit) {
+ limit.rlim_cur = limit.rlim_max = new_limit;
+ if (setrlimit(RLIMIT_NOFILE, &limit) == 0) {
+ LOG(DEBUG) << "increased open file limit to " << new_limit;
+ }
}
}
@@ -934,18 +957,18 @@ bool MappedFileOnlyExistInMemory(const char* filename) {
}
std::string GetCompleteProcessName(pid_t pid) {
- std::string s;
- if (!android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/cmdline", pid), &s)) {
- s.clear();
- }
- for (size_t i = 0; i < s.size(); ++i) {
- // /proc/pid/cmdline uses 0 to separate arguments.
- if (isspace(s[i]) || s[i] == 0) {
- s.resize(i);
- break;
- }
- }
- return s;
+ std::string argv0;
+ if (!android::base::ReadFileToString("/proc/" + std::to_string(pid) + "/cmdline", &argv0)) {
+ // Maybe we don't have permission to read it.
+ return std::string();
+ }
+ size_t pos = argv0.find('\0');
+ if (pos != std::string::npos) {
+ argv0.resize(pos);
+ }
+ // argv0 can be empty if the process is in zombie state. In that case, we don't want to pass argv0
+ // to Basename(), which returns ".".
+ return argv0.empty() ? std::string() : android::base::Basename(argv0);
}
const char* GetTraceFsDir() {
diff --git a/simpleperf/environment.h b/simpleperf/environment.h
index 34ce7fa5..a69abaa9 100644
--- a/simpleperf/environment.h
+++ b/simpleperf/environment.h
@@ -85,6 +85,7 @@ bool SetCpuTimeMaxPercent(size_t percent);
bool GetPerfEventMlockKb(uint64_t* mlock_kb);
bool SetPerfEventMlockKb(uint64_t mlock_kb);
bool CanRecordRawData();
+std::optional<uint64_t> GetMemorySize();
ArchType GetMachineArch();
void PrepareVdsoFile();
diff --git a/simpleperf/environment_test.cpp b/simpleperf/environment_test.cpp
index a95caca8..511f1844 100644
--- a/simpleperf/environment_test.cpp
+++ b/simpleperf/environment_test.cpp
@@ -136,3 +136,9 @@ TEST(environment, GetAppType) {
ASSERT_EQ(GetAppType("com.android.simpleperf.profileable"), "profileable");
ASSERT_EQ(GetAppType("com.android.simpleperf.app_not_exist"), "not_exist");
}
+
+TEST(environment, GetMemorySize) {
+ auto value = GetMemorySize();
+ ASSERT_TRUE(value);
+ ASSERT_GT(value.value(), 0);
+}
diff --git a/simpleperf/event_attr.cpp b/simpleperf/event_attr.cpp
index dea77a94..b8acc341 100644
--- a/simpleperf/event_attr.cpp
+++ b/simpleperf/event_attr.cpp
@@ -145,7 +145,7 @@ void DumpPerfEventAttr(const perf_event_attr& attr, size_t indent) {
PrintIndented(indent + 1, "sample_stack_user 0x%" PRIx64 "\n", attr.sample_stack_user);
}
-bool GetCommonEventIdPositionsForAttrs(std::vector<perf_event_attr>& attrs,
+bool GetCommonEventIdPositionsForAttrs(const EventAttrIds& attrs,
size_t* event_id_pos_in_sample_records,
size_t* event_id_reverse_pos_in_non_sample_records) {
// When there are more than one perf_event_attrs, we need to read event id
@@ -153,7 +153,7 @@ bool GetCommonEventIdPositionsForAttrs(std::vector<perf_event_attr>& attrs,
// we need to determine the event id position in a record here.
std::vector<uint64_t> sample_types;
for (const auto& attr : attrs) {
- sample_types.push_back(attr.sample_type);
+ sample_types.push_back(attr.attr.sample_type);
}
// First determine event_id_pos_in_sample_records.
// If PERF_SAMPLE_IDENTIFIER is enabled, it is just after perf_event_header.
@@ -192,7 +192,7 @@ bool GetCommonEventIdPositionsForAttrs(std::vector<perf_event_attr>& attrs,
// also be the same.
bool sample_id_all_enabled = true;
for (const auto& attr : attrs) {
- if (attr.sample_id_all == 0) {
+ if (attr.attr.sample_id_all == 0) {
sample_id_all_enabled = false;
}
}
@@ -254,4 +254,11 @@ std::string GetEventNameByAttr(const perf_event_attr& attr) {
return name;
}
+void ReplaceRegAndStackWithCallChain(perf_event_attr& attr) {
+ attr.sample_type &= ~(PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER);
+ attr.exclude_callchain_user = 0;
+ attr.sample_regs_user = 0;
+ attr.sample_stack_user = 0;
+}
+
} // namespace simpleperf
diff --git a/simpleperf/event_attr.h b/simpleperf/event_attr.h
index a9d8bba7..5521f328 100644
--- a/simpleperf/event_attr.h
+++ b/simpleperf/event_attr.h
@@ -18,6 +18,7 @@
#define SIMPLE_PERF_EVENT_ATTR_H_
#include <stddef.h>
+#include <string.h>
#include <string>
#include <vector>
@@ -29,15 +30,17 @@ namespace simpleperf {
struct EventType;
struct EventAttrWithId {
- const perf_event_attr* attr;
+ perf_event_attr attr;
std::vector<uint64_t> ids;
};
+using EventAttrIds = std::vector<EventAttrWithId>;
+
inline constexpr uint64_t INFINITE_SAMPLE_PERIOD = 1ULL << 62;
perf_event_attr CreateDefaultPerfEventAttr(const EventType& event_type);
void DumpPerfEventAttr(const perf_event_attr& attr, size_t indent = 0);
-bool GetCommonEventIdPositionsForAttrs(std::vector<perf_event_attr>& attrs,
+bool GetCommonEventIdPositionsForAttrs(const EventAttrIds& attrs,
size_t* event_id_pos_in_sample_records,
size_t* event_id_reverse_pos_in_non_sample_records);
bool IsTimestampSupported(const perf_event_attr& attr);
@@ -45,6 +48,15 @@ bool IsCpuSupported(const perf_event_attr& attr);
// Return event name with modifier if the event is found, otherwise return "unknown".
// This function is slow for using linear search, so only used when reporting.
std::string GetEventNameByAttr(const perf_event_attr& attr);
+void ReplaceRegAndStackWithCallChain(perf_event_attr& attr);
+
+inline bool operator==(const perf_event_attr& attr1, const perf_event_attr& attr2) {
+ return memcmp(&attr1, &attr2, sizeof(perf_event_attr)) == 0;
+}
+
+inline bool operator!=(const perf_event_attr& attr1, const perf_event_attr& attr2) {
+ return !(attr1 == attr2);
+}
} // namespace simpleperf
diff --git a/simpleperf/event_selection_set.cpp b/simpleperf/event_selection_set.cpp
index 58b4b956..2fdb88f5 100644
--- a/simpleperf/event_selection_set.cpp
+++ b/simpleperf/event_selection_set.cpp
@@ -367,16 +367,17 @@ bool EventSelectionSet::ExcludeKernel() const {
return true;
}
-std::vector<EventAttrWithId> EventSelectionSet::GetEventAttrWithId() const {
- std::vector<EventAttrWithId> result;
+EventAttrIds EventSelectionSet::GetEventAttrWithId() const {
+ EventAttrIds result;
for (const auto& group : groups_) {
for (const auto& selection : group) {
- EventAttrWithId attr_id;
- attr_id.attr = &selection.event_attr;
+ std::vector<uint64_t> ids;
for (const auto& fd : selection.event_fds) {
- attr_id.ids.push_back(fd->Id());
+ ids.push_back(fd->Id());
}
- result.push_back(attr_id);
+ result.resize(result.size() + 1);
+ result.back().attr = selection.event_attr;
+ result.back().ids = std::move(ids);
}
}
return result;
diff --git a/simpleperf/event_selection_set.h b/simpleperf/event_selection_set.h
index 07754fd2..757034dc 100644
--- a/simpleperf/event_selection_set.h
+++ b/simpleperf/event_selection_set.h
@@ -115,7 +115,7 @@ class EventSelectionSet {
std::vector<const EventType*> GetTracepointEvents() const;
bool ExcludeKernel() const;
bool HasAuxTrace() const { return has_aux_trace_; }
- std::vector<EventAttrWithId> GetEventAttrWithId() const;
+ EventAttrIds GetEventAttrWithId() const;
std::unordered_map<uint64_t, std::string> GetEventNamesById() const;
void SetEnableOnExec(bool enable);
diff --git a/simpleperf/get_test_data.h b/simpleperf/get_test_data.h
index 2711d5e4..e246366d 100644
--- a/simpleperf/get_test_data.h
+++ b/simpleperf/get_test_data.h
@@ -97,8 +97,6 @@ static const std::string PERF_DATA_FOR_BUILD_ID_CHECK = "perf_for_build_id_check
static const std::string CORRECT_SYMFS_FOR_BUILD_ID_CHECK = "data/correct_symfs_for_build_id_check";
static const std::string WRONG_SYMFS_FOR_BUILD_ID_CHECK = "data/wrong_symfs_for_build_id_check";
-static const std::string SYMFS_FOR_NO_SYMBOL_TABLE_WARNING =
- "data/symfs_for_no_symbol_table_warning";
static const std::string SYMFS_FOR_READ_ELF_FILE_WARNING = "data/symfs_for_read_elf_file_warning";
static BuildId CHECK_ELF_FILE_BUILD_ID("91b1c10fdd9fe2221dfec525497637f2229bfdbb");
@@ -144,6 +142,6 @@ 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";
+static const std::string PERF_DATA_ETM_TEST_LOOP = "etm/perf_etm.data";
#endif // SIMPLE_PERF_GET_TEST_DATA_H_
diff --git a/simpleperf/gtest_main.cpp b/simpleperf/gtest_main.cpp
index 3fce9857..c8942b69 100644
--- a/simpleperf/gtest_main.cpp
+++ b/simpleperf/gtest_main.cpp
@@ -76,6 +76,7 @@ int main(int argc, char** argv) {
return RunSimpleperfCmd(argc, argv) ? 0 : 1;
}
+ testing::InitGoogleTest(&argc, argv);
android::base::InitLogging(argv, android::base::StderrLogger);
android::base::LogSeverity log_severity = android::base::WARNING;
testdata_dir = std::string(dirname(argv[0])) + "/testdata";
@@ -104,7 +105,6 @@ int main(int argc, char** argv) {
ScopedEnablingPerf scoped_enabling_perf;
#endif
- testing::InitGoogleTest(&argc, argv);
if (!::testing::GTEST_FLAG(list_tests)) {
if (!IsDir(testdata_dir)) {
LOG(ERROR) << "testdata wasn't found. Use \"" << argv[0] << " -t <testdata_dir>\"";
diff --git a/simpleperf/include/simpleperf_profcollect.hpp b/simpleperf/include/simpleperf_profcollect.hpp
index 67123e58..d1e6661a 100644
--- a/simpleperf/include/simpleperf_profcollect.hpp
+++ b/simpleperf/include/simpleperf_profcollect.hpp
@@ -18,6 +18,8 @@ extern "C" {
bool HasDriverSupport();
bool HasDeviceSupport();
-bool Record(const char* event_name, const char* output, float duration);
+bool Record(const char* event_name, const char* output, float duration, const char* binary_filter);
bool Inject(const char* traceInput, const char* profileOutput, const char* binary_filter);
+void SetLogFile(const char* filename);
+void ResetLogFile();
}
diff --git a/simpleperf/kallsyms.cpp b/simpleperf/kallsyms.cpp
index f9a93589..9cec7848 100644
--- a/simpleperf/kallsyms.cpp
+++ b/simpleperf/kallsyms.cpp
@@ -24,6 +24,7 @@
#include <android-base/properties.h>
#include "environment.h"
+#include "read_elf.h"
#include "utils.h"
namespace simpleperf {
@@ -263,6 +264,10 @@ bool ProcessKernelSymbols(std::string& symbol_data,
p = data_end;
}
if (ret >= 3) {
+ if (IsArmMappingSymbol(name)) {
+ continue;
+ }
+
symbol.name = name;
size_t module_len = strlen(module);
if (module_len > 2 && module[0] == '[' && module[module_len - 1] == ']') {
diff --git a/simpleperf/kallsyms_test.cpp b/simpleperf/kallsyms_test.cpp
index d45aeaa6..cacd1634 100644
--- a/simpleperf/kallsyms_test.cpp
+++ b/simpleperf/kallsyms_test.cpp
@@ -64,6 +64,25 @@ TEST(kallsyms, ProcessKernelSymbols) {
data, std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol)));
}
+TEST(kallsyms, ProcessKernelSymbols_ignore_arm_mapping_symbols) {
+ std::string data =
+ "aaaaaaaaaaaaaaaa t $x.9 [coresight_etm4x]\n"
+ "bbbbbbbbbbbbbbbb t etm4_pm_clear [coresight_etm4x]\n";
+ bool has_normal_symbol = false;
+ bool has_arm_mapping_symbol = false;
+ auto callback = [&](const KernelSymbol& sym) {
+ if (strcmp(sym.name, "etm4_pm_clear") == 0) {
+ has_normal_symbol = true;
+ } else {
+ has_arm_mapping_symbol = true;
+ }
+ return false;
+ };
+ ProcessKernelSymbols(data, callback);
+ ASSERT_TRUE(has_normal_symbol);
+ ASSERT_FALSE(has_arm_mapping_symbol);
+}
+
#if defined(__ANDROID__)
TEST(kallsyms, GetKernelStartAddress) {
TEST_REQUIRE_ROOT();
diff --git a/simpleperf/libsimpleperf_report_fuzzer.cpp b/simpleperf/libsimpleperf_report_fuzzer.cpp
index 4c68610f..8c6d8788 100644
--- a/simpleperf/libsimpleperf_report_fuzzer.cpp
+++ b/simpleperf/libsimpleperf_report_fuzzer.cpp
@@ -1,14 +1,17 @@
#include <android-base/file.h>
+#include "command.h"
#include "report_lib_interface.cpp"
+#include "test_util.h"
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
- TemporaryFile tmpfile;
- android::base::WriteStringToFd(std::string(reinterpret_cast<const char*>(data), size),
- tmpfile.fd);
+using namespace simpleperf;
+
+namespace {
+
+void TestReportLib(const char* record_file) {
ReportLib* report_lib = CreateReportLib();
- SetRecordFile(report_lib, tmpfile.path);
+ SetRecordFile(report_lib, record_file);
while (true) {
Sample* sample = GetNextSample(report_lib);
if (sample == nullptr) {
@@ -16,5 +19,21 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
}
}
DestroyReportLib(report_lib);
+}
+
+void TestDumpCmd(const char* record_file) {
+ std::unique_ptr<Command> dump_cmd = CreateCommandInstance("dump");
+ CaptureStdout capture;
+ capture.Start();
+ dump_cmd->Run({"-i", record_file, "--dump-etm", "raw,packet,element"});
+}
+
+} // namespace
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ TemporaryFile tmpfile;
+ android::base::WriteFully(tmpfile.fd, data, size);
+ TestReportLib(tmpfile.path);
+ TestDumpCmd(tmpfile.path);
return 0;
}
diff --git a/simpleperf/nonlinux_support/nonlinux_support.cpp b/simpleperf/nonlinux_support/nonlinux_support.cpp
index 23471f6a..e7084fef 100644
--- a/simpleperf/nonlinux_support/nonlinux_support.cpp
+++ b/simpleperf/nonlinux_support/nonlinux_support.cpp
@@ -23,10 +23,22 @@
namespace simpleperf {
+bool GetThreadMmapsInProcess(pid_t, std::vector<ThreadMmap>*) {
+ return false;
+}
+
bool GetKernelBuildId(BuildId*) {
return false;
}
+bool GetModuleBuildId(const std::string&, BuildId*, const std::string&) {
+ return false;
+}
+
+bool ReadThreadNameAndPid(pid_t, std::string*, pid_t*) {
+ return false;
+}
+
bool CanRecordRawData() {
return false;
}
diff --git a/simpleperf/perf_regs.cpp b/simpleperf/perf_regs.cpp
index 59db45f6..55bd2134 100644
--- a/simpleperf/perf_regs.cpp
+++ b/simpleperf/perf_regs.cpp
@@ -34,6 +34,8 @@ ArchType GetArchType(const std::string& arch) {
return ARCH_X86_32;
} else if (arch == "x86_64") {
return ARCH_X86_64;
+ } else if (arch == "riscv64") {
+ return ARCH_RISCV64;
} else if (arch == "aarch64") {
return ARCH_ARM64;
} else if (android::base::StartsWith(arch, "arm")) {
@@ -81,6 +83,8 @@ std::string GetArchString(ArchType arch) {
return "arm64";
case ARCH_ARM:
return "arm";
+ case ARCH_RISCV64:
+ return "riscv64";
default:
break;
}
@@ -99,6 +103,8 @@ uint64_t GetSupportedRegMask(ArchType arch) {
return ((1ULL << PERF_REG_ARM_MAX) - 1);
case ARCH_ARM64:
return ((1ULL << PERF_REG_ARM64_MAX) - 1);
+ case ARCH_RISCV64:
+ return ((1ULL << PERF_REG_RISCV_MAX) - 1);
default:
return 0;
}
@@ -125,6 +131,41 @@ static std::unordered_map<size_t, std::string> arm64_reg_map = {
{PERF_REG_ARM64_PC, "pc"},
};
+static std::unordered_map<size_t, std::string> riscv64_reg_map = {
+ {PERF_REG_RISCV_PC, "pc"},
+ {PERF_REG_RISCV_RA, "ra"},
+ {PERF_REG_RISCV_SP, "sp"},
+ {PERF_REG_RISCV_GP, "gp"},
+ {PERF_REG_RISCV_TP, "tp"},
+ {PERF_REG_RISCV_T0, "t0"},
+ {PERF_REG_RISCV_T1, "t1"},
+ {PERF_REG_RISCV_T2, "t2"},
+ {PERF_REG_RISCV_S0, "s0"},
+ {PERF_REG_RISCV_S1, "s1"},
+ {PERF_REG_RISCV_A0, "a0"},
+ {PERF_REG_RISCV_A1, "a1"},
+ {PERF_REG_RISCV_A2, "a2"},
+ {PERF_REG_RISCV_A3, "a3"},
+ {PERF_REG_RISCV_A4, "a4"},
+ {PERF_REG_RISCV_A5, "a5"},
+ {PERF_REG_RISCV_A6, "a6"},
+ {PERF_REG_RISCV_A7, "a7"},
+ {PERF_REG_RISCV_S2, "s2"},
+ {PERF_REG_RISCV_S3, "s3"},
+ {PERF_REG_RISCV_S4, "s4"},
+ {PERF_REG_RISCV_S5, "s5"},
+ {PERF_REG_RISCV_S6, "s6"},
+ {PERF_REG_RISCV_S7, "s7"},
+ {PERF_REG_RISCV_S8, "s8"},
+ {PERF_REG_RISCV_S9, "s9"},
+ {PERF_REG_RISCV_S10, "s10"},
+ {PERF_REG_RISCV_S11, "s11"},
+ {PERF_REG_RISCV_T3, "t3"},
+ {PERF_REG_RISCV_T4, "t4"},
+ {PERF_REG_RISCV_T5, "t5"},
+ {PERF_REG_RISCV_T6, "t6"},
+};
+
std::string GetRegName(size_t regno, ArchType arch) {
// Cast regno to int type to avoid -Werror=type-limits.
int reg = static_cast<int>(regno);
@@ -157,6 +198,11 @@ std::string GetRegName(size_t regno, ArchType arch) {
CHECK(it != arm64_reg_map.end()) << "unknown reg " << reg;
return it->second;
}
+ case ARCH_RISCV64: {
+ auto it = riscv64_reg_map.find(reg);
+ CHECK(it != riscv64_reg_map.end()) << "unknown reg " << reg;
+ return it->second;
+ }
default:
return "unknown";
}
@@ -200,6 +246,9 @@ bool RegSet::GetSpRegValue(uint64_t* value) const {
case ARCH_ARM64:
regno = PERF_REG_ARM64_SP;
break;
+ case ARCH_RISCV64:
+ regno = PERF_REG_RISCV_SP;
+ break;
default:
return false;
}
@@ -219,6 +268,9 @@ bool RegSet::GetIpRegValue(uint64_t* value) const {
case ARCH_ARM64:
regno = PERF_REG_ARM64_PC;
break;
+ case ARCH_RISCV64:
+ regno = PERF_REG_RISCV_PC;
+ break;
default:
return false;
}
diff --git a/simpleperf/perf_regs.h b/simpleperf/perf_regs.h
index 569a469c..6e083e07 100644
--- a/simpleperf/perf_regs.h
+++ b/simpleperf/perf_regs.h
@@ -19,14 +19,24 @@
#if defined(USE_BIONIC_UAPI_HEADERS)
#include <uapi/asm-arm/asm/perf_regs.h>
+#undef PERF_REG_EXTENDED_MASK
#include <uapi/asm-x86/asm/perf_regs.h>
+#undef PERF_REG_EXTENDED_MASK
+#include <uapi/asm-riscv/asm/perf_regs.h>
+#undef PERF_REG_EXTENDED_MASK
#define perf_event_arm_regs perf_event_arm64_regs
#include <uapi/asm-arm64/asm/perf_regs.h>
+#undef PERF_REG_EXTENDED_MASK
#else
#include <asm-arm/asm/perf_regs.h>
+#undef PERF_REG_EXTENDED_MASK
#include <asm-x86/asm/perf_regs.h>
+#undef PERF_REG_EXTENDED_MASK
+#include <asm-riscv/asm/perf_regs.h>
+#undef PERF_REG_EXTENDED_MASK
#define perf_event_arm_regs perf_event_arm64_regs
#include <asm-arm64/asm/perf_regs.h>
+#undef PERF_REG_EXTENDED_MASK
#endif
#include <stdint.h>
@@ -42,6 +52,7 @@ enum ArchType {
ARCH_X86_64,
ARCH_ARM,
ARCH_ARM64,
+ ARCH_RISCV64,
ARCH_UNSUPPORTED,
};
@@ -54,6 +65,8 @@ constexpr ArchType GetTargetArch() {
return ARCH_ARM64;
#elif defined(__arm__)
return ARCH_ARM;
+#elif defined(__riscv)
+ return ARCH_RISCV64;
#else
return ARCH_UNSUPPORTED;
#endif
diff --git a/simpleperf/perf_regs_test.cpp b/simpleperf/perf_regs_test.cpp
index fc75cca3..0af97478 100644
--- a/simpleperf/perf_regs_test.cpp
+++ b/simpleperf/perf_regs_test.cpp
@@ -21,9 +21,10 @@
using namespace simpleperf;
TEST(RegSet, arch) {
- ArchType arch_pairs[2][2] = {
+ ArchType arch_pairs[3][2] = {
{ARCH_X86_32, ARCH_X86_64},
{ARCH_ARM, ARCH_ARM64},
+ {ARCH_RISCV64, ARCH_RISCV64},
};
for (ArchType* arch_pair : arch_pairs) {
for (size_t i = 0; i < 2; i++) {
diff --git a/simpleperf/profcollect.cpp b/simpleperf/profcollect.cpp
index 724111e8..9cffbddc 100644
--- a/simpleperf/profcollect.cpp
+++ b/simpleperf/profcollect.cpp
@@ -14,6 +14,11 @@
* limitations under the License.
*/
+#include <time.h>
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+
#include <wakelock/wakelock.h>
#include <include/simpleperf_profcollect.hpp>
@@ -26,48 +31,96 @@
using namespace simpleperf;
bool HasDriverSupport() {
- return ETMRecorder::GetInstance().IsETMDriverAvailable();
+ bool result = ETMRecorder::GetInstance().IsETMDriverAvailable();
+ LOG(INFO) << "HasDriverSupport result " << result;
+ return result;
}
bool HasDeviceSupport() {
auto result = ETMRecorder::GetInstance().CheckEtmSupport();
if (!result.ok()) {
- LOG(DEBUG) << result.error();
+ LOG(INFO) << "HasDeviceSupport check failed: " << result.error();
return false;
}
const EventType* type = FindEventTypeByName("cs-etm", false);
if (type == nullptr) {
+ LOG(INFO) << "HasDeviceSupport check failed: no etm event";
return false;
}
- return IsEventAttrSupported(CreateDefaultPerfEventAttr(*type), type->name);
+ bool ret = IsEventAttrSupported(CreateDefaultPerfEventAttr(*type), type->name);
+ LOG(INFO) << "HasDeviceSupport result " << ret;
+ return ret;
}
-bool Record(const char* event_name, const char* output, float duration) {
+bool Record(const char* event_name, const char* output, float duration, const char* binary_filter) {
+ LOG(INFO) << "Record " << event_name << ", duration " << duration << ", output " << output
+ << ", binary_filter " << binary_filter;
// The kernel may panic when trying to hibernate or hotplug CPUs while collecting
// ETM data. So get wakelock to keep the CPUs on.
auto wakelock = android::wakelock::WakeLock::tryGet("profcollectd");
if (!wakelock) {
- LOG(ERROR) << "Failed to request wakelock.";
+ LOG(ERROR) << "Record failed: Failed to request wakelock.";
return false;
}
auto recordCmd = CreateCommandInstance("record");
- std::vector<std::string> args;
- args.push_back("-a");
- args.insert(args.end(), {"-e", event_name});
- args.insert(args.end(), {"--duration", std::to_string(duration)});
- args.insert(args.end(), {"-o", output});
- return recordCmd->Run(args);
+ std::vector<std::string> args = {"-a",
+ "-e",
+ event_name,
+ "--duration",
+ std::to_string(duration),
+ "--decode-etm",
+ "--exclude-perf",
+ "--binary",
+ binary_filter,
+ "-o",
+ output};
+ bool result = recordCmd->Run(args);
+ LOG(INFO) << "Record result " << result;
+ return result;
}
bool Inject(const char* traceInput, const char* profileOutput, const char* binary_filter) {
+ LOG(INFO) << "Inject traceInput " << traceInput << ", profileOutput " << profileOutput
+ << ", binary_filter " << binary_filter;
auto injectCmd = CreateCommandInstance("inject");
- std::vector<std::string> args;
- args.insert(args.end(), {"-i", traceInput});
- args.insert(args.end(), {"-o", profileOutput});
- if (binary_filter) {
- args.insert(args.end(), {"--binary", binary_filter});
+ std::vector<std::string> args = {"-i", traceInput, "-o", profileOutput,
+ "--output", "branch-list", "--binary", binary_filter};
+ bool result = injectCmd->Run(args);
+ LOG(INFO) << "Inject result " << result;
+ return result;
+}
+
+static android::base::unique_fd log_fd;
+static android::base::LogFunction saved_log_func;
+
+static void FileLogger(android::base::LogId id, android::base::LogSeverity severity,
+ const char* tag, const char* file, unsigned int line, const char* message) {
+ if (log_fd.ok()) {
+ static const char log_characters[] = "VDIWEFF";
+ char severity_char = log_characters[severity];
+ struct tm now;
+ time_t t = time(nullptr);
+ localtime_r(&t, &now);
+ char timestamp[32];
+ strftime(timestamp, sizeof(timestamp), "%m-%d %H:%M:%S", &now);
+ std::string s = android::base::StringPrintf("%s %c %s %s:%u] %s\n", tag, severity_char,
+ timestamp, file, line, message);
+ WriteStringToFd(s, log_fd);
}
- args.insert(args.end(), {"--output", "branch-list"});
- args.emplace_back("--exclude-perf");
- return injectCmd->Run(args);
+ saved_log_func(id, severity, tag, file, line, message);
+}
+
+void SetLogFile(const char* filename) {
+ int fd = TEMP_FAILURE_RETRY(open(filename, O_APPEND | O_CREAT | O_WRONLY | O_CLOEXEC, 0600));
+ if (fd == -1) {
+ PLOG(ERROR) << "failed to open " << filename;
+ return;
+ }
+ log_fd.reset(fd);
+ saved_log_func = SetLogger(FileLogger);
+}
+
+void ResetLogFile() {
+ log_fd.reset();
+ SetLogger(std::move(saved_log_func));
}
diff --git a/simpleperf/record.cpp b/simpleperf/record.cpp
index 4590b9ef..29e5d112 100644
--- a/simpleperf/record.cpp
+++ b/simpleperf/record.cpp
@@ -39,7 +39,12 @@ namespace simpleperf {
} \
} while (0)
-#define CHECK_SIZE_U64(p, end, u64_count) CHECK_SIZE(p, end, (u64_count) * sizeof(uint64_t))
+#define CHECK_SIZE_U64(p, end, u64_count) \
+ do { \
+ if (UNLIKELY(((end) - (p)) / sizeof(uint64_t) < (u64_count))) { \
+ return false; \
+ } \
+ } while (0)
static std::string RecordTypeToString(int record_type) {
static std::unordered_map<int, std::string> record_type_names = {
@@ -205,7 +210,9 @@ bool Record::ParseHeader(char*& p, char*& end) {
binary_ = p;
CHECK(end != nullptr);
CHECK_SIZE(p, end, sizeof(perf_event_header));
- header = RecordHeader(p);
+ if (!header.Parse(p)) {
+ return false;
+ }
CHECK_SIZE(p, end, header.size);
end = p + header.size;
p += sizeof(perf_event_header);
@@ -496,9 +503,16 @@ bool SampleRecord::Parse(const perf_event_attr& attr, char* p, char* end) {
CHECK_SIZE_U64(p, end, 1);
MoveFromBinaryFormat(nr, p);
}
- size_t u64_count = (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) ? 1 : 0;
+ uint64_t u64_count = (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) ? 1 : 0;
u64_count += (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) ? 1 : 0;
- u64_count += ((read_format & PERF_FORMAT_ID) ? 2 : 1) * nr;
+ if (__builtin_add_overflow(u64_count, nr, &u64_count)) {
+ return false;
+ }
+ if (read_format & PERF_FORMAT_ID) {
+ if (__builtin_add_overflow(u64_count, nr, &u64_count)) {
+ return false;
+ }
+ }
CHECK_SIZE_U64(p, end, u64_count);
if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) {
MoveFromBinaryFormat(read_data.time_enabled, p);
@@ -543,6 +557,8 @@ bool SampleRecord::Parse(const perf_event_attr& attr, char* p, char* end) {
MoveFromBinaryFormat(regs_user_data.abi, p);
if (regs_user_data.abi == 0) {
regs_user_data.reg_mask = 0;
+ regs_user_data.reg_nr = 0;
+ regs_user_data.regs = nullptr;
} else {
regs_user_data.reg_mask = attr.sample_regs_user;
size_t bit_nr = __builtin_popcountll(regs_user_data.reg_mask);
@@ -566,7 +582,7 @@ bool SampleRecord::Parse(const perf_event_attr& attr, char* p, char* end) {
}
// TODO: Add parsing of other PERF_SAMPLE_*.
if (UNLIKELY(p < end)) {
- LOG(DEBUG) << "Record has " << end - p << " bytes left\n";
+ LOG(DEBUG) << "Sample (" << time_data.time << ") has " << end - p << " bytes left";
}
return true;
}
@@ -697,10 +713,16 @@ SampleRecord::SampleRecord(const perf_event_attr& attr, uint64_t id, uint64_t ip
}
void SampleRecord::ReplaceRegAndStackWithCallChain(const std::vector<uint64_t>& ips) {
- uint32_t size_added_in_callchain = sizeof(uint64_t) * (ips.size() + 1);
- uint32_t size_reduced_in_reg_stack =
- regs_user_data.reg_nr * sizeof(uint64_t) + stack_user_data.size + sizeof(uint64_t);
- uint32_t new_size = size() + size_added_in_callchain - size_reduced_in_reg_stack;
+ uint32_t add_size_in_callchain = ips.empty() ? 0 : sizeof(uint64_t) * (ips.size() + 1);
+ uint32_t reduce_size_in_reg = (regs_user_data.reg_nr + 1) * sizeof(uint64_t);
+ uint32_t reduce_size_in_stack =
+ stack_user_data.size == 0 ? sizeof(uint64_t) : (stack_user_data.size + 2 * sizeof(uint64_t));
+ uint32_t reduce_size = reduce_size_in_reg + reduce_size_in_stack;
+
+ uint32_t new_size = size() + add_size_in_callchain;
+ CHECK_GT(new_size, reduce_size);
+ new_size -= reduce_size;
+ sample_type &= ~(PERF_SAMPLE_STACK_USER | PERF_SAMPLE_REGS_USER);
BuildBinaryWithNewCallChain(new_size, ips);
}
@@ -802,16 +824,21 @@ void SampleRecord::BuildBinaryWithNewCallChain(uint32_t new_size,
memcpy(p, &raw_data.size, sizeof(uint32_t));
}
uint64_t* p64 = reinterpret_cast<uint64_t*>(p);
- p64 -= ips.size();
- memcpy(p64, ips.data(), ips.size() * sizeof(uint64_t));
- *--p64 = PERF_CONTEXT_USER;
- if (callchain_data.ip_nr > 0) {
- p64 -= callchain_data.ip_nr;
+ if (!ips.empty()) {
+ p64 -= ips.size();
+ memcpy(p64, ips.data(), ips.size() * sizeof(uint64_t));
+ *--p64 = PERF_CONTEXT_USER;
+ }
+ p64 -= callchain_data.ip_nr;
+ if (p64 != callchain_data.ips) {
memcpy(p64, callchain_data.ips, callchain_data.ip_nr * sizeof(uint64_t));
+ callchain_data.ips = p64;
+ }
+ p64--;
+ if (!ips.empty()) {
+ callchain_data.ip_nr += 1 + ips.size();
+ *p64 = callchain_data.ip_nr;
}
- callchain_data.ips = p64;
- callchain_data.ip_nr += 1 + ips.size();
- *--p64 = callchain_data.ip_nr;
CHECK_EQ(callchain_pos, static_cast<size_t>(reinterpret_cast<char*>(p64) - new_binary))
<< "record time " << time_data.time;
if (new_binary != binary_) {
@@ -1084,6 +1111,7 @@ bool AuxTraceInfoRecord::Parse(const perf_event_attr&, char* p, char* end) {
return false;
}
for (uint32_t i = 0; i < data->nr_cpu; ++i) {
+ CHECK_SIZE(p, end, sizeof(uint64_t));
uint64_t magic = *reinterpret_cast<uint64_t*>(p);
if (magic == MAGIC_ETM4) {
CHECK_SIZE(p, end, sizeof(ETM4Info));
@@ -1338,8 +1366,10 @@ TracingDataRecord::TracingDataRecord(const std::vector<char>& tracing_data) {
}
void TracingDataRecord::DumpData(size_t indent) const {
- Tracing tracing(std::vector<char>(data, data + data_size));
- tracing.Dump(indent);
+ auto tracing = Tracing::Create(std::vector<char>(data, data + data_size));
+ if (tracing) {
+ tracing->Dump(indent);
+ }
}
bool EventIdRecord::Parse(const perf_event_attr&, char* p, char* end) {
diff --git a/simpleperf/record.h b/simpleperf/record.h
index 325f6011..925eaaa0 100644
--- a/simpleperf/record.h
+++ b/simpleperf/record.h
@@ -152,7 +152,7 @@ struct RecordHeader {
RecordHeader() : type(0), misc(0), size(0) {}
- explicit RecordHeader(const char* p) {
+ bool Parse(const char* p) {
auto pheader = reinterpret_cast<const perf_event_header*>(p);
if (pheader->type < SIMPLE_PERF_RECORD_TYPE_START) {
type = pheader->type;
@@ -164,6 +164,11 @@ struct RecordHeader {
misc = 0;
size = (sheader->size1 << 16) | sheader->size0;
}
+ if (size < sizeof(perf_event_header)) {
+ LOG(ERROR) << "invalid record";
+ return false;
+ }
+ return true;
}
void MoveToBinaryFormat(char*& p) const {
@@ -433,7 +438,7 @@ struct AuxRecord : public Record {
uint64_t aux_offset;
uint64_t aux_size;
uint64_t flags;
- } * data;
+ }* data;
bool Parse(const perf_event_attr& attr, char* p, char* end) override;
bool Unformatted() const { return data->flags & PERF_AUX_FLAG_CORESIGHT_FORMAT_RAW; }
@@ -514,7 +519,7 @@ struct AuxTraceInfoRecord : public Record {
uint32_t pmu_type;
uint64_t snapshot;
uint64_t info[0];
- } * data;
+ }* data;
AuxTraceInfoRecord() {}
AuxTraceInfoRecord(const DataType& data, const std::vector<ETEInfo>& ete_info);
@@ -533,7 +538,7 @@ struct AuxTraceRecord : public Record {
uint32_t tid;
uint32_t cpu;
uint32_t reserved1;
- } * data;
+ }* 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 {
@@ -545,6 +550,7 @@ struct AuxTraceRecord : public Record {
AuxTraceRecord(uint64_t aux_size, uint64_t offset, uint32_t idx, uint32_t tid, uint32_t cpu);
bool Parse(const perf_event_attr& attr, char* p, char* end) override;
+ uint32_t Cpu() const override { return data->cpu; }
static size_t Size() { return sizeof(perf_event_header) + sizeof(DataType); }
protected:
diff --git a/simpleperf/record_file.h b/simpleperf/record_file.h
index 1ada0804..fa696269 100644
--- a/simpleperf/record_file.h
+++ b/simpleperf/record_file.h
@@ -81,7 +81,7 @@ class RecordFileWriter {
RecordFileWriter(const std::string& filename, FILE* fp, bool own_fp);
~RecordFileWriter();
- bool WriteAttrSection(const std::vector<EventAttrWithId>& attr_ids);
+ bool WriteAttrSection(const EventAttrIds& attr_ids);
bool WriteRecord(const Record& record);
bool WriteData(const void* buf, size_t len);
@@ -141,14 +141,7 @@ class RecordFileReader {
const PerfFileFormat::FileHeader& FileHeader() const { return header_; }
- std::vector<EventAttrWithId> AttrSection() const {
- std::vector<EventAttrWithId> result(file_attrs_.size());
- for (size_t i = 0; i < file_attrs_.size(); ++i) {
- result[i].attr = &file_attrs_[i].attr;
- result[i].ids = event_ids_for_file_attrs_[i];
- }
- return result;
- }
+ const EventAttrIds& AttrSection() const { return event_attrs_; }
const std::unordered_map<uint64_t, size_t>& EventIdMap() const { return event_id_to_attr_map_; }
@@ -183,18 +176,24 @@ class RecordFileReader {
// File feature section contains many file information. This function reads
// one file information located at [read_pos]. [read_pos] is 0 at the first
- // call, and is updated to point to the next file information. Return true
- // if read successfully, and return false if there is no more file
- // information.
- bool ReadFileFeature(size_t& read_pos, FileFeature* file);
+ // call, and is updated to point to the next file information.
+ // When read successfully, return true and set error to false.
+ // When no more data to read, return false and set error to false.
+ // When having error, return false and set error to true.
+ bool ReadFileFeature(uint64_t& read_pos, FileFeature& file, bool& error);
const std::unordered_map<std::string, std::string>& GetMetaInfoFeature() { return meta_info_; }
std::string GetClockId();
std::optional<DebugUnwindFeature> ReadDebugUnwindFeature();
- void LoadBuildIdAndFileFeatures(ThreadTree& thread_tree);
+ bool LoadBuildIdAndFileFeatures(ThreadTree& thread_tree);
- bool ReadAuxData(uint32_t cpu, uint64_t aux_offset, void* buf, size_t size);
+ // Read aux data into buf.
+ // When read successfully, return true and set error to false.
+ // When the data isn't available, return false and set error to false.
+ // When having error, return false and set error to true.
+ bool ReadAuxData(uint32_t cpu, uint64_t aux_offset, size_t size, std::vector<uint8_t>& buf,
+ bool& error);
bool Close();
@@ -204,12 +203,13 @@ class RecordFileReader {
private:
RecordFileReader(const std::string& filename, FILE* fp);
bool ReadHeader();
- bool CheckSectionDesc(const PerfFileFormat::SectionDesc& desc, uint64_t min_offset);
+ bool CheckSectionDesc(const PerfFileFormat::SectionDesc& desc, uint64_t min_offset,
+ uint64_t alignment = 1);
bool ReadAttrSection();
- bool ReadIdsForAttr(const PerfFileFormat::FileAttr& attr, std::vector<uint64_t>* ids);
+ bool ReadIdSection(const PerfFileFormat::SectionDesc& section, std::vector<uint64_t>* ids);
bool ReadFeatureSectionDescriptors();
- bool ReadFileV1Feature(size_t& read_pos, FileFeature* file);
- bool ReadFileV2Feature(size_t& read_pos, FileFeature* file);
+ bool ReadFileV1Feature(uint64_t& read_pos, uint64_t max_size, FileFeature& file);
+ bool ReadFileV2Feature(uint64_t& read_pos, uint64_t max_size, FileFeature& file);
bool ReadMetaInfoFeature();
void UseRecordingEnvironment();
std::unique_ptr<Record> ReadRecord();
@@ -222,8 +222,7 @@ class RecordFileReader {
uint64_t file_size_;
PerfFileFormat::FileHeader header_;
- std::vector<PerfFileFormat::FileAttr> file_attrs_;
- std::vector<std::vector<uint64_t>> event_ids_for_file_attrs_;
+ EventAttrIds event_attrs_;
std::unordered_map<uint64_t, size_t> event_id_to_attr_map_;
std::map<int, PerfFileFormat::SectionDesc> feature_section_descriptors_;
diff --git a/simpleperf/record_file_format.h b/simpleperf/record_file_format.h
index 53c6719b..0b36f351 100644
--- a/simpleperf/record_file_format.h
+++ b/simpleperf/record_file_format.h
@@ -82,6 +82,9 @@ file2 feature section (used to replace file feature section):
uint32_t file_msg2_size;
FileFeature file_msg2;
...
+
+etm_branch_list feature section:
+ ETMBranchList etm_branch_list; // from etm_branch_list.proto
*/
namespace simpleperf {
@@ -116,6 +119,7 @@ enum {
FEAT_DEBUG_UNWIND,
FEAT_DEBUG_UNWIND_FILE,
FEAT_FILE2,
+ FEAT_ETM_BRANCH_LIST,
FEAT_MAX_NUM = 256,
};
diff --git a/simpleperf/record_file_reader.cpp b/simpleperf/record_file_reader.cpp
index 5bfc5ac7..1b1bab7e 100644
--- a/simpleperf/record_file_reader.cpp
+++ b/simpleperf/record_file_reader.cpp
@@ -18,7 +18,9 @@
#include <fcntl.h>
#include <string.h>
+
#include <set>
+#include <string_view>
#include <vector>
#include <android-base/logging.h>
@@ -58,6 +60,7 @@ static const std::map<int, std::string> feature_name_map = {
{FEAT_DEBUG_UNWIND, "debug_unwind"},
{FEAT_DEBUG_UNWIND_FILE, "debug_unwind_file"},
{FEAT_FILE2, "file2"},
+ {FEAT_ETM_BRANCH_LIST, "etm_branch_list"},
};
std::string GetFeatureName(int feature_id) {
@@ -133,18 +136,26 @@ bool RecordFileReader::ReadHeader() {
return true;
}
-bool RecordFileReader::CheckSectionDesc(const SectionDesc& desc, uint64_t min_offset) {
+bool RecordFileReader::CheckSectionDesc(const SectionDesc& desc, uint64_t min_offset,
+ uint64_t alignment) {
uint64_t desc_end;
if (desc.offset < min_offset || __builtin_add_overflow(desc.offset, desc.size, &desc_end) ||
desc_end > file_size_) {
return false;
}
+ if (desc.size % alignment != 0) {
+ return false;
+ }
return true;
}
bool RecordFileReader::ReadAttrSection() {
size_t attr_count = header_.attrs.size / header_.attr_size;
if (header_.attr_size != sizeof(FileAttr)) {
+ if (header_.attr_size <= sizeof(SectionDesc)) {
+ LOG(ERROR) << "invalid attr section in " << filename_;
+ return false;
+ }
LOG(DEBUG) << "attr size (" << header_.attr_size << ") in " << filename_
<< " doesn't match expected size (" << sizeof(FileAttr) << ")";
}
@@ -156,42 +167,42 @@ bool RecordFileReader::ReadAttrSection() {
PLOG(ERROR) << "fseek() failed";
return false;
}
+ event_attrs_.resize(attr_count);
+ std::vector<SectionDesc> id_sections(attr_count);
+ size_t attr_size_in_file = header_.attr_size - sizeof(SectionDesc);
for (size_t i = 0; i < attr_count; ++i) {
std::vector<char> buf(header_.attr_size);
if (!Read(buf.data(), buf.size())) {
return false;
}
- // The size of perf_event_attr is changing between different linux kernel versions.
- // Make sure we copy correct data to memory.
- FileAttr attr;
- memset(&attr, 0, sizeof(attr));
- size_t section_desc_size = sizeof(attr.ids);
- size_t perf_event_attr_size = header_.attr_size - section_desc_size;
- memcpy(&attr.attr, &buf[0], std::min(sizeof(attr.attr), perf_event_attr_size));
- memcpy(&attr.ids, &buf[perf_event_attr_size], section_desc_size);
- if (!CheckSectionDesc(attr.ids, 0)) {
+ // The struct perf_event_attr is defined in a Linux header file. It can be extended in newer
+ // kernel versions with more fields and a bigger size. To disable these extensions, set their
+ // values to zero. So to copy perf_event_attr from file to memory safely, ensure the copy
+ // doesn't overflow the file or memory, and set the values of any extra fields in memory to
+ // zero.
+ if (attr_size_in_file >= sizeof(perf_event_attr)) {
+ memcpy(&event_attrs_[i].attr, &buf[0], sizeof(perf_event_attr));
+ } else {
+ memset(&event_attrs_[i].attr, 0, sizeof(perf_event_attr));
+ memcpy(&event_attrs_[i].attr, &buf[0], attr_size_in_file);
+ }
+ memcpy(&id_sections[i], &buf[attr_size_in_file], sizeof(SectionDesc));
+ if (!CheckSectionDesc(id_sections[i], 0, sizeof(uint64_t))) {
LOG(ERROR) << "invalid attr section in " << filename_;
return false;
}
- file_attrs_.push_back(attr);
}
- if (file_attrs_.size() > 1) {
- std::vector<perf_event_attr> attrs;
- for (const auto& file_attr : file_attrs_) {
- attrs.push_back(file_attr.attr);
- }
- if (!GetCommonEventIdPositionsForAttrs(attrs, &event_id_pos_in_sample_records_,
+ if (event_attrs_.size() > 1) {
+ if (!GetCommonEventIdPositionsForAttrs(event_attrs_, &event_id_pos_in_sample_records_,
&event_id_reverse_pos_in_non_sample_records_)) {
return false;
}
}
- for (size_t i = 0; i < file_attrs_.size(); ++i) {
- std::vector<uint64_t> ids;
- if (!ReadIdsForAttr(file_attrs_[i], &ids)) {
+ for (size_t i = 0; i < attr_count; ++i) {
+ if (!ReadIdSection(id_sections[i], &event_attrs_[i].ids)) {
return false;
}
- event_ids_for_file_attrs_.push_back(ids);
- for (auto id : ids) {
+ for (auto id : event_attrs_[i].ids) {
event_id_to_attr_map_[id] = i;
}
}
@@ -227,14 +238,14 @@ bool RecordFileReader::ReadFeatureSectionDescriptors() {
return true;
}
-bool RecordFileReader::ReadIdsForAttr(const FileAttr& attr, std::vector<uint64_t>* ids) {
- size_t id_count = attr.ids.size / sizeof(uint64_t);
- if (fseek(record_fp_, attr.ids.offset, SEEK_SET) != 0) {
+bool RecordFileReader::ReadIdSection(const SectionDesc& section, std::vector<uint64_t>* ids) {
+ size_t id_count = section.size / sizeof(uint64_t);
+ if (fseek(record_fp_, section.offset, SEEK_SET) != 0) {
PLOG(ERROR) << "fseek() failed";
return false;
}
ids->resize(id_count);
- if (!Read(ids->data(), attr.ids.size)) {
+ if (!Read(ids->data(), section.size)) {
return false;
}
return true;
@@ -289,36 +300,37 @@ bool RecordFileReader::ReadRecord(std::unique_ptr<Record>& record) {
std::unique_ptr<Record> RecordFileReader::ReadRecord() {
char header_buf[Record::header_size()];
- if (!Read(header_buf, Record::header_size())) {
+ RecordHeader header;
+ if (!Read(header_buf, Record::header_size()) || !header.Parse(header_buf)) {
return nullptr;
}
- RecordHeader header(header_buf);
std::unique_ptr<char[]> p;
if (header.type == SIMPLE_PERF_RECORD_SPLIT) {
// Read until meeting a RECORD_SPLIT_END record.
std::vector<char> buf;
- size_t cur_size = 0;
- char header_buf[Record::header_size()];
while (header.type == SIMPLE_PERF_RECORD_SPLIT) {
- size_t bytes_to_read = header.size - Record::header_size();
- buf.resize(cur_size + bytes_to_read);
- if (!Read(&buf[cur_size], bytes_to_read)) {
+ size_t add_size = header.size - Record::header_size();
+ size_t old_size = buf.size();
+ buf.resize(old_size + add_size);
+ if (!Read(&buf[old_size], add_size)) {
return nullptr;
}
- cur_size += bytes_to_read;
read_record_size_ += header.size;
- if (!Read(header_buf, Record::header_size())) {
+ if (!Read(header_buf, Record::header_size()) || !header.Parse(header_buf)) {
return nullptr;
}
- header = RecordHeader(header_buf);
}
if (header.type != SIMPLE_PERF_RECORD_SPLIT_END) {
LOG(ERROR) << "SPLIT records are not followed by a SPLIT_END record.";
return nullptr;
}
read_record_size_ += header.size;
- header = RecordHeader(buf.data());
- p.reset(new char[header.size]);
+ if (buf.size() < Record::header_size() || !header.Parse(buf.data()) ||
+ header.size != buf.size()) {
+ LOG(ERROR) << "invalid record merged from SPLIT records";
+ return nullptr;
+ }
+ p.reset(new char[buf.size()]);
memcpy(p.get(), buf.data(), buf.size());
} else {
p.reset(new char[header.size]);
@@ -331,8 +343,8 @@ std::unique_ptr<Record> RecordFileReader::ReadRecord() {
read_record_size_ += header.size;
}
- const perf_event_attr* attr = &file_attrs_[0].attr;
- if (file_attrs_.size() > 1 && header.type < PERF_RECORD_USER_DEFINED_TYPE_START) {
+ const perf_event_attr* attr = &event_attrs_[0].attr;
+ if (event_attrs_.size() > 1 && header.type < PERF_RECORD_USER_DEFINED_TYPE_START) {
bool has_event_id = false;
uint64_t event_id;
if (header.type == PERF_RECORD_SAMPLE) {
@@ -350,7 +362,7 @@ std::unique_ptr<Record> RecordFileReader::ReadRecord() {
if (has_event_id) {
auto it = event_id_to_attr_map_.find(event_id);
if (it != event_id_to_attr_map_.end()) {
- attr = &file_attrs_[it->second].attr;
+ attr = &event_attrs_[it->second].attr;
}
}
}
@@ -390,8 +402,9 @@ bool RecordFileReader::ReadAtOffset(uint64_t offset, void* buf, size_t len) {
void RecordFileReader::ProcessEventIdRecord(const EventIdRecord& r) {
for (size_t i = 0; i < r.count; ++i) {
- event_ids_for_file_attrs_[r.data[i].attr_id].push_back(r.data[i].event_id);
- event_id_to_attr_map_[r.data[i].event_id] = r.data[i].attr_id;
+ const auto& data = r.data[i];
+ event_attrs_[data.attr_id].ids.push_back(data.event_id);
+ event_id_to_attr_map_[data.event_id] = data.attr_id;
}
}
@@ -440,22 +453,25 @@ bool RecordFileReader::ReadFeatureSection(int feature, std::string* data) {
std::vector<std::string> RecordFileReader::ReadCmdlineFeature() {
std::vector<char> buf;
if (!ReadFeatureSection(FEAT_CMDLINE, &buf)) {
- return std::vector<std::string>();
+ return {};
}
- const char* p = buf.data();
- const char* end = buf.data() + buf.size();
+ BinaryReader reader(buf.data(), buf.size());
std::vector<std::string> cmdline;
- uint32_t arg_count;
- MoveFromBinaryFormat(arg_count, p);
- CHECK_LE(p, end);
- for (size_t i = 0; i < arg_count; ++i) {
- uint32_t len;
- MoveFromBinaryFormat(len, p);
- CHECK_LE(p + len, end);
- cmdline.push_back(p);
- p += len;
+
+ uint32_t arg_count = 0;
+ reader.Read(arg_count);
+ for (size_t i = 0; i < arg_count && !reader.error; ++i) {
+ uint32_t aligned_len;
+ reader.Read(aligned_len);
+ cmdline.emplace_back(reader.ReadString());
+ uint32_t len = cmdline.back().size() + 1;
+ if (aligned_len != Align(len, 64)) {
+ reader.error = true;
+ break;
+ }
+ reader.Move(aligned_len - len);
}
- return cmdline;
+ return reader.error ? std::vector<std::string>() : cmdline;
}
std::vector<BuildIdRecord> RecordFileReader::ReadBuildIdFeature() {
@@ -466,16 +482,16 @@ std::vector<BuildIdRecord> RecordFileReader::ReadBuildIdFeature() {
const char* p = buf.data();
const char* end = buf.data() + buf.size();
std::vector<BuildIdRecord> result;
- while (p < end) {
+ while (p + sizeof(perf_event_header) < end) {
auto header = reinterpret_cast<const perf_event_header*>(p);
- if (p + header->size > end) {
+ if ((header->size <= sizeof(perf_event_header)) || (header->size > end - p)) {
return {};
}
std::unique_ptr<char[]> binary(new char[header->size]);
memcpy(binary.get(), p, header->size);
p += header->size;
BuildIdRecord record;
- if (!record.Parse(file_attrs_[0].attr, binary.get(), binary.get() + header->size)) {
+ if (!record.Parse(event_attrs_[0].attr, binary.get(), binary.get() + header->size)) {
return {};
}
binary.release();
@@ -492,12 +508,11 @@ std::string RecordFileReader::ReadFeatureString(int feature) {
if (!ReadFeatureSection(feature, &buf)) {
return std::string();
}
- const char* p = buf.data();
- const char* end = buf.data() + buf.size();
- uint32_t len;
- MoveFromBinaryFormat(len, p);
- CHECK_LE(p + len, end);
- return p;
+ BinaryReader reader(buf.data(), buf.size());
+ uint32_t len = 0;
+ reader.Read(len);
+ std::string s = reader.ReadString();
+ return reader.error ? "" : s;
}
std::vector<uint64_t> RecordFileReader::ReadAuxTraceFeature() {
@@ -505,144 +520,163 @@ std::vector<uint64_t> RecordFileReader::ReadAuxTraceFeature() {
if (!ReadFeatureSection(FEAT_AUXTRACE, &buf)) {
return {};
}
- std::vector<uint64_t> auxtrace_offset;
- const char* p = buf.data();
- const char* end = buf.data() + buf.size();
- if (buf.size() / sizeof(uint64_t) % 2 == 1) {
+ BinaryReader reader(buf.data(), buf.size());
+ if (reader.LeftSize() % sizeof(uint64_t) != 0) {
+ return {};
+ }
+ if (reader.LeftSize() / sizeof(uint64_t) % 2 == 1) {
// Recording files generated by linux perf contain an extra uint64 field. Skip it here.
- p += sizeof(uint64_t);
+ reader.Move(sizeof(uint64_t));
}
- while (p < end) {
+
+ std::vector<uint64_t> auxtrace_offset;
+ while (!reader.error && reader.LeftSize() > 0u) {
uint64_t offset;
uint64_t size;
- MoveFromBinaryFormat(offset, p);
+ reader.Read(offset);
+ reader.Read(size);
auxtrace_offset.push_back(offset);
- MoveFromBinaryFormat(size, p);
- CHECK_EQ(size, AuxTraceRecord::Size());
+ if (size != AuxTraceRecord::Size()) {
+ reader.error = true;
+ }
}
- return auxtrace_offset;
+ return reader.error ? std::vector<uint64_t>() : auxtrace_offset;
}
-bool RecordFileReader::ReadFileFeature(size_t& read_pos, FileFeature* file) {
- file->Clear();
- if (HasFeature(FEAT_FILE)) {
- return ReadFileV1Feature(read_pos, file);
- }
- if (HasFeature(FEAT_FILE2)) {
- return ReadFileV2Feature(read_pos, file);
- }
- return false;
-}
+bool RecordFileReader::ReadFileFeature(uint64_t& read_pos, FileFeature& file, bool& error) {
+ file.Clear();
+ error = false;
-bool RecordFileReader::ReadFileV1Feature(size_t& read_pos, FileFeature* file) {
- auto it = feature_section_descriptors_.find(FEAT_FILE);
- if (it == feature_section_descriptors_.end()) {
+ bool use_v1 = false;
+ PerfFileFormat::SectionDesc desc;
+ if (auto it = feature_section_descriptors_.find(FEAT_FILE);
+ it != feature_section_descriptors_.end()) {
+ use_v1 = true;
+ desc = it->second;
+ } else if (auto it = feature_section_descriptors_.find(FEAT_FILE2);
+ it != feature_section_descriptors_.end()) {
+ desc = it->second;
+ } else {
return false;
}
- if (read_pos >= it->second.size) {
+
+ if (read_pos >= desc.size) {
return false;
}
if (read_pos == 0) {
- if (fseek(record_fp_, it->second.offset, SEEK_SET) != 0) {
+ if (fseek(record_fp_, desc.offset, SEEK_SET) != 0) {
PLOG(ERROR) << "fseek() failed";
+ error = true;
return false;
}
}
- uint32_t size;
- if (!Read(&size, 4)) {
+
+ bool result = false;
+ if (use_v1) {
+ result = ReadFileV1Feature(read_pos, desc.size - read_pos, file);
+ } else {
+ result = ReadFileV2Feature(read_pos, desc.size - read_pos, file);
+ }
+ if (!result) {
+ LOG(ERROR) << "failed to read file feature section";
+ error = true;
+ }
+ return result;
+}
+
+bool RecordFileReader::ReadFileV1Feature(uint64_t& read_pos, uint64_t max_size, FileFeature& file) {
+ uint32_t size = 0;
+ if (max_size < 4 || !Read(&size, 4) || max_size - 4 < size) {
return false;
}
+ read_pos += 4;
std::vector<char> buf(size);
if (!Read(buf.data(), size)) {
return false;
}
- read_pos += 4 + size;
- const char* p = buf.data();
- file->path = p;
- p += file->path.size() + 1;
- uint32_t file_type;
- MoveFromBinaryFormat(file_type, p);
+ read_pos += size;
+ BinaryReader reader(buf.data(), buf.size());
+ file.path = reader.ReadString();
+ uint32_t file_type = 0;
+ reader.Read(file_type);
if (file_type > DSO_UNKNOWN_FILE) {
- LOG(ERROR) << "unknown file type for " << file->path
+ LOG(ERROR) << "unknown file type for " << file.path
<< " in file feature section: " << file_type;
return false;
}
- file->type = static_cast<DsoType>(file_type);
- MoveFromBinaryFormat(file->min_vaddr, p);
- uint32_t symbol_count;
- MoveFromBinaryFormat(symbol_count, p);
- file->symbols.reserve(symbol_count);
- for (uint32_t i = 0; i < symbol_count; ++i) {
- uint64_t start_vaddr;
- uint32_t len;
- MoveFromBinaryFormat(start_vaddr, p);
- MoveFromBinaryFormat(len, p);
- std::string name = p;
- p += name.size() + 1;
- file->symbols.emplace_back(name, start_vaddr, len);
- }
- if (file->type == DSO_DEX_FILE) {
- uint32_t offset_count;
- MoveFromBinaryFormat(offset_count, p);
- file->dex_file_offsets.resize(offset_count);
- MoveFromBinaryFormat(file->dex_file_offsets.data(), offset_count, p);
- }
- file->file_offset_of_min_vaddr = std::numeric_limits<uint64_t>::max();
- if ((file->type == DSO_ELF_FILE || file->type == DSO_KERNEL_MODULE) &&
- static_cast<size_t>(p - buf.data()) < size) {
- MoveFromBinaryFormat(file->file_offset_of_min_vaddr, p);
- }
- CHECK_EQ(size, static_cast<size_t>(p - buf.data()))
- << "file " << file->path << ", type " << file->type;
- return true;
-}
-
-bool RecordFileReader::ReadFileV2Feature(size_t& read_pos, FileFeature* file) {
- auto it = feature_section_descriptors_.find(FEAT_FILE2);
- if (it == feature_section_descriptors_.end()) {
- return false;
- }
- if (read_pos >= it->second.size) {
+ file.type = static_cast<DsoType>(file_type);
+ reader.Read(file.min_vaddr);
+ uint32_t symbol_count = 0;
+ reader.Read(symbol_count);
+ if (symbol_count > size) {
return false;
}
- if (read_pos == 0) {
- if (fseek(record_fp_, it->second.offset, SEEK_SET) != 0) {
- PLOG(ERROR) << "fseek() failed";
+ file.symbols.reserve(symbol_count);
+ while (symbol_count-- > 0) {
+ uint64_t start_vaddr = 0;
+ uint32_t len = 0;
+ reader.Read(start_vaddr);
+ reader.Read(len);
+ std::string name = reader.ReadString();
+ file.symbols.emplace_back(name, start_vaddr, len);
+ }
+ if (file.type == DSO_DEX_FILE) {
+ uint32_t offset_count = 0;
+ reader.Read(offset_count);
+ if (offset_count > size) {
return false;
}
+ file.dex_file_offsets.resize(offset_count);
+ reader.Read(file.dex_file_offsets.data(), offset_count);
}
+ file.file_offset_of_min_vaddr = std::numeric_limits<uint64_t>::max();
+ if ((file.type == DSO_ELF_FILE || file.type == DSO_KERNEL_MODULE) && !reader.error &&
+ reader.LeftSize() > 0) {
+ reader.Read(file.file_offset_of_min_vaddr);
+ }
+ return !reader.error && reader.LeftSize() == 0;
+}
+
+bool RecordFileReader::ReadFileV2Feature(uint64_t& read_pos, uint64_t max_size, FileFeature& file) {
uint32_t size;
- if (!Read(&size, 4)) {
+ if (max_size < 4 || !Read(&size, 4) || max_size - 4 < size) {
return false;
}
- read_pos += 4 + size;
+ read_pos += 4;
std::string s(size, '\0');
if (!Read(s.data(), size)) {
return false;
}
+ read_pos += size;
proto::FileFeature proto_file;
if (!proto_file.ParseFromString(s)) {
return false;
}
- file->path = proto_file.path();
- file->type = static_cast<DsoType>(proto_file.type());
- file->min_vaddr = proto_file.min_vaddr();
- file->symbols.reserve(proto_file.symbol_size());
+ file.path = proto_file.path();
+ file.type = static_cast<DsoType>(proto_file.type());
+ file.min_vaddr = proto_file.min_vaddr();
+ file.symbols.reserve(proto_file.symbol_size());
for (size_t i = 0; i < proto_file.symbol_size(); i++) {
const auto& proto_symbol = proto_file.symbol(i);
- file->symbols.emplace_back(proto_symbol.name(), proto_symbol.vaddr(), proto_symbol.len());
+ file.symbols.emplace_back(proto_symbol.name(), proto_symbol.vaddr(), proto_symbol.len());
}
- if (file->type == DSO_DEX_FILE) {
- CHECK(proto_file.has_dex_file());
+ if (file.type == DSO_DEX_FILE) {
+ if (!proto_file.has_dex_file()) {
+ return false;
+ }
const auto& dex_file_offsets = proto_file.dex_file().dex_file_offset();
- file->dex_file_offsets.insert(file->dex_file_offsets.end(), dex_file_offsets.begin(),
- dex_file_offsets.end());
- } else if (file->type == DSO_ELF_FILE) {
- CHECK(proto_file.has_elf_file());
- file->file_offset_of_min_vaddr = proto_file.elf_file().file_offset_of_min_vaddr();
- } else if (file->type == DSO_KERNEL_MODULE) {
- CHECK(proto_file.has_kernel_module());
- file->file_offset_of_min_vaddr = proto_file.kernel_module().memory_offset_of_min_vaddr();
+ file.dex_file_offsets.insert(file.dex_file_offsets.end(), dex_file_offsets.begin(),
+ dex_file_offsets.end());
+ } else if (file.type == DSO_ELF_FILE) {
+ if (!proto_file.has_elf_file()) {
+ return false;
+ }
+ file.file_offset_of_min_vaddr = proto_file.elf_file().file_offset_of_min_vaddr();
+ } else if (file.type == DSO_KERNEL_MODULE) {
+ if (!proto_file.has_kernel_module()) {
+ return false;
+ }
+ file.file_offset_of_min_vaddr = proto_file.kernel_module().memory_offset_of_min_vaddr();
}
return true;
}
@@ -653,14 +687,24 @@ bool RecordFileReader::ReadMetaInfoFeature() {
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;
+ std::string_view s(buf.data(), buf.size());
+ size_t key_start = 0;
+ while (key_start < s.size()) {
+ // Parse a C-string for key.
+ size_t key_end = s.find('\0', key_start);
+ if (key_end == key_start || key_end == s.npos) {
+ LOG(ERROR) << "invalid meta info in " << filename_;
+ return false;
+ }
+ // Parse a C-string for value.
+ size_t value_start = key_end + 1;
+ size_t value_end = s.find('\0', value_start);
+ if (value_end == value_start || value_end == s.npos) {
+ LOG(ERROR) << "invalid meta info in " << filename_;
+ return false;
+ }
+ meta_info_[&s[key_start]] = &s[value_start];
+ key_start = value_end + 1;
}
}
return true;
@@ -691,7 +735,7 @@ std::optional<DebugUnwindFeature> RecordFileReader::ReadDebugUnwindFeature() {
return std::nullopt;
}
-void RecordFileReader::LoadBuildIdAndFileFeatures(ThreadTree& thread_tree) {
+bool RecordFileReader::LoadBuildIdAndFileFeatures(ThreadTree& thread_tree) {
std::vector<BuildIdRecord> records = ReadBuildIdFeature();
std::vector<std::pair<std::string, BuildId>> build_ids;
for (auto& r : records) {
@@ -699,22 +743,34 @@ void RecordFileReader::LoadBuildIdAndFileFeatures(ThreadTree& thread_tree) {
}
Dso::SetBuildIds(build_ids);
- if (HasFeature(PerfFileFormat::FEAT_FILE) || HasFeature(PerfFileFormat::FEAT_FILE2)) {
- FileFeature file_feature;
- size_t read_pos = 0;
- while (ReadFileFeature(read_pos, &file_feature)) {
- thread_tree.AddDsoInfo(file_feature);
+ FileFeature file_feature;
+ uint64_t read_pos = 0;
+ bool error = false;
+ while (ReadFileFeature(read_pos, file_feature, error)) {
+ if (!thread_tree.AddDsoInfo(file_feature)) {
+ return false;
}
}
+ return !error;
}
-bool RecordFileReader::ReadAuxData(uint32_t cpu, uint64_t aux_offset, void* buf, size_t size) {
+bool RecordFileReader::ReadAuxData(uint32_t cpu, uint64_t aux_offset, size_t size,
+ std::vector<uint8_t>& buf, bool& error) {
+ error = false;
long saved_pos = ftell(record_fp_);
if (saved_pos == -1) {
PLOG(ERROR) << "ftell() failed";
+ error = true;
+ return false;
+ }
+ OverflowResult aux_end = SafeAdd(aux_offset, size);
+ if (aux_end.overflow) {
+ LOG(ERROR) << "aux_end overflow";
+ error = true;
return false;
}
if (aux_data_location_.empty() && !BuildAuxDataLocation()) {
+ error = true;
return false;
}
AuxDataLocation* location = nullptr;
@@ -726,21 +782,27 @@ bool RecordFileReader::ReadAuxData(uint32_t cpu, uint64_t aux_offset, void* buf,
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) {
+ if (location_it->aux_offset + location_it->aux_size >= aux_end.value) {
location = &*location_it;
}
}
}
if (location == nullptr) {
- LOG(ERROR) << "failed to find file offset of aux data: cpu " << cpu << ", aux_offset "
- << aux_offset << ", size " << size;
+ // ETM data can be dropped when recording if the userspace buffer is full. This isn't an error.
+ LOG(INFO) << "aux data is missing: cpu " << cpu << ", aux_offset " << aux_offset << ", size "
+ << size << ". Probably the data is lost when recording.";
return false;
}
- if (!ReadAtOffset(aux_offset - location->aux_offset + location->file_offset, buf, size)) {
+ if (buf.size() < size) {
+ buf.resize(size);
+ }
+ if (!ReadAtOffset(aux_offset - location->aux_offset + location->file_offset, buf.data(), size)) {
+ error = true;
return false;
}
if (fseek(record_fp_, saved_pos, SEEK_SET) != 0) {
PLOG(ERROR) << "fseek() failed";
+ error = true;
return false;
}
return true;
@@ -748,21 +810,36 @@ bool RecordFileReader::ReadAuxData(uint32_t cpu, uint64_t aux_offset, void* buf,
bool RecordFileReader::BuildAuxDataLocation() {
std::vector<uint64_t> auxtrace_offset = ReadAuxTraceFeature();
- if (auxtrace_offset.empty()) {
- LOG(ERROR) << "failed to read auxtrace feature section";
- return false;
- }
std::unique_ptr<char[]> buf(new char[AuxTraceRecord::Size()]);
for (auto offset : auxtrace_offset) {
if (!ReadAtOffset(offset, buf.get(), AuxTraceRecord::Size())) {
return false;
}
AuxTraceRecord auxtrace;
- if (!auxtrace.Parse(file_attrs_[0].attr, buf.get(), buf.get() + AuxTraceRecord::Size())) {
+ if (!auxtrace.Parse(event_attrs_[0].attr, buf.get(), buf.get() + AuxTraceRecord::Size())) {
return false;
}
- aux_data_location_[auxtrace.data->cpu].emplace_back(
- auxtrace.data->offset, auxtrace.data->aux_size, offset + auxtrace.size());
+ AuxDataLocation location(auxtrace.data->offset, auxtrace.data->aux_size,
+ offset + auxtrace.size());
+ OverflowResult aux_end = SafeAdd(location.aux_offset, location.aux_size);
+ OverflowResult file_end = SafeAdd(location.file_offset, location.aux_size);
+ if (aux_end.overflow || file_end.overflow || file_end.value > file_size_) {
+ LOG(ERROR) << "invalid auxtrace feature section";
+ return false;
+ }
+ auto location_it = aux_data_location_.find(auxtrace.data->cpu);
+ if (location_it != aux_data_location_.end()) {
+ const AuxDataLocation& prev_location = location_it->second.back();
+ uint64_t prev_aux_end = prev_location.aux_offset + prev_location.aux_size;
+ // The AuxTraceRecords should be sorted by aux_offset for each cpu.
+ if (prev_aux_end > location.aux_offset) {
+ LOG(ERROR) << "invalid auxtrace feature section";
+ return false;
+ }
+ location_it->second.emplace_back(location);
+ } else {
+ aux_data_location_[auxtrace.data->cpu].emplace_back(location);
+ }
}
return true;
}
diff --git a/simpleperf/record_file_test.cpp b/simpleperf/record_file_test.cpp
index bf0436ad..e43e4bdf 100644
--- a/simpleperf/record_file_test.cpp
+++ b/simpleperf/record_file_test.cpp
@@ -40,20 +40,18 @@ class RecordFileTest : public ::testing::Test {
void SetUp() override { close(tmpfile_.release()); }
void AddEventType(const std::string& event_type_str) {
+ uint64_t fake_id = attr_ids_.size();
+ attr_ids_.resize(attr_ids_.size() + 1);
+ EventAttrWithId& attr_id = attr_ids_.back();
std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType(event_type_str);
ASSERT_TRUE(event_type_modifier != nullptr);
- perf_event_attr attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
- attr.sample_id_all = 1;
- attrs_.push_back(std::unique_ptr<perf_event_attr>(new perf_event_attr(attr)));
- EventAttrWithId attr_id;
- attr_id.attr = attrs_.back().get();
- attr_id.ids.push_back(attrs_.size()); // Fake id.
- attr_ids_.push_back(attr_id);
+ attr_id.attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
+ attr_id.attr.sample_id_all = 1;
+ attr_id.ids.push_back(fake_id);
}
TemporaryFile tmpfile_;
- std::vector<std::unique_ptr<perf_event_attr>> attrs_;
- std::vector<EventAttrWithId> attr_ids_;
+ EventAttrIds attr_ids_;
};
TEST_F(RecordFileTest, smoke) {
@@ -66,7 +64,7 @@ TEST_F(RecordFileTest, smoke) {
ASSERT_TRUE(writer->WriteAttrSection(attr_ids_));
// Write data section.
- MmapRecord mmap_record(*(attr_ids_[0].attr), true, 1, 1, 0x1000, 0x2000, 0x3000,
+ MmapRecord mmap_record(attr_ids_[0].attr, true, 1, 1, 0x1000, 0x2000, 0x3000,
"mmap_record_example", attr_ids_[0].ids[0]);
ASSERT_TRUE(writer->WriteRecord(mmap_record));
@@ -86,9 +84,9 @@ TEST_F(RecordFileTest, smoke) {
// Read from a record file.
std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile_.path);
ASSERT_TRUE(reader != nullptr);
- std::vector<EventAttrWithId> attrs = reader->AttrSection();
+ const EventAttrIds& attrs = reader->AttrSection();
ASSERT_EQ(1u, attrs.size());
- ASSERT_EQ(0, memcmp(attrs[0].attr, attr_ids_[0].attr, sizeof(perf_event_attr)));
+ ASSERT_EQ(0, memcmp(&attrs[0].attr, &attr_ids_[0].attr, sizeof(perf_event_attr)));
ASSERT_EQ(attrs[0].ids, attr_ids_[0].ids);
// Read and check data section.
@@ -120,10 +118,10 @@ TEST_F(RecordFileTest, record_more_than_one_attr) {
// Read from a record file.
std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile_.path);
ASSERT_TRUE(reader != nullptr);
- std::vector<EventAttrWithId> attrs = reader->AttrSection();
+ const EventAttrIds& attrs = reader->AttrSection();
ASSERT_EQ(3u, attrs.size());
for (size_t i = 0; i < attrs.size(); ++i) {
- ASSERT_EQ(0, memcmp(attrs[i].attr, attr_ids_[i].attr, sizeof(perf_event_attr)));
+ ASSERT_EQ(0, memcmp(&attrs[i].attr, &attr_ids_[i].attr, sizeof(perf_event_attr)));
ASSERT_EQ(attrs[i].ids, attr_ids_[i].ids);
}
}
@@ -218,15 +216,16 @@ TEST_F(RecordFileTest, write_file2_feature_section) {
// Read from a record file.
std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile_.path);
ASSERT_TRUE(reader != nullptr);
- size_t read_pos = 0;
+ uint64_t read_pos = 0;
FileFeature file;
+ bool error = false;
auto check_symbol = [](const Symbol& sym1, const Symbol& sym2) {
return sym1.addr == sym2.addr && sym1.len == sym2.len && strcmp(sym1.Name(), sym2.Name()) == 0;
};
size_t file_id = 0;
- while (reader->ReadFileFeature(read_pos, &file)) {
+ while (reader->ReadFileFeature(read_pos, file, error)) {
ASSERT_LT(file_id, files.size());
const FileFeature& expected_file = files[file_id++];
ASSERT_EQ(file.path, expected_file.path);
@@ -253,5 +252,6 @@ TEST_F(RecordFileTest, write_file2_feature_section) {
ASSERT_EQ(file.file_offset_of_min_vaddr, expected_file.file_offset_of_min_vaddr);
}
}
+ ASSERT_FALSE(error);
ASSERT_EQ(file_id, files.size());
} \ No newline at end of file
diff --git a/simpleperf/record_file_writer.cpp b/simpleperf/record_file_writer.cpp
index 07737778..ed94e398 100644
--- a/simpleperf/record_file_writer.cpp
+++ b/simpleperf/record_file_writer.cpp
@@ -74,7 +74,7 @@ RecordFileWriter::~RecordFileWriter() {
}
}
-bool RecordFileWriter::WriteAttrSection(const std::vector<EventAttrWithId>& attr_ids) {
+bool RecordFileWriter::WriteAttrSection(const EventAttrIds& attr_ids) {
if (attr_ids.empty()) {
return false;
}
@@ -102,7 +102,7 @@ bool RecordFileWriter::WriteAttrSection(const std::vector<EventAttrWithId>& attr
}
for (auto& attr_id : attr_ids) {
FileAttr file_attr;
- file_attr.attr = *attr_id.attr;
+ file_attr.attr = attr_id.attr;
file_attr.ids.offset = id_section_offset;
file_attr.ids.size = attr_id.ids.size() * sizeof(uint64_t);
id_section_offset += file_attr.ids.size;
@@ -121,7 +121,7 @@ bool RecordFileWriter::WriteAttrSection(const std::vector<EventAttrWithId>& attr
data_section_offset_ = data_section_offset;
// Save event_attr for use when reading records.
- event_attr_ = *attr_ids[0].attr;
+ event_attr_ = attr_ids[0].attr;
return true;
}
@@ -202,7 +202,10 @@ bool RecordFileWriter::ReadDataSection(const std::function<void(const Record*)>&
if (!Read(record_buf.data(), Record::header_size())) {
return false;
}
- RecordHeader header(record_buf.data());
+ RecordHeader header;
+ if (!header.Parse(record_buf.data())) {
+ return false;
+ }
if (record_buf.size() < header.size) {
record_buf.resize(header.size);
}
@@ -365,7 +368,13 @@ bool RecordFileWriter::WriteFileFeature(const FileFeature& file) {
proto::FileFeature::Symbol* proto_symbol = proto_file.add_symbol();
proto_symbol->set_vaddr(symbol.addr);
proto_symbol->set_len(symbol.len);
- proto_symbol->set_name(symbol.Name());
+ // Store demangled names for rust symbols. Because simpleperf on windows host doesn't know
+ // how to demangle them.
+ if (strncmp(symbol.Name(), "_R", 2) == 0) {
+ proto_symbol->set_name(symbol.DemangledName());
+ } else {
+ proto_symbol->set_name(symbol.Name());
+ }
};
for (const Symbol& symbol : file.symbols) {
write_symbol(symbol);
diff --git a/simpleperf/record_test.cpp b/simpleperf/record_test.cpp
index ddecc9bd..20c9c18a 100644
--- a/simpleperf/record_test.cpp
+++ b/simpleperf/record_test.cpp
@@ -104,14 +104,29 @@ TEST_F(RecordTest, SampleRecord_exclude_kernel_callchain) {
}
TEST_F(RecordTest, SampleRecord_ReplaceRegAndStackWithCallChain) {
- event_attr.sample_type |= PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
- SampleRecord expected(event_attr, 0, 1, 2, 3, 4, 5, 6, {}, {1, PERF_CONTEXT_USER, 2, 3, 4, 5}, {},
- 0);
- for (size_t stack_size : {8, 1024}) {
- SampleRecord r(event_attr, 0, 1, 2, 3, 4, 5, 6, {}, {1}, std::vector<char>(stack_size), 10);
- r.ReplaceRegAndStackWithCallChain({2, 3, 4, 5});
- CheckRecordMatchBinary(r);
- CheckRecordEqual(r, expected);
+ event_attr.sample_type |= PERF_SAMPLE_CALLCHAIN;
+ std::vector<std::vector<uint64_t>> user_ip_tests = {
+ {}, // no userspace ips, just remove stack and reg fields
+ {2}, // add one userspace ip, no need to allocate new binary
+ {2, 3, 4, 5, 6, 7, 8}, // add more userspace ips, may need to allocate new binary
+ };
+ std::vector<uint64_t> stack_size_tests = {0, 8, 1024};
+
+ for (const auto& user_ips : user_ip_tests) {
+ std::vector<uint64_t> ips = {1};
+ if (!user_ips.empty()) {
+ ips.push_back(PERF_CONTEXT_USER);
+ ips.insert(ips.end(), user_ips.begin(), user_ips.end());
+ }
+ SampleRecord expected(event_attr, 0, 1, 2, 3, 4, 5, 6, {}, ips, {}, 0);
+ for (size_t stack_size : stack_size_tests) {
+ event_attr.sample_type |= PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
+ SampleRecord r(event_attr, 0, 1, 2, 3, 4, 5, 6, {}, {1}, std::vector<char>(stack_size), 10);
+ event_attr.sample_type &= ~(PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER);
+ r.ReplaceRegAndStackWithCallChain(user_ips);
+ CheckRecordMatchBinary(r);
+ CheckRecordEqual(r, expected);
+ }
}
}
diff --git a/simpleperf/report_lib_interface.cpp b/simpleperf/report_lib_interface.cpp
index 1025c325..3654c57e 100644
--- a/simpleperf/report_lib_interface.cpp
+++ b/simpleperf/report_lib_interface.cpp
@@ -200,6 +200,7 @@ class ReportLib {
const char* GetSupportedTraceOffCpuModes();
bool SetTraceOffCpuMode(const char* mode);
bool SetSampleFilter(const char** filters, int filters_len);
+ bool AggregateThreads(const char** thread_name_regex, int thread_name_regex_len);
Sample* GetNextSample();
Event* GetEventOfCurrentSample() { return &current_event_; }
@@ -240,6 +241,7 @@ class ReportLib {
FeatureSection feature_section_;
std::vector<char> feature_section_data_;
CallChainReportBuilder callchain_report_builder_;
+ ThreadReportBuilder thread_report_builder_;
std::unique_ptr<Tracing> tracing_;
RecordFilter record_filter_;
};
@@ -310,17 +312,27 @@ bool ReportLib::SetSampleFilter(const char** filters, int filters_len) {
return record_filter_.ParseOptions(options);
}
+bool ReportLib::AggregateThreads(const char** thread_name_regex, int thread_name_regex_len) {
+ std::vector<std::string> regs(thread_name_regex_len);
+ for (int i = 0; i < thread_name_regex_len; ++i) {
+ regs[i] = thread_name_regex[i];
+ }
+ return thread_report_builder_.AggregateThreads(regs);
+}
+
bool ReportLib::OpenRecordFileIfNecessary() {
if (record_file_reader_ == nullptr) {
record_file_reader_ = RecordFileReader::CreateInstance(record_filename_);
if (record_file_reader_ == nullptr) {
return false;
}
- record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
+ if (!record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_)) {
+ return false;
+ }
auto& meta_info = record_file_reader_->GetMetaInfoFeature();
if (auto it = meta_info.find("trace_offcpu"); it != meta_info.end() && it->second == "true") {
// If recorded with --trace-offcpu, default is to report on-off-cpu samples.
- std::string event_name = GetEventNameByAttr(*record_file_reader_->AttrSection()[0].attr);
+ std::string event_name = GetEventNameByAttr(record_file_reader_->AttrSection()[0].attr);
if (!android::base::StartsWith(event_name, "cpu-clock") &&
!android::base::StartsWith(event_name, "task-clock")) {
LOG(ERROR) << "Recording file " << record_filename_ << " is no longer supported. "
@@ -362,7 +374,10 @@ Sample* ReportLib::GetNextSample() {
} else if (record->type() == PERF_RECORD_TRACING_DATA ||
record->type() == SIMPLE_PERF_RECORD_TRACING_DATA) {
const auto& r = *static_cast<TracingDataRecord*>(record.get());
- tracing_.reset(new Tracing(std::vector<char>(r.data, r.data + r.data_size)));
+ tracing_ = Tracing::Create(std::vector<char>(r.data, r.data + r.data_size));
+ if (!tracing_) {
+ return nullptr;
+ }
}
}
SetCurrentSample(*sample_record_queue_.front());
@@ -442,10 +457,11 @@ void ReportLib::SetCurrentSample(const SampleRecord& r) {
current_mappings_.clear();
callchain_entries_.clear();
current_sample_.ip = r.ip_data.ip;
- current_sample_.pid = r.tid_data.pid;
- current_sample_.tid = r.tid_data.tid;
current_thread_ = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
- current_sample_.thread_comm = current_thread_->comm;
+ ThreadReport thread_report = thread_report_builder_.Build(*current_thread_);
+ current_sample_.pid = thread_report.pid;
+ current_sample_.tid = thread_report.tid;
+ current_sample_.thread_comm = thread_report.thread_name;
current_sample_.time = r.time_data.time;
current_sample_.in_kernel = r.InKernel();
current_sample_.cpu = r.cpu_data.cpu;
@@ -501,10 +517,10 @@ const EventInfo* ReportLib::FindEventOfCurrentSample() {
}
void ReportLib::CreateEvents() {
- std::vector<EventAttrWithId> attrs = record_file_reader_->AttrSection();
+ const EventAttrIds& attrs = record_file_reader_->AttrSection();
events_.resize(attrs.size());
for (size_t i = 0; i < attrs.size(); ++i) {
- events_[i].attr = *attrs[i].attr;
+ events_[i].attr = attrs[i].attr;
events_[i].name = GetEventNameByAttr(events_[i].attr);
EventInfo::TracingInfo& tracing_info = events_[i].tracing_info;
if (events_[i].attr.type == PERF_TYPE_TRACEPOINT && tracing_) {
@@ -599,6 +615,8 @@ bool AddProguardMappingFile(ReportLib* report_lib, const char* mapping_file) EXP
const char* GetSupportedTraceOffCpuModes(ReportLib* report_lib) EXPORT;
bool SetTraceOffCpuMode(ReportLib* report_lib, const char* mode) EXPORT;
bool SetSampleFilter(ReportLib* report_lib, const char** filters, int filters_len) EXPORT;
+bool AggregateThreads(ReportLib* report_lib, const char** thread_name_regex,
+ int thread_name_regex_len) EXPORT;
Sample* GetNextSample(ReportLib* report_lib) EXPORT;
Event* GetEventOfCurrentSample(ReportLib* report_lib) EXPORT;
@@ -663,6 +681,11 @@ bool SetSampleFilter(ReportLib* report_lib, const char** filters, int filters_le
return report_lib->SetSampleFilter(filters, filters_len);
}
+bool AggregateThreads(ReportLib* report_lib, const char** thread_name_regex,
+ int thread_name_regex_len) {
+ return report_lib->AggregateThreads(thread_name_regex, thread_name_regex_len);
+}
+
Sample* GetNextSample(ReportLib* report_lib) {
return report_lib->GetNextSample();
}
diff --git a/simpleperf/report_utils.cpp b/simpleperf/report_utils.cpp
index a0ff7846..ecbc6aa8 100644
--- a/simpleperf/report_utils.cpp
+++ b/simpleperf/report_utils.cpp
@@ -16,6 +16,10 @@
#include "report_utils.h"
+#include <stdlib.h>
+
+#include <android-base/parsebool.h>
+#include <android-base/scopeguard.h>
#include <android-base/strings.h>
#include "JITDebugReader.h"
@@ -23,62 +27,188 @@
namespace simpleperf {
-static bool IsArtEntry(const CallChainReportEntry& entry, bool* is_jni_trampoline) {
- if (entry.execution_type == CallChainExecutionType::NATIVE_METHOD) {
- // art_jni_trampoline/art_quick_generic_jni_trampoline are trampolines used to call jni
- // methods in art runtime. We want to hide them when hiding art frames.
- *is_jni_trampoline = android::base::EndsWith(entry.symbol->Name(), "jni_trampoline");
- return *is_jni_trampoline || android::base::EndsWith(entry.dso->Path(), "/libart.so") ||
- android::base::EndsWith(entry.dso->Path(), "/libartd.so");
- }
- return false;
-};
-
-bool CallChainReportBuilder::AddProguardMappingFile(std::string_view mapping_file) {
+bool ProguardMappingRetrace::AddProguardMappingFile(std::string_view mapping_file) {
// The mapping file format is described in
// https://www.guardsquare.com/en/products/proguard/manual/retrace.
- LineReader reader(mapping_file);
- if (!reader.Ok()) {
+ // Additional info provided by R8 is described in
+ // https://r8.googlesource.com/r8/+/refs/heads/main/doc/retrace.md.
+ line_reader_.reset(new LineReader(mapping_file));
+ android::base::ScopeGuard g([&]() { line_reader_ = nullptr; });
+
+ if (!line_reader_->Ok()) {
PLOG(ERROR) << "failed to read " << mapping_file;
return false;
}
- ProguardMappingClass* cur_class = nullptr;
+
+ MoveToNextLine();
+ while (cur_line_.type != LineType::LINE_EOF) {
+ if (cur_line_.type == LineType::CLASS_LINE) {
+ // Match line "original_classname -> obfuscated_classname:".
+ std::string_view s = cur_line_.data;
+ auto arrow_pos = s.find(" -> ");
+ auto arrow_end_pos = arrow_pos + strlen(" -> ");
+ if (auto colon_pos = s.find(':', arrow_end_pos); colon_pos != s.npos) {
+ std::string_view original_classname = s.substr(0, arrow_pos);
+ std::string obfuscated_classname(s.substr(arrow_end_pos, colon_pos - arrow_end_pos));
+ MappingClass& cur_class = class_map_[obfuscated_classname];
+ cur_class.original_classname = original_classname;
+ MoveToNextLine();
+ if (cur_line_.type == LineType::SYNTHESIZED_COMMENT) {
+ cur_class.synthesized = true;
+ MoveToNextLine();
+ }
+
+ while (cur_line_.type == LineType::METHOD_LINE) {
+ ParseMethod(cur_class);
+ }
+ continue;
+ }
+ }
+
+ // Skip unparsed line.
+ MoveToNextLine();
+ }
+ return true;
+}
+
+void ProguardMappingRetrace::ParseMethod(MappingClass& mapping_class) {
+ // Match line "... [original_classname.]original_methodname(...)... -> obfuscated_methodname".
+ std::string_view s = cur_line_.data;
+ auto arrow_pos = s.find(" -> ");
+ auto arrow_end_pos = arrow_pos + strlen(" -> ");
+ if (auto left_brace_pos = s.rfind('(', arrow_pos); left_brace_pos != s.npos) {
+ if (auto space_pos = s.rfind(' ', left_brace_pos); space_pos != s.npos) {
+ std::string_view name = s.substr(space_pos + 1, left_brace_pos - space_pos - 1);
+ bool contains_classname = name.find('.') != name.npos;
+ if (contains_classname && android::base::StartsWith(name, mapping_class.original_classname)) {
+ name.remove_prefix(mapping_class.original_classname.size() + 1);
+ contains_classname = false;
+ }
+ std::string original_methodname(name);
+ std::string obfuscated_methodname(s.substr(arrow_end_pos));
+ bool synthesized = false;
+
+ MoveToNextLine();
+ if (cur_line_.type == LineType::SYNTHESIZED_COMMENT) {
+ synthesized = true;
+ MoveToNextLine();
+ }
+
+ auto& method_map = mapping_class.method_map;
+ if (auto it = method_map.find(obfuscated_methodname); it != method_map.end()) {
+ // The obfuscated method name already exists. We don't know which one to choose.
+ // So just prefer the latter one unless it's synthesized.
+ if (!synthesized) {
+ it->second.original_name = original_methodname;
+ it->second.contains_classname = contains_classname;
+ it->second.synthesized = synthesized;
+ }
+ } else {
+ auto& method = method_map[obfuscated_methodname];
+ method.original_name = original_methodname;
+ method.contains_classname = contains_classname;
+ method.synthesized = synthesized;
+ }
+ return;
+ }
+ }
+
+ // Skip unparsed line.
+ MoveToNextLine();
+}
+
+void ProguardMappingRetrace::MoveToNextLine() {
std::string* line;
- while ((line = reader.ReadLine()) != nullptr) {
+ while ((line = line_reader_->ReadLine()) != nullptr) {
std::string_view s = *line;
- if (s.empty() || s[0] == '#') {
+ if (s.empty()) {
+ continue;
+ }
+ size_t non_space_pos = s.find_first_not_of(' ');
+ if (non_space_pos != s.npos && s[non_space_pos] == '#') {
+ // Skip all comments unless it's synthesized comment.
+ if (s.find("com.android.tools.r8.synthesized") != s.npos) {
+ cur_line_.type = SYNTHESIZED_COMMENT;
+ cur_line_.data = s;
+ return;
+ }
continue;
}
- auto arrow_pos = s.find(" -> ");
- if (arrow_pos == s.npos) {
+ if (s.find(" -> ") == s.npos) {
+ // Skip unknown lines.
continue;
}
- auto arrow_end_pos = arrow_pos + strlen(" -> ");
+ cur_line_.data = s;
+ if (s[0] == ' ') {
+ cur_line_.type = METHOD_LINE;
+ } else {
+ cur_line_.type = CLASS_LINE;
+ }
+ return;
+ }
+ cur_line_.type = LINE_EOF;
+}
- if (s[0] != ' ') {
- // Match line "original_classname -> obfuscated_classname:".
- if (auto colon_pos = s.find(':', arrow_end_pos); colon_pos != s.npos) {
- std::string_view original_classname = s.substr(0, arrow_pos);
- std::string obfuscated_classname(s.substr(arrow_end_pos, colon_pos - arrow_end_pos));
- cur_class = &proguard_class_map_[obfuscated_classname];
- cur_class->original_classname = original_classname;
- }
- } else if (cur_class != nullptr) {
- // Match line "... [original_classname.]original_methodname(...)... ->
- // obfuscated_methodname".
- if (auto left_brace_pos = s.rfind('(', arrow_pos); left_brace_pos != s.npos) {
- if (auto space_pos = s.rfind(' ', left_brace_pos); space_pos != s.npos) {
- auto original_methodname = s.substr(space_pos + 1, left_brace_pos - space_pos - 1);
- if (android::base::StartsWith(original_methodname, cur_class->original_classname)) {
- original_methodname.remove_prefix(cur_class->original_classname.size() + 1);
- }
- std::string obfuscated_methodname(s.substr(arrow_end_pos));
- cur_class->method_map[obfuscated_methodname] = original_methodname;
+bool ProguardMappingRetrace::DeObfuscateJavaMethods(std::string_view obfuscated_name,
+ std::string* original_name, bool* synthesized) {
+ if (auto split_pos = obfuscated_name.rfind('.'); split_pos != obfuscated_name.npos) {
+ std::string obfuscated_classname(obfuscated_name.substr(0, split_pos));
+
+ if (auto it = class_map_.find(obfuscated_classname); it != class_map_.end()) {
+ const MappingClass& mapping_class = it->second;
+ const auto& method_map = mapping_class.method_map;
+ std::string obfuscated_methodname(obfuscated_name.substr(split_pos + 1));
+
+ if (auto method_it = method_map.find(obfuscated_methodname); method_it != method_map.end()) {
+ const auto& method = method_it->second;
+ if (method.contains_classname) {
+ *original_name = method.original_name;
+ } else {
+ *original_name = mapping_class.original_classname + "." + method.original_name;
}
+ *synthesized = method.synthesized;
+ } else {
+ // Only the classname is obfuscated.
+ *original_name = mapping_class.original_classname + "." + obfuscated_methodname;
+ *synthesized = mapping_class.synthesized;
}
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool IsArtEntry(const CallChainReportEntry& entry, bool* is_jni_trampoline) {
+ if (entry.execution_type == CallChainExecutionType::NATIVE_METHOD) {
+ // art_jni_trampoline/art_quick_generic_jni_trampoline are trampolines used to call jni
+ // methods in art runtime. We want to hide them when hiding art frames.
+ *is_jni_trampoline = android::base::EndsWith(entry.symbol->Name(), "jni_trampoline");
+ return *is_jni_trampoline || android::base::EndsWith(entry.dso->Path(), "/libart.so") ||
+ android::base::EndsWith(entry.dso->Path(), "/libartd.so");
+ }
+ return false;
+};
+
+CallChainReportBuilder::CallChainReportBuilder(ThreadTree& thread_tree)
+ : thread_tree_(thread_tree) {
+ const char* env_name = "REMOVE_R8_SYNTHESIZED_FRAME";
+ const char* s = getenv(env_name);
+ if (s != nullptr) {
+ auto result = android::base::ParseBool(s);
+ if (result == android::base::ParseBoolResult::kError) {
+ LOG(WARNING) << "invalid value in env variable " << env_name;
+ } else if (result == android::base::ParseBoolResult::kTrue) {
+ LOG(INFO) << "R8 synthesized frames will be removed.";
+ remove_r8_synthesized_frame_ = true;
}
}
- return true;
+}
+
+bool CallChainReportBuilder::AddProguardMappingFile(std::string_view mapping_file) {
+ if (!retrace_) {
+ retrace_.reset(new ProguardMappingRetrace);
+ }
+ return retrace_->AddProguardMappingFile(mapping_file);
}
std::vector<CallChainReportEntry> CallChainReportBuilder::Build(const ThreadEntry* thread,
@@ -118,7 +248,7 @@ std::vector<CallChainReportEntry> CallChainReportBuilder::Build(const ThreadEntr
if (convert_jit_frame_) {
ConvertJITFrame(result);
}
- if (!proguard_class_map_.empty()) {
+ if (retrace_) {
DeObfuscateJavaMethods(result);
}
return result;
@@ -227,24 +357,78 @@ static bool IsJavaEntry(const CallChainReportEntry& entry) {
}
void CallChainReportBuilder::DeObfuscateJavaMethods(std::vector<CallChainReportEntry>& callchain) {
- for (auto& entry : callchain) {
+ for (size_t i = 0; i < callchain.size();) {
+ auto& entry = callchain[i];
if (!IsJavaEntry(entry)) {
+ i++;
continue;
}
std::string_view name = entry.symbol->FunctionName();
- if (auto split_pos = name.rfind('.'); split_pos != name.npos) {
- std::string obfuscated_classname(name.substr(0, split_pos));
- if (auto it = proguard_class_map_.find(obfuscated_classname);
- it != proguard_class_map_.end()) {
- const ProguardMappingClass& proguard_class = it->second;
- std::string obfuscated_methodname(name.substr(split_pos + 1));
- if (auto method_it = proguard_class.method_map.find(obfuscated_methodname);
- method_it != proguard_class.method_map.end()) {
- std::string new_symbol_name = proguard_class.original_classname + "." + method_it->second;
- entry.symbol->SetDemangledName(new_symbol_name);
- }
+ std::string original_name;
+ bool synthesized;
+ if (retrace_->DeObfuscateJavaMethods(name, &original_name, &synthesized)) {
+ if (synthesized && remove_r8_synthesized_frame_) {
+ callchain.erase(callchain.begin() + i);
+ continue;
}
+ entry.symbol->SetDemangledName(original_name);
+ }
+ i++;
+ }
+}
+
+bool ThreadReportBuilder::AggregateThreads(const std::vector<std::string>& thread_name_regex) {
+ size_t i = thread_regs_.size();
+ thread_regs_.resize(i + thread_name_regex.size());
+ for (const auto& reg_str : thread_name_regex) {
+ std::unique_ptr<RegEx> re = RegEx::Create(reg_str);
+ if (!re) {
+ return false;
+ }
+ thread_regs_[i++].re = std::move(re);
+ }
+ return true;
+}
+
+ThreadReport ThreadReportBuilder::Build(const ThreadEntry& thread) {
+ ThreadReport report(thread.pid, thread.tid, thread.comm);
+ ModifyReportToAggregateThreads(report);
+ return report;
+}
+
+void ThreadReportBuilder::ModifyReportToAggregateThreads(ThreadReport& report) {
+ if (thread_regs_.empty()) {
+ // No modification when there are no regular expressions.
+ return;
+ }
+ const std::string thread_name = report.thread_name;
+ if (auto it = thread_map_.find(thread_name); it != thread_map_.end()) {
+ // Found cached result in thread_map_.
+ if (it->second != -1) {
+ report = thread_regs_[it->second].report;
+ }
+ return;
+ }
+ // Run the slow path to walk through every regular expression.
+ size_t index;
+ for (index = 0; index < thread_regs_.size(); ++index) {
+ if (thread_regs_[index].re->Match(thread_name)) {
+ break;
+ }
+ }
+ if (index == thread_regs_.size()) {
+ thread_map_[thread_name] = -1;
+ } else {
+ thread_map_[thread_name] = static_cast<int>(index);
+ // Modify thread report.
+ auto& aggregated_report = thread_regs_[index].report;
+ if (aggregated_report.thread_name == nullptr) {
+ // Use regular expression as the name of the aggregated thread. So users know it's an
+ // aggregated thread.
+ aggregated_report =
+ ThreadReport(report.pid, report.tid, thread_regs_[index].re->GetPattern().c_str());
}
+ report = aggregated_report;
}
}
diff --git a/simpleperf/report_utils.h b/simpleperf/report_utils.h
index e4cc23d0..fa42bba6 100644
--- a/simpleperf/report_utils.h
+++ b/simpleperf/report_utils.h
@@ -23,11 +23,56 @@
#include <unordered_map>
#include <vector>
+#include "RegEx.h"
#include "dso.h"
#include "thread_tree.h"
+#include "utils.h"
namespace simpleperf {
+class ProguardMappingRetrace {
+ public:
+ // Add proguard mapping.txt to de-obfuscate minified symbols.
+ bool AddProguardMappingFile(std::string_view mapping_file);
+
+ bool DeObfuscateJavaMethods(std::string_view obfuscated_name, std::string* original_name,
+ bool* synthesized);
+
+ private:
+ struct MappingMethod {
+ std::string original_name;
+ bool contains_classname;
+ bool synthesized;
+ };
+
+ struct MappingClass {
+ std::string original_classname;
+ bool synthesized = false;
+ // Map from obfuscated method names to MappingMethod.
+ std::unordered_map<std::string, MappingMethod> method_map;
+ };
+
+ enum LineType {
+ SYNTHESIZED_COMMENT,
+ CLASS_LINE,
+ METHOD_LINE,
+ LINE_EOF,
+ };
+
+ struct LineInfo {
+ LineType type;
+ std::string_view data;
+ };
+
+ void ParseMethod(MappingClass& mapping_class);
+ void MoveToNextLine();
+
+ // Map from obfuscated class names to ProguardMappingClass.
+ std::unordered_map<std::string, MappingClass> class_map_;
+ std::unique_ptr<LineReader> line_reader_;
+ LineInfo cur_line_;
+};
+
enum class CallChainExecutionType {
NATIVE_METHOD,
INTERPRETED_JVM_METHOD,
@@ -48,7 +93,7 @@ struct CallChainReportEntry {
class CallChainReportBuilder {
public:
- CallChainReportBuilder(ThreadTree& thread_tree) : thread_tree_(thread_tree) {}
+ CallChainReportBuilder(ThreadTree& thread_tree);
// If true, remove interpreter frames both before and after a Java frame.
// Default is true.
void SetRemoveArtFrame(bool enable) { remove_art_frame_ = enable; }
@@ -67,12 +112,6 @@ class CallChainReportBuilder {
JavaMethod(Dso* dso, const Symbol* symbol) : dso(dso), symbol(symbol) {}
};
- struct ProguardMappingClass {
- std::string original_classname;
- // Map from minified method names to original method names.
- std::unordered_map<std::string, std::string> method_map;
- };
-
void MarkArtFrame(std::vector<CallChainReportEntry>& callchain);
void ConvertJITFrame(std::vector<CallChainReportEntry>& callchain);
void CollectJavaMethods();
@@ -80,11 +119,41 @@ class CallChainReportBuilder {
ThreadTree& thread_tree_;
bool remove_art_frame_ = true;
+ bool remove_r8_synthesized_frame_ = false;
bool convert_jit_frame_ = true;
bool java_method_initialized_ = false;
std::unordered_map<std::string, JavaMethod> java_method_map_;
- // Map from minified class names to ProguardMappingClass.
- std::unordered_map<std::string, ProguardMappingClass> proguard_class_map_;
+ std::unique_ptr<ProguardMappingRetrace> retrace_;
+};
+
+struct ThreadReport {
+ int pid;
+ int tid;
+ const char* thread_name;
+
+ ThreadReport(int pid = 0, int tid = 0, const char* thread_name = nullptr)
+ : pid(pid), tid(tid), thread_name(thread_name) {}
+};
+
+// Report thread info of a sample.
+class ThreadReportBuilder {
+ public:
+ // Aggregate threads with names matching the same regex.
+ bool AggregateThreads(const std::vector<std::string>& thread_name_regex);
+ ThreadReport Build(const ThreadEntry& thread);
+
+ private:
+ void ModifyReportToAggregateThreads(ThreadReport& report);
+
+ struct ThreadNameRegInfo {
+ std::unique_ptr<RegEx> re;
+ ThreadReport report;
+ };
+
+ std::vector<ThreadNameRegInfo> thread_regs_;
+ // Map from thread name to the corresponding index in thread_regs_.
+ // Return -1 if the thread name doesn't match any regular expression.
+ std::unordered_map<std::string, int> thread_map_;
};
} // namespace simpleperf
diff --git a/simpleperf/report_utils_test.cpp b/simpleperf/report_utils_test.cpp
index 401bbd7e..55771218 100644
--- a/simpleperf/report_utils_test.cpp
+++ b/simpleperf/report_utils_test.cpp
@@ -16,6 +16,8 @@
#include <gtest/gtest.h>
+#include <stdlib.h>
+
#include <string>
#include <vector>
@@ -25,6 +27,47 @@
using namespace simpleperf;
+TEST(ProguardMappingRetrace, smoke) {
+ TemporaryFile tmpfile;
+ close(tmpfile.release());
+ ASSERT_TRUE(
+ android::base::WriteStringToFile("original.class.A -> A:\n"
+ "\n"
+ " void method_a() -> a\n"
+ " void method_b() -> b\n"
+ " # {\"id\":\"com.android.tools.r8.synthesized\"}\n"
+ " # some other comments\n"
+ " void original.class.M.method_c() -> c\n"
+ " void original.class.A.method_d() -> d\n"
+ "original.class.B -> B:\n"
+ "# some other comments\n"
+ "original.class.C -> C:\n"
+ "# {\'id\':\'com.android.tools.r8.synthesized\'}\n",
+ tmpfile.path));
+ ProguardMappingRetrace retrace;
+ ASSERT_TRUE(retrace.AddProguardMappingFile(tmpfile.path));
+ std::string original_name;
+ bool synthesized;
+ ASSERT_TRUE(retrace.DeObfuscateJavaMethods("A.a", &original_name, &synthesized));
+ ASSERT_EQ(original_name, "original.class.A.method_a");
+ ASSERT_FALSE(synthesized);
+ ASSERT_TRUE(retrace.DeObfuscateJavaMethods("A.b", &original_name, &synthesized));
+ ASSERT_EQ(original_name, "original.class.A.method_b");
+ ASSERT_TRUE(synthesized);
+ ASSERT_TRUE(retrace.DeObfuscateJavaMethods("A.c", &original_name, &synthesized));
+ ASSERT_EQ(original_name, "original.class.M.method_c");
+ ASSERT_FALSE(synthesized);
+ ASSERT_TRUE(retrace.DeObfuscateJavaMethods("A.d", &original_name, &synthesized));
+ ASSERT_EQ(original_name, "original.class.A.method_d");
+ ASSERT_FALSE(synthesized);
+ ASSERT_TRUE(retrace.DeObfuscateJavaMethods("B.b", &original_name, &synthesized));
+ ASSERT_EQ(original_name, "original.class.B.b");
+ ASSERT_FALSE(synthesized);
+ ASSERT_TRUE(retrace.DeObfuscateJavaMethods("C.c", &original_name, &synthesized));
+ ASSERT_EQ(original_name, "original.class.C.c");
+ ASSERT_TRUE(synthesized);
+}
+
class CallChainReportBuilderTest : public testing::Test {
protected:
virtual void SetUp() {
@@ -65,6 +108,7 @@ class CallChainReportBuilderTest : public testing::Test {
Symbol("java_method2", 0x3000, 0x100),
Symbol("java_method3", 0x3100, 0x100),
Symbol("obfuscated_class.obfuscated_java_method2", 0x3200, 0x100),
+ Symbol("obfuscated_class.java_method4", 0x3300, 0x100),
});
// Add map layout for libraries used in the thread:
@@ -265,11 +309,12 @@ TEST_F(CallChainReportBuilderTest, add_proguard_mapping_file) {
std::vector<uint64_t> fake_ips = {
0x2200, // 2200, // obfuscated_class.obfuscated_java_method
0x3200, // 3200, // obfuscated_class.obfuscated_java_method2
+ 0x3300, // 3300, // obfuscated_class.java_method4
};
CallChainReportBuilder builder(thread_tree);
// Symbol names aren't changed when not given proguard mapping files.
std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
- ASSERT_EQ(entries.size(), 2);
+ ASSERT_EQ(entries.size(), 3);
ASSERT_EQ(entries[0].ip, 0x2200);
ASSERT_STREQ(entries[0].symbol->DemangledName(), "obfuscated_class.obfuscated_java_method");
ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
@@ -280,6 +325,11 @@ TEST_F(CallChainReportBuilderTest, add_proguard_mapping_file) {
ASSERT_EQ(entries[1].dso->Path(), fake_jit_cache_path);
ASSERT_EQ(entries[1].vaddr_in_file, 0x3200);
ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
+ ASSERT_EQ(entries[2].ip, 0x3300);
+ ASSERT_STREQ(entries[2].symbol->DemangledName(), "obfuscated_class.java_method4");
+ ASSERT_EQ(entries[2].dso->Path(), fake_jit_cache_path);
+ ASSERT_EQ(entries[2].vaddr_in_file, 0x3300);
+ ASSERT_EQ(entries[2].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
// Symbol names are changed when given a proguard mapping file.
TemporaryFile tmpfile;
@@ -294,6 +344,47 @@ TEST_F(CallChainReportBuilderTest, add_proguard_mapping_file) {
tmpfile.path));
builder.AddProguardMappingFile(tmpfile.path);
entries = builder.Build(thread, fake_ips, 0);
+ ASSERT_EQ(entries.size(), 3);
+ ASSERT_EQ(entries[0].ip, 0x2200);
+ ASSERT_STREQ(entries[0].symbol->DemangledName(),
+ "android.support.v4.app.RemoteActionCompatParcelizer.read");
+ ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path);
+ ASSERT_EQ(entries[0].vaddr_in_file, 0x200);
+ ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD);
+ ASSERT_EQ(entries[1].ip, 0x3200);
+ ASSERT_STREQ(entries[1].symbol->DemangledName(),
+ "android.support.v4.app.RemoteActionCompatParcelizer.read2");
+ ASSERT_EQ(entries[1].dso->Path(), fake_jit_cache_path);
+ ASSERT_EQ(entries[1].vaddr_in_file, 0x3200);
+ ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
+ ASSERT_STREQ(entries[2].symbol->DemangledName(),
+ "android.support.v4.app.RemoteActionCompatParcelizer.java_method4");
+ ASSERT_EQ(entries[2].dso->Path(), fake_jit_cache_path);
+ ASSERT_EQ(entries[2].vaddr_in_file, 0x3300);
+ ASSERT_EQ(entries[2].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
+}
+
+TEST_F(CallChainReportBuilderTest, not_remove_synthesized_frame_by_default) {
+ std::vector<uint64_t> fake_ips = {
+ 0x2200, // 2200, // obfuscated_class.obfuscated_java_method
+ 0x3200, // 3200, // obfuscated_class.obfuscated_java_method2
+ };
+
+ TemporaryFile tmpfile;
+ ASSERT_TRUE(android::base::WriteStringToFile(
+ "android.support.v4.app.RemoteActionCompatParcelizer -> obfuscated_class:\n"
+ " 13:13:androidx.core.app.RemoteActionCompat read(androidx.versionedparcelable.Versioned"
+ "Parcel) -> obfuscated_java_method\n"
+ " # {\"id\":\"com.android.tools.r8.synthesized\"}\n"
+ " 13:13:androidx.core.app.RemoteActionCompat "
+ "android.support.v4.app.RemoteActionCompatParcelizer.read2(androidx.versionedparcelable."
+ "VersionedParcel) -> obfuscated_java_method2",
+ tmpfile.path));
+
+ // By default, synthesized frames are kept.
+ CallChainReportBuilder builder(thread_tree);
+ builder.AddProguardMappingFile(tmpfile.path);
+ std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
ASSERT_EQ(entries.size(), 2);
ASSERT_EQ(entries[0].ip, 0x2200);
ASSERT_STREQ(entries[0].symbol->DemangledName(),
@@ -309,6 +400,41 @@ TEST_F(CallChainReportBuilderTest, add_proguard_mapping_file) {
ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
}
+TEST_F(CallChainReportBuilderTest, remove_synthesized_frame_with_env_variable) {
+ // Windows doesn't support setenv and unsetenv. So don't test on it.
+#if !defined(__WIN32)
+ std::vector<uint64_t> fake_ips = {
+ 0x2200, // 2200, // obfuscated_class.obfuscated_java_method
+ 0x3200, // 3200, // obfuscated_class.obfuscated_java_method2
+ };
+
+ TemporaryFile tmpfile;
+ ASSERT_TRUE(android::base::WriteStringToFile(
+ "android.support.v4.app.RemoteActionCompatParcelizer -> obfuscated_class:\n"
+ " 13:13:androidx.core.app.RemoteActionCompat read(androidx.versionedparcelable.Versioned"
+ "Parcel) -> obfuscated_java_method\n"
+ " # {\"id\":\"com.android.tools.r8.synthesized\"}\n"
+ " 13:13:androidx.core.app.RemoteActionCompat "
+ "android.support.v4.app.RemoteActionCompatParcelizer.read2(androidx.versionedparcelable."
+ "VersionedParcel) -> obfuscated_java_method2",
+ tmpfile.path));
+
+ // With environment variable set, synthesized frames are removed.
+ ASSERT_EQ(setenv("REMOVE_R8_SYNTHESIZED_FRAME", "1", 1), 0);
+ CallChainReportBuilder builder(thread_tree);
+ ASSERT_EQ(unsetenv("REMOVE_R8_SYNTHESIZED_FRAME"), 0);
+ builder.AddProguardMappingFile(tmpfile.path);
+ std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0);
+ ASSERT_EQ(entries.size(), 1);
+ ASSERT_EQ(entries[0].ip, 0x3200);
+ ASSERT_STREQ(entries[0].symbol->DemangledName(),
+ "android.support.v4.app.RemoteActionCompatParcelizer.read2");
+ ASSERT_EQ(entries[0].dso->Path(), fake_jit_cache_path);
+ ASSERT_EQ(entries[0].vaddr_in_file, 0x3200);
+ ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
+#endif // !defined(__WIN32)
+}
+
TEST_F(CallChainReportBuilderTest, add_proguard_mapping_file_for_jit_method_with_signature) {
std::vector<uint64_t> fake_ips = {
0x3200, // 3200, // void ctep.v(cteo, ctgc, ctbn)
@@ -413,3 +539,45 @@ TEST_F(CallChainReportBuilderTest, convert_jit_frame_for_jit_method_with_signatu
ASSERT_EQ(entries[1].vaddr_in_file, 0x200);
ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD);
}
+
+class ThreadReportBuilderTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ thread_tree.SetThreadName(1, 1, "thread1");
+ thread_tree.SetThreadName(1, 2, "thread-pool1");
+ thread_tree.SetThreadName(1, 3, "thread-pool2");
+ }
+
+ bool IsReportEqual(const ThreadReport& report1, const ThreadReport& report2) {
+ return report1.pid == report2.pid && report1.tid == report2.tid &&
+ strcmp(report1.thread_name, report2.thread_name) == 0;
+ }
+
+ ThreadTree thread_tree;
+};
+
+TEST_F(ThreadReportBuilderTest, no_setting) {
+ ThreadReportBuilder builder;
+ ThreadEntry* thread = thread_tree.FindThread(1);
+ ThreadReport report = builder.Build(*thread);
+ ASSERT_TRUE(IsReportEqual(report, ThreadReport(1, 1, "thread1")));
+}
+
+TEST_F(ThreadReportBuilderTest, aggregate_threads) {
+ ThreadReportBuilder builder;
+ ASSERT_TRUE(builder.AggregateThreads({"thread-pool.*"}));
+ ThreadEntry* thread = thread_tree.FindThread(1);
+ ThreadReport report = builder.Build(*thread);
+ ASSERT_TRUE(IsReportEqual(report, ThreadReport(1, 1, "thread1")));
+ thread = thread_tree.FindThread(2);
+ report = builder.Build(*thread);
+ ASSERT_TRUE(IsReportEqual(report, ThreadReport(1, 2, "thread-pool.*")));
+ thread = thread_tree.FindThread(3);
+ report = builder.Build(*thread);
+ ASSERT_TRUE(IsReportEqual(report, ThreadReport(1, 2, "thread-pool.*")));
+}
+
+TEST_F(ThreadReportBuilderTest, aggregate_threads_bad_regex) {
+ ThreadReportBuilder builder;
+ ASSERT_FALSE(builder.AggregateThreads({"?thread-pool*"}));
+}
diff --git a/simpleperf/runtest/Android.bp b/simpleperf/runtest/Android.bp
index dee4ffd8..53fdfefe 100644
--- a/simpleperf/runtest/Android.bp
+++ b/simpleperf/runtest/Android.bp
@@ -96,3 +96,10 @@ cc_binary {
defaults: ["simpleperf_runtest_defaults"],
srcs: ["run_and_sleep.cpp"],
}
+
+// Used as an example in collect_etm_data_for_autofdo.md.
+cc_binary {
+ name: "etm_test_loop",
+ srcs: ["etm_test_loop.cpp"],
+ // afdo: true,
+}
diff --git a/simpleperf/runtest/etm_test_loop.cpp b/simpleperf/runtest/etm_test_loop.cpp
new file mode 100644
index 00000000..a5f4f971
--- /dev/null
+++ b/simpleperf/runtest/etm_test_loop.cpp
@@ -0,0 +1,20 @@
+
+void f1() {
+ for (volatile int i = 0; i < 100; i++) {
+ }
+}
+
+void f2() {
+ for (volatile int i = 0; i < 1000; i++) {
+ }
+}
+
+int main() {
+ for (volatile int i = 0; i < 10; i++) {
+ if (i * 3 < 6) {
+ f1();
+ } else {
+ f2();
+ }
+ }
+}
diff --git a/simpleperf/rust/lib.rs b/simpleperf/rust/lib.rs
index 7d39273e..94ad8795 100644
--- a/simpleperf/rust/lib.rs
+++ b/simpleperf/rust/lib.rs
@@ -48,7 +48,7 @@ pub enum RecordScope {
}
/// Trigger an ETM trace event.
-pub fn record(trace_file: &Path, duration: &Duration, scope: RecordScope) {
+pub fn record(trace_file: &Path, duration: &Duration, binary_filter: &str, scope: RecordScope) {
let event_name: CString = match scope {
RecordScope::USERSPACE => CString::new("cs-etm:u").unwrap(),
RecordScope::KERNEL => CString::new("cs-etm:k").unwrap(),
@@ -56,9 +56,15 @@ pub fn record(trace_file: &Path, duration: &Duration, scope: RecordScope) {
};
let trace_file = path_to_cstr(trace_file);
let duration = duration.as_secs_f32();
+ let binary_filter = CString::new(binary_filter).unwrap();
unsafe {
- simpleperf_profcollect_bindgen::Record(event_name.as_ptr(), trace_file.as_ptr(), duration);
+ simpleperf_profcollect_bindgen::Record(
+ event_name.as_ptr(),
+ trace_file.as_ptr(),
+ duration,
+ binary_filter.as_ptr(),
+ );
}
}
@@ -76,3 +82,18 @@ pub fn process(trace_path: &Path, profile_path: &Path, binary_filter: &str) {
);
}
}
+
+/// Save logs in file.
+pub fn set_log_file(filename: &Path) {
+ let log_file = path_to_cstr(filename);
+ unsafe {
+ simpleperf_profcollect_bindgen::SetLogFile(log_file.as_ptr());
+ }
+}
+
+/// Stop using log file.
+pub fn reset_log_file() {
+ unsafe {
+ simpleperf_profcollect_bindgen::ResetLogFile();
+ }
+}
diff --git a/simpleperf/scripts/Android.bp b/simpleperf/scripts/Android.bp
new file mode 100644
index 00000000..abbbe249
--- /dev/null
+++ b/simpleperf/scripts/Android.bp
@@ -0,0 +1,37 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_extras_simpleperf_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_extras_simpleperf_license"],
+}
+
+python_library_host {
+ name: "simpleperf_report_lib",
+ srcs: [
+ "simpleperf_report_lib.py",
+ "simpleperf_utils.py",
+ ],
+ data: [
+ "bin/darwin/x86_64/libsimpleperf_report.dylib",
+ "bin/linux/x86_64/libsimpleperf_report.so",
+ "bin/windows/**/*.dll",
+ ],
+}
diff --git a/simpleperf/scripts/app_profiler.py b/simpleperf/scripts/app_profiler.py
index 808e3e9c..a6399ef1 100755
--- a/simpleperf/scripts/app_profiler.py
+++ b/simpleperf/scripts/app_profiler.py
@@ -233,7 +233,7 @@ class ProfilerBase(object):
raise NotImplementedError
def start_profiling(self, target_args):
- """Start simpleperf reocrd process on device."""
+ """Start simpleperf record process on device."""
args = ['/data/local/tmp/simpleperf', 'record', '-o', '/data/local/tmp/perf.data',
self.args.record_options]
if self.adb.run(['shell', 'ls', NATIVE_LIBS_DIR_ON_DEVICE]):
diff --git a/simpleperf/scripts/bin/android/arm/simpleperf b/simpleperf/scripts/bin/android/arm/simpleperf
index db2fde7f..bf6b0563 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 785b6e33..a81a14b0 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 97a168d5..32fc9197 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 c250c450..cb7f7bf8 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 191e51f8..cd829eb4 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 1a6a8875..4a4396d7 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 ca444ec9..1f9f13fb 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 dca7b30d..d8dd8400 100755
--- a/simpleperf/scripts/bin/linux/x86_64/simpleperf
+++ b/simpleperf/scripts/bin/linux/x86_64/simpleperf
Binary files differ
diff --git a/simpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dll b/simpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dll
index b49e8f8f..4282b4c0 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/simpleperf.exe b/simpleperf/scripts/bin/windows/x86_64/simpleperf.exe
index 9838e771..47fcd20f 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 4322e2c0..46c8532a 100755
--- a/simpleperf/scripts/binary_cache_builder.py
+++ b/simpleperf/scripts/binary_cache_builder.py
@@ -19,13 +19,14 @@
it, and put them in binary_cache.
"""
-from dataclasses import dataclass
+from collections import defaultdict
import logging
import os
import os.path
from pathlib import Path
import shutil
-from typing import List, Optional, Union
+import sys
+from typing import Dict, List, Optional, Tuple, Union
from simpleperf_report_lib import ReportLib
from simpleperf_utils import (
@@ -37,27 +38,233 @@ def is_jit_symfile(dso_name):
return dso_name.split('/')[-1].startswith('TemporaryFile')
-class BinaryCacheBuilder(object):
+class BinaryCache:
+ def __init__(self, binary_dir: Path):
+ self.binary_dir = binary_dir
+
+ def get_path_in_cache(self, device_path: str, build_id: str) -> Path:
+ """ Given a binary path in perf.data, return its corresponding path in the cache.
+ """
+ if build_id:
+ filename = device_path.split('/')[-1]
+ # Add build id to make the filename unique.
+ unique_filename = build_id[2:] + '-' + filename
+ return self.binary_dir / unique_filename
+
+ # For elf file without build id, we can only follow its path on device. Otherwise,
+ # simpleperf can't find it. However, we don't prefer this way. Because:
+ # 1) It doesn't work for native libs loaded directly from apk
+ # (android:extractNativeLibs=”false”).
+ # 2) It may exceed path limit on windows.
+ if device_path.startswith('/'):
+ device_path = device_path[1:]
+ device_path = device_path.replace('/', os.sep)
+ return Path(os.path.join(self.binary_dir, device_path))
+
+
+class BinarySource:
+ """ Source to find debug binaries. """
+
+ def __init__(self, readelf: ReadElf):
+ self.readelf = readelf
+
+ def collect_binaries(self, binaries: Dict[str, str], binary_cache: BinaryCache):
+ """ pull binaries needed in perf.data to binary_cache.
+ binaries: maps from binary path to its build_id in perf.data.
+ """
+ raise Exception('not implemented')
+
+ def read_build_id(self, path: Path):
+ return self.readelf.get_build_id(path)
+
+
+class BinarySourceFromDevice(BinarySource):
+ """ Pull binaries from device. """
+
+ def __init__(self, readelf: ReadElf, disable_adb_root: bool):
+ super().__init__(readelf)
+ self.adb = AdbHelper(enable_switch_to_root=not disable_adb_root)
+
+ def collect_binaries(self, binaries: Dict[str, str], binary_cache: BinaryCache):
+ if not self.adb.is_device_available():
+ return
+ for path, build_id in binaries.items():
+ self.collect_binary(path, build_id, binary_cache)
+ self.pull_kernel_symbols(binary_cache.binary_dir / 'kallsyms')
+
+ def collect_binary(self, path: str, build_id: str, binary_cache: BinaryCache):
+ if not path.startswith('/') or path == "//anon" or path.startswith("/dev/"):
+ # [kernel.kallsyms] or unknown, or something we can't find binary.
+ return
+ binary_cache_file = binary_cache.get_path_in_cache(path, build_id)
+ self.check_and_pull_binary(path, build_id, binary_cache_file)
+
+ def check_and_pull_binary(self, path: str, expected_build_id: str, binary_cache_file: Path):
+ """If the binary_cache_file exists and has the expected_build_id, there
+ is no need to pull the binary from device. Otherwise, pull it.
+ """
+ if binary_cache_file.is_file() and (
+ not expected_build_id or expected_build_id == self.read_build_id(binary_cache_file)
+ ):
+ logging.info('use current file in binary_cache: %s', binary_cache_file)
+ else:
+ logging.info('pull file to binary_cache: %s to %s', path, binary_cache_file)
+ target_dir = binary_cache_file.parent
+ if not target_dir.is_dir():
+ os.makedirs(target_dir)
+ if binary_cache_file.is_file():
+ binary_cache_file.unlink()
+ self.pull_file_from_device(path, binary_cache_file)
+
+ def pull_file_from_device(self, device_path: str, host_path: Path):
+ if self.adb.run(['pull', device_path, str(host_path)]):
+ return True
+ # On non-root devices, we can't pull /data/app/XXX/base.odex directly.
+ # Instead, we can first copy the file to /data/local/tmp, then pull it.
+ filename = device_path[device_path.rfind('/')+1:]
+ if (self.adb.run(['shell', 'cp', device_path, '/data/local/tmp']) and
+ self.adb.run(['pull', '/data/local/tmp/' + filename, host_path])):
+ self.adb.run(['shell', 'rm', '/data/local/tmp/' + filename])
+ return True
+ logging.warning('failed to pull %s from device', device_path)
+ return False
+
+ def pull_kernel_symbols(self, file_path: Path):
+ if file_path.is_file():
+ file_path.unlink()
+ if self.adb.switch_to_root():
+ self.adb.run(['shell', 'echo', '0', '>/proc/sys/kernel/kptr_restrict'])
+ self.adb.run(['pull', '/proc/kallsyms', file_path])
+
+
+class BinarySourceFromLibDirs(BinarySource):
+ """ Collect binaries from lib dirs. """
+
+ def __init__(self, readelf: ReadElf, lib_dirs: List[Path]):
+ super().__init__(readelf)
+ self.lib_dirs = lib_dirs
+ self.filename_map = None
+ self.build_id_map = None
+ self.binary_cache = None
+
+ def collect_binaries(self, binaries: Dict[str, str], binary_cache: BinaryCache):
+ self.create_filename_map(binaries)
+ self.create_build_id_map(binaries)
+ self.binary_cache = binary_cache
+
+ # Search all files in lib_dirs, and copy matching files to build_cache.
+ for lib_dir in self.lib_dirs:
+ if self.is_platform_symbols_dir(lib_dir):
+ self.search_platform_symbols_dir(lib_dir)
+ else:
+ self.search_dir(lib_dir)
+
+ def create_filename_map(self, binaries: Dict[str, str]):
+ """ Create a map mapping from filename to binaries having the name. """
+ self.filename_map = defaultdict(list)
+ for path, build_id in binaries.items():
+ index = path.rfind('/')
+ filename = path[index + 1:]
+ self.filename_map[filename].append((path, build_id))
+
+ def create_build_id_map(self, binaries: Dict[str, str]):
+ """ Create a map mapping from build id to binary path. """
+ self.build_id_map = {}
+ for path, build_id in binaries.items():
+ if build_id:
+ self.build_id_map[build_id] = path
+
+ def is_platform_symbols_dir(self, lib_dir: Path):
+ """ Check if lib_dir points to $ANDROID_PRODUCT_OUT/symbols. """
+ subdir_names = [p.name for p in lib_dir.iterdir()]
+ return lib_dir.name == 'symbols' and 'system' in subdir_names
+
+ def search_platform_symbols_dir(self, lib_dir: Path):
+ """ Platform symbols dir contains too many binaries. Reading build ids for
+ all of them takes a long time. So we only read build ids for binaries
+ having names exist in filename_map.
+ """
+ for root, _, files in os.walk(lib_dir):
+ for filename in files:
+ binaries = self.filename_map.get(filename)
+ if not binaries:
+ continue
+ file_path = Path(os.path.join(root, filename))
+ build_id = self.read_build_id(file_path)
+ for path, expected_build_id in binaries:
+ if expected_build_id == build_id:
+ self.copy_to_binary_cache(file_path, build_id, path)
+
+ def search_dir(self, lib_dir: Path):
+ """ For a normal lib dir, it's unlikely to contain many binaries. So we can read
+ build ids for all binaries in it. But users may give debug binaries with a name
+ different from the one recorded in perf.data. So we should only rely on build id
+ if it is available.
+ """
+ for root, _, files in os.walk(lib_dir):
+ for filename in files:
+ file_path = Path(os.path.join(root, filename))
+ build_id = self.read_build_id(file_path)
+ if build_id:
+ # For elf file with build id, use build id to match.
+ device_path = self.build_id_map.get(build_id)
+ if device_path:
+ self.copy_to_binary_cache(file_path, build_id, device_path)
+ elif self.readelf.is_elf_file(file_path):
+ # For elf file without build id, use filename to match.
+ for path, expected_build_id in self.filename_map.get(filename, []):
+ if not expected_build_id:
+ self.copy_to_binary_cache(file_path, '', path)
+ break
+
+ def copy_to_binary_cache(
+ self, from_path: Path, expected_build_id: str, device_path: str):
+ to_path = self.binary_cache.get_path_in_cache(device_path, expected_build_id)
+ if not self.need_to_copy(from_path, to_path, expected_build_id):
+ # The existing file in binary_cache can provide more information, so no need to copy.
+ return
+ to_dir = to_path.parent
+ if not to_dir.is_dir():
+ os.makedirs(to_dir)
+ logging.info('copy to binary_cache: %s to %s', from_path, to_path)
+ shutil.copy(from_path, to_path)
+
+ def need_to_copy(self, from_path: Path, to_path: Path, expected_build_id: str):
+ if not to_path.is_file() or self.read_build_id(to_path) != expected_build_id:
+ return True
+ return self.get_file_stripped_level(from_path) < self.get_file_stripped_level(to_path)
+
+ def get_file_stripped_level(self, path: Path) -> int:
+ """Return stripped level of an ELF file. Larger value means more stripped."""
+ sections = self.readelf.get_sections(path)
+ if '.debug_line' in sections:
+ return 0
+ if '.symtab' in sections:
+ return 1
+ return 2
+
+
+class BinaryCacheBuilder:
"""Collect all binaries needed by perf.data in binary_cache."""
def __init__(self, ndk_path: Optional[str], disable_adb_root: bool):
- self.adb = AdbHelper(enable_switch_to_root=not disable_adb_root)
self.readelf = ReadElf(ndk_path)
- self.binary_cache_dir = 'binary_cache'
- if not os.path.isdir(self.binary_cache_dir):
- os.makedirs(self.binary_cache_dir)
+ self.device_source = BinarySourceFromDevice(self.readelf, disable_adb_root)
+ self.binary_cache_dir = Path('binary_cache')
+ self.binary_cache = BinaryCache(self.binary_cache_dir)
self.binaries = {}
- def build_binary_cache(self, perf_data_path: str, symfs_dirs: List[Union[Path, str]]):
+ def build_binary_cache(self, perf_data_path: str, symfs_dirs: List[Union[Path, str]]) -> bool:
+ self.binary_cache_dir.mkdir(exist_ok=True)
self.collect_used_binaries(perf_data_path)
- self.copy_binaries_from_symfs_dirs(symfs_dirs)
- if self.adb.is_device_available():
- self.pull_binaries_from_device()
- self._pull_kernel_symbols()
+ if not self.copy_binaries_from_symfs_dirs(symfs_dirs):
+ return False
+ self.pull_binaries_from_device()
self.create_build_id_list()
+ return True
def collect_used_binaries(self, perf_data_path):
- """read perf.data, collect all used binaries and their build id (if available)."""
+ """read perf.data, collect all used binaries and their build id(if available)."""
# A dict mapping from binary name to build_id
binaries = {}
lib = ReportLib()
@@ -82,149 +289,45 @@ class BinaryCacheBuilder(object):
binaries[name] = lib.GetBuildIdForPath(dso_name)
self.binaries = binaries
- def copy_binaries_from_symfs_dirs(self, symfs_dirs: List[Union[Path, str]]):
- """collect all files in symfs_dirs."""
- if not symfs_dirs:
- return
-
- # It is possible that the path of the binary in symfs_dirs doesn't match
- # the one recorded in perf.data. For example, a file in symfs_dirs might
- # be "debug/arm/obj/armeabi-v7a/libsudo-game-jni.so", but the path in
- # perf.data is "/data/app/xxxx/lib/arm/libsudo-game-jni.so". So we match
- # binaries if they have the same filename (like libsudo-game-jni.so)
- # and same build_id.
-
- # Map from filename to binary paths.
- filename_dict = {}
- for binary in self.binaries:
- index = binary.rfind('/')
- filename = binary[index+1:]
- paths = filename_dict.get(filename)
- if paths is None:
- filename_dict[filename] = paths = []
- paths.append(binary)
-
- # Walk through all files in symfs_dirs, and copy matching files to build_cache.
- for symfs_dir in symfs_dirs:
- for root, _, files in os.walk(symfs_dir):
- for filename in files:
- paths = filename_dict.get(filename)
- if not paths:
- continue
- build_id = self._read_build_id(os.path.join(root, filename))
- for binary in paths:
- expected_build_id = self.binaries.get(binary)
- if expected_build_id == build_id:
- self._copy_to_binary_cache(os.path.join(root, filename),
- expected_build_id, binary)
- break
-
- def _copy_to_binary_cache(self, from_path, expected_build_id, target_file):
- if target_file[0] == '/':
- target_file = target_file[1:]
- target_file = target_file.replace('/', os.sep)
- target_file = os.path.join(self.binary_cache_dir, target_file)
- if not self._need_to_copy(from_path, target_file, expected_build_id):
- # The existing file in binary_cache can provide more information, so no need to copy.
- return
- target_dir = os.path.dirname(target_file)
- if not os.path.isdir(target_dir):
- os.makedirs(target_dir)
- logging.info('copy to binary_cache: %s to %s' % (from_path, target_file))
- shutil.copy(from_path, target_file)
-
- def _need_to_copy(self, source_file, target_file, expected_build_id):
- if not os.path.isfile(target_file):
- return True
- if self._read_build_id(target_file) != expected_build_id:
- return True
- return self._get_file_stripped_level(source_file) < self._get_file_stripped_level(
- target_file)
-
- def _get_file_stripped_level(self, file_path):
- """Return stripped level of an ELF file. Larger value means more stripped."""
- sections = self.readelf.get_sections(file_path)
- if '.debug_line' in sections:
- return 0
- if '.symtab' in sections:
- return 1
- return 2
+ def copy_binaries_from_symfs_dirs(self, symfs_dirs: List[Union[str, Path]]) -> bool:
+ if symfs_dirs:
+ lib_dirs: List[Path] = []
+ for symfs_dir in symfs_dirs:
+ if isinstance(symfs_dir, str):
+ symfs_dir = Path(symfs_dir)
+ if not symfs_dir.is_dir():
+ logging.error("can't find dir %s", symfs_dir)
+ return False
+ lib_dirs.append(symfs_dir)
+ lib_dir_source = BinarySourceFromLibDirs(self.readelf, lib_dirs)
+ lib_dir_source.collect_binaries(self.binaries, self.binary_cache)
+ return True
def pull_binaries_from_device(self):
- """pull binaries needed in perf.data to binary_cache."""
- for binary in self.binaries:
- build_id = self.binaries[binary]
- if not binary.startswith('/') or binary == "//anon" or binary.startswith("/dev/"):
- # [kernel.kallsyms] or unknown, or something we can't find binary.
- continue
- binary_cache_file = binary[1:].replace('/', os.sep)
- binary_cache_file = os.path.join(self.binary_cache_dir, binary_cache_file)
- self._check_and_pull_binary(binary, build_id, binary_cache_file)
-
- def _check_and_pull_binary(self, binary, expected_build_id, binary_cache_file):
- """If the binary_cache_file exists and has the expected_build_id, there
- is no need to pull the binary from device. Otherwise, pull it.
- """
- need_pull = True
- if os.path.isfile(binary_cache_file):
- need_pull = False
- if expected_build_id:
- build_id = self._read_build_id(binary_cache_file)
- if expected_build_id != build_id:
- need_pull = True
- if need_pull:
- target_dir = os.path.dirname(binary_cache_file)
- if not os.path.isdir(target_dir):
- os.makedirs(target_dir)
- if os.path.isfile(binary_cache_file):
- os.remove(binary_cache_file)
- logging.info('pull file to binary_cache: %s to %s' % (binary, binary_cache_file))
- self._pull_file_from_device(binary, binary_cache_file)
- else:
- logging.info('use current file in binary_cache: %s' % binary_cache_file)
-
- def _read_build_id(self, file_path):
- """read build id of a binary on host."""
- return self.readelf.get_build_id(file_path)
-
- def _pull_file_from_device(self, device_path, host_path):
- if self.adb.run(['pull', device_path, host_path]):
- return True
- # In non-root device, we can't pull /data/app/XXX/base.odex directly.
- # Instead, we can first copy the file to /data/local/tmp, then pull it.
- filename = device_path[device_path.rfind('/')+1:]
- if (self.adb.run(['shell', 'cp', device_path, '/data/local/tmp']) and
- self.adb.run(['pull', '/data/local/tmp/' + filename, host_path])):
- self.adb.run(['shell', 'rm', '/data/local/tmp/' + filename])
- return True
- logging.warning('failed to pull %s from device' % device_path)
- return False
-
- def _pull_kernel_symbols(self):
- file_path = os.path.join(self.binary_cache_dir, 'kallsyms')
- 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(['pull', '/proc/kallsyms', file_path])
+ self.device_source.collect_binaries(self.binaries, self.binary_cache)
def create_build_id_list(self):
""" Create build_id_list. So report scripts can find a binary by its build_id instead of
path.
"""
- build_id_list_path = os.path.join(self.binary_cache_dir, 'build_id_list')
+ build_id_list_path = self.binary_cache_dir / 'build_id_list'
+ # Write in binary mode to avoid "\r\n" problem on windows, which can confuse simpleperf.
with open(build_id_list_path, 'wb') as fh:
for root, _, files in os.walk(self.binary_cache_dir):
for filename in files:
- path = os.path.join(root, filename)
- relative_path = path[len(self.binary_cache_dir) + 1:]
- build_id = self._read_build_id(path)
+ path = Path(os.path.join(root, filename))
+ build_id = self.readelf.get_build_id(path)
if build_id:
+ relative_path = path.relative_to(self.binary_cache_dir)
line = f'{build_id}={relative_path}\n'
fh.write(str_to_bytes(line))
+ def find_path_in_cache(self, device_path: str) -> Optional[Path]:
+ build_id = self.binaries.get(device_path)
+ return self.binary_cache.get_path_in_cache(device_path, build_id)
+
-def main():
+def main() -> bool:
parser = BaseArgumentParser(description="""
Pull binaries needed by perf.data from device to binary_cache directory.""")
parser.add_argument('-i', '--perf_data_path', default='perf.data', type=extant_file, help="""
@@ -238,8 +341,8 @@ def main():
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)
- builder.build_binary_cache(args.perf_data_path, symfs_dirs)
+ return builder.build_binary_cache(args.perf_data_path, symfs_dirs)
if __name__ == '__main__':
- main()
+ sys.exit(0 if main() else 1)
diff --git a/simpleperf/scripts/gecko_profile_generator.py b/simpleperf/scripts/gecko_profile_generator.py
index 396c9a58..741841c2 100755
--- a/simpleperf/scripts/gecko_profile_generator.py
+++ b/simpleperf/scripts/gecko_profile_generator.py
@@ -25,13 +25,15 @@
Then open gecko-profile.json.gz in https://profiler.firefox.com/
"""
+from collections import Counter
+from dataclasses import dataclass, field
import json
+import logging
import sys
+from typing import List, Dict, Optional, NamedTuple, Tuple
-from dataclasses import dataclass, field
from simpleperf_report_lib import ReportLib
-from simpleperf_utils import BaseArgumentParser, flatten_arg_list, ReportLibOptions
-from typing import List, Dict, Optional, NamedTuple, Set, Tuple
+from simpleperf_utils import BaseArgumentParser, ReportLibOptions
StringID = int
@@ -67,6 +69,10 @@ class Sample(NamedTuple):
stack_id: Optional[StackID]
time_ms: Milliseconds
responsiveness: int
+ complete_stack: bool
+
+ def to_json(self):
+ return [self.stack_id, self.time_ms, self.responsiveness]
# Schema: https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/profile.js#L425
@@ -108,6 +114,13 @@ CATEGORIES = [
"color": 'green',
"subcategories": ['Other']
},
+ {
+ "name": 'Off-CPU',
+ # Follow Brendan Gregg's Flamegraph convention: blue for off-CPU
+ # https://github.com/brendangregg/FlameGraph/blob/810687f180f3c4929b5d965f54817a5218c9d89b/flamegraph.pl#L470
+ "color": 'blue',
+ "subcategories": ['Other']
+ },
# Not used by this exporter yet, but some Firefox Profiler code assumes
# there is an 'Other' category by searching for a category with
# color=grey, so include this.
@@ -119,6 +132,14 @@ CATEGORIES = [
]
+def is_complete_stack(stack: List[str]) -> bool:
+ """ Check if the callstack is complete. The stack starts from root. """
+ for entry in stack:
+ if ('__libc_init' in entry) or ('__start_thread' in entry):
+ return True
+ return False
+
+
@dataclass
class Thread:
"""A builder for a profile of a single thread.
@@ -183,12 +204,19 @@ class Thread:
# Heuristic: kernel code contains "kallsyms" as the library name.
if "kallsyms" in frame_str or ".ko" in frame_str:
category = 1
+ if frame_str.startswith("__schedule "):
+ category = 5
elif ".so" in frame_str:
category = 2
elif ".vdex" in frame_str:
category = 3
elif ".oat" in frame_str:
category = 4
+ # Heuristic: empirically, off-CPU profiles mostly measure off-CPU time
+ # accounted to the linux kernel __schedule function, which handles
+ # blocking. This only works if we have kernel symbol (kallsyms)
+ # access though.
+ # https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/kernel/sched/core.c;l=6593;drc=0c99414a07ddaa18d8eb4be90b551d2687cbde2f
self.frameTable.append(Frame(
string_id=string_id,
@@ -203,7 +231,7 @@ class Thread:
))
return frame_id
- def _add_sample(self, comm: str, stack: List[str], time_ms: Milliseconds) -> None:
+ def add_sample(self, comm: str, stack: List[str], time_ms: Milliseconds) -> None:
"""Add a timestamped stack trace sample to the thread builder.
Args:
@@ -223,13 +251,43 @@ class Thread:
self.samples.append(Sample(stack_id=prefix_stack_id,
time_ms=time_ms,
- responsiveness=0))
+ responsiveness=0,
+ complete_stack=is_complete_stack(stack)))
- def _to_json_dict(self) -> Dict:
- """Converts this Thread to GeckoThread JSON format."""
- # The samples aren't guaranteed to be in order. Sort them by time.
+ def sort_samples(self) -> None:
+ """ The samples aren't guaranteed to be in order. Sort them by time. """
self.samples.sort(key=lambda s: s.time_ms)
+ def remove_stack_gaps(self, max_remove_gap_length: int, gap_distr: Dict[int, int]) -> None:
+ """ Ideally all callstacks are complete. But some may be broken for different reasons.
+ To create a smooth view in "Stack Chart", remove small gaps of broken callstacks.
+
+ Args:
+ max_remove_gap_length: the max length of continuous broken-stack samples to remove
+ """
+ if max_remove_gap_length == 0:
+ return
+ i = 0
+ remove_flags = [False] * len(self.samples)
+ while i < len(self.samples):
+ if self.samples[i].complete_stack:
+ i += 1
+ continue
+ n = 1
+ while (i + n < len(self.samples)) and (not self.samples[i + n].complete_stack):
+ n += 1
+ gap_distr[n] += 1
+ if n <= max_remove_gap_length:
+ for j in range(i, i + n):
+ remove_flags[j] = True
+ i += n
+ if True in remove_flags:
+ old_samples = self.samples
+ self.samples = [s for s, remove in zip(old_samples, remove_flags) if not remove]
+
+ def to_json_dict(self) -> Dict:
+ """Converts this Thread to GeckoThread JSON format."""
+
# Gecko profile format is row-oriented data as List[List],
# And a schema for interpreting each index.
# Schema:
@@ -258,7 +316,7 @@ class Thread:
"time": 1,
"responsiveness": 2,
},
- "data": self.samples
+ "data": [s.to_json() for s in self.samples],
},
# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L156
"frameTable": {
@@ -291,11 +349,37 @@ class Thread:
}
+def remove_stack_gaps(max_remove_gap_length: int, thread_map: Dict[int, Thread]) -> None:
+ """ Remove stack gaps for each thread, and print status. """
+ if max_remove_gap_length == 0:
+ return
+ total_sample_count = 0
+ remove_sample_count = 0
+ gap_distr = Counter()
+ for tid in list(thread_map.keys()):
+ thread = thread_map[tid]
+ old_n = len(thread.samples)
+ thread.remove_stack_gaps(max_remove_gap_length, gap_distr)
+ new_n = len(thread.samples)
+ total_sample_count += old_n
+ remove_sample_count += old_n - new_n
+ if new_n == 0:
+ del thread_map[tid]
+ if total_sample_count != 0:
+ logging.info('Remove stack gaps with length <= %d. %d (%.2f%%) samples are removed.',
+ max_remove_gap_length, remove_sample_count,
+ remove_sample_count / total_sample_count * 100
+ )
+ logging.debug('Stack gap length distribution among samples (gap_length: count): %s',
+ gap_distr)
+
+
def _gecko_profile(
record_file: str,
symfs_dir: Optional[str],
kallsyms_file: Optional[str],
- report_lib_options: ReportLibOptions) -> GeckoProfile:
+ report_lib_options: ReportLibOptions,
+ max_remove_gap_length: int) -> GeckoProfile:
"""convert a simpleperf profile to gecko format"""
lib = ReportLib()
@@ -319,7 +403,6 @@ def _gecko_profile(
if sample is None:
lib.Close()
break
- event = lib.GetEventOfCurrentSample()
symbol = lib.GetSymbolOfCurrentSample()
callchain = lib.GetCallChainOfCurrentSample()
sample_time_ms = sample.time / 1000000
@@ -336,7 +419,7 @@ def _gecko_profile(
if thread is None:
thread = Thread(comm=sample.thread_comm, pid=sample.pid, tid=sample.tid)
threadMap[sample.tid] = thread
- thread._add_sample(
+ thread.add_sample(
comm=sample.thread_comm,
stack=stack,
# We are being a bit fast and loose here with time here. simpleperf
@@ -347,7 +430,12 @@ def _gecko_profile(
# setting `simpleperf record --clockid realtime`.
time_ms=sample_time_ms)
- threads = [thread._to_json_dict() for thread in threadMap.values()]
+ for thread in threadMap.values():
+ thread.sort_samples()
+
+ remove_stack_gaps(max_remove_gap_length, threadMap)
+
+ threads = [thread.to_json_dict() for thread in threadMap.values()]
profile_timestamp = meta_info.get('timestamp')
end_time_ms = (int(profile_timestamp) * 1000) if profile_timestamp else 0
@@ -397,13 +485,22 @@ def main() -> None:
parser.add_argument('--kallsyms', help='Set the path to find kernel symbols.')
parser.add_argument('-i', '--record_file', nargs='?', default='perf.data',
help='Default is perf.data.')
+ parser.add_argument('--remove-gaps', metavar='MAX_GAP_LENGTH', dest='max_remove_gap_length',
+ type=int, default=3, help="""
+ Ideally all callstacks are complete. But some may be broken for different
+ reasons. To create a smooth view in "Stack Chart", remove small gaps of
+ broken callstacks. MAX_GAP_LENGTH is the max length of continuous
+ broken-stack samples we want to remove.
+ """
+ )
parser.add_report_lib_options()
args = parser.parse_args()
profile = _gecko_profile(
record_file=args.record_file,
symfs_dir=args.symfs,
kallsyms_file=args.kallsyms,
- report_lib_options=args.report_lib_options)
+ report_lib_options=args.report_lib_options,
+ max_remove_gap_length=args.max_remove_gap_length)
json.dump(profile, sys.stdout, sort_keys=True)
diff --git a/simpleperf/scripts/inferno/Android.bp b/simpleperf/scripts/inferno/Android.bp
index 14d18778..e60a6999 100644
--- a/simpleperf/scripts/inferno/Android.bp
+++ b/simpleperf/scripts/inferno/Android.bp
@@ -32,9 +32,4 @@ python_library_host {
"inferno.b64",
"script.js",
],
- version: {
- py2: {
- enabled: true,
- },
- },
}
diff --git a/simpleperf/scripts/pprof_proto_generator.py b/simpleperf/scripts/pprof_proto_generator.py
index 57c988b9..a22e424f 100755
--- a/simpleperf/scripts/pprof_proto_generator.py
+++ b/simpleperf/scripts/pprof_proto_generator.py
@@ -35,8 +35,9 @@ from simpleperf_utils import (Addr2Nearestline, BaseArgumentParser, BinaryFinder
flatten_arg_list, log_exit, ReadElf, ToolFinder)
try:
import profile_pb2
-except ImportError:
- log_exit('google.protobuf module is missing. Please install it first.')
+except ImportError as e:
+ log_exit(f'{e}\nprotobuf package is missing or too old. Please install it like ' +
+ '`pip install protobuf==4.21`.')
# Some units of common event names
diff --git a/simpleperf/scripts/profile_pb2.py b/simpleperf/scripts/profile_pb2.py
index 707b23ac..79db092e 100644
--- a/simpleperf/scripts/profile_pb2.py
+++ b/simpleperf/scripts/profile_pb2.py
@@ -1,13 +1,11 @@
+# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: profile.proto
-
-import sys
-_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+"""Generated protocol buffer code."""
+from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
-from google.protobuf import message as _message
-from google.protobuf import reflection as _reflection
+from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
-from google.protobuf import descriptor_pb2
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
@@ -15,583 +13,28 @@ _sym_db = _symbol_database.Default()
-DESCRIPTOR = _descriptor.FileDescriptor(
- name='profile.proto',
- package='perftools.profiles',
- syntax='proto3',
- serialized_pb=_b('\n\rprofile.proto\x12\x12perftools.profiles\"\xd5\x03\n\x07Profile\x12\x32\n\x0bsample_type\x18\x01 \x03(\x0b\x32\x1d.perftools.profiles.ValueType\x12*\n\x06sample\x18\x02 \x03(\x0b\x32\x1a.perftools.profiles.Sample\x12,\n\x07mapping\x18\x03 \x03(\x0b\x32\x1b.perftools.profiles.Mapping\x12.\n\x08location\x18\x04 \x03(\x0b\x32\x1c.perftools.profiles.Location\x12.\n\x08\x66unction\x18\x05 \x03(\x0b\x32\x1c.perftools.profiles.Function\x12\x14\n\x0cstring_table\x18\x06 \x03(\t\x12\x13\n\x0b\x64rop_frames\x18\x07 \x01(\x03\x12\x13\n\x0bkeep_frames\x18\x08 \x01(\x03\x12\x12\n\ntime_nanos\x18\t \x01(\x03\x12\x16\n\x0e\x64uration_nanos\x18\n \x01(\x03\x12\x32\n\x0bperiod_type\x18\x0b \x01(\x0b\x32\x1d.perftools.profiles.ValueType\x12\x0e\n\x06period\x18\x0c \x01(\x03\x12\x0f\n\x07\x63omment\x18\r \x03(\x03\x12\x1b\n\x13\x64\x65\x66\x61ult_sample_type\x18\x0e \x01(\x03\"\'\n\tValueType\x12\x0c\n\x04type\x18\x01 \x01(\x03\x12\x0c\n\x04unit\x18\x02 \x01(\x03\"V\n\x06Sample\x12\x13\n\x0blocation_id\x18\x01 \x03(\x04\x12\r\n\x05value\x18\x02 \x03(\x03\x12(\n\x05label\x18\x03 \x03(\x0b\x32\x19.perftools.profiles.Label\".\n\x05Label\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\x0b\n\x03str\x18\x02 \x01(\x03\x12\x0b\n\x03num\x18\x03 \x01(\x03\"\xdd\x01\n\x07Mapping\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x14\n\x0cmemory_start\x18\x02 \x01(\x04\x12\x14\n\x0cmemory_limit\x18\x03 \x01(\x04\x12\x13\n\x0b\x66ile_offset\x18\x04 \x01(\x04\x12\x10\n\x08\x66ilename\x18\x05 \x01(\x03\x12\x10\n\x08\x62uild_id\x18\x06 \x01(\x03\x12\x15\n\rhas_functions\x18\x07 \x01(\x08\x12\x15\n\rhas_filenames\x18\x08 \x01(\x08\x12\x18\n\x10has_line_numbers\x18\t \x01(\x08\x12\x19\n\x11has_inline_frames\x18\n \x01(\x08\"c\n\x08Location\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x12\n\nmapping_id\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x64\x64ress\x18\x03 \x01(\x04\x12&\n\x04line\x18\x04 \x03(\x0b\x32\x18.perftools.profiles.Line\")\n\x04Line\x12\x13\n\x0b\x66unction_id\x18\x01 \x01(\x04\x12\x0c\n\x04line\x18\x02 \x01(\x03\"_\n\x08\x46unction\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x0c\n\x04name\x18\x02 \x01(\x03\x12\x13\n\x0bsystem_name\x18\x03 \x01(\x03\x12\x10\n\x08\x66ilename\x18\x04 \x01(\x03\x12\x12\n\nstart_line\x18\x05 \x01(\x03\x42-\n\x1d\x63om.google.perftools.profilesB\x0cProfileProtob\x06proto3')
-)
-_sym_db.RegisterFileDescriptor(DESCRIPTOR)
-
-
-
-
-_PROFILE = _descriptor.Descriptor(
- name='Profile',
- full_name='perftools.profiles.Profile',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='sample_type', full_name='perftools.profiles.Profile.sample_type', index=0,
- number=1, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='sample', full_name='perftools.profiles.Profile.sample', index=1,
- number=2, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='mapping', full_name='perftools.profiles.Profile.mapping', index=2,
- number=3, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='location', full_name='perftools.profiles.Profile.location', index=3,
- number=4, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='function', full_name='perftools.profiles.Profile.function', index=4,
- number=5, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='string_table', full_name='perftools.profiles.Profile.string_table', index=5,
- number=6, type=9, cpp_type=9, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='drop_frames', full_name='perftools.profiles.Profile.drop_frames', index=6,
- number=7, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='keep_frames', full_name='perftools.profiles.Profile.keep_frames', index=7,
- number=8, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='time_nanos', full_name='perftools.profiles.Profile.time_nanos', index=8,
- number=9, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='duration_nanos', full_name='perftools.profiles.Profile.duration_nanos', index=9,
- number=10, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='period_type', full_name='perftools.profiles.Profile.period_type', index=10,
- number=11, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='period', full_name='perftools.profiles.Profile.period', index=11,
- number=12, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='comment', full_name='perftools.profiles.Profile.comment', index=12,
- number=13, type=3, cpp_type=2, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='default_sample_type', full_name='perftools.profiles.Profile.default_sample_type', index=13,
- number=14, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=38,
- serialized_end=507,
-)
-
-
-_VALUETYPE = _descriptor.Descriptor(
- name='ValueType',
- full_name='perftools.profiles.ValueType',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='type', full_name='perftools.profiles.ValueType.type', index=0,
- number=1, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='unit', full_name='perftools.profiles.ValueType.unit', index=1,
- number=2, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=509,
- serialized_end=548,
-)
-
-
-_SAMPLE = _descriptor.Descriptor(
- name='Sample',
- full_name='perftools.profiles.Sample',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='location_id', full_name='perftools.profiles.Sample.location_id', index=0,
- number=1, type=4, cpp_type=4, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='value', full_name='perftools.profiles.Sample.value', index=1,
- number=2, type=3, cpp_type=2, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='label', full_name='perftools.profiles.Sample.label', index=2,
- number=3, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=550,
- serialized_end=636,
-)
-
-
-_LABEL = _descriptor.Descriptor(
- name='Label',
- full_name='perftools.profiles.Label',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='key', full_name='perftools.profiles.Label.key', index=0,
- number=1, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='str', full_name='perftools.profiles.Label.str', index=1,
- number=2, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='num', full_name='perftools.profiles.Label.num', index=2,
- number=3, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=638,
- serialized_end=684,
-)
-
-
-_MAPPING = _descriptor.Descriptor(
- name='Mapping',
- full_name='perftools.profiles.Mapping',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='id', full_name='perftools.profiles.Mapping.id', index=0,
- number=1, type=4, cpp_type=4, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='memory_start', full_name='perftools.profiles.Mapping.memory_start', index=1,
- number=2, type=4, cpp_type=4, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='memory_limit', full_name='perftools.profiles.Mapping.memory_limit', index=2,
- number=3, type=4, cpp_type=4, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='file_offset', full_name='perftools.profiles.Mapping.file_offset', index=3,
- number=4, type=4, cpp_type=4, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='filename', full_name='perftools.profiles.Mapping.filename', index=4,
- number=5, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='build_id', full_name='perftools.profiles.Mapping.build_id', index=5,
- number=6, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='has_functions', full_name='perftools.profiles.Mapping.has_functions', index=6,
- number=7, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='has_filenames', full_name='perftools.profiles.Mapping.has_filenames', index=7,
- number=8, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='has_line_numbers', full_name='perftools.profiles.Mapping.has_line_numbers', index=8,
- number=9, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='has_inline_frames', full_name='perftools.profiles.Mapping.has_inline_frames', index=9,
- number=10, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=687,
- serialized_end=908,
-)
-
-
-_LOCATION = _descriptor.Descriptor(
- name='Location',
- full_name='perftools.profiles.Location',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='id', full_name='perftools.profiles.Location.id', index=0,
- number=1, type=4, cpp_type=4, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='mapping_id', full_name='perftools.profiles.Location.mapping_id', index=1,
- number=2, type=4, cpp_type=4, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='address', full_name='perftools.profiles.Location.address', index=2,
- number=3, type=4, cpp_type=4, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='line', full_name='perftools.profiles.Location.line', index=3,
- number=4, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=910,
- serialized_end=1009,
-)
-
-
-_LINE = _descriptor.Descriptor(
- name='Line',
- full_name='perftools.profiles.Line',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='function_id', full_name='perftools.profiles.Line.function_id', index=0,
- number=1, type=4, cpp_type=4, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='line', full_name='perftools.profiles.Line.line', index=1,
- number=2, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=1011,
- serialized_end=1052,
-)
-
-
-_FUNCTION = _descriptor.Descriptor(
- name='Function',
- full_name='perftools.profiles.Function',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='id', full_name='perftools.profiles.Function.id', index=0,
- number=1, type=4, cpp_type=4, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='name', full_name='perftools.profiles.Function.name', index=1,
- number=2, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='system_name', full_name='perftools.profiles.Function.system_name', index=2,
- number=3, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='filename', full_name='perftools.profiles.Function.filename', index=3,
- number=4, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='start_line', full_name='perftools.profiles.Function.start_line', index=4,
- number=5, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=1054,
- serialized_end=1149,
-)
-
-_PROFILE.fields_by_name['sample_type'].message_type = _VALUETYPE
-_PROFILE.fields_by_name['sample'].message_type = _SAMPLE
-_PROFILE.fields_by_name['mapping'].message_type = _MAPPING
-_PROFILE.fields_by_name['location'].message_type = _LOCATION
-_PROFILE.fields_by_name['function'].message_type = _FUNCTION
-_PROFILE.fields_by_name['period_type'].message_type = _VALUETYPE
-_SAMPLE.fields_by_name['label'].message_type = _LABEL
-_LOCATION.fields_by_name['line'].message_type = _LINE
-DESCRIPTOR.message_types_by_name['Profile'] = _PROFILE
-DESCRIPTOR.message_types_by_name['ValueType'] = _VALUETYPE
-DESCRIPTOR.message_types_by_name['Sample'] = _SAMPLE
-DESCRIPTOR.message_types_by_name['Label'] = _LABEL
-DESCRIPTOR.message_types_by_name['Mapping'] = _MAPPING
-DESCRIPTOR.message_types_by_name['Location'] = _LOCATION
-DESCRIPTOR.message_types_by_name['Line'] = _LINE
-DESCRIPTOR.message_types_by_name['Function'] = _FUNCTION
-
-Profile = _reflection.GeneratedProtocolMessageType('Profile', (_message.Message,), dict(
- DESCRIPTOR = _PROFILE,
- __module__ = 'profile_pb2'
- # @@protoc_insertion_point(class_scope:perftools.profiles.Profile)
- ))
-_sym_db.RegisterMessage(Profile)
-
-ValueType = _reflection.GeneratedProtocolMessageType('ValueType', (_message.Message,), dict(
- DESCRIPTOR = _VALUETYPE,
- __module__ = 'profile_pb2'
- # @@protoc_insertion_point(class_scope:perftools.profiles.ValueType)
- ))
-_sym_db.RegisterMessage(ValueType)
-
-Sample = _reflection.GeneratedProtocolMessageType('Sample', (_message.Message,), dict(
- DESCRIPTOR = _SAMPLE,
- __module__ = 'profile_pb2'
- # @@protoc_insertion_point(class_scope:perftools.profiles.Sample)
- ))
-_sym_db.RegisterMessage(Sample)
-
-Label = _reflection.GeneratedProtocolMessageType('Label', (_message.Message,), dict(
- DESCRIPTOR = _LABEL,
- __module__ = 'profile_pb2'
- # @@protoc_insertion_point(class_scope:perftools.profiles.Label)
- ))
-_sym_db.RegisterMessage(Label)
-
-Mapping = _reflection.GeneratedProtocolMessageType('Mapping', (_message.Message,), dict(
- DESCRIPTOR = _MAPPING,
- __module__ = 'profile_pb2'
- # @@protoc_insertion_point(class_scope:perftools.profiles.Mapping)
- ))
-_sym_db.RegisterMessage(Mapping)
-
-Location = _reflection.GeneratedProtocolMessageType('Location', (_message.Message,), dict(
- DESCRIPTOR = _LOCATION,
- __module__ = 'profile_pb2'
- # @@protoc_insertion_point(class_scope:perftools.profiles.Location)
- ))
-_sym_db.RegisterMessage(Location)
-
-Line = _reflection.GeneratedProtocolMessageType('Line', (_message.Message,), dict(
- DESCRIPTOR = _LINE,
- __module__ = 'profile_pb2'
- # @@protoc_insertion_point(class_scope:perftools.profiles.Line)
- ))
-_sym_db.RegisterMessage(Line)
-
-Function = _reflection.GeneratedProtocolMessageType('Function', (_message.Message,), dict(
- DESCRIPTOR = _FUNCTION,
- __module__ = 'profile_pb2'
- # @@protoc_insertion_point(class_scope:perftools.profiles.Function)
- ))
-_sym_db.RegisterMessage(Function)
-
-
-DESCRIPTOR.has_options = True
-DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\035com.google.perftools.profilesB\014ProfileProto'))
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rprofile.proto\x12\x12perftools.profiles\"\xd5\x03\n\x07Profile\x12\x32\n\x0bsample_type\x18\x01 \x03(\x0b\x32\x1d.perftools.profiles.ValueType\x12*\n\x06sample\x18\x02 \x03(\x0b\x32\x1a.perftools.profiles.Sample\x12,\n\x07mapping\x18\x03 \x03(\x0b\x32\x1b.perftools.profiles.Mapping\x12.\n\x08location\x18\x04 \x03(\x0b\x32\x1c.perftools.profiles.Location\x12.\n\x08\x66unction\x18\x05 \x03(\x0b\x32\x1c.perftools.profiles.Function\x12\x14\n\x0cstring_table\x18\x06 \x03(\t\x12\x13\n\x0b\x64rop_frames\x18\x07 \x01(\x03\x12\x13\n\x0bkeep_frames\x18\x08 \x01(\x03\x12\x12\n\ntime_nanos\x18\t \x01(\x03\x12\x16\n\x0e\x64uration_nanos\x18\n \x01(\x03\x12\x32\n\x0bperiod_type\x18\x0b \x01(\x0b\x32\x1d.perftools.profiles.ValueType\x12\x0e\n\x06period\x18\x0c \x01(\x03\x12\x0f\n\x07\x63omment\x18\r \x03(\x03\x12\x1b\n\x13\x64\x65\x66\x61ult_sample_type\x18\x0e \x01(\x03\"\'\n\tValueType\x12\x0c\n\x04type\x18\x01 \x01(\x03\x12\x0c\n\x04unit\x18\x02 \x01(\x03\"V\n\x06Sample\x12\x13\n\x0blocation_id\x18\x01 \x03(\x04\x12\r\n\x05value\x18\x02 \x03(\x03\x12(\n\x05label\x18\x03 \x03(\x0b\x32\x19.perftools.profiles.Label\"@\n\x05Label\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\x0b\n\x03str\x18\x02 \x01(\x03\x12\x0b\n\x03num\x18\x03 \x01(\x03\x12\x10\n\x08num_unit\x18\x04 \x01(\x03\"\xdd\x01\n\x07Mapping\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x14\n\x0cmemory_start\x18\x02 \x01(\x04\x12\x14\n\x0cmemory_limit\x18\x03 \x01(\x04\x12\x13\n\x0b\x66ile_offset\x18\x04 \x01(\x04\x12\x10\n\x08\x66ilename\x18\x05 \x01(\x03\x12\x10\n\x08\x62uild_id\x18\x06 \x01(\x03\x12\x15\n\rhas_functions\x18\x07 \x01(\x08\x12\x15\n\rhas_filenames\x18\x08 \x01(\x08\x12\x18\n\x10has_line_numbers\x18\t \x01(\x08\x12\x19\n\x11has_inline_frames\x18\n \x01(\x08\"v\n\x08Location\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x12\n\nmapping_id\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x64\x64ress\x18\x03 \x01(\x04\x12&\n\x04line\x18\x04 \x03(\x0b\x32\x18.perftools.profiles.Line\x12\x11\n\tis_folded\x18\x05 \x01(\x08\")\n\x04Line\x12\x13\n\x0b\x66unction_id\x18\x01 \x01(\x04\x12\x0c\n\x04line\x18\x02 \x01(\x03\"_\n\x08\x46unction\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x0c\n\x04name\x18\x02 \x01(\x03\x12\x13\n\x0bsystem_name\x18\x03 \x01(\x03\x12\x10\n\x08\x66ilename\x18\x04 \x01(\x03\x12\x12\n\nstart_line\x18\x05 \x01(\x03\x42-\n\x1d\x63om.google.perftools.profilesB\x0cProfileProtob\x06proto3')
+
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'profile_pb2', globals())
+if _descriptor._USE_C_DESCRIPTORS == False:
+
+ DESCRIPTOR._options = None
+ DESCRIPTOR._serialized_options = b'\n\035com.google.perftools.profilesB\014ProfileProto'
+ _PROFILE._serialized_start=38
+ _PROFILE._serialized_end=507
+ _VALUETYPE._serialized_start=509
+ _VALUETYPE._serialized_end=548
+ _SAMPLE._serialized_start=550
+ _SAMPLE._serialized_end=636
+ _LABEL._serialized_start=638
+ _LABEL._serialized_end=702
+ _MAPPING._serialized_start=705
+ _MAPPING._serialized_end=926
+ _LOCATION._serialized_start=928
+ _LOCATION._serialized_end=1046
+ _LINE._serialized_start=1048
+ _LINE._serialized_end=1089
+ _FUNCTION._serialized_start=1091
+ _FUNCTION._serialized_end=1186
# @@protoc_insertion_point(module_scope)
diff --git a/simpleperf/scripts/report_sample.py b/simpleperf/scripts/report_sample.py
index dc5c4e2b..9b6947f7 100755
--- a/simpleperf/scripts/report_sample.py
+++ b/simpleperf/scripts/report_sample.py
@@ -18,6 +18,7 @@
"""report_sample.py: report samples in the same format as `perf script`.
"""
+import sys
from simpleperf_report_lib import ReportLib
from simpleperf_utils import BaseArgumentParser, flatten_arg_list, ReportLibOptions
from typing import List, Set, Optional
@@ -88,8 +89,13 @@ def main():
parser.add_argument('--show_tracing_data', action='store_true', help='print tracing data.')
parser.add_argument('--header', action='store_true',
help='Show metadata header, like perf script --header')
+ parser.add_argument('-o', '--output_file', default='', help="""
+ The path of the generated report. Default is stdout.""")
parser.add_report_lib_options()
args = parser.parse_args()
+ # If the output file has been set, redirect stdout.
+ if args.output_file != '' and args.output_file != '-':
+ sys.stdout = open(file=args.output_file, mode='w')
report_sample(
record_file=args.record_file,
symfs_dir=args.symfs,
diff --git a/simpleperf/scripts/simpleperf_report_lib.py b/simpleperf/scripts/simpleperf_report_lib.py
index 48b31199..5f990ac8 100644
--- a/simpleperf/scripts/simpleperf_report_lib.py
+++ b/simpleperf/scripts/simpleperf_report_lib.py
@@ -264,6 +264,8 @@ class ReportLib(object):
self._SetTraceOffCpuModeFunc.restype = ct.c_bool
self._SetSampleFilterFunc = self._lib.SetSampleFilter
self._SetSampleFilterFunc.restype = ct.c_bool
+ self._AggregateThreadsFunc = self._lib.AggregateThreads
+ self._AggregateThreadsFunc.restype = ct.c_bool
self._GetNextSampleFunc = self._lib.GetNextSample
self._GetNextSampleFunc.restype = ct.POINTER(SampleStruct)
self._GetEventOfCurrentSampleFunc = self._lib.GetEventOfCurrentSample
@@ -309,6 +311,8 @@ class ReportLib(object):
self.SetTraceOffCpuMode(options.trace_offcpu)
if options.sample_filters:
self.SetSampleFilter(options.sample_filters)
+ if options.aggregate_threads:
+ self.AggregateThreads(options.aggregate_threads)
def SetLogSeverity(self, log_level: str = 'info'):
""" Set log severity of native lib, can be verbose,debug,info,error,fatal."""
@@ -399,6 +403,18 @@ class ReportLib(object):
res: bool = self._SetSampleFilterFunc(self.getInstance(), filter_array, len(filters))
_check(res, f'Failed to call SetSampleFilter({filters})')
+ def AggregateThreads(self, thread_name_regex_list: List[str]):
+ """ Given a list of thread name regex, threads with names matching the same regex are merged
+ into one thread. As a result, samples from different threads (like a thread pool) can be
+ shown in one flamegraph.
+ """
+ regex_array = (ct.c_char_p * len(thread_name_regex_list))()
+ regex_array[:] = [_char_pt(f) for f in thread_name_regex_list]
+ res: bool = self._AggregateThreadsFunc(
+ self.getInstance(),
+ regex_array, len(thread_name_regex_list))
+ _check(res, f'Failed to call AggregateThreads({thread_name_regex_list})')
+
def GetNextSample(self) -> Optional[SampleStruct]:
""" Return the next sample. If no more samples, return None. """
psample = self._GetNextSampleFunc(self.getInstance())
diff --git a/simpleperf/scripts/simpleperf_utils.py b/simpleperf/scripts/simpleperf_utils.py
index 2a7dfd35..f8d50dca 100644
--- a/simpleperf/scripts/simpleperf_utils.py
+++ b/simpleperf/scripts/simpleperf_utils.py
@@ -447,7 +447,7 @@ class BinaryFinder:
return path
# Find binary by path in binary cache.
if self.binary_cache_dir:
- path = self.binary_cache_dir / dso_path_in_record_file[1:]
+ path = self.binary_cache_dir / dso_path_in_record_file[1:].replace('/', os.sep)
if self._check_path(path, expected_build_id):
return path
# Find binary by its absolute path.
@@ -1006,6 +1006,7 @@ class ReportLibOptions:
trace_offcpu: str
proguard_mapping_files: List[str]
sample_filters: List[str]
+ aggregate_threads: List[str]
class BaseArgumentParser(argparse.ArgumentParser):
@@ -1036,6 +1037,11 @@ class BaseArgumentParser(argparse.ArgumentParser):
If not set, mixed-on-off-cpu mode is used.
""")
self._add_sample_filter_options(sample_filter_group, sample_filter_with_pid_shortcut)
+ parser.add_argument(
+ '--aggregate-threads', nargs='+', metavar='thread_name_regex',
+ help="""Aggregate threads with names matching the same regex. As a result, samples from
+ different threads (like a thread pool) can be shown in one flamegraph.
+ """)
def _add_sample_filter_options(
self, group: Optional[Any] = None, with_pid_shortcut: bool = True):
@@ -1118,7 +1124,7 @@ class BaseArgumentParser(argparse.ArgumentParser):
sample_filters = self._build_sample_filter(namespace)
report_lib_options = ReportLibOptions(
namespace.show_art_frames, namespace.trace_offcpu, namespace.proguard_mapping_file,
- sample_filters)
+ sample_filters, namespace.aggregate_threads)
setattr(namespace, 'report_lib_options', report_lib_options)
if not Log.initialized:
diff --git a/simpleperf/scripts/test/app_test.py b/simpleperf/scripts/test/app_test.py
index c5db86b8..a13200af 100644
--- a/simpleperf/scripts/test/app_test.py
+++ b/simpleperf/scripts/test/app_test.py
@@ -29,16 +29,15 @@ from . test_utils import TestBase, TestHelper, AdbHelper, INFERNO_SCRIPT
class TestExampleBase(TestBase):
@classmethod
- def prepare(cls, example_name, package_name, activity_name, abi=None, adb_root=False):
+ def prepare(cls, example_name, package_name, activity_name, abi=None, adb_root=False,
+ apk_name: str = 'app-debug.apk'):
cls.adb = AdbHelper(enable_switch_to_root=adb_root)
cls.example_path = TestHelper.testdata_path(example_name)
if not os.path.isdir(cls.example_path):
log_fatal("can't find " + cls.example_path)
- apk_files = list(Path(cls.example_path).glob('**/app-profiling.apk'))
+ apk_files = list(Path(cls.example_path).glob(f'**/{apk_name}'))
if not apk_files:
- apk_files = list(Path(cls.example_path).glob('**/app-debug.apk'))
- if not apk_files:
- log_fatal("can't find apk under " + cls.example_path)
+ log_fatal(f"can't find {apk_name} under " + cls.example_path)
cls.apk_path = apk_files[0]
cls.package_name = package_name
cls.activity_name = activity_name
@@ -56,12 +55,16 @@ class TestExampleBase(TestBase):
@classmethod
def tearDownClass(cls):
- remove(cls.testcase_dir)
+ if hasattr(cls, 'testcase_dir'):
+ remove(cls.testcase_dir)
if hasattr(cls, 'package_name'):
cls.adb.check_run(["uninstall", cls.package_name])
def setUp(self):
super(TestExampleBase, self).setUp()
+ if TestHelper.android_version == 8 and (
+ 'ExampleJava' in self.id() or 'ExampleKotlin' in self.id()):
+ self.skipTest('Profiling java code needs wrap.sh on Android O (8.x)')
if 'TraceOffCpu' in self.id() and not TestHelper.is_trace_offcpu_supported():
self.skipTest('trace-offcpu is not supported on device')
# Use testcase_dir to share a common perf.data for reporting. So we don't need to
diff --git a/simpleperf/scripts/test/binary_cache_builder_test.py b/simpleperf/scripts/test/binary_cache_builder_test.py
index 21b21339..ece7d6d4 100644
--- a/simpleperf/scripts/test/binary_cache_builder_test.py
+++ b/simpleperf/scripts/test/binary_cache_builder_test.py
@@ -19,6 +19,7 @@ import os
from pathlib import Path
import shutil
import tempfile
+import zipfile
from binary_cache_builder import BinaryCacheBuilder
from simpleperf_utils import ReadElf, remove, ToolFinder
@@ -28,7 +29,7 @@ from . test_utils import TestBase, TestHelper
class TestBinaryCacheBuilder(TestBase):
def test_copy_binaries_from_symfs_dirs(self):
readelf = ReadElf(TestHelper.ndk_path)
- strip = ToolFinder.find_tool_path('llvm-strip', arch='arm')
+ strip = ToolFinder.find_tool_path('llvm-strip', ndk_path=TestHelper.ndk_path, arch='arm')
self.assertIsNotNone(strip)
symfs_dir = os.path.join(self.test_dir, 'symfs_dir')
remove(symfs_dir)
@@ -36,12 +37,12 @@ class TestBinaryCacheBuilder(TestBase):
filename = 'simpleperf_runtest_two_functions_arm'
origin_file = TestHelper.testdata_path(filename)
source_file = os.path.join(symfs_dir, filename)
- target_file = os.path.join('binary_cache', filename)
- expected_build_id = readelf.get_build_id(origin_file)
+ build_id = readelf.get_build_id(origin_file)
binary_cache_builder = BinaryCacheBuilder(TestHelper.ndk_path, False)
- binary_cache_builder.binaries['simpleperf_runtest_two_functions_arm'] = expected_build_id
+ binary_cache_builder.binaries['simpleperf_runtest_two_functions_arm'] = build_id
# Copy binary if target file doesn't exist.
+ target_file = binary_cache_builder.find_path_in_cache(filename)
remove(target_file)
self.run_cmd([strip, '--strip-all', '-o', source_file, origin_file])
binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir])
@@ -62,34 +63,69 @@ class TestBinaryCacheBuilder(TestBase):
binary_cache_builder.binaries['elf'] = ''
symfs_dir = TestHelper.testdata_path('data/symfs_without_build_id')
source_file = os.path.join(symfs_dir, 'elf')
- target_file = os.path.join('binary_cache', 'elf')
+ target_file = binary_cache_builder.find_path_in_cache('elf')
binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir])
self.assertTrue(filecmp.cmp(target_file, source_file))
binary_cache_builder.pull_binaries_from_device()
self.assertTrue(filecmp.cmp(target_file, source_file))
+ def test_copy_binary_with_different_name(self):
+ # Build symfs_dir.
+ symfs_dir = self.test_dir / 'symfs_dir'
+ remove(symfs_dir)
+ symfs_dir.mkdir()
+ filename = 'simpleperf_runtest_two_functions_arm'
+ origin_file = TestHelper.testdata_path(filename)
+ modified_name = 'two_functions_arm'
+ source_file = os.path.join(symfs_dir, modified_name)
+ shutil.copy(origin_file, source_file)
+
+ # Copy binary with the same build id but a different name.
+ builder = BinaryCacheBuilder(TestHelper.ndk_path, False)
+ builder.binaries[filename] = builder.readelf.get_build_id(origin_file)
+ builder.copy_binaries_from_symfs_dirs([symfs_dir])
+
+ target_file = builder.find_path_in_cache(filename)
+ self.assertTrue(filecmp.cmp(target_file, source_file))
+
+ def test_copy_binary_for_native_lib_embedded_in_apk(self):
+ apk_path = TestHelper.testdata_path('data/app/com.example.hellojni-1/base.apk')
+ symfs_dir = self.test_dir / 'symfs_dir'
+ with zipfile.ZipFile(apk_path, 'r') as zip_ref:
+ zip_ref.extractall(symfs_dir)
+ builder = BinaryCacheBuilder(TestHelper.ndk_path, False)
+ builder.collect_used_binaries(
+ TestHelper.testdata_path('has_embedded_native_libs_apk_perf.data'))
+ builder.copy_binaries_from_symfs_dirs([symfs_dir])
+
+ device_path = [p for p in builder.binaries if 'libhello-jni.so' in p][0]
+ target_file = builder.find_path_in_cache(device_path)
+ self.assertTrue(target_file.is_file())
+ # Check that we are not using path format of embedded lib in apk. Because
+ # simpleperf can't use it from binary_cache.
+ self.assertNotIn('!/', str(target_file))
+
def test_prefer_binary_with_debug_info(self):
binary_cache_builder = BinaryCacheBuilder(TestHelper.ndk_path, False)
binary_cache_builder.collect_used_binaries(
TestHelper.testdata_path('runtest_two_functions_arm64_perf.data'))
+ filename = 'simpleperf_runtest_two_functions_arm64'
# Create a symfs_dir, which contains elf file with and without debug info.
with tempfile.TemporaryDirectory() as tmp_dir:
shutil.copy(
TestHelper.testdata_path(
'simpleperf_runtest_two_functions_arm64_without_debug_info'),
- Path(tmp_dir) / 'simpleperf_runtest_two_functions_arm64')
+ Path(tmp_dir) / filename)
debug_dir = Path(tmp_dir) / 'debug'
debug_dir.mkdir()
- shutil.copy(TestHelper.testdata_path(
- 'simpleperf_runtest_two_functions_arm64'), debug_dir)
+ debug_file = TestHelper.testdata_path(filename)
+ shutil.copy(debug_file, debug_dir)
# Check if the elf file with debug info is chosen.
binary_cache_builder.copy_binaries_from_symfs_dirs([tmp_dir])
- elf_path = (Path(binary_cache_builder.binary_cache_dir) / 'data' /
- 'local' / 'tmp' / 'simpleperf_runtest_two_functions_arm64')
- self.assertTrue(elf_path.is_file())
- self.assertIn('.debug_info', binary_cache_builder.readelf.get_sections(elf_path))
+ target_file = binary_cache_builder.find_path_in_cache('/data/local/tmp/' + filename)
+ self.assertTrue(filecmp.cmp(target_file, debug_file))
def test_create_build_id_list(self):
symfs_dir = TestHelper.testdata_dir
@@ -97,9 +133,10 @@ class TestBinaryCacheBuilder(TestBase):
binary_cache_builder.collect_used_binaries(
TestHelper.testdata_path('runtest_two_functions_arm64_perf.data'))
binary_cache_builder.copy_binaries_from_symfs_dirs([symfs_dir])
- elf_path = (Path(binary_cache_builder.binary_cache_dir) / 'data' /
- 'local' / 'tmp' / 'simpleperf_runtest_two_functions_arm64')
- self.assertTrue(elf_path.is_file())
+
+ target_file = binary_cache_builder.find_path_in_cache(
+ '/data/local/tmp/simpleperf_runtest_two_functions_arm64')
+ self.assertTrue(target_file.is_file())
binary_cache_builder.create_build_id_list()
build_id_list_path = Path(binary_cache_builder.binary_cache_dir) / 'build_id_list'
diff --git a/simpleperf/scripts/test/cpp_app_test.py b/simpleperf/scripts/test/cpp_app_test.py
index 86667d39..0939cce8 100644
--- a/simpleperf/scripts/test/cpp_app_test.py
+++ b/simpleperf/scripts/test/cpp_app_test.py
@@ -15,6 +15,7 @@
# limitations under the License.
import os
+import unittest
from simpleperf_utils import remove
from . app_test import TestExampleBase
@@ -75,6 +76,21 @@ class TestExampleCpp(TestExampleBase):
'--add_disassembly', '--binary_filter', "libnative-lib.so"])
+class TestExampleCppProfileableApk(TestExampleCpp):
+ """ Test profiling a profileable released apk."""
+ @classmethod
+ def setUpClass(cls):
+ if TestHelper.android_version >= 10:
+ cls.prepare("SimpleperfExampleCpp",
+ "simpleperf.example.cpp",
+ ".MainActivity", apk_name='app-release.apk')
+
+ def setUp(self):
+ if TestHelper().android_version < 10:
+ raise unittest.SkipTest("Profileable apk isn't supported on Android < Q.")
+ super().setUp()
+
+
class TestExampleCppRoot(TestExampleBase):
@classmethod
def setUpClass(cls):
diff --git a/simpleperf/scripts/test/do_test.py b/simpleperf/scripts/test/do_test.py
index b95a1fef..012dc629 100755
--- a/simpleperf/scripts/test/do_test.py
+++ b/simpleperf/scripts/test/do_test.py
@@ -205,14 +205,13 @@ class Device:
@dataclass
class TestResult:
try_time: int
- ok: bool
+ status: str
duration: str
def __str__(self) -> str:
- if self.ok:
- s = 'OK'
- else:
- s = f'FAILED (at try_time {self.try_time})'
+ s = self.status
+ if s == 'FAILED':
+ s += f' (at try_time {self.try_time})'
s += f' {self.duration}'
return s
@@ -289,7 +288,6 @@ class TestProcess:
def _process_msg(self, msg: str):
test_name, test_success, test_duration = msg.split()
- test_success = test_success == 'OK'
self.test_results[test_name] = TestResult(self.try_time, test_success, test_duration)
def join(self):
@@ -304,7 +302,7 @@ class TestProcess:
for test in self.tests:
if test not in self.test_results:
test_duration = '%.3fs' % (time.time() - self.last_update_time)
- self.test_results[test] = TestResult(self.try_time, False, test_duration)
+ self.test_results[test] = TestResult(self.try_time, 'FAILED', test_duration)
return False
self.try_time += 1
@@ -366,7 +364,7 @@ class TestSummary:
def failed_test_count(self) -> int:
count = 0
for result in self.results.values():
- if result is None or not result.ok:
+ if result is None or result.status == 'FAILED':
count += 1
return count
@@ -392,7 +390,7 @@ class TestSummary:
result = self.results[key]
message = f'{test_name} {test_env} {result}'
print(message, file=fh)
- if not result or not result.ok:
+ if not result or result.status == 'FAILED':
print(message, file=failed_fh)
@@ -518,15 +516,6 @@ def run_tests_in_child_process(tests: List[str], args: argparse.Namespace) -> bo
return False
-def sign_executables_on_darwin():
- """ Sign executables on M1 Mac, otherwise they can't run. """
- if not is_darwin():
- return
- bin_dir = Path(__file__).resolve().parents[1] / 'bin' / 'darwin' / 'x86_64'
- for path in bin_dir.iterdir():
- subprocess.run(f'codesign --force -s - {path}', shell=True, check=True)
-
-
def main() -> bool:
args = get_args()
tests = get_host_tests() if args.only_host_test else get_all_tests()
@@ -542,5 +531,4 @@ def main() -> bool:
# Switch to the test dir.
os.chdir(test_dir)
build_testdata(Path('testdata'))
- sign_executables_on_darwin()
return run_tests_in_child_process(tests, args)
diff --git a/simpleperf/scripts/test/gecko_profile_generator_test.py b/simpleperf/scripts/test/gecko_profile_generator_test.py
index 5a0d45ec..b96dfc85 100644
--- a/simpleperf/scripts/test/gecko_profile_generator_test.py
+++ b/simpleperf/scripts/test/gecko_profile_generator_test.py
@@ -18,7 +18,7 @@ import json
import os
import re
import tempfile
-from typing import List, Optional, Set
+from typing import Dict, List, Optional, Set
from . test_utils import TestBase, TestHelper
@@ -31,19 +31,42 @@ class TestGeckoProfileGenerator(TestBase):
args.extend(options)
return self.run_cmd(args, return_output=True)
+ def generate_profile(self, testdata_file: str, options: Optional[List[str]] = None) -> Dict:
+ output = self.run_generator(testdata_file, options)
+ return json.loads(output)
+
def test_golden(self):
- output = self.run_generator('perf_with_interpreter_frames.data')
+ output = self.run_generator('perf_with_interpreter_frames.data', ['--remove-gaps', '0'])
got = json.loads(output)
golden_path = TestHelper.testdata_path('perf_with_interpreter_frames.gecko.json')
with open(golden_path) as f:
want = json.load(f)
+ # Golden data is formatted with `jq` tool (https://stedolan.github.io/jq/).
+ # Regenerate golden data by running:
+ # $ apt install jq
+ # $ ./gecko_profile_generator.py --remove-gaps 0 -i ../testdata/perf_with_interpreter_frames.data | jq > test/script_testdata/perf_with_interpreter_frames.gecko.json
+ self.assertEqual(
+ json.dumps(got, sort_keys=True, indent=2),
+ json.dumps(want, sort_keys=True, indent=2))
+
+ def test_golden_offcpu(self):
+ output = self.run_generator('perf_with_tracepoint_event.data', ['--remove-gaps', '0'])
+ got = json.loads(output)
+ golden_path = TestHelper.testdata_path('perf_with_tracepoint_event.gecko.json')
+ with open(golden_path) as f:
+ want = json.load(f)
+ # Golden data is formatted with `jq` tool (https://stedolan.github.io/jq/).
+ # Regenerate golden data by running:
+ # $ apt install jq
+ # $ ./gecko_profile_generator.py --remove-gaps 0 -i ../testdata/perf_with_tracepoint_event.data | jq > test/script_testdata/perf_with_tracepoint_event.gecko.json
self.assertEqual(
json.dumps(got, sort_keys=True, indent=2),
json.dumps(want, sort_keys=True, indent=2))
def test_sample_filters(self):
def get_threads_for_filter(filter: str) -> Set[int]:
- report = self.run_generator('perf_display_bitmaps.data', filter.split())
+ report = self.run_generator('perf_display_bitmaps.data',
+ filter.split() + ['--remove-gaps', '0'])
pattern = re.compile(r'"tid":\s+(\d+),')
threads = set()
for m in re.finditer(pattern, report):
@@ -79,3 +102,17 @@ class TestGeckoProfileGenerator(TestBase):
self.assertNotIn(art_frame_str, report)
report = self.run_generator('perf_with_interpreter_frames.data', ['--show-art-frames'])
self.assertIn(art_frame_str, report)
+
+ def test_remove_gaps(self):
+ testdata = 'perf_with_interpreter_frames.data'
+
+ def get_sample_count(options: Optional[List[str]] = None) -> int:
+ data = self.generate_profile(testdata, options)
+ sample_count = 0
+ for thread in data['threads']:
+ sample_count += len(thread['samples']['data'])
+ return sample_count
+ # By default, the gap sample is removed.
+ self.assertEqual(4031, get_sample_count())
+ # Use `--remove-gaps 0` to disable removing gaps.
+ self.assertEqual(4032, get_sample_count(['--remove-gaps', '0']))
diff --git a/simpleperf/scripts/test/java_app_test.py b/simpleperf/scripts/test/java_app_test.py
index 03e7bafb..5f74c643 100644
--- a/simpleperf/scripts/test/java_app_test.py
+++ b/simpleperf/scripts/test/java_app_test.py
@@ -19,17 +19,18 @@ import signal
import subprocess
import sys
import time
+import unittest
from simpleperf_utils import is_windows, remove
from . app_test import TestExampleBase
from . test_utils import TestHelper, INFERNO_SCRIPT
-class TestExamplePureJava(TestExampleBase):
+class TestExampleJava(TestExampleBase):
@classmethod
def setUpClass(cls):
- cls.prepare("SimpleperfExamplePureJava",
- "com.example.simpleperf.simpleperfexamplepurejava",
+ cls.prepare("SimpleperfExampleJava",
+ "simpleperf.example.java",
".MainActivity")
def test_app_profiler(self):
@@ -39,7 +40,7 @@ class TestExamplePureJava(TestExampleBase):
self.run_app_profiler(start_activity=True, build_binary_cache=False)
self.run_cmd(["report.py", "-g", "-o", "report.txt"])
self.check_strings_in_file("report.txt", [
- "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run",
+ "simpleperf.example.java.MainActivity$1.run",
"__start_thread"])
def test_app_profiler_multiprocesses(self):
@@ -59,6 +60,8 @@ class TestExamplePureJava(TestExampleBase):
time.sleep(1)
args = [sys.executable, TestHelper.script_path("app_profiler.py"),
"--app", self.package_name, "-r", "--duration 10000", "--disable_adb_root"]
+ if TestHelper.ndk_path:
+ args += ['--ndk_path', TestHelper.ndk_path]
subproc = subprocess.Popen(args)
time.sleep(3)
@@ -70,9 +73,11 @@ 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, TestHelper.script_path('app_profiler.py'),
- '--app', self.package_name, '-r', '--duration 10000', '--disable_adb_root'])
+ args = [sys.executable, TestHelper.script_path('app_profiler.py'),
+ '--app', self.package_name, '-r', '--duration 10000', '--disable_adb_root']
+ if TestHelper.ndk_path:
+ args += ['--ndk_path', TestHelper.ndk_path]
+ subproc = subprocess.Popen(args)
time.sleep(3)
self.adb.check_run(['shell', 'am', 'force-stop', self.package_name])
subproc.wait()
@@ -88,18 +93,18 @@ class TestExamplePureJava(TestExampleBase):
self.common_test_report()
self.run_cmd(["report.py", "-g", "-o", "report.txt"])
self.check_strings_in_file("report.txt", [
- "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run",
+ "simpleperf.example.java.MainActivity$1.run",
"__start_thread"])
def test_profile_with_process_id(self):
self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity'])
time.sleep(1)
- pid = self.adb.check_run_and_return_output([
- 'shell', 'pidof', 'com.example.simpleperf.simpleperfexamplepurejava']).strip()
+ pid = self.adb.check_run_and_return_output(
+ ['shell', 'pidof', 'simpleperf.example.java']).strip()
self.run_app_profiler(start_activity=False, record_arg='-g --duration 10 -p ' + pid)
self.run_cmd(["report.py", "-g", "-o", "report.txt"])
self.check_strings_in_file("report.txt", [
- "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run",
+ "simpleperf.example.java.MainActivity$1.run",
"__start_thread"])
def test_annotate(self):
@@ -117,29 +122,28 @@ class TestExamplePureJava(TestExampleBase):
def test_report_sample(self):
self.common_test_report_sample(
- ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run",
+ ["simpleperf.example.java.MainActivity$1.run",
"__start_thread"])
def test_pprof_proto_generator(self):
check_strings_with_lines = []
if self.use_compiled_java_code:
check_strings_with_lines = [
- "com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java",
+ "simpleperf/example/java/MainActivity.java",
"run"]
self.common_test_pprof_proto_generator(
check_strings_with_lines=check_strings_with_lines,
- check_strings_without_lines=[
- "com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run"])
+ check_strings_without_lines=["simpleperf.example.java.MainActivity$1.run"])
def test_inferno(self):
self.common_test_inferno()
self.run_app_profiler()
self.run_cmd([INFERNO_SCRIPT, "-sc"])
self.check_inferno_report_html(
- [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run', 80)])
+ [('simpleperf.example.java.MainActivity$1.run', 80)])
self.run_cmd([INFERNO_SCRIPT, "-sc", "-o", "report2.html"])
self.check_inferno_report_html(
- [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run', 80)],
+ [('simpleperf.example.java.MainActivity$1.run', 80)],
"report2.html")
def test_inferno_in_another_dir(self):
@@ -169,11 +173,26 @@ class TestExamplePureJava(TestExampleBase):
self.run_cmd(["report.py", "-g", "-o", "report.txt"])
-class TestExamplePureJavaRoot(TestExampleBase):
+class TestExampleJavaProfileableApk(TestExampleJava):
+ """ Test profiling a profileable released apk."""
+ @classmethod
+ def setUpClass(cls):
+ if TestHelper.android_version >= 10:
+ cls.prepare("SimpleperfExampleJava",
+ "simpleperf.example.java",
+ ".MainActivity", apk_name='app-release.apk')
+
+ def setUp(self):
+ if TestHelper().android_version < 10:
+ raise unittest.SkipTest("Profileable apk isn't supported on Android < Q.")
+ super().setUp()
+
+
+class TestExampleJavaRoot(TestExampleBase):
@classmethod
def setUpClass(cls):
- cls.prepare("SimpleperfExamplePureJava",
- "com.example.simpleperf.simpleperfexamplepurejava",
+ cls.prepare("SimpleperfExampleJava",
+ "simpleperf.example.java",
".MainActivity",
adb_root=True)
@@ -181,20 +200,20 @@ class TestExamplePureJavaRoot(TestExampleBase):
self.common_test_app_profiler()
-class TestExamplePureJavaTraceOffCpu(TestExampleBase):
+class TestExampleJavaTraceOffCpu(TestExampleBase):
@classmethod
def setUpClass(cls):
- cls.prepare("SimpleperfExamplePureJava",
- "com.example.simpleperf.simpleperfexamplepurejava",
+ cls.prepare("SimpleperfExampleJava",
+ "simpleperf.example.java",
".SleepActivity")
def test_smoke(self):
self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-clock:u --trace-offcpu")
self.run_cmd(["report.py", "-g", "-o", "report.txt"])
self.check_strings_in_file("report.txt", [
- "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run",
- "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction",
- "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction"
+ "simpleperf.example.java.SleepActivity$1.run",
+ "simpleperf.example.java.SleepActivity$1.RunFunction",
+ "simpleperf.example.java.SleepActivity$1.SleepFunction"
])
remove("annotated_files")
self.run_cmd(["annotate.py", "-s", self.example_path, '--summary-width', '1000'])
@@ -208,11 +227,9 @@ class TestExamplePureJavaTraceOffCpu(TestExampleBase):
("RunFunction", 20, 20),
("SleepFunction", 20, 0),
("line 24", 1, 0),
- ("line 32", 20, 0)])
+ ("line 31", 20, 0)])
self.run_cmd([INFERNO_SCRIPT, "-sc"])
self.check_inferno_report_html(
- [('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run', 80),
- ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction',
- 20),
- ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction',
- 20)])
+ [('simpleperf.example.java.SleepActivity$1.run', 80),
+ ('simpleperf.example.java.SleepActivity$1.RunFunction', 20),
+ ('simpleperf.example.java.SleepActivity$1.SleepFunction', 20)])
diff --git a/simpleperf/scripts/test/kotlin_app_test.py b/simpleperf/scripts/test/kotlin_app_test.py
index 7b18e058..74f50229 100644
--- a/simpleperf/scripts/test/kotlin_app_test.py
+++ b/simpleperf/scripts/test/kotlin_app_test.py
@@ -15,17 +15,18 @@
# limitations under the License.
import os
+import unittest
from simpleperf_utils import remove
from . app_test import TestExampleBase
-from . test_utils import INFERNO_SCRIPT
+from . test_utils import INFERNO_SCRIPT, TestHelper
-class TestExampleOfKotlin(TestExampleBase):
+class TestExampleKotlin(TestExampleBase):
@classmethod
def setUpClass(cls):
- cls.prepare("SimpleperfExampleOfKotlin",
- "com.example.simpleperf.simpleperfexampleofkotlin",
+ cls.prepare("SimpleperfExampleKotlin",
+ "simpleperf.example.kotlin",
".MainActivity")
def test_app_profiler(self):
@@ -35,14 +36,14 @@ class TestExampleOfKotlin(TestExampleBase):
self.run_app_profiler(start_activity=True, build_binary_cache=False)
self.run_cmd(["report.py", "-g", "-o", "report.txt"])
self.check_strings_in_file("report.txt", [
- "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." +
+ "simpleperf.example.kotlin.MainActivity$createBusyThread$1." +
"run", "__start_thread"])
def test_report(self):
self.common_test_report()
self.run_cmd(["report.py", "-g", "-o", "report.txt"])
self.check_strings_in_file("report.txt", [
- "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." +
+ "simpleperf.example.kotlin.MainActivity$createBusyThread$1." +
"run", "__start_thread"])
def test_annotate(self):
@@ -60,36 +61,51 @@ class TestExampleOfKotlin(TestExampleBase):
def test_report_sample(self):
self.common_test_report_sample([
- "com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1." +
+ "simpleperf.example.kotlin.MainActivity$createBusyThread$1." +
"run", "__start_thread"])
def test_pprof_proto_generator(self):
check_strings_with_lines = []
if self.use_compiled_java_code:
check_strings_with_lines = [
- "com/example/simpleperf/simpleperfexampleofkotlin/MainActivity.kt",
+ "simpleperf/example/kotlin/MainActivity.kt",
"run"]
self.common_test_pprof_proto_generator(
check_strings_with_lines=check_strings_with_lines,
- check_strings_without_lines=["com.example.simpleperf.simpleperfexampleofkotlin." +
- "MainActivity$createBusyThread$1.run"])
+ check_strings_without_lines=[
+ 'simpleperf.example.kotlin.MainActivity$createBusyThread$1.run'])
def test_inferno(self):
self.common_test_inferno()
self.run_app_profiler()
self.run_cmd([INFERNO_SCRIPT, "-sc"])
- self.check_inferno_report_html([('com.example.simpleperf.simpleperfexampleofkotlin.' +
- 'MainActivity$createBusyThread$1.run', 80)])
+ self.check_inferno_report_html([
+ ('simpleperf.example.kotlin.MainActivity$createBusyThread$1.run', 80)])
def test_report_html(self):
self.common_test_report_html()
-class TestExampleOfKotlinRoot(TestExampleBase):
+class TestExampleKotlinProfileableApk(TestExampleKotlin):
+ """ Test profiling a profileable released apk."""
+ @classmethod
+ def setUpClass(cls):
+ if TestHelper.android_version >= 10:
+ cls.prepare("SimpleperfExampleKotlin",
+ "simpleperf.example.kotlin",
+ ".MainActivity", apk_name='app-release.apk')
+
+ def setUp(self):
+ if TestHelper().android_version < 10:
+ raise unittest.SkipTest("Profileable apk isn't supported on Android < Q.")
+ super().setUp()
+
+
+class TestExampleKotlinRoot(TestExampleBase):
@classmethod
def setUpClass(cls):
- cls.prepare("SimpleperfExampleOfKotlin",
- "com.example.simpleperf.simpleperfexampleofkotlin",
+ cls.prepare("SimpleperfExampleKotlin",
+ "simpleperf.example.kotlin",
".MainActivity",
adb_root=True)
@@ -97,18 +113,17 @@ class TestExampleOfKotlinRoot(TestExampleBase):
self.common_test_app_profiler()
-class TestExampleOfKotlinTraceOffCpu(TestExampleBase):
+class TestExampleKotlinTraceOffCpu(TestExampleBase):
@classmethod
def setUpClass(cls):
- cls.prepare("SimpleperfExampleOfKotlin",
- "com.example.simpleperf.simpleperfexampleofkotlin",
+ cls.prepare("SimpleperfExampleKotlin",
+ "simpleperf.example.kotlin",
".SleepActivity")
def test_smoke(self):
self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-clock:u --trace-offcpu")
self.run_cmd(["report.py", "-g", "-o", "report.txt"])
- function_prefix = "com.example.simpleperf.simpleperfexampleofkotlin." + \
- "SleepActivity$createRunSleepThread$1."
+ function_prefix = 'simpleperf.example.kotlin.SleepActivity$createRunSleepThread$1.'
self.check_strings_in_file("report.txt", [
function_prefix + "run",
function_prefix + "RunFunction",
@@ -125,8 +140,8 @@ class TestExampleOfKotlinTraceOffCpu(TestExampleBase):
("run", 80, 0),
("RunFunction", 20, 20),
("SleepFunction", 20, 0),
- ("line 24", 20, 0),
- ("line 32", 20, 0)])
+ ("line 23", 20, 0),
+ ("line 31", 20, 0)])
self.run_cmd([INFERNO_SCRIPT, "-sc"])
self.check_inferno_report_html([
diff --git a/simpleperf/scripts/test/pprof_proto_generator_test.py b/simpleperf/scripts/test/pprof_proto_generator_test.py
index cbeb8d6a..297cf149 100644
--- a/simpleperf/scripts/test/pprof_proto_generator_test.py
+++ b/simpleperf/scripts/test/pprof_proto_generator_test.py
@@ -217,8 +217,8 @@ class TestPprofProtoGenerator(TestBase):
binary_cache_builder.build_binary_cache(testdata_file, [TestHelper.testdata_dir])
# Read recording file.
- config = {'ndk_path': None, 'max_chain_length': 1000000,
- 'report_lib_options': ReportLibOptions(False, '', None, None)}
+ config = {'ndk_path': TestHelper.ndk_path, 'max_chain_length': 1000000,
+ 'report_lib_options': ReportLibOptions(False, '', None, None, None)}
generator = PprofProfileGenerator(config)
generator.load_record_file(testdata_file)
diff --git a/simpleperf/scripts/test/report_html_test.py b/simpleperf/scripts/test/report_html_test.py
index 250ad033..7241a5e1 100644
--- a/simpleperf/scripts/test/report_html_test.py
+++ b/simpleperf/scripts/test/report_html_test.py
@@ -18,7 +18,7 @@ import collections
import json
import os
import tempfile
-from typing import Any, Dict, List, Set
+from typing import Any, Dict, List, Optional, Set
from binary_cache_builder import BinaryCacheBuilder
from . test_utils import TestBase, TestHelper
@@ -254,3 +254,25 @@ class TestReportHtml(TestBase):
self.assertNotIn(art_frame_str, report)
report = self.get_record_data_string(options + ['--show-art-frames'])
self.assertIn(art_frame_str, report)
+
+ def test_aggregate_threads(self):
+ def get_thread_names(aggregate_threads_option: Optional[List[str]]) -> Dict[str, int]:
+ options = ['-i', TestHelper.testdata_path('perf_display_bitmaps.data')]
+ if aggregate_threads_option:
+ options += ['--aggregate-threads'] + aggregate_threads_option
+ record_data = self.get_record_data(options)
+ thread_names = {}
+ try:
+ for thread in record_data['sampleInfo'][0]['processes'][0]['threads']:
+ tid = str(thread['tid'])
+ thread_names[record_data['threadNames'][tid]] = thread['sampleCount']
+ except IndexError:
+ pass
+ return thread_names
+ thread_names = get_thread_names(None)
+ self.assertEqual(thread_names['AsyncTask #3'], 6)
+ self.assertEqual(thread_names['AsyncTask #4'], 13)
+ thread_names = get_thread_names(['AsyncTask.*'])
+ self.assertEqual(thread_names['AsyncTask.*'], 19)
+ self.assertNotIn('AsyncTask #3', thread_names)
+ self.assertNotIn('AsyncTask #4', thread_names)
diff --git a/simpleperf/scripts/test/report_lib_test.py b/simpleperf/scripts/test/report_lib_test.py
index 1447c5ae..8e212a27 100644
--- a/simpleperf/scripts/test/report_lib_test.py
+++ b/simpleperf/scripts/test/report_lib_test.py
@@ -16,7 +16,7 @@
import os
import tempfile
-from typing import List, Set
+from typing import Dict, List, Optional, Set
from simpleperf_report_lib import ReportLib
from . test_utils import TestBase, TestHelper
@@ -311,3 +311,24 @@ class TestReportLib(TestBase):
self.assertIn(31881, threads)
self.assertNotIn(31850, threads)
os.unlink(filter_file.name)
+
+ def test_aggregate_threads(self):
+ """ Test using ReportLib.AggregateThreads(). """
+ def get_thread_names(aggregate_regex_list: Optional[List[str]]) -> Dict[str, int]:
+ self.report_lib.Close()
+ self.report_lib = ReportLib()
+ self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_display_bitmaps.data'))
+ if aggregate_regex_list:
+ self.report_lib.AggregateThreads(aggregate_regex_list)
+ thread_names = {}
+ while self.report_lib.GetNextSample():
+ sample = self.report_lib.GetCurrentSample()
+ thread_names[sample.thread_comm] = thread_names.get(sample.thread_comm, 0) + 1
+ return thread_names
+ thread_names = get_thread_names(None)
+ self.assertEqual(thread_names['AsyncTask #3'], 6)
+ self.assertEqual(thread_names['AsyncTask #4'], 13)
+ thread_names = get_thread_names(['AsyncTask.*'])
+ self.assertEqual(thread_names['AsyncTask.*'], 19)
+ self.assertNotIn('AsyncTask #3', thread_names)
+ self.assertNotIn('AsyncTask #4', thread_names)
diff --git a/simpleperf/scripts/test/report_sample_test.py b/simpleperf/scripts/test/report_sample_test.py
index 5b6681cb..83f1d36b 100644
--- a/simpleperf/scripts/test/report_sample_test.py
+++ b/simpleperf/scripts/test/report_sample_test.py
@@ -20,6 +20,7 @@ import tempfile
from typing import List, Optional, Set
from . test_utils import TestBase, TestHelper
+from simpleperf_utils import remove
class TestReportSample(TestBase):
@@ -36,6 +37,33 @@ class TestReportSample(TestBase):
want = f.read()
self.assertEqual(got, want)
+ def test_output_flag(self):
+ # Test short form flag.
+ remove('some.output')
+ got = self.get_record_data_string('perf_display_bitmaps.data',
+ ['-o', 'some.output'])
+ self.assertEqual(got, '')
+ self.check_exist(filename='some.output')
+
+ # Test long form flag
+ remove("some.output")
+ got = self.get_record_data_string('perf_display_bitmaps.data',
+ ['--output_file', 'some.output'])
+ self.assertEqual(got, '')
+ self.check_exist(filename="some.output")
+
+ # Verify that the output file contains expected data
+ with open(TestHelper.testdata_path('perf_display_bitmaps.perf-script')) as f:
+ want = f.read()
+ with open('some.output') as f:
+ got = f.read()
+ self.assertEqual(got, want)
+
+ # Verify that an output spec of '-' is stdout
+ remove("some.output")
+ got = self.get_record_data_string('perf_display_bitmaps.data', ['-o', '-'])
+ self.assertEqual(got, want)
+
def test_comm_filter_to_renderthread(self):
got = self.get_record_data_string('perf_display_bitmaps.data', ['--comm', 'RenderThread'])
self.assertIn('RenderThread', got)
diff --git a/simpleperf/scripts/test/script_testdata/perf_with_interpreter_frames.gecko.json b/simpleperf/scripts/test/script_testdata/perf_with_interpreter_frames.gecko.json
index 1903451a..e423687d 100644
--- a/simpleperf/scripts/test/script_testdata/perf_with_interpreter_frames.gecko.json
+++ b/simpleperf/scripts/test/script_testdata/perf_with_interpreter_frames.gecko.json
@@ -40,6 +40,13 @@
]
},
{
+ "color": "blue",
+ "name": "Off-CPU",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
"color": "grey",
"name": "Other",
"subcategories": [
diff --git a/simpleperf/scripts/test/script_testdata/perf_with_tracepoint_event.gecko.json b/simpleperf/scripts/test/script_testdata/perf_with_tracepoint_event.gecko.json
new file mode 100644
index 00000000..75442324
--- /dev/null
+++ b/simpleperf/scripts/test/script_testdata/perf_with_tracepoint_event.gecko.json
@@ -0,0 +1,542 @@
+{
+ "libs": [],
+ "meta": {
+ "abi": "aarch64",
+ "asyncstack": 1,
+ "categories": [
+ {
+ "color": "yellow",
+ "name": "User",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
+ "color": "orange",
+ "name": "Kernel",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
+ "color": "yellow",
+ "name": "Native",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
+ "color": "green",
+ "name": "DEX",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
+ "color": "green",
+ "name": "OAT",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
+ "color": "blue",
+ "name": "Off-CPU",
+ "subcategories": [
+ "Other"
+ ]
+ },
+ {
+ "color": "grey",
+ "name": "Other",
+ "subcategories": [
+ "Other"
+ ]
+ }
+ ],
+ "debug": 0,
+ "device": "Google:Pixel 2:walleye",
+ "gcpoison": 0,
+ "interval": 1,
+ "markerSchema": [],
+ "oscpu": null,
+ "platform": null,
+ "presymbolicated": true,
+ "processType": 0,
+ "product": "/data/local/tmp/simpleperf record -e sched:sched_switch -e cpu-cycles sleep 1",
+ "shutdownTime": null,
+ "stackwalk": 1,
+ "startTime": 1512941771000,
+ "version": 24
+ },
+ "pausedRanges": [],
+ "processes": [],
+ "threads": [
+ {
+ "frameTable": {
+ "data": [
+ [
+ 0,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 1,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 2,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 5,
+ 0
+ ],
+ [
+ 3,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 4,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 5,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 6,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 7,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 8,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 9,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ],
+ [
+ 10,
+ false,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0
+ ]
+ ],
+ "schema": {
+ "category": 7,
+ "column": 6,
+ "implementation": 3,
+ "innerWindowID": 2,
+ "line": 5,
+ "location": 0,
+ "optimizations": 4,
+ "relevantForJS": 1,
+ "subcategory": 8
+ }
+ },
+ "markers": {
+ "data": [],
+ "schema": {
+ "category": 4,
+ "data": 5,
+ "endTime": 2,
+ "name": 0,
+ "phase": 3,
+ "startTime": 1
+ }
+ },
+ "name": "sleep",
+ "pid": 9896,
+ "processType": "default",
+ "registerTime": 0,
+ "samples": {
+ "data": [
+ [
+ 0,
+ 536732415.65594,
+ 0
+ ],
+ [
+ 1,
+ 536732415.663024,
+ 0
+ ],
+ [
+ 2,
+ 536732415.855628,
+ 0
+ ],
+ [
+ 3,
+ 536732416.562399,
+ 0
+ ],
+ [
+ 2,
+ 536732416.922451,
+ 0
+ ],
+ [
+ 4,
+ 536732418.120784,
+ 0
+ ],
+ [
+ 2,
+ 536732418.371201,
+ 0
+ ],
+ [
+ 5,
+ 536732419.142503,
+ 0
+ ],
+ [
+ 5,
+ 536732419.14969,
+ 0
+ ],
+ [
+ 2,
+ 536732419.966826,
+ 0
+ ],
+ [
+ 6,
+ 536732420.235419,
+ 0
+ ],
+ [
+ 2,
+ 536732420.924794,
+ 0
+ ],
+ [
+ 7,
+ 536732421.171461,
+ 0
+ ],
+ [
+ 2,
+ 536732421.250211,
+ 0
+ ],
+ [
+ 4,
+ 536732421.445836,
+ 0
+ ],
+ [
+ 2,
+ 536732421.544899,
+ 0
+ ],
+ [
+ 7,
+ 536732421.800888,
+ 0
+ ],
+ [
+ 8,
+ 536732421.836878,
+ 0
+ ],
+ [
+ 9,
+ 536732421.841878,
+ 0
+ ],
+ [
+ 2,
+ 536732421.87318,
+ 0
+ ],
+ [
+ 2,
+ 536732421.900315,
+ 0
+ ],
+ [
+ 5,
+ 536732422.094638,
+ 0
+ ],
+ [
+ 2,
+ 536732422.188128,
+ 0
+ ],
+ [
+ 2,
+ 536732422.43094,
+ 0
+ ],
+ [
+ 2,
+ 536732422.702972,
+ 0
+ ],
+ [
+ 2,
+ 536732423.006357,
+ 0
+ ],
+ [
+ 2,
+ 536732423.138076,
+ 0
+ ],
+ [
+ 2,
+ 536732423.500628,
+ 0
+ ],
+ [
+ 2,
+ 536732423.76943,
+ 0
+ ],
+ [
+ 2,
+ 536732425.676201,
+ 0
+ ],
+ [
+ 2,
+ 536732426.086722,
+ 0
+ ],
+ [
+ 2,
+ 536732426.35693,
+ 0
+ ],
+ [
+ 2,
+ 536732426.801513,
+ 0
+ ],
+ [
+ 2,
+ 536732426.916774,
+ 0
+ ],
+ [
+ 2,
+ 536732427.020263,
+ 0
+ ],
+ [
+ 2,
+ 536732427.455524,
+ 0
+ ],
+ [
+ 2,
+ 536732428.163961,
+ 0
+ ],
+ [
+ 2,
+ 536732428.383753,
+ 0
+ ],
+ [
+ 2,
+ 536732428.868284,
+ 0
+ ],
+ [
+ 2,
+ 536732429.336982,
+ 0
+ ],
+ [
+ 2,
+ 536732429.894222,
+ 0
+ ],
+ [
+ 2,
+ 536732430.261357,
+ 0
+ ],
+ [
+ 10,
+ 536732430.486097,
+ 0
+ ],
+ [
+ 2,
+ 536732433.741565,
+ 0
+ ],
+ [
+ 2,
+ 536732434.293857,
+ 0
+ ]
+ ],
+ "schema": {
+ "responsiveness": 2,
+ "stack": 0,
+ "time": 1
+ }
+ },
+ "stackTable": {
+ "data": [
+ [
+ null,
+ 0,
+ 0
+ ],
+ [
+ null,
+ 1,
+ 0
+ ],
+ [
+ null,
+ 2,
+ 0
+ ],
+ [
+ null,
+ 3,
+ 0
+ ],
+ [
+ null,
+ 4,
+ 0
+ ],
+ [
+ null,
+ 5,
+ 0
+ ],
+ [
+ null,
+ 6,
+ 0
+ ],
+ [
+ null,
+ 7,
+ 0
+ ],
+ [
+ null,
+ 8,
+ 0
+ ],
+ [
+ null,
+ 9,
+ 0
+ ],
+ [
+ null,
+ 10,
+ 0
+ ]
+ ],
+ "schema": {
+ "category": 2,
+ "frame": 1,
+ "prefix": 0
+ }
+ },
+ "stringTable": [
+ "perf_event_exec (in [kernel.kallsyms])",
+ "memcpy (in [kernel.kallsyms])",
+ "__schedule (in [kernel.kallsyms])",
+ "schedule_timeout (in [kernel.kallsyms])",
+ "filemap_fault (in [kernel.kallsyms])",
+ "_raw_spin_unlock_irq (in [kernel.kallsyms])",
+ "__wait_on_bit_lock (in [kernel.kallsyms])",
+ "generic_file_read_iter (in [kernel.kallsyms])",
+ "__do_softirq (in [kernel.kallsyms])",
+ "_raw_spin_unlock_irqrestore (in [kernel.kallsyms])",
+ "__clean_dcache_area_pou (in [kernel.kallsyms])"
+ ],
+ "tid": 9896,
+ "unregisterTime": null
+ }
+ ]
+}
diff --git a/simpleperf/scripts/test/test_utils.py b/simpleperf/scripts/test/test_utils.py
index 422f04de..f10ea9bf 100644
--- a/simpleperf/scripts/test/test_utils.py
+++ b/simpleperf/scripts/test/test_utils.py
@@ -139,6 +139,8 @@ class TestBase(unittest.TestCase):
elif result.failures and result.failures[-1][0] == self:
status = 'FAILED'
err_info = result.failures[-1][1]
+ elif result.skipped and result.skipped[-1][0] == self:
+ status = 'SKIPPED'
else:
status = 'OK'
@@ -146,7 +148,7 @@ class TestBase(unittest.TestCase):
TestHelper.log(
'end test %s.%s %s (%.3fs)' %
(self.__class__.__name__, self._testMethodName, status, time_taken))
- if status != 'OK':
+ if status == 'FAILED':
TestHelper.log(err_info)
# Remove test data for passed tests to save space.
@@ -162,7 +164,7 @@ class TestBase(unittest.TestCase):
args += TestHelper.browser_option
if TestHelper.ndk_path:
if args[0] in ['app_profiler.py', 'binary_cache_builder.py', 'pprof_proto_generator.py',
- 'report_html.py']:
+ 'report_html.py', 'annotate.py']:
args += ['--ndk_path', TestHelper.ndk_path]
if args[0].endswith('.py'):
args = [sys.executable, TestHelper.script_path(args[0])] + args[1:]
diff --git a/simpleperf/scripts/test/tools_test.py b/simpleperf/scripts/test/tools_test.py
index 83ccf481..9c9fbd7e 100644
--- a/simpleperf/scripts/test/tools_test.py
+++ b/simpleperf/scripts/test/tools_test.py
@@ -298,7 +298,7 @@ system/extras/simpleperf/runtest/two_functions.cpp:21:3
def test_source_file_searcher(self):
searcher = SourceFileSearcher(
[TestHelper.testdata_path('SimpleperfExampleCpp'),
- TestHelper.testdata_path('SimpleperfExampleOfKotlin')])
+ TestHelper.testdata_path('SimpleperfExampleKotlin')])
def format_path(path):
return os.path.join(TestHelper.testdata_dir, path.replace('/', os.sep))
@@ -317,8 +317,9 @@ system/extras/simpleperf/runtest/two_functions.cpp:21:3
searcher.get_real_path('cpp/MainActivity.java'))
# Find a Kotlin file.
self.assertEqual(
- format_path('SimpleperfExampleOfKotlin/app/src/main/java/com/example/' +
- 'simpleperf/simpleperfexampleofkotlin/MainActivity.kt'),
+ format_path(
+ 'SimpleperfExampleKotlin/app/src/main/java/simpleperf/example/kotlin/' +
+ 'MainActivity.kt'),
searcher.get_real_path('MainActivity.kt'))
def test_is_elf_file(self):
@@ -340,18 +341,22 @@ system/extras/simpleperf/runtest/two_functions.cpp:21:3
build_id = readelf.get_build_id(elf_path)
self.assertGreater(len(build_id), 0)
binary_cache_builder.binaries[elf_name] = build_id
+
+ filename_without_build_id = '/data/symfs_without_build_id/elf'
+ binary_cache_builder.binaries[filename_without_build_id] = ''
+
binary_cache_builder.copy_binaries_from_symfs_dirs([TestHelper.testdata_dir])
binary_cache_builder.create_build_id_list()
# Test BinaryFinder.
- path_in_binary_cache = Path(binary_cache_builder.binary_cache_dir, elf_name)
+ path_in_binary_cache = binary_cache_builder.find_path_in_cache(elf_name)
binary_finder = BinaryFinder(binary_cache_builder.binary_cache_dir, readelf)
# Find binary using build id.
path = binary_finder.find_binary('[not_exist_file]', build_id)
self.assertEqual(path, path_in_binary_cache)
# Find binary using path.
- path = binary_finder.find_binary('/' + elf_name, None)
- self.assertEqual(path, path_in_binary_cache)
+ path = binary_finder.find_binary(filename_without_build_id, None)
+ self.assertIsNotNone(path)
# Find binary using absolute path.
path = binary_finder.find_binary(str(path_in_binary_cache), None)
self.assertEqual(path, path_in_binary_cache)
diff --git a/simpleperf/test_util.cpp b/simpleperf/test_util.cpp
index 43e21831..60d8ea35 100644
--- a/simpleperf/test_util.cpp
+++ b/simpleperf/test_util.cpp
@@ -43,6 +43,10 @@ bool IsInNativeAbi() {
if (s.find("arm") == std::string::npos && s.find("aarch64") == std::string::npos) {
in_native_abi = 0;
}
+ } else if (GetTargetArch() == ARCH_RISCV64) {
+ if (s.find("riscv") == std::string::npos) {
+ in_native_abi = 0;
+ }
}
}
return in_native_abi == 1;
@@ -81,9 +85,10 @@ bool HasHardwareCounter() {
android::base::StartsWith(fingerprint, "google/sdk_gpc") ||
android::base::StartsWith(fingerprint, "generic/cf");
- if (arch == ARCH_X86_64 || arch == ARCH_X86_32 || is_emulator) {
- // On x86 and x86_64, it's likely to run on an emulator or vm without hardware perf
- // counters. It's hard to enumerate them all. So check the support at runtime.
+ if (arch == ARCH_X86_64 || arch == ARCH_X86_32 || !IsInNativeAbi() || is_emulator) {
+ // On x86 and x86_64, or when we are not in native abi, it's likely to run on an emulator or
+ // vm without hardware perf counters. It's hard to enumerate them all. So check the support
+ // at runtime.
const simpleperf::EventType* type = simpleperf::FindEventTypeByName("cpu-cycles", false);
CHECK(type != nullptr);
perf_event_attr attr = CreateDefaultPerfEventAttr(*type);
diff --git a/simpleperf/test_util.h b/simpleperf/test_util.h
index 662c3b2f..21b89a5e 100644
--- a/simpleperf/test_util.h
+++ b/simpleperf/test_util.h
@@ -188,6 +188,8 @@ class AppHelper {
return "arm64-v8a";
#elif defined(__arm__)
return "armeabi-v7a";
+#elif defined(__riscv)
+ return "riscv64";
#else
#error "unrecognized ABI"
#endif
@@ -195,4 +197,4 @@ class AppHelper {
std::vector<std::string> installed_packages_;
std::unique_ptr<Workload> app_start_proc_;
-}; \ No newline at end of file
+};
diff --git a/simpleperf/testdata/DisplayBitmaps.apk b/simpleperf/testdata/DisplayBitmaps.apk
index 63b40744..2911edde 100644
--- a/simpleperf/testdata/DisplayBitmaps.apk
+++ b/simpleperf/testdata/DisplayBitmaps.apk
Binary files differ
diff --git a/simpleperf/testdata/DisplayBitmapsTest.apk b/simpleperf/testdata/DisplayBitmapsTest.apk
index 15807198..db5575ef 100644
--- a/simpleperf/testdata/DisplayBitmapsTest.apk
+++ b/simpleperf/testdata/DisplayBitmapsTest.apk
Binary files differ
diff --git a/simpleperf/testdata/data/symfs_for_no_symbol_table_warning/elf b/simpleperf/testdata/data/symfs_for_no_symbol_table_warning/elf
deleted file mode 100644
index a92e41fa..00000000
--- a/simpleperf/testdata/data/symfs_for_no_symbol_table_warning/elf
+++ /dev/null
Binary files differ
diff --git a/simpleperf/testdata/etm/perf.data b/simpleperf/testdata/etm/perf_etm.data
index 595443d0..595443d0 100644
--- a/simpleperf/testdata/etm/perf.data
+++ b/simpleperf/testdata/etm/perf_etm.data
Binary files differ
diff --git a/simpleperf/testdata/etm/perf_inject.data b/simpleperf/testdata/etm/perf_inject.data
index 4c121a32..9f83430b 100644
--- a/simpleperf/testdata/etm/perf_inject.data
+++ b/simpleperf/testdata/etm/perf_inject.data
@@ -20,5 +20,6 @@
10a0->1054:1
10b0->0:1
10ec->0:1
+// build_id: 0x0c9a20bf9c009d0e4e8bbf9fad0300ae00000000
// /data/local/tmp/etm_test_loop
diff --git a/simpleperf/testdata/etm/perf_with_missing_aux_data.data b/simpleperf/testdata/etm/perf_with_missing_aux_data.data
new file mode 100644
index 00000000..781a0a35
--- /dev/null
+++ b/simpleperf/testdata/etm/perf_with_missing_aux_data.data
Binary files differ
diff --git a/simpleperf/thread_tree.cpp b/simpleperf/thread_tree.cpp
index 28987222..a6d2e81e 100644
--- a/simpleperf/thread_tree.cpp
+++ b/simpleperf/thread_tree.cpp
@@ -49,7 +49,15 @@ void ThreadTree::SetThreadName(int pid, int tid, const std::string& comm) {
}
}
-void ThreadTree::ForkThread(int pid, int tid, int ppid, int ptid) {
+bool ThreadTree::ForkThread(int pid, int tid, int ppid, int ptid) {
+ // Check thread ID.
+ if (tid == ptid) {
+ return false;
+ }
+ // Check thread group ID (pid here) as in https://linux.die.net/man/2/clone2.
+ if (pid != tid && pid != ppid) {
+ return false;
+ }
ThreadEntry* parent = FindThreadOrNew(ppid, ptid);
ThreadEntry* child = FindThreadOrNew(pid, tid);
child->comm = parent->comm;
@@ -64,6 +72,7 @@ void ThreadTree::ForkThread(int pid, int tid, int ppid, int ptid) {
}
}
}
+ return true;
}
ThreadEntry* ThreadTree::FindThread(int tid) const {
@@ -159,6 +168,7 @@ void ThreadTree::AddThreadMap(int pid, int tid, uint64_t start_addr, uint64_t le
const std::string& filename, uint32_t flags) {
ThreadEntry* thread = FindThreadOrNew(pid, tid);
Dso* dso = FindUserDsoOrNew(filename, start_addr);
+ CHECK(dso != nullptr);
InsertMap(*thread->maps, MapEntry(start_addr, len, pgoff, dso, false, flags));
}
@@ -197,6 +207,9 @@ Dso* ThreadTree::FindUserDsoOrNew(const std::string& filename, uint64_t start_ad
if (it == user_dso_tree_.end()) {
bool force_64bit = start_addr > UINT_MAX;
std::unique_ptr<Dso> dso = Dso::CreateDso(dso_type, filename, force_64bit);
+ if (!dso) {
+ return nullptr;
+ }
auto pair = user_dso_tree_.insert(std::make_pair(filename, std::move(dso)));
CHECK(pair.second);
it = pair.first;
@@ -347,7 +360,7 @@ void ThreadTree::ClearThreadAndMap() {
map_storage_.clear();
}
-void ThreadTree::AddDsoInfo(FileFeature& file) {
+bool ThreadTree::AddDsoInfo(FileFeature& file) {
DsoType dso_type = file.type;
Dso* dso = nullptr;
if (dso_type == DSO_KERNEL) {
@@ -357,11 +370,15 @@ void ThreadTree::AddDsoInfo(FileFeature& file) {
} else {
dso = FindUserDsoOrNew(file.path, 0, dso_type);
}
+ if (!dso) {
+ return false;
+ }
dso->SetMinExecutableVaddr(file.min_vaddr, file.file_offset_of_min_vaddr);
dso->SetSymbols(&file.symbols);
for (uint64_t offset : file.dex_file_offsets) {
dso->AddDexFileOffset(offset);
}
+ return true;
}
void ThreadTree::AddDexFileOffset(const std::string& file_path, uint64_t dex_file_offset) {
@@ -394,11 +411,13 @@ void ThreadTree::Update(const Record& record) {
const ForkRecord& r = *static_cast<const ForkRecord*>(&record);
ForkThread(r.data->pid, r.data->tid, r.data->ppid, r.data->ptid);
} else if (record.type() == PERF_RECORD_EXIT) {
- const ExitRecord& r = *static_cast<const ExitRecord*>(&record);
- ExitThread(r.data->pid, r.data->tid);
+ if (!disable_thread_exit_records_) {
+ const ExitRecord& r = *static_cast<const ExitRecord*>(&record);
+ ExitThread(r.data->pid, r.data->tid);
+ }
} else if (record.type() == SIMPLE_PERF_RECORD_KERNEL_SYMBOL) {
const auto& r = *static_cast<const KernelSymbolRecord*>(&record);
- Dso::SetKallsyms(std::move(r.kallsyms));
+ Dso::SetKallsyms(std::string(r.kallsyms, r.kallsyms_size));
}
}
diff --git a/simpleperf/thread_tree.h b/simpleperf/thread_tree.h
index 33e85450..ea723766 100644
--- a/simpleperf/thread_tree.h
+++ b/simpleperf/thread_tree.h
@@ -100,8 +100,9 @@ class ThreadTree {
}
virtual ~ThreadTree() {}
+ void DisableThreadExitRecords() { disable_thread_exit_records_ = true; }
void SetThreadName(int pid, int tid, const std::string& comm);
- void ForkThread(int pid, int tid, int ppid, int ptid);
+ bool ForkThread(int pid, int tid, int ppid, int ptid);
virtual ThreadEntry* FindThread(int tid) const;
ThreadEntry* FindThreadOrNew(int pid, int tid);
void ExitThread(int pid, int tid);
@@ -131,7 +132,7 @@ class ThreadTree {
// Clear thread and map information, but keep loaded dso information. It saves
// the time to reload dso information.
void ClearThreadAndMap();
- void AddDsoInfo(FileFeature& file);
+ bool AddDsoInfo(FileFeature& file);
void AddDexFileOffset(const std::string& file_path, uint64_t dex_file_offset);
// Update thread tree with information provided by record.
@@ -167,6 +168,7 @@ class ThreadTree {
bool show_ip_for_unknown_symbol_;
bool show_mark_for_unknown_symbol_;
Symbol unknown_symbol_;
+ bool disable_thread_exit_records_ = false;
};
} // namespace simpleperf
diff --git a/simpleperf/thread_tree_test.cpp b/simpleperf/thread_tree_test.cpp
index cefa3f74..f5b71ec0 100644
--- a/simpleperf/thread_tree_test.cpp
+++ b/simpleperf/thread_tree_test.cpp
@@ -143,3 +143,10 @@ TEST_F(ThreadTreeTest, add_symbols_for_process) {
ASSERT_STREQ("two", FindSymbol(1, 1, 0x2010)->Name());
ASSERT_STREQ("three", FindSymbol(1, 1, 0x302f)->Name());
}
+
+TEST_F(ThreadTreeTest, invalid_fork) {
+ // tid == ptid
+ ASSERT_FALSE(thread_tree_.ForkThread(1, 2, 1, 2));
+ // pid != tid && pid != ppid
+ ASSERT_FALSE(thread_tree_.ForkThread(1, 2, 3, 1));
+}
diff --git a/simpleperf/tracing.cpp b/simpleperf/tracing.cpp
index cb6a862c..ab9c3fa7 100644
--- a/simpleperf/tracing.cpp
+++ b/simpleperf/tracing.cpp
@@ -21,7 +21,6 @@
#include <map>
#include <optional>
-#include <regex>
#include <string>
#include <vector>
@@ -31,6 +30,7 @@
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#include "RegEx.h"
#include "environment.h"
#include "perf_event.h"
#include "utils.h"
@@ -40,15 +40,6 @@ using android::base::StartsWith;
namespace simpleperf {
-template <>
-void MoveFromBinaryFormat(std::string& data, const char*& p) {
- data.clear();
- while (*p != '\0') {
- data.push_back(*p++);
- }
- p++;
-}
-
const char TRACING_INFO_MAGIC[10] = {23, 8, 68, 't', 'r', 'a', 'c', 'i', 'n', 'g'};
template <class T>
@@ -78,12 +69,18 @@ static void AppendFile(std::vector<char>& data, const std::string& file,
data.insert(data.end(), file.begin(), file.end());
}
-static void DetachFile(const char*& p, std::string& file, uint32_t file_size_bytes = 8) {
- uint64_t file_size = ConvertBytesToValue(p, file_size_bytes);
- p += file_size_bytes;
- file.clear();
- file.insert(file.end(), p, p + file_size);
- p += file_size;
+static std::string DetachFile(BinaryReader& reader, uint32_t file_size_bytes = 8) {
+ if (!reader.CheckLeftSize(file_size_bytes)) {
+ return "";
+ }
+ uint64_t file_size = ConvertBytesToValue(reader.head, file_size_bytes);
+ reader.head += file_size_bytes;
+ if (!reader.CheckLeftSize(file_size)) {
+ return "";
+ }
+ std::string result(reader.head, file_size);
+ reader.head += file_size;
+ return result;
}
static bool ReadTraceFsFile(const std::string& path, std::string* content,
@@ -119,7 +116,7 @@ class TracingFile {
bool RecordKallsymsFile();
bool RecordPrintkFormatsFile();
std::vector<char> BinaryFormat() const;
- void LoadFromBinary(const std::vector<char>& data);
+ bool LoadFromBinary(const std::vector<char>& data);
void Dump(size_t indent) const;
std::vector<TracingFormat> LoadTracingFormatsFromEventFiles() const;
const std::string& GetKallsymsFile() const { return kallsyms_file; }
@@ -207,44 +204,44 @@ std::vector<char> TracingFile::BinaryFormat() const {
return ret;
}
-void TracingFile::LoadFromBinary(const std::vector<char>& data) {
- const char* p = data.data();
- const char* end = data.data() + data.size();
- CHECK(memcmp(p, magic, sizeof(magic)) == 0);
- p += sizeof(magic);
- MoveFromBinaryFormat(version, p);
- MoveFromBinaryFormat(endian, p);
- MoveFromBinaryFormat(size_of_long, p);
- MoveFromBinaryFormat(page_size, p);
- std::string filename;
- MoveFromBinaryFormat(filename, p);
- CHECK_EQ(filename, "header_page");
- DetachFile(p, header_page_file);
- MoveFromBinaryFormat(filename, p);
- CHECK_EQ(filename, "header_event");
- DetachFile(p, header_event_file);
- uint32_t count;
- MoveFromBinaryFormat(count, p);
- ftrace_format_files.resize(count);
- for (uint32_t i = 0; i < count; ++i) {
- DetachFile(p, ftrace_format_files[i]);
- }
- MoveFromBinaryFormat(count, p);
+bool TracingFile::LoadFromBinary(const std::vector<char>& data) {
+ BinaryReader reader(data.data(), data.size());
+ if (!reader.CheckLeftSize(sizeof(magic)) || memcmp(reader.head, magic, sizeof(magic)) != 0) {
+ return false;
+ }
+ reader.head += sizeof(magic);
+ version = reader.ReadString();
+ reader.Read(endian);
+ reader.Read(size_of_long);
+ reader.Read(page_size);
+ if (reader.ReadString() != "header_page") {
+ return false;
+ }
+ header_page_file = DetachFile(reader);
+ if (reader.ReadString() != "header_event") {
+ return false;
+ }
+ header_event_file = DetachFile(reader);
+ uint32_t count = 0;
+ reader.Read(count);
+ ftrace_format_files.clear();
+ while (count-- > 0 && !reader.error) {
+ ftrace_format_files.emplace_back(DetachFile(reader));
+ }
+ reader.Read(count);
event_format_files.clear();
- for (uint32_t i = 0; i < count; ++i) {
- std::string system;
- MoveFromBinaryFormat(system, p);
- uint32_t count_in_system;
- MoveFromBinaryFormat(count_in_system, p);
- for (uint32_t i = 0; i < count_in_system; ++i) {
- std::string format;
- DetachFile(p, format);
+ while (count-- > 0 && !reader.error) {
+ std::string system = reader.ReadString();
+ uint32_t count_in_system = 0;
+ reader.Read(count_in_system);
+ while (count_in_system-- > 0 && !reader.error) {
+ std::string format = DetachFile(reader);
event_format_files.push_back(std::make_pair(system, std::move(format)));
}
}
- DetachFile(p, kallsyms_file, 4);
- DetachFile(p, printk_formats_file, 4);
- CHECK_EQ(p, end);
+ kallsyms_file = DetachFile(reader, 4);
+ printk_formats_file = DetachFile(reader, 4);
+ return !reader.error && reader.head == reader.end;
}
void TracingFile::Dump(size_t indent) const {
@@ -286,14 +283,13 @@ static TracingField ParseTracingField(const std::string& s) {
TracingField field;
std::string name;
std::string value;
- std::regex re(R"((\w+):(.+?);)");
+ auto re = RegEx::Create(R"((\w+):(.+?);)");
- std::sregex_iterator match_it(s.begin(), s.end(), re);
- std::sregex_iterator match_end;
- while (match_it != match_end) {
- std::smatch match = *match_it++;
- std::string name = match.str(1);
- std::string value = match.str(2);
+ std::unique_ptr<RegExMatch> match = re->SearchAll(s);
+ while (match->IsValid()) {
+ std::string name = match->GetField(1);
+ std::string value = match->GetField(2);
+ match->MoveToNextMatch();
if (name == "field") {
std::string last_value_part = Split(value, " \t").back();
@@ -372,14 +368,18 @@ std::vector<TracingFormat> TracingFile::LoadTracingFormatsFromEventFiles() const
return formats;
}
-Tracing::Tracing(const std::vector<char>& data) {
- tracing_file_ = new TracingFile;
- tracing_file_->LoadFromBinary(data);
+std::unique_ptr<Tracing> Tracing::Create(const std::vector<char>& data) {
+ std::unique_ptr<Tracing> tracing(new Tracing);
+ if (!tracing->tracing_file_->LoadFromBinary(data)) {
+ LOG(ERROR) << "Failed to load tracing data";
+ return nullptr;
+ }
+ return tracing;
}
-Tracing::~Tracing() {
- delete tracing_file_;
-}
+Tracing::Tracing() : tracing_file_(new TracingFile) {}
+
+Tracing::~Tracing() {}
void Tracing::Dump(size_t indent) {
tracing_file_->Dump(indent);
diff --git a/simpleperf/tracing.h b/simpleperf/tracing.h
index 101ac555..9194d026 100644
--- a/simpleperf/tracing.h
+++ b/simpleperf/tracing.h
@@ -98,7 +98,7 @@ class TracingFile;
class Tracing {
public:
- explicit Tracing(const std::vector<char>& data);
+ static std::unique_ptr<Tracing> Create(const std::vector<char>& data);
~Tracing();
void Dump(size_t indent);
TracingFormat GetTracingFormatHavingId(uint64_t trace_event_id);
@@ -107,7 +107,9 @@ class Tracing {
uint32_t GetPageSize() const;
private:
- TracingFile* tracing_file_;
+ Tracing();
+
+ std::unique_ptr<TracingFile> tracing_file_;
std::vector<TracingFormat> tracing_formats_;
};
diff --git a/simpleperf/utils.cpp b/simpleperf/utils.cpp
index a0418167..86004677 100644
--- a/simpleperf/utils.cpp
+++ b/simpleperf/utils.cpp
@@ -40,6 +40,9 @@
#include <Xz.h>
#include <XzCrc64.h>
+#include "RegEx.h"
+#include "environment.h"
+
namespace simpleperf {
using android::base::ParseInt;
@@ -424,6 +427,54 @@ std::optional<std::set<pid_t>> GetTidsFromString(const std::string& s, bool chec
return tids;
}
+std::optional<std::set<pid_t>> GetPidsFromStrings(const std::vector<std::string>& strs,
+ bool check_if_exists,
+ bool support_progress_name_regex) {
+ std::set<pid_t> pids;
+ std::vector<std::unique_ptr<RegEx>> regs;
+ for (const auto& s : strs) {
+ for (const auto& p : Split(s, ",")) {
+ int pid;
+ if (ParseInt(p.c_str(), &pid, 0)) {
+ if (check_if_exists && !IsDir(StringPrintf("/proc/%d", pid))) {
+ LOG(ERROR) << "no process with pid " << pid;
+ return std::nullopt;
+ }
+ pids.insert(pid);
+ } else if (support_progress_name_regex) {
+ auto reg = RegEx::Create(p);
+ if (!reg) {
+ return std::nullopt;
+ }
+ regs.emplace_back(std::move(reg));
+ } else {
+ LOG(ERROR) << "invalid pid: " << p;
+ return std::nullopt;
+ }
+ }
+ }
+ if (!regs.empty()) {
+#if defined(__linux__)
+ for (pid_t pid : GetAllProcesses()) {
+ std::string process_name = GetCompleteProcessName(pid);
+ if (process_name.empty()) {
+ continue;
+ }
+ for (const auto& reg : regs) {
+ if (reg->Search(process_name)) {
+ pids.insert(pid);
+ break;
+ }
+ }
+ }
+#else // defined(__linux__)
+ LOG(ERROR) << "progress name regex isn't supported";
+ return std::nullopt;
+#endif // defined(__linux__)
+ }
+ return pids;
+}
+
size_t SafeStrlen(const char* s, const char* end) {
const char* p = s;
while (p < end && *p != '\0') {
@@ -432,4 +483,19 @@ size_t SafeStrlen(const char* s, const char* end) {
return p - s;
}
+OverflowResult SafeAdd(uint64_t a, uint64_t b) {
+ OverflowResult result;
+ if (__builtin_add_overflow(a, b, &result.value)) {
+ result.overflow = true;
+ }
+ return result;
+}
+
+void OverflowSafeAdd(uint64_t& dest, uint64_t add) {
+ if (__builtin_add_overflow(dest, add, &dest)) {
+ LOG(WARNING) << "Branch count overflow happened.";
+ dest = UINT64_MAX;
+ }
+}
+
} // namespace simpleperf
diff --git a/simpleperf/utils.h b/simpleperf/utils.h
index 10de9324..cda9bbac 100644
--- a/simpleperf/utils.h
+++ b/simpleperf/utils.h
@@ -37,6 +37,10 @@
namespace simpleperf {
+static constexpr size_t kKilobyte = 1024;
+static constexpr size_t kMegabyte = 1024 * kKilobyte;
+static constexpr uint64_t kGigabyte = 1024 * kMegabyte;
+
static inline uint64_t AlignDown(uint64_t value, uint64_t alignment) {
return value & ~(alignment - 1);
}
@@ -148,6 +152,81 @@ void MoveToBinaryFormat(const T* data_p, size_t n, char*& p) {
p += size;
}
+// Read info from binary data.
+struct BinaryReader {
+ public:
+ BinaryReader(const char* head, size_t size) : head(head), end(head + size), error(false) {}
+
+ size_t LeftSize() const { return end - head; }
+
+ bool CheckLeftSize(size_t size) {
+ if (UNLIKELY(error)) {
+ return false;
+ }
+ if (UNLIKELY(LeftSize() < size)) {
+ error = true;
+ return false;
+ }
+ return true;
+ }
+
+ void Move(size_t size) {
+ if (CheckLeftSize(size)) {
+ head += size;
+ }
+ }
+
+ template <class T>
+ void Read(T& data) {
+ static_assert(std::is_standard_layout<T>::value, "not standard layout");
+ if (UNLIKELY(error)) {
+ return;
+ }
+ if (UNLIKELY(LeftSize() < sizeof(T))) {
+ error = true;
+ } else {
+ memcpy(&data, head, sizeof(T));
+ head += sizeof(T);
+ }
+ }
+
+ template <class T>
+ void Read(T* data_p, size_t n) {
+ static_assert(std::is_standard_layout<T>::value, "not standard layout");
+ if (UNLIKELY(error)) {
+ return;
+ }
+ size_t size;
+ if (UNLIKELY(__builtin_mul_overflow(n, sizeof(T), &size) || LeftSize() < size)) {
+ error = true;
+ } else {
+ memcpy(data_p, head, size);
+ head += size;
+ }
+ }
+
+ // Read a string ending with '\0'.
+ std::string ReadString() {
+ if (UNLIKELY(error)) {
+ return "";
+ }
+ std::string result;
+ while (head < end && *head != '\0') {
+ result.push_back(*head++);
+ }
+ if (LIKELY(head < end && *head == '\0')) {
+ head++;
+ return result;
+ }
+ error = true;
+ return "";
+ }
+
+ const char* head;
+ const char* end;
+ bool error;
+};
+
void PrintIndented(size_t indent, const char* fmt, ...);
void FprintIndented(FILE* fp, size_t indent, const char* fmt, ...);
@@ -177,6 +256,9 @@ std::string GetSimpleperfVersion();
std::optional<std::set<int>> GetCpusFromString(const std::string& s);
std::optional<std::set<pid_t>> GetTidsFromString(const std::string& s, bool check_if_exists);
+std::optional<std::set<pid_t>> GetPidsFromStrings(const std::vector<std::string>& strs,
+ bool check_if_exists,
+ bool support_progress_name_regex);
template <typename T>
std::optional<std::set<T>> ParseUintVector(const std::string& s) {
@@ -200,6 +282,14 @@ static inline void HashCombine(size_t& seed, const T& val) {
size_t SafeStrlen(const char* s, const char* end);
+struct OverflowResult {
+ bool overflow = false;
+ uint64_t value = 0;
+};
+
+OverflowResult SafeAdd(uint64_t a, uint64_t b);
+void OverflowSafeAdd(uint64_t& dest, uint64_t add);
+
} // namespace simpleperf
#endif // SIMPLE_PERF_UTILS_H_
diff --git a/simpleperf/utils_test.cpp b/simpleperf/utils_test.cpp
index 725ae4db..2dfbd901 100644
--- a/simpleperf/utils_test.cpp
+++ b/simpleperf/utils_test.cpp
@@ -14,12 +14,14 @@
* limitations under the License.
*/
+#include "utils.h"
+
#include <gtest/gtest.h>
#include <android-base/file.h>
+#include "environment.h"
#include "get_test_data.h"
-#include "utils.h"
using namespace simpleperf;
@@ -75,6 +77,20 @@ TEST(utils, GetTidsFromString) {
ASSERT_EQ(GetTidsFromString("-2", false), std::nullopt);
}
+TEST(utils, GetPidsFromStrings) {
+ ASSERT_EQ(GetPidsFromStrings({"0,12", "9"}, false, false),
+ std::make_optional(std::set<pid_t>({0, 9, 12})));
+ ASSERT_EQ(GetPidsFromStrings({"-2"}, false, false), std::nullopt);
+#if defined(__linux__)
+ pid_t pid = getpid();
+ ASSERT_EQ(GetPidsFromStrings({std::to_string(pid)}, true, false),
+ std::make_optional(std::set<pid_t>({pid})));
+ std::string process_name = GetCompleteProcessName(pid);
+ ASSERT_EQ(GetPidsFromStrings({process_name}, true, true),
+ std::make_optional(std::set<pid_t>({pid})));
+#endif // defined(__linux__)
+}
+
TEST(utils, LineReader) {
TemporaryFile tmpfile;
close(tmpfile.release());
diff --git a/squashfs_utils/Android.bp b/squashfs_utils/Android.bp
index d777a496..421c0d88 100644
--- a/squashfs_utils/Android.bp
+++ b/squashfs_utils/Android.bp
@@ -47,7 +47,7 @@ cc_library {
}
sh_binary_host {
- name: "mksquashfsimage.sh",
+ name: "mksquashfsimage",
src: "mksquashfsimage.sh",
required: [
"img2simg",
diff --git a/tests/kernel.config/OWNERS b/tests/kernel.config/OWNERS
new file mode 100644
index 00000000..65d27f47
--- /dev/null
+++ b/tests/kernel.config/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 119452
+
+# TODO: we likely want to delete or factor out these tests
+# b/261015480#comment6
+
+smoreland@google.com
diff --git a/toolchain-extras/Android.bp b/toolchain-extras/Android.bp
index d48f081f..f15defda 100644
--- a/toolchain-extras/Android.bp
+++ b/toolchain-extras/Android.bp
@@ -82,6 +82,10 @@ cc_defaults {
cc_library_static {
name: "libprofile-clang-extras",
defaults: ["libprofile-clang-platform-defaults"],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
}
cc_library_static {