summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-04-28 16:06:52 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-04-28 16:06:52 +0000
commit347c35ae1adcbe86ead7ec93bdc9f19689e26706 (patch)
treeff52905359cc3119eefdfdfd89ff245124845a77
parent5be069b7bc339dbae1a9a8d04e739551de6b5d37 (diff)
parentcd6b038e799ea13120e9676e0f1b23cdda4223db (diff)
downloadextras-347c35ae1adcbe86ead7ec93bdc9f19689e26706.tar.gz
Snap for 8512216 from cd6b038e799ea13120e9676e0f1b23cdda4223db to tm-frc-media-swcodec-release
Change-Id: Iaa07b2b7e3031a9cd67a508a9f526b4bf98de810
-rw-r--r--alloc-stress/Android.bp1
-rw-r--r--alloc-stress/alloc-stress.cpp34
-rw-r--r--ext4_utils/mkuserimg_mke2fs.py4
-rw-r--r--ioblame/androidFsParser.py175
-rw-r--r--ioblame/ioblame.py528
-rw-r--r--ioblame/uidProcessMapper.py125
-rw-r--r--ioshark/compile_ioshark.c2
-rw-r--r--ioshark/compile_ioshark_subr.c2
-rw-r--r--ioshark/convert_format.c2
-rw-r--r--ioshark/dump_ioshark_filenames.c2
-rw-r--r--ioshark/ioshark_bench.c2
-rw-r--r--ioshark/ioshark_bench_mmap.c2
-rw-r--r--libfec/Android.bp2
-rw-r--r--profcollectd/README.md56
-rw-r--r--profcollectd/libprofcollectd/lib.rs5
-rw-r--r--simpleperf/Android.bp37
-rw-r--r--simpleperf/Android.mk2
-rw-r--r--simpleperf/ETMDecoder.cpp9
-rw-r--r--simpleperf/IOEventLoop.cpp32
-rw-r--r--simpleperf/IOEventLoop.h25
-rw-r--r--simpleperf/IOEventLoop_test.cpp42
-rw-r--r--simpleperf/JITDebugReader.cpp78
-rw-r--r--simpleperf/JITDebugReader.h27
-rw-r--r--simpleperf/OfflineUnwinder.cpp3
-rw-r--r--simpleperf/OfflineUnwinder.h5
-rw-r--r--simpleperf/cmd_monitor.cpp13
-rw-r--r--simpleperf/cmd_record.cpp44
-rw-r--r--simpleperf/cmd_record_test.cpp8
-rw-r--r--simpleperf/doc/README.md3
-rw-r--r--simpleperf/doc/collect_etm_data_for_autofdo.md30
-rw-r--r--simpleperf/doc/pictures/android_studio_profiler_flame_chart.pngbin0 -> 86154 bytes
-rw-r--r--simpleperf/doc/pictures/android_studio_profiler_open_perf_trace.pngbin0 -> 144080 bytes
-rw-r--r--simpleperf/doc/pictures/android_studio_profiler_select_process.pngbin0 -> 13849 bytes
-rw-r--r--simpleperf/doc/pictures/android_studio_profiler_select_recording_method.pngbin0 -> 10779 bytes
-rw-r--r--simpleperf/doc/pictures/continuous_pprof.pngbin0 -> 460055 bytes
-rw-r--r--simpleperf/doc/pictures/firefox_profiler.pngbin0 -> 335384 bytes
-rw-r--r--simpleperf/doc/pictures/flamescope.pngbin0 -> 70236 bytes
-rw-r--r--simpleperf/doc/pictures/flamescope_click.pngbin0 -> 28199 bytes
-rw-r--r--simpleperf/doc/pictures/flamescope_flamegraph.pngbin0 -> 281403 bytes
-rw-r--r--simpleperf/doc/pictures/report_command.pngbin0 -> 151472 bytes
-rw-r--r--simpleperf/doc/pictures/report_html.pngbin0 -> 291742 bytes
-rw-r--r--simpleperf/doc/scripts_reference.md3
-rw-r--r--simpleperf/doc/view_the_profile.md342
-rw-r--r--simpleperf/dso.cpp109
-rw-r--r--simpleperf/dso.h15
-rw-r--r--simpleperf/dso_test.cpp12
-rw-r--r--simpleperf/environment.h3
-rw-r--r--simpleperf/event_selection_set.cpp9
-rw-r--r--simpleperf/profcollect.cpp8
-rw-r--r--simpleperf/report_lib_interface.cpp15
-rwxr-xr-xsimpleperf/scripts/annotate.py27
-rwxr-xr-xsimpleperf/scripts/bin/android/arm/simpleperfbin2807668 -> 2812664 bytes
-rwxr-xr-xsimpleperf/scripts/bin/android/arm64/simpleperfbin3810832 -> 3819224 bytes
-rwxr-xr-xsimpleperf/scripts/bin/android/x86/simpleperfbin4372260 -> 4386820 bytes
-rwxr-xr-xsimpleperf/scripts/bin/android/x86_64/simpleperfbin4249312 -> 4244008 bytes
-rwxr-xr-xsimpleperf/scripts/bin/darwin/x86_64/libsimpleperf_report.dylibbin13978839 -> 13980215 bytes
-rwxr-xr-xsimpleperf/scripts/bin/darwin/x86_64/simpleperfbin13918013 -> 13902701 bytes
-rwxr-xr-xsimpleperf/scripts/bin/linux/x86_64/libsimpleperf_report.sobin7219712 -> 7208384 bytes
-rwxr-xr-xsimpleperf/scripts/bin/linux/x86_64/simpleperfbin7193024 -> 7181056 bytes
-rwxr-xr-xsimpleperf/scripts/bin/windows/x86_64/libsimpleperf_report.dllbin5598720 -> 5583872 bytes
-rwxr-xr-xsimpleperf/scripts/bin/windows/x86_64/simpleperf.exebin4654080 -> 4638208 bytes
-rwxr-xr-xsimpleperf/scripts/gecko_profile_generator.py27
-rwxr-xr-xsimpleperf/scripts/inferno/inferno.py17
-rwxr-xr-xsimpleperf/scripts/pprof_proto_generator.py31
-rwxr-xr-xsimpleperf/scripts/purgatorio/purgatorio.py15
-rwxr-xr-xsimpleperf/scripts/report_html.py32
-rwxr-xr-xsimpleperf/scripts/report_sample.py31
-rwxr-xr-xsimpleperf/scripts/run_simpleperf_without_usb_connection.py4
-rw-r--r--simpleperf/scripts/simpleperf_report_lib.py23
-rw-r--r--simpleperf/scripts/simpleperf_utils.py70
-rwxr-xr-xsimpleperf/scripts/stackcollapse.py27
-rw-r--r--simpleperf/scripts/test/annotate_test.py7
-rwxr-xr-xsimpleperf/scripts/test/do_test.py13
-rw-r--r--simpleperf/scripts/test/gecko_profile_generator_test.py24
-rw-r--r--simpleperf/scripts/test/inferno_test.py9
-rw-r--r--simpleperf/scripts/test/pprof_proto_generator_test.py4
-rw-r--r--simpleperf/scripts/test/purgatorio_test.py8
-rw-r--r--simpleperf/scripts/test/report_html_test.py15
-rw-r--r--simpleperf/scripts/test/report_lib_test.py29
-rw-r--r--simpleperf/scripts/test/report_sample_test.py57
-rw-r--r--simpleperf/scripts/test/stackcollapse_test.py70
-rw-r--r--tests/kernel.config/AndroidTest.xml2
-rw-r--r--toolchain-extras/Android.bp85
-rw-r--r--toolchain-extras/profile-clang-extras.cpp6
-rw-r--r--verity/Android.bp7
-rw-r--r--verity/build_verity_metadata.py20
-rw-r--r--verity/fec/Android.bp5
87 files changed, 1930 insertions, 551 deletions
diff --git a/alloc-stress/Android.bp b/alloc-stress/Android.bp
index 4d74841a..e64c822b 100644
--- a/alloc-stress/Android.bp
+++ b/alloc-stress/Android.bp
@@ -24,6 +24,7 @@ cc_binary {
shared_libs: [
"libhardware",
"libcutils",
+ "libprocessgroup",
],
cppflags: [
"-g",
diff --git a/alloc-stress/alloc-stress.cpp b/alloc-stress/alloc-stress.cpp
index 33c12635..ad2353cc 100644
--- a/alloc-stress/alloc-stress.cpp
+++ b/alloc-stress/alloc-stress.cpp
@@ -14,6 +14,8 @@
#include <tuple>
#include <vector>
+#include <processgroup/processgroup.h>
+
//#define TRACE_CHILD_LIFETIME
#ifdef TRACE_CHILD_LIFETIME
@@ -157,36 +159,6 @@ static void write_oomadj_to_lmkd(int oomadj) {
cout << "Wrote " << written << " bytes to lmkd control socket." << endl;
}
-static void create_memcg() {
- char buf[256];
- uid_t uid = getuid();
- pid_t pid = getpid();
-
- snprintf(buf, sizeof(buf), "/dev/memcg/apps/uid_%u", uid);
- int tasks = mkdir(buf, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
- if (tasks < 0 && errno != EEXIST) {
- cerr << "Failed to create memory cgroup under " << buf << endl;
- return;
- }
-
- snprintf(buf, sizeof(buf), "/dev/memcg/apps/uid_%u/pid_%u", uid, pid);
- tasks = mkdir(buf, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
- if (tasks < 0) {
- cerr << "Failed to create memory cgroup under " << buf << endl;
- return;
- }
-
- snprintf(buf, sizeof(buf), "/dev/memcg/apps/uid_%u/pid_%u/tasks", uid, pid);
- tasks = open(buf, O_WRONLY);
- if (tasks < 0) {
- cerr << "Unable to add process to memory cgroup" << endl;
- return;
- }
- snprintf(buf, sizeof(buf), "%u", pid);
- write(tasks, buf, strlen(buf));
- close(tasks);
-}
-
void usage() {
cout << "Application allocates memory until it's killed." << endl
<< "It starts at max oom_score_adj and gradually "
@@ -202,7 +174,7 @@ int main(int argc, char* argv[]) {
if ((argc > 1) && (std::string(argv[1]) == "--worker")) {
if (std::string(argv[5]) == "1") {
- create_memcg();
+ createProcessGroup(getuid(), getpid(), true);
}
write_oomadj_to_lmkd(atoi(argv[2]));
diff --git a/ext4_utils/mkuserimg_mke2fs.py b/ext4_utils/mkuserimg_mke2fs.py
index 599e40ee..41ae8cd7 100644
--- a/ext4_utils/mkuserimg_mke2fs.py
+++ b/ext4_utils/mkuserimg_mke2fs.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2018 The Android Open Source Project
#
@@ -165,7 +165,7 @@ def ConstructE2fsCommands(args):
if args.flash_erase_block_size:
mke2fs_extended_opts.append("stripe_width={}".format(
- int(args.flash_erase_block_size) / BLOCKSIZE))
+ int(args.flash_erase_block_size) // BLOCKSIZE))
if args.flash_logical_block_size:
# stride should be the max of 8kb and the logical block size
stride = max(int(args.flash_logical_block_size), 8192)
diff --git a/ioblame/androidFsParser.py b/ioblame/androidFsParser.py
new file mode 100644
index 00000000..6ea38dc3
--- /dev/null
+++ b/ioblame/androidFsParser.py
@@ -0,0 +1,175 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+"""Trace parser for android_fs 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) 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]+)"
+
+MIN_PID_BYTES = 1024 * 1024 # 1 MiB
+SMALL_FILE_BYTES = 1024 # 1 KiB
+
+
+class ProcessTrace:
+
+ def __init__(self, cmdLine, filename, numBytes):
+ self.cmdLine = cmdLine
+ self.totalBytes = numBytes
+ self.bytesByFiles = {filename: numBytes}
+
+ def add_file_trace(self, filename, numBytes):
+ self.totalBytes += numBytes
+ if filename in self.bytesByFiles:
+ self.bytesByFiles[filename] += numBytes
+ else:
+ self.bytesByFiles[filename] = numBytes
+
+ def dump(self, mode, outputFile):
+ smallFileCnt = 0
+ smallFileBytes = 0
+ for _, numBytes in self.bytesByFiles.items():
+ if numBytes < SMALL_FILE_BYTES:
+ smallFileCnt += 1
+ smallFileBytes += numBytes
+
+ if (smallFileCnt != 0):
+ outputFile.write(
+ "Process: {}, Traced {} KB: {}, Small file count: {}, Small file KB: {}\n"
+ .format(self.cmdLine, mode, to_kib(self.totalBytes), smallFileCnt,
+ to_kib(smallFileBytes)))
+
+ else:
+ outputFile.write("Process: {}, Traced {} KB: {}\n".format(
+ self.cmdLine, mode, to_kib(self.totalBytes)))
+
+ if (smallFileCnt == len(self.bytesByFiles)):
+ return
+
+ sortedEntries = collections.OrderedDict(
+ sorted(
+ self.bytesByFiles.items(), key=lambda item: item[1], reverse=True))
+
+ for i in range(len(sortedEntries)):
+ filename, numBytes = sortedEntries.popitem(last=False)
+ if numBytes < SMALL_FILE_BYTES:
+ # Entries are sorted by bytes. So, break on the first small file entry.
+ break
+
+ outputFile.write("File: {}, {} KB: {}\n".format(filename, mode,
+ to_kib(numBytes)))
+
+
+class UidTrace:
+
+ def __init__(self, uid, cmdLine, filename, numBytes):
+ self.uid = uid
+ self.packageName = ""
+ self.totalBytes = numBytes
+ self.traceByProcess = {cmdLine: ProcessTrace(cmdLine, filename, numBytes)}
+
+ def add_process_trace(self, cmdLine, filename, numBytes):
+ self.totalBytes += numBytes
+ if cmdLine in self.traceByProcess:
+ self.traceByProcess[cmdLine].add_file_trace(filename, numBytes)
+ else:
+ self.traceByProcess[cmdLine] = ProcessTrace(cmdLine, filename, numBytes)
+
+ def dump(self, mode, outputFile):
+ outputFile.write("Traced {} KB: {}\n\n".format(mode,
+ to_kib(self.totalBytes)))
+
+ if self.totalBytes < MIN_PID_BYTES:
+ return
+
+ sortedEntries = collections.OrderedDict(
+ sorted(
+ self.traceByProcess.items(),
+ key=lambda item: item[1].totalBytes,
+ reverse=True))
+ totalEntries = len(sortedEntries)
+ for i in range(totalEntries):
+ _, processTrace = sortedEntries.popitem(last=False)
+ if processTrace.totalBytes < MIN_PID_BYTES:
+ # Entries are sorted by bytes. So, break on the first small PID entry.
+ break
+
+ processTrace.dump(mode, outputFile)
+ if i < totalEntries - 1:
+ outputFile.write("\n")
+
+
+class AndroidFsParser:
+
+ def __init__(self, re_string, uidProcessMapper):
+ self.traceByUid = {} # Key: uid, Value: UidTrace
+ if (re_string == RE_WRITE_START):
+ self.mode = "write"
+ else:
+ self.mode = "read"
+ self.re_matcher = re.compile(re_string)
+ self.uidProcessMapper = uidProcessMapper
+ self.totalBytes = 0
+
+ def parse(self, line):
+ match = self.re_matcher.match(line)
+ if not match:
+ return False
+ try:
+ self.do_parse_start(line, match)
+ except Exception:
+ print("cannot parse: {}".format(line))
+ raise
+ return True
+
+ def do_parse_start(self, line, match):
+ pid = int(match.group(1))
+ # start_time = float(match.group(2)) * 1000 #ms
+ filename = match.group(3)
+ # offset = int(match.group(4))
+ numBytes = int(match.group(5))
+ cmdLine = match.group(6)
+ pid = int(match.group(7))
+ # isize = int(match.group(8))
+ # ino = int(match.group(9))
+ self.totalBytes += numBytes
+ uid = self.uidProcessMapper.get_uid(cmdLine, pid)
+
+ if uid in self.traceByUid:
+ self.traceByUid[uid].add_process_trace(cmdLine, filename, numBytes)
+ else:
+ self.traceByUid[uid] = UidTrace(uid, cmdLine, filename, numBytes)
+
+ def dumpTotal(self, outputFile):
+ if self.totalBytes > 0:
+ outputFile.write("Traced system-wide {} KB: {}\n\n".format(
+ self.mode, to_kib(self.totalBytes)))
+
+ def dump(self, uid, outputFile):
+ if uid not in self.traceByUid:
+ return
+
+ uidTrace = self.traceByUid[uid]
+ uidTrace.dump(self.mode, outputFile)
+
+
+def to_kib(bytes):
+ return bytes / 1024
diff --git a/ioblame/ioblame.py b/ioblame/ioblame.py
new file mode 100644
index 00000000..e00a5ecc
--- /dev/null
+++ b/ioblame/ioblame.py
@@ -0,0 +1,528 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+"""Reports disk I/O usage by UID/Package, process, and file level breakdowns."""
+
+from datetime import datetime
+from collections import namedtuple
+
+import androidFsParser
+import argparse
+import collections
+import os
+import psutil
+import re
+import signal
+import subprocess
+import sys
+import threading
+import time
+import uidProcessMapper
+
+# ex) lrwxrwxrwx 1 root root 16 1970-01-06 13:22 userdata -> /dev/block/sda14
+RE_LS_BLOCK_DEVICE = r"\S+\s[0-9]+\s\S+\s\S+\s+[0-9]+\s[0-9\-]+\s[0-9]+\:[0-9]+\suserdata\s\-\>\s\/dev\/block\/(\S+)"
+
+# ex) 1002 246373245 418936352 1818624 0 0 0 0 0 0 0
+RE_UID_IO_STATS_LINE = r"([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)"
+
+# ex) 253 5 dm-5 3117 0 354656 3324 0 0 0 0 0 2696 3324 0 0 0 0
+RE_DISK_STATS_LINE = r"\s+([0-9]+)\s+([0-9]+)\s([a-z\-0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)"
+
+ADB_CMD = "adb"
+
+TEMP_TRACE_FILE = "temp_trace_file.txt"
+CARWATCHDOG_DUMP = "carwatchdog_dump.txt"
+OUTPUT_FILE = "ioblame_out.txt"
+
+WATCHDOG_BUFFER_SECS = 600
+
+DID_RECEIVE_SIGINT = False
+
+
+def signal_handler(sig, frame):
+ global DID_RECEIVE_SIGINT
+ DID_RECEIVE_SIGINT = True
+ print("Received signal interrupt")
+
+
+def init_arguments():
+ parser = argparse.ArgumentParser(
+ description="Collect and process android_fs traces")
+ parser.add_argument(
+ "-s",
+ "--serial",
+ dest="serial",
+ action="store",
+ help="Android device serial number")
+ parser.add_argument(
+ "-r",
+ "--trace_reads",
+ default=False,
+ action="store_true",
+ dest="traceReads",
+ help="Trace android_fs_dataread_start")
+ parser.add_argument(
+ "-w",
+ "--trace_writes",
+ default=False,
+ action="store_true",
+ dest="traceWrites",
+ help="Trace android_fs_datawrite_start")
+ parser.add_argument(
+ "-d",
+ "--trace_duration",
+ type=int,
+ default=3600,
+ dest="traceDuration",
+ help="Total trace duration in seconds")
+ parser.add_argument(
+ "-i",
+ "--sampling_interval",
+ type=int,
+ default=300,
+ dest="samplingInterval",
+ help="Sampling interval in seconds for CarWatchdog collection (applicable only on"
+ " automotive form-factor")
+ parser.add_argument(
+ "-o",
+ "--output_directory",
+ type=dir_path,
+ default=os.getcwd(),
+ dest="outputDir",
+ help="Output directory")
+
+ return parser.parse_args()
+
+
+def verify_arguments(args):
+ if args.serial is not None:
+ global ADB_CMD
+ ADB_CMD = "%s %s" % ("adb -s", args.serial)
+ if not args.traceReads and not args.traceWrites:
+ raise argparse.ArgumentTypeError(
+ "Must provide at least one of the --trace_reads or --trace_writes options"
+ )
+
+
+def dir_path(path):
+ if os.path.isdir(path):
+ return path
+ else:
+ raise argparse.ArgumentTypeError(
+ "{} is not a valid directory path".format(path))
+
+
+def run_adb_cmd(cmd):
+ r = subprocess.check_output(ADB_CMD + " " + cmd, shell=True)
+ return r.decode("utf-8")
+
+
+def run_adb_shell_cmd(cmd):
+ return run_adb_cmd("shell " + cmd)
+
+
+def run_adb_shell_cmd_strip_output(cmd):
+ return run_adb_cmd("shell " + cmd).strip()
+
+
+def run_adb_shell_cmd_ignore_err(cmd):
+ try:
+ r = subprocess.run(
+ ADB_CMD + " shell " + cmd, shell=True, capture_output=True)
+ return r.stdout.decode("utf-8")
+ except Exception:
+ return ""
+
+
+def run_shell_cmd(cmd):
+ return subprocess.check_output(cmd, shell=True)
+
+
+def run_bg_adb_shell_cmd(cmd):
+ return subprocess.Popen(ADB_CMD + " shell " + cmd, shell=True)
+
+
+def run_bg_shell_cmd(cmd):
+ return subprocess.Popen(cmd, shell=True)
+
+
+def get_block_dev():
+ model = run_adb_shell_cmd_strip_output(
+ "'getprop ro.product.name' | sed \'s/[ \\t\\r\\n]*$//\'")
+ print("Found %s Device" % model)
+
+ if "emu" in model:
+ return "vda"
+
+ result = run_adb_shell_cmd_strip_output(
+ "'ls -la /dev/block/bootdevice/by-name | grep userdata'")
+
+ match = re.compile(RE_LS_BLOCK_DEVICE).match(result)
+ if not match:
+ print("Unknown Device {} -- trying Pixel config".format(model))
+ return "sda"
+
+ return match.group(1)
+
+
+def prep_to_do_something():
+ run_adb_shell_cmd("'echo 3 > /proc/sys/vm/drop_caches'")
+ time.sleep(1)
+
+
+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 isTraceEnabled == 0:
+ raise RuntimeError("Android FS tracing is not enabled")
+
+ run_adb_shell_cmd("'echo 0 > /sys/kernel/tracing/tracing_on;\
+ echo 0 > /sys/kernel/tracing/trace;\
+ echo 0 > /sys/kernel/tracing/events/ext4/enable;\
+ echo 0 > /sys/kernel/tracing/events/block/enable'")
+
+ if shouldTraceReads:
+ run_adb_shell_cmd(
+ "'echo 1 > /sys/kernel/tracing/events/android_fs/android_fs_dataread_start/enable'"
+ )
+
+ if shouldTraceWrites:
+ run_adb_shell_cmd(
+ "'echo 1 > /sys/kernel/tracing/events/android_fs/android_fs_datawrite_start/enable'"
+ )
+
+ run_adb_shell_cmd("'echo 1 > /sys/kernel/tracing/tracing_on'")
+
+
+def clear_tracing(shouldTraceReads, shouldTraceWrites):
+ if shouldTraceReads:
+ run_adb_shell_cmd(
+ "'echo 0 > /sys/kernel/tracing/events/android_fs/android_fs_dataread_start/enable'"
+ )
+
+ if shouldTraceWrites:
+ run_adb_shell_cmd(
+ "'echo 0 > /sys/kernel/tracing/events/android_fs/android_fs_datawrite_start/enable'"
+ )
+
+ run_adb_shell_cmd("'echo 0 > /sys/kernel/tracing/tracing_on'")
+
+
+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'\
+ > {}".format(traceFile))
+
+
+def stop_streaming_trace(sub_proc):
+ process = psutil.Process(sub_proc.pid)
+ for child_proc in process.children(recursive=True):
+ child_proc.kill()
+ process.kill()
+
+
+class carwatchdog_collection(threading.Thread):
+
+ def __init__(self, traceDuration, samplingInterval):
+ threading.Thread.__init__(self)
+ self.traceDuration = traceDuration
+ self.samplingInterval = samplingInterval
+
+ def run(self):
+ isBootCompleted = 0
+
+ while isBootCompleted == 0:
+ isBootCompleted = run_adb_shell_cmd_strip_output(
+ "'getprop sys.boot_completed'")
+ time.sleep(1)
+
+ # Clean up previous state.
+ run_adb_shell_cmd(
+ "'dumpsys android.automotive.watchdog.ICarWatchdog/default\
+ --stop_perf &>/dev/null'")
+
+ run_adb_shell_cmd(
+ "'dumpsys android.automotive.watchdog.ICarWatchdog/default \
+ --start_perf --max_duration {} --interval {}'".format(
+ self.traceDuration + WATCHDOG_BUFFER_SECS, self.samplingInterval))
+
+
+def stop_carwatchdog_collection(outputDir):
+ run_adb_shell_cmd("'dumpsys android.automotive.watchdog.ICarWatchdog/default"
+ " --stop_perf' > {}/{}".format(outputDir, CARWATCHDOG_DUMP))
+
+
+def do_something(outpuDir, traceDuration, samplingInterval, uidProcessMapperObj):
+ buildChars = run_adb_shell_cmd_strip_output(
+ "'getprop ro.build.characteristics'")
+
+ carwatchdog_collection_thread = None
+ if "automotive" in buildChars:
+ carwatchdog_collection_thread = carwatchdog_collection(
+ traceDuration, samplingInterval)
+ carwatchdog_collection_thread.start()
+
+ for i in range(1, traceDuration):
+ if DID_RECEIVE_SIGINT:
+ break
+ now = time.process_time()
+ read_uid_process_mapping(uidProcessMapperObj)
+ taken = time.process_time() - now
+ if (taken < 1):
+ time.sleep(1 - taken)
+
+ read_uid_package_mapping(uidProcessMapperObj)
+
+ if "automotive" in buildChars:
+ carwatchdog_collection_thread.join()
+ stop_carwatchdog_collection(outpuDir)
+
+
+def read_uid_process_mapping(uidProcessMapperObj):
+ procStatusDump = run_adb_shell_cmd_ignore_err(
+ "'cat /proc/*/status /proc/*/task/*/status 2> /dev/null'")
+
+ uidProcessMapperObj.parse_proc_status_dump(procStatusDump)
+
+
+def read_uid_package_mapping(uidProcessMapperObj):
+ packageMappingDump = run_adb_shell_cmd_ignore_err(
+ "'pm list packages -a -U | sort | uniq'")
+
+ uidProcessMapperObj.parse_uid_package_dump(packageMappingDump)
+
+
+# Parser for "/proc/diskstats".
+class DiskStats:
+
+ def __init__(self, readIos, readSectors, writeIos, writeSectors):
+ self.readIos = readIos
+ self.readSectors = readSectors
+ self.writeIos = writeIos
+ self.writeSectors = writeSectors
+
+ def delta(self, other):
+ return DiskStats(self.readIos - other.readIos,
+ self.readSectors - other.readSectors,
+ self.writeIos - other.writeIos,
+ self.writeSectors - other.writeSectors)
+
+ def dump(self, shouldDumpReads, shouldDumpWrites, outputFile):
+ if self.readIos is None or self.readIos is None or self.readIos is None\
+ or self.readIos is None:
+ outputFile.write("Missing disk stats")
+ return
+
+ if (shouldDumpReads):
+ outputFile.write("Total dev block reads: {} KB, IOs: {}\n".format(
+ self.readSectors / 2, self.readIos))
+
+ if (shouldDumpWrites):
+ outputFile.write("Total dev block writes: {} KB, IOs: {}\n".format(
+ self.writeSectors / 2, self.writeIos))
+
+
+def get_disk_stats(blockDev):
+ line = run_adb_shell_cmd(
+ "'cat /proc/diskstats' | fgrep -w {}".format(blockDev))
+ matcher = re.compile(RE_DISK_STATS_LINE)
+ match = matcher.match(line)
+
+ if not match:
+ return None
+
+ readIos = int(match.group(4))
+ readSectors = int(match.group(6))
+ writeIos = int(match.group(8))
+ writeSectors = int(match.group(10))
+
+ return DiskStats(readIos, readSectors, writeIos, writeSectors)
+
+
+IoBytes = namedtuple("IoBytes", "rdBytes wrBytes")
+
+
+# Parser for "/proc/uid_io/stats".
+class UidIoStats:
+
+ def __init__(self):
+ self.uidIoStatsReMatcher = re.compile(RE_UID_IO_STATS_LINE)
+ self.ioBytesByUid = {} # Key: UID, Value: IoBytes
+ self.totalIoBytes = IoBytes(rdBytes=0, wrBytes=0)
+
+ def parse(self, dump):
+ totalRdBytes = 0
+ totalWrBytes = 0
+ for line in dump.split("\n"):
+ (uid, ioBytes) = self.parse_uid_io_bytes(line)
+ self.ioBytesByUid[uid] = ioBytes
+ totalRdBytes += ioBytes.rdBytes
+ totalWrBytes += ioBytes.wrBytes
+
+ self.totalIoBytes = IoBytes(rdBytes=totalRdBytes, wrBytes=totalWrBytes)
+
+ def parse_uid_io_bytes(self, line):
+ match = self.uidIoStatsReMatcher.match(line)
+ if not match:
+ return None
+ return (int(match.group(1)),
+ IoBytes(
+ rdBytes=(int(match.group(4)) + int(match.group(8))),
+ wrBytes=(int(match.group(5)) + int(match.group(9)))))
+
+ def delta(self, other):
+ deltaStats = UidIoStats()
+ deltaStats.totalIoBytes = IoBytes(
+ rdBytes=self.totalIoBytes.rdBytes - other.totalIoBytes.rdBytes,
+ wrBytes=self.totalIoBytes.wrBytes - other.totalIoBytes.wrBytes)
+
+ for uid, ioBytes in self.ioBytesByUid.items():
+ if uid not in other.ioBytesByUid:
+ deltaStats.ioBytesByUid[uid] = ioBytes
+ continue
+ otherIoBytes = other.ioBytesByUid[uid]
+ rdBytes = ioBytes.rdBytes - otherIoBytes.rdBytes if ioBytes.rdBytes > otherIoBytes.rdBytes\
+ else 0
+ wrBytes = ioBytes.wrBytes - otherIoBytes.wrBytes if ioBytes.wrBytes > otherIoBytes.wrBytes\
+ else 0
+ deltaStats.ioBytesByUid[uid] = IoBytes(rdBytes=rdBytes, wrBytes=wrBytes)
+ return deltaStats
+
+ def dumpTotal(self, mode, outputFile):
+ totalBytes = self.totalIoBytes.wrBytes if mode == "write" else self.totalIoBytes.rdBytes
+ outputFile.write("Total system-wide {} KB: {}\n".format(
+ mode, to_kib(totalBytes)))
+
+ def dump(self, uidProcessMapperObj, mode, func, outputFile):
+ sortedEntries = collections.OrderedDict(
+ sorted(
+ self.ioBytesByUid.items(),
+ key=lambda item: item[1].wrBytes
+ if mode == "write" else item[1].rdBytes,
+ reverse=True))
+ totalEntries = len(sortedEntries)
+ for i in range(totalEntries):
+ uid, ioBytes = sortedEntries.popitem(last=False)
+ totalBytes = ioBytes.wrBytes if mode == "write" else ioBytes.rdBytes
+ if totalBytes < androidFsParser.MIN_PID_BYTES:
+ continue
+ uidInfo = uidProcessMapperObj.get_uid_info(uid)
+ outputFile.write("{}, Total {} KB: {}\n".format(uidInfo.to_string(), mode,
+ to_kib(totalBytes)))
+ func(uid)
+ outputFile.write("\n" + ("=" * 100) + "\n")
+ if i < totalEntries - 1:
+ outputFile.write("\n")
+
+
+def get_uid_io_stats():
+ uidIoStatsDump = run_adb_shell_cmd_strip_output("'cat /proc/uid_io/stats'")
+ uidIoStats = UidIoStats()
+ uidIoStats.parse(uidIoStatsDump)
+ return uidIoStats
+
+
+def to_kib(bytes):
+ return bytes / 1024
+
+
+def main(argv):
+ signal.signal(signal.SIGINT, signal_handler)
+
+ args = init_arguments()
+ verify_arguments(args)
+
+ run_adb_cmd("root")
+ buildDesc = run_adb_shell_cmd_strip_output("'getprop ro.build.description'")
+ blockDev = get_block_dev()
+
+ prep_to_do_something()
+ setup_tracepoints(args.traceReads, args.traceWrites)
+ diskStatsBefore = get_disk_stats(blockDev)
+ uidIoStatsBefore = get_uid_io_stats()
+
+ traceFile = "{}/{}".format(args.outputDir, TEMP_TRACE_FILE)
+
+ startDateTime = datetime.now()
+ proc = start_streaming_trace(traceFile)
+ print("Started trace streaming")
+
+ uidProcessMapperObj = uidProcessMapper.UidProcessMapper()
+ do_something(args.outputDir, args.traceDuration, args.samplingInterval,
+ uidProcessMapperObj)
+
+ stop_streaming_trace(proc)
+ endDateTime = datetime.now()
+ print("Stopped trace streaming")
+
+ clear_tracing(args.traceReads, args.traceWrites)
+
+ diskStatsAfter = get_disk_stats(blockDev)
+ uidIoStatsAfter = get_uid_io_stats()
+ diskStatsDelta = diskStatsAfter.delta(diskStatsBefore)
+ uidIoStatsDelta = uidIoStatsAfter.delta(uidIoStatsBefore)
+
+ print("Completed device side collection")
+
+ writeParser = androidFsParser.AndroidFsParser(androidFsParser.RE_WRITE_START,
+ uidProcessMapperObj)
+ readParser = androidFsParser.AndroidFsParser(androidFsParser.RE_READ_START,
+ uidProcessMapperObj)
+ with open(traceFile) as file:
+ for line in file:
+ if args.traceWrites and writeParser.parse(line):
+ continue
+ if args.traceReads:
+ readParser.parse(line)
+
+ outputFile = open("{}/{}".format(args.outputDir, OUTPUT_FILE), "w")
+ outputFile.write("Collection datetime: {}, Total duration: {}\n".format(
+ endDateTime, endDateTime - startDateTime))
+ outputFile.write("Build description: {}\n".format(buildDesc))
+ outputFile.write(
+ "Minimum KB per process or UID: {}, Small file KB: {}\n\n".format(
+ to_kib(androidFsParser.MIN_PID_BYTES),
+ to_kib(androidFsParser.SMALL_FILE_BYTES)))
+
+ diskStatsDelta.dump(args.traceReads, args.traceWrites, outputFile)
+
+ if args.traceWrites:
+ uidIoStatsDelta.dumpTotal("write", outputFile)
+ writeParser.dumpTotal(outputFile)
+ uidIoStatsDelta.dump(uidProcessMapperObj, "write",
+ lambda uid: writeParser.dump(uid, outputFile),
+ outputFile)
+
+ if args.traceWrites and args.traceReads:
+ outputFile.write("\n\n\n")
+
+ if args.traceReads:
+ uidIoStatsDelta.dumpTotal("read", outputFile)
+ readParser.dumpTotal(outputFile)
+ uidIoStatsDelta.dump(uidProcessMapperObj, "read",
+ lambda uid: readParser.dump(uid, outputFile),
+ outputFile)
+
+ outputFile.close()
+ run_shell_cmd("rm {}/{}".format(args.outputDir, TEMP_TRACE_FILE))
+
+
+if __name__ == "__main__":
+ main(sys.argv)
diff --git a/ioblame/uidProcessMapper.py b/ioblame/uidProcessMapper.py
new file mode 100644
index 00000000..0c0566b9
--- /dev/null
+++ b/ioblame/uidProcessMapper.py
@@ -0,0 +1,125 @@
+#!/usr/bin/env python
+#
+# 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 <-> UID <-> Process mapper."""
+
+import re
+
+# ex) Name: init
+PROC_STATUS_NAME_LINE = r"Name:\s+(\S+)"
+
+# ex) Pid: 1
+PROC_STATUS_PID_LINE = r"Pid:\s+([0-9]+)"
+
+# ex) Uid: 0 0 0 0
+PROC_STATUS_UID_LINE = r"Uid:\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)"
+
+# ex) package:com.google.android.car.uxr.sample uid:1000
+PACKAGE_UID_LINE = r"package:(\S+)\suid:([0-9]+)"
+
+USER_ID_OFFSET = 100000
+AID_APP_START = 10000
+UNKNOWN_UID = -1
+
+
+class UidInfo:
+
+ def __init__(self, uid, packageName=None):
+ self.uid = uid
+ self.packageName = packageName
+
+ def to_string(self):
+ appId = int(self.uid % USER_ID_OFFSET)
+ if self.uid == UNKNOWN_UID:
+ return "UID: UNKNOWN"
+ elif self.packageName is None and appId < AID_APP_START:
+ return "User ID: {}, Native service AID: {}".format(
+ int(self.uid / USER_ID_OFFSET), appId)
+ elif self.packageName is None:
+ return "User ID: {}, App ID: {}".format(
+ int(self.uid / USER_ID_OFFSET), appId)
+ else:
+ return "User ID: {}, Package name: {}".format(
+ int(self.uid / USER_ID_OFFSET), self.packageName)
+
+
+class UidProcessMapper:
+
+ def __init__(self):
+ self.nameReMatcher = re.compile(PROC_STATUS_NAME_LINE)
+ self.pidReMatcher = re.compile(PROC_STATUS_PID_LINE)
+ self.uidReMatcher = re.compile(PROC_STATUS_UID_LINE)
+ self.packageUidMatcher = re.compile(PACKAGE_UID_LINE)
+ self.uidByProcessDict = {} # Key: Process Name, Value: {PID: UID}
+ self.packageNameByAppId = {} # Key: App ID, Value: Package name
+
+ def parse_proc_status_dump(self, dump):
+ name, pid, uid = "", "", ""
+
+ for line in dump.split("\n"):
+ if line.startswith("Name:"):
+ name = self.match_re(self.nameReMatcher, line)
+ pid, uid = "", ""
+ elif line.startswith("Pid:"):
+ pid = self.match_re(self.pidReMatcher, line)
+ uid = ""
+ elif line.startswith("Uid:"):
+ uid = self.match_re(self.uidReMatcher, line)
+ if name != "" and pid != "" and uid != "":
+ self.add_mapping(name, int(pid), int(uid))
+ name, pid, uid = "", "", ""
+
+ def parse_uid_package_dump(self, dump):
+ for line in dump.split("\n"):
+ if line == "":
+ continue
+
+ match = self.packageUidMatcher.match(line)
+ if (match):
+ packageName = match.group(1)
+ appId = int(match.group(2))
+ if appId in self.packageNameByAppId:
+ self.packageNameByAppId[appId].add(packageName)
+ else:
+ self.packageNameByAppId[appId] = {packageName}
+ else:
+ print("'{}' line doesn't match '{}' regex".format(
+ line, self.packageUidMatcher))
+
+ def match_re(self, reMatcher, line):
+ match = reMatcher.match(line)
+ if not match:
+ return ""
+ return match.group(1)
+
+ def add_mapping(self, name, pid, uid):
+ if name in self.uidByProcessDict:
+ self.uidByProcessDict[name][pid] = uid
+ else:
+ self.uidByProcessDict[name] = {pid: uid}
+
+ def get_uid(self, name, pid):
+ if name in self.uidByProcessDict:
+ if pid in self.uidByProcessDict[name]:
+ return self.uidByProcessDict[name][pid]
+ return UNKNOWN_UID
+
+ def get_uid_info(self, uid):
+ appId = uid % USER_ID_OFFSET
+ if appId in self.packageNameByAppId:
+ return UidInfo(uid, " | ".join(self.packageNameByAppId[appId]))
+ else:
+ return UidInfo(uid)
diff --git a/ioshark/compile_ioshark.c b/ioshark/compile_ioshark.c
index 13755299..fe9085cf 100644
--- a/ioshark/compile_ioshark.c
+++ b/ioshark/compile_ioshark.c
@@ -22,7 +22,7 @@
#include <signal.h>
#include <string.h>
#include <sys/stat.h>
-#include <sys/errno.h>
+#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include "ioshark.h"
diff --git a/ioshark/compile_ioshark_subr.c b/ioshark/compile_ioshark_subr.c
index 43fb2544..6fc301cc 100644
--- a/ioshark/compile_ioshark_subr.c
+++ b/ioshark/compile_ioshark_subr.c
@@ -21,7 +21,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
-#include <sys/errno.h>
+#include <errno.h>
#include "ioshark.h"
#include "compile_ioshark.h"
#include <endian.h>
diff --git a/ioshark/convert_format.c b/ioshark/convert_format.c
index 3436ca77..989bfcb8 100644
--- a/ioshark/convert_format.c
+++ b/ioshark/convert_format.c
@@ -22,7 +22,7 @@
#include <signal.h>
#include <string.h>
#include <sys/stat.h>
-#include <sys/errno.h>
+#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <endian.h>
diff --git a/ioshark/dump_ioshark_filenames.c b/ioshark/dump_ioshark_filenames.c
index c082c274..a28ab790 100644
--- a/ioshark/dump_ioshark_filenames.c
+++ b/ioshark/dump_ioshark_filenames.c
@@ -21,7 +21,7 @@
#include <signal.h>
#include <string.h>
#include <sys/stat.h>
-#include <sys/errno.h>
+#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include "ioshark.h"
diff --git a/ioshark/ioshark_bench.c b/ioshark/ioshark_bench.c
index e44a2a53..9540b741 100644
--- a/ioshark/ioshark_bench.c
+++ b/ioshark/ioshark_bench.c
@@ -22,7 +22,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
-#include <sys/errno.h>
+#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <assert.h>
diff --git a/ioshark/ioshark_bench_mmap.c b/ioshark/ioshark_bench_mmap.c
index e8b3acce..3b37fbf6 100644
--- a/ioshark/ioshark_bench_mmap.c
+++ b/ioshark/ioshark_bench_mmap.c
@@ -23,7 +23,7 @@
#include <string.h>
#include <pthread.h>
#include <sys/stat.h>
-#include <sys/errno.h>
+#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <assert.h>
diff --git a/libfec/Android.bp b/libfec/Android.bp
index c19f6a90..302d8e40 100644
--- a/libfec/Android.bp
+++ b/libfec/Android.bp
@@ -58,7 +58,7 @@ cc_defaults {
"-DFEC_NO_KLOG",
],
},
- linux_glibc: {
+ host_linux: {
sanitize: {
misc_undefined: ["integer"],
},
diff --git a/profcollectd/README.md b/profcollectd/README.md
index dba75a2c..e9066000 100644
--- a/profcollectd/README.md
+++ b/profcollectd/README.md
@@ -37,6 +37,62 @@ are controlled by the following configurations:
Setting the frequency value to `0` disables collection for the corresponding event.
+#### Custom configuration
+
+In adb root:
+
+```
+# Record every 60s (By default, record every 10m). The actual interval will be longer than the
+# set value if the device goes to hibernation.
+oriole:/ # setprop persist.device_config.profcollect_native_boot.collection_interval 60
+
+# Each time recording, record ETM data for 1s (By default, it's 0.5s).
+oriole:/ # setprop persist.device_config.profcollect_native_boot.sampling_period 1000
+
+# Set ETM data storage limit to 50G (By default, it is 512M).
+oriole:/ # setprop persist.device_config.profcollect_native_boot.max_trace_limit 53687091200
+
+# Enable ETM data collection (By default, it's decided by the server).
+oriole:/ # setprop persist.device_config.profcollect_native_boot.enabled true
+
+# After adjusting configuration, need to restart profcollectd
+oriole:/ # setprop ctl.stop profcollectd
+# Wait for a few seconds.
+oriole:/ # setprop ctl.start profcollectd
+
+# Check if profcollectd is running
+oriole:/ # ps -e | grep profcollectd
+root 918 1 10945660 47040 binder_wait_for_work 0 S profcollectd
+
+# Check if the new configuration takes effect.
+oriole:/ # cat /data/misc/profcollectd/output/config.json
+{"version":1,"node_id":[189,15,145,225,97,167],"build_fingerprint":"google/oriole/oriole:Tiramisu/TP1A.220223.002/8211650:userdebug/dev-keys","collection_interval":{"secs":60,"nanos":0},"sampling_period":{"secs":1,"nanos":0},"binary_filter":"^/(system|apex/.+)/(bin|lib|lib64)/.+","max_trace_limit":53687091200}
+```
+
+To check existing collected ETM data:
+```
+oriole:/ # cd data/misc/profcollectd/trace/
+oriole:/data/misc/profcollectd/trace # ls
+```
+
+To check if ETM data can be collected successfully:
+```
+# Trigger one collection manually.
+oriole:/ # profcollectctl once
+Trace once
+
+# Check trace directory to see if there is a recent manual trace file.
+oriole:/ # ls /data/misc/profcollectd/trace/
+20220224-222946_manual.etmtrace
+```
+
+If there are too many trace files, we need to processing them to avoid reaching storage limit.
+It may take a long time.
+```
+oriole:/ # profcollectctl process
+Processing traces
+```
+
### Processing
The raw tracing data needs to be combined with the original binary to create the AutoFDO branch
diff --git a/profcollectd/libprofcollectd/lib.rs b/profcollectd/libprofcollectd/lib.rs
index f95132f4..da178f27 100644
--- a/profcollectd/libprofcollectd/lib.rs
+++ b/profcollectd/libprofcollectd/lib.rs
@@ -127,6 +127,9 @@ pub fn reset() -> Result<()> {
pub fn init_logging() {
let min_log_level = if cfg!(feature = "test") { log::Level::Info } else { log::Level::Error };
android_logger::init_once(
- android_logger::Config::default().with_tag("profcollectd").with_min_level(min_log_level),
+ android_logger::Config::default()
+ .with_tag("profcollectd")
+ .with_min_level(min_log_level)
+ .with_log_id(android_logger::LogId::System),
);
}
diff --git a/simpleperf/Android.bp b/simpleperf/Android.bp
index ba4ef198..58943931 100644
--- a/simpleperf/Android.bp
+++ b/simpleperf/Android.bp
@@ -131,6 +131,9 @@ cc_defaults {
linux_glibc_x86_64: {
stl: "libc++_static",
},
+ linux_musl_x86_64: {
+ stl: "libc++_static",
+ },
windows: {
enabled: true,
stl: "libc++_static",
@@ -328,7 +331,10 @@ cc_library {
srcs: ["profcollect.cpp"],
host_supported: false,
static_libs: ["libsimpleperf"],
- shared_libs: ["libLLVM_android"],
+ shared_libs: [
+ "libLLVM_android",
+ "libpower",
+ ],
}
rust_bindgen {
@@ -431,6 +437,16 @@ cc_binary {
dir: "simpleperf/linux/x86_64",
},
},
+ linux_musl_x86: {
+ dist: {
+ dir: "simpleperf/linux_musl/x86",
+ },
+ },
+ linux_musl_x86_64: {
+ dist: {
+ dir: "simpleperf/linux_musl/x86_64",
+ },
+ },
windows_x86: {
dist: {
dir: "simpleperf/windows/x86",
@@ -515,6 +531,16 @@ cc_library_shared {
dir: "simpleperf/linux/x86_64",
},
},
+ linux_musl_x86: {
+ dist: {
+ dir: "simpleperf/linux_musl/x86",
+ },
+ },
+ linux_musl_x86_64: {
+ dist: {
+ dir: "simpleperf/linux_musl/x86_64",
+ },
+ },
windows_x86: {
dist: {
dir: "simpleperf/windows/x86",
@@ -530,6 +556,11 @@ cc_library_shared {
cc_defaults {
name: "simpleperf_test_srcs",
+ tidy_timeout_srcs: [
+ "record_test.cpp",
+ "cmd_report_sample_test.cpp",
+ "cmd_report_test.cpp",
+ ],
srcs: [
"cmd_inject_test.cpp",
"cmd_kmem_test.cpp",
@@ -561,6 +592,10 @@ cc_defaults {
],
},
linux: {
+ tidy_timeout_srcs: [
+ "cmd_stat_test.cpp",
+ "cmd_record_test.cpp",
+ ],
srcs: [
"CallChainJoiner_test.cpp",
"cmd_api_test.cpp",
diff --git a/simpleperf/Android.mk b/simpleperf/Android.mk
index df0009f4..954c28c0 100644
--- a/simpleperf/Android.mk
+++ b/simpleperf/Android.mk
@@ -33,4 +33,6 @@ SIMPLEPERF_SCRIPT_PATH := \
$(SIMPLEPERF_SCRIPT_PATH) : $(SOONG_ZIP)
$(hide) $(SOONG_ZIP) -d -o $@ -C system/extras/simpleperf $(SIMPLEPERF_SCRIPT_LIST)
+$(call declare-1p-target,$(SIMPLEPERF_SCRIPT_PATH),system/extras)
+
$(call dist-for-goals,simpleperf,$(SIMPLEPERF_SCRIPT_PATH):simpleperf/simpleperf_script.zip)
diff --git a/simpleperf/ETMDecoder.cpp b/simpleperf/ETMDecoder.cpp
index 41a7d130..62fe997c 100644
--- a/simpleperf/ETMDecoder.cpp
+++ b/simpleperf/ETMDecoder.cpp
@@ -730,12 +730,19 @@ class ETMDecoderImpl : public ETMDecoder {
return false;
}
size_t left_size = size;
+ const size_t MAX_RESET_RETRY_COUNT = 3;
+ size_t reset_retry_count = 0;
while (left_size > 0) {
uint32_t processed;
auto resp = decoder.TraceDataIn(OCSD_OP_DATA, data_index_, left_size, data, &processed);
if (IsRespError(resp)) {
// A decoding error shouldn't ruin all data. Reset decoders to recover from it.
- LOG(INFO) << "reset etm decoders for seeing a decode failure, resp " << resp;
+ // But some errors may not be recoverable by resetting decoders. So use a max retry limit.
+ if (++reset_retry_count > MAX_RESET_RETRY_COUNT) {
+ break;
+ }
+ LOG(DEBUG) << "reset etm decoders for seeing a decode failure, resp " << resp
+ << ", reset_retry_count is " << reset_retry_count;
decoder.TraceDataIn(OCSD_OP_RESET, data_index_ + processed, 0, nullptr, nullptr);
}
data += processed;
diff --git a/simpleperf/IOEventLoop.cpp b/simpleperf/IOEventLoop.cpp
index 06bdd713..239fff97 100644
--- a/simpleperf/IOEventLoop.cpp
+++ b/simpleperf/IOEventLoop.cpp
@@ -84,6 +84,10 @@ bool IOEventLoop::EnsureInit() {
return false;
}
event_config_free(cfg);
+ if (event_base_priority_init(ebase_, 2) != 0) {
+ LOG(ERROR) << "event_base_priority_init failed";
+ return false;
+ }
}
if (ebase_ == nullptr) {
LOG(ERROR) << "failed to create event_base";
@@ -110,39 +114,44 @@ static bool MakeFdNonBlocking(int fd) {
return true;
}
-IOEventRef IOEventLoop::AddReadEvent(int fd, const std::function<bool()>& callback) {
+IOEventRef IOEventLoop::AddReadEvent(int fd, const std::function<bool()>& callback,
+ IOEventPriority priority) {
if (!MakeFdNonBlocking(fd)) {
return nullptr;
}
- return AddEvent(fd, EV_READ | EV_PERSIST, nullptr, callback);
+ return AddEvent(fd, EV_READ | EV_PERSIST, nullptr, callback, priority);
}
-IOEventRef IOEventLoop::AddWriteEvent(int fd, const std::function<bool()>& callback) {
+IOEventRef IOEventLoop::AddWriteEvent(int fd, const std::function<bool()>& callback,
+ IOEventPriority priority) {
if (!MakeFdNonBlocking(fd)) {
return nullptr;
}
- return AddEvent(fd, EV_WRITE | EV_PERSIST, nullptr, callback);
+ return AddEvent(fd, EV_WRITE | EV_PERSIST, nullptr, callback, priority);
}
-bool IOEventLoop::AddSignalEvent(int sig, const std::function<bool()>& callback) {
- return AddEvent(sig, EV_SIGNAL | EV_PERSIST, nullptr, callback) != nullptr;
+bool IOEventLoop::AddSignalEvent(int sig, const std::function<bool()>& callback,
+ IOEventPriority priority) {
+ return AddEvent(sig, EV_SIGNAL | EV_PERSIST, nullptr, callback, priority) != nullptr;
}
-bool IOEventLoop::AddSignalEvents(std::vector<int> sigs, const std::function<bool()>& callback) {
+bool IOEventLoop::AddSignalEvents(std::vector<int> sigs, const std::function<bool()>& callback,
+ IOEventPriority priority) {
for (auto sig : sigs) {
- if (!AddSignalEvent(sig, callback)) {
+ if (!AddSignalEvent(sig, callback, priority)) {
return false;
}
}
return true;
}
-IOEventRef IOEventLoop::AddPeriodicEvent(timeval duration, const std::function<bool()>& callback) {
- return AddEvent(-1, EV_PERSIST, &duration, callback);
+IOEventRef IOEventLoop::AddPeriodicEvent(timeval duration, const std::function<bool()>& callback,
+ IOEventPriority priority) {
+ return AddEvent(-1, EV_PERSIST, &duration, callback, priority);
}
IOEventRef IOEventLoop::AddEvent(int fd_or_sig, int16_t events, timeval* timeout,
- const std::function<bool()>& callback) {
+ const std::function<bool()>& callback, IOEventPriority priority) {
if (!EnsureInit()) {
return nullptr;
}
@@ -152,6 +161,7 @@ IOEventRef IOEventLoop::AddEvent(int fd_or_sig, int16_t events, timeval* timeout
LOG(ERROR) << "event_new() failed";
return nullptr;
}
+ event_priority_set(e->e, priority);
if (event_add(e->e, timeout) != 0) {
LOG(ERROR) << "event_add() failed";
return nullptr;
diff --git a/simpleperf/IOEventLoop.h b/simpleperf/IOEventLoop.h
index f0192969..1578a4d0 100644
--- a/simpleperf/IOEventLoop.h
+++ b/simpleperf/IOEventLoop.h
@@ -18,6 +18,7 @@
#define SIMPLE_PERF_IOEVENT_LOOP_H_
#include <stdint.h>
+#include <sys/time.h>
#include <time.h>
#include <functional>
@@ -31,6 +32,12 @@ namespace simpleperf {
struct IOEvent;
typedef IOEvent* IOEventRef;
+enum IOEventPriority {
+ // Lower value means higher priority.
+ IOEventHighPriority = 0,
+ IOEventLowPriority = 1,
+};
+
// IOEventLoop is a class wrapper of libevent, it monitors events happened,
// and calls the corresponding callbacks. Possible events are: file ready to
// read, file ready to write, signal happens, periodic timer timeout.
@@ -45,22 +52,27 @@ class IOEventLoop {
// Register a read Event, so [callback] is called when [fd] can be read
// without blocking. If registered successfully, return the reference
// to control the Event, otherwise return nullptr.
- IOEventRef AddReadEvent(int fd, const std::function<bool()>& callback);
+ IOEventRef AddReadEvent(int fd, const std::function<bool()>& callback,
+ IOEventPriority priority = IOEventLowPriority);
// Register a write Event, so [callback] is called when [fd] can be written
// without blocking.
- IOEventRef AddWriteEvent(int fd, const std::function<bool()>& callback);
+ IOEventRef AddWriteEvent(int fd, const std::function<bool()>& callback,
+ IOEventPriority priority = IOEventLowPriority);
// Register a signal Event, so [callback] is called each time signal [sig]
// happens.
- bool AddSignalEvent(int sig, const std::function<bool()>& callback);
+ bool AddSignalEvent(int sig, const std::function<bool()>& callback,
+ IOEventPriority priority = IOEventLowPriority);
// Register a vector of signal Events.
- bool AddSignalEvents(std::vector<int> sigs, const std::function<bool()>& callback);
+ bool AddSignalEvents(std::vector<int> sigs, const std::function<bool()>& callback,
+ IOEventPriority priority = IOEventLowPriority);
// Register a periodic Event, so [callback] is called periodically every
// [duration].
- IOEventRef AddPeriodicEvent(timeval duration, const std::function<bool()>& callback);
+ IOEventRef AddPeriodicEvent(timeval duration, const std::function<bool()>& callback,
+ IOEventPriority priority = IOEventLowPriority);
// Run a loop polling for Events. It only exits when ExitLoop() is called
// in a callback function of registered Events.
@@ -80,7 +92,8 @@ class IOEventLoop {
private:
bool EnsureInit();
IOEventRef AddEvent(int fd_or_sig, int16_t events, timeval* timeout,
- const std::function<bool()>& callback);
+ const std::function<bool()>& callback,
+ IOEventPriority priority = IOEventLowPriority);
static void EventCallbackFn(int, int16_t, void*);
event_base* ebase_;
diff --git a/simpleperf/IOEventLoop_test.cpp b/simpleperf/IOEventLoop_test.cpp
index 09f64522..658fe820 100644
--- a/simpleperf/IOEventLoop_test.cpp
+++ b/simpleperf/IOEventLoop_test.cpp
@@ -250,3 +250,45 @@ TEST(IOEventLoop, exit_before_loop) {
IOEventLoop loop;
ASSERT_TRUE(loop.ExitLoop());
}
+
+TEST(IOEventLoop, priority) {
+ int low_priority_fd[2];
+ ASSERT_EQ(0, pipe(low_priority_fd));
+ int high_priority_fd[2];
+ ASSERT_EQ(0, pipe(high_priority_fd));
+
+ IOEventLoop loop;
+ int count = 0;
+
+ ASSERT_NE(nullptr, loop.AddReadEvent(
+ low_priority_fd[0],
+ [&]() {
+ char c;
+ read(low_priority_fd[0], &c, 1);
+ CHECK_EQ(count, 1);
+ count++;
+ return loop.ExitLoop();
+ },
+ IOEventLowPriority));
+
+ ASSERT_NE(nullptr, loop.AddReadEvent(
+ high_priority_fd[0],
+ [&]() {
+ char c;
+ read(high_priority_fd[0], &c, 1);
+ CHECK_EQ(count, 0);
+ count++;
+ return true;
+ },
+ IOEventHighPriority));
+
+ char c;
+ CHECK_EQ(write(low_priority_fd[1], &c, 1), 1);
+ CHECK_EQ(write(high_priority_fd[1], &c, 1), 1);
+ ASSERT_TRUE(loop.RunLoop());
+ ASSERT_EQ(2, count);
+ for (int i = 0; i < 2; i++) {
+ close(low_priority_fd[i]);
+ close(high_priority_fd[i]);
+ }
+}
diff --git a/simpleperf/JITDebugReader.cpp b/simpleperf/JITDebugReader.cpp
index 52db2959..28058940 100644
--- a/simpleperf/JITDebugReader.cpp
+++ b/simpleperf/JITDebugReader.cpp
@@ -411,17 +411,15 @@ bool JITDebugReader::InitializeProcess(Process& process) {
if (art_lib_path.empty()) {
return false;
}
- process.is_64bit = art_lib_path.find("lib64") != std::string::npos;
// 2. Read libart.so to find the addresses of __jit_debug_descriptor and __dex_debug_descriptor.
- const DescriptorsLocation* location = GetDescriptorsLocation(art_lib_path, process.is_64bit);
+ const DescriptorsLocation* location = GetDescriptorsLocation(art_lib_path);
if (location == nullptr) {
return false;
}
- process.descriptors_addr = location->relative_addr + min_vaddr_in_memory;
- process.descriptors_size = location->size;
- process.jit_descriptor_offset = location->jit_descriptor_offset;
- process.dex_descriptor_offset = location->dex_descriptor_offset;
+ process.is_64bit = location->is_64bit;
+ process.jit_descriptor_addr = location->jit_descriptor_addr + min_vaddr_in_memory;
+ process.dex_descriptor_addr = location->dex_descriptor_addr + min_vaddr_in_memory;
for (auto& map : thread_mmaps) {
if (StartsWith(map.name, kJITZygoteCacheMmapPrefix)) {
@@ -434,10 +432,10 @@ bool JITDebugReader::InitializeProcess(Process& process) {
}
const JITDebugReader::DescriptorsLocation* JITDebugReader::GetDescriptorsLocation(
- const std::string& art_lib_path, bool is_64bit) {
+ const std::string& art_lib_path) {
auto it = descriptors_location_cache_.find(art_lib_path);
if (it != descriptors_location_cache_.end()) {
- return it->second.relative_addr == 0u ? nullptr : &it->second;
+ return it->second.jit_descriptor_addr == 0u ? nullptr : &it->second;
}
DescriptorsLocation& location = descriptors_location_cache_[art_lib_path];
@@ -469,18 +467,9 @@ const JITDebugReader::DescriptorsLocation* JITDebugReader::GetDescriptorsLocatio
if (jit_addr == 0u || dex_addr == 0u) {
return nullptr;
}
- location.relative_addr = std::min(jit_addr, dex_addr);
- location.size = std::max(jit_addr, dex_addr) +
- (is_64bit ? sizeof(JITDescriptor64) : sizeof(JITDescriptor32)) -
- location.relative_addr;
- if (location.size >= 4096u) {
- PLOG(WARNING) << "The descriptors_size is unexpected large: " << location.size;
- }
- if (descriptors_buf_.size() < location.size) {
- descriptors_buf_.resize(location.size);
- }
- location.jit_descriptor_offset = jit_addr - location.relative_addr;
- location.dex_descriptor_offset = dex_addr - location.relative_addr;
+ location.is_64bit = elf->Is64Bit();
+ location.jit_descriptor_addr = jit_addr;
+ location.dex_descriptor_addr = dex_addr;
return &location;
}
@@ -505,14 +494,40 @@ bool JITDebugReader::ReadRemoteMem(Process& process, uint64_t remote_addr, uint6
bool JITDebugReader::ReadDescriptors(Process& process, Descriptor* jit_descriptor,
Descriptor* dex_descriptor) {
- if (!ReadRemoteMem(process, process.descriptors_addr, process.descriptors_size,
- descriptors_buf_.data())) {
+ if (process.is_64bit) {
+ return ReadDescriptorsImpl<JITDescriptor64>(process, jit_descriptor, dex_descriptor);
+ }
+ return ReadDescriptorsImpl<JITDescriptor32>(process, jit_descriptor, dex_descriptor);
+}
+
+template <typename DescriptorT>
+bool JITDebugReader::ReadDescriptorsImpl(Process& process, Descriptor* jit_descriptor,
+ Descriptor* dex_descriptor) {
+ DescriptorT raw_jit_descriptor;
+ DescriptorT raw_dex_descriptor;
+ iovec local_iovs[2];
+ local_iovs[0].iov_base = &raw_jit_descriptor;
+ local_iovs[0].iov_len = sizeof(DescriptorT);
+ local_iovs[1].iov_base = &raw_dex_descriptor;
+ local_iovs[1].iov_len = sizeof(DescriptorT);
+ iovec remote_iovs[2];
+ remote_iovs[0].iov_base =
+ reinterpret_cast<void*>(static_cast<uintptr_t>(process.jit_descriptor_addr));
+ remote_iovs[0].iov_len = sizeof(DescriptorT);
+ remote_iovs[1].iov_base =
+ reinterpret_cast<void*>(static_cast<uintptr_t>(process.dex_descriptor_addr));
+ remote_iovs[1].iov_len = sizeof(DescriptorT);
+ ssize_t result = process_vm_readv(process.pid, local_iovs, 2, remote_iovs, 2, 0);
+ if (static_cast<size_t>(result) != sizeof(DescriptorT) * 2) {
+ PLOG(DEBUG) << "ReadDescriptor(pid " << process.pid << ", jit_addr " << std::hex
+ << process.jit_descriptor_addr << ", dex_addr " << process.dex_descriptor_addr
+ << ") failed";
+ process.died = true;
return false;
}
- if (!LoadDescriptor(process.is_64bit, &descriptors_buf_[process.jit_descriptor_offset],
- jit_descriptor) ||
- !LoadDescriptor(process.is_64bit, &descriptors_buf_[process.dex_descriptor_offset],
- dex_descriptor)) {
+
+ if (!ParseDescriptor(raw_jit_descriptor, jit_descriptor) ||
+ !ParseDescriptor(raw_dex_descriptor, dex_descriptor)) {
return false;
}
jit_descriptor->type = DescriptorType::kJIT;
@@ -520,17 +535,8 @@ bool JITDebugReader::ReadDescriptors(Process& process, Descriptor* jit_descripto
return true;
}
-bool JITDebugReader::LoadDescriptor(bool is_64bit, const char* data, Descriptor* descriptor) {
- if (is_64bit) {
- return LoadDescriptorImpl<JITDescriptor64>(data, descriptor);
- }
- return LoadDescriptorImpl<JITDescriptor32>(data, descriptor);
-}
-
template <typename DescriptorT>
-bool JITDebugReader::LoadDescriptorImpl(const char* data, Descriptor* descriptor) {
- DescriptorT raw_descriptor;
- MoveFromBinaryFormat(raw_descriptor, data);
+bool JITDebugReader::ParseDescriptor(const DescriptorT& raw_descriptor, Descriptor* descriptor) {
if (!raw_descriptor.Valid()) {
return false;
}
diff --git a/simpleperf/JITDebugReader.h b/simpleperf/JITDebugReader.h
index 72f3790b..b9e984d5 100644
--- a/simpleperf/JITDebugReader.h
+++ b/simpleperf/JITDebugReader.h
@@ -162,13 +162,10 @@ class JITDebugReader {
bool initialized = false;
bool died = false;
bool is_64bit = false;
- // The jit descriptor and dex descriptor can be read in one process_vm_readv() call.
- uint64_t descriptors_addr = 0;
- uint64_t descriptors_size = 0;
- // offset relative to descriptors_addr
- uint64_t jit_descriptor_offset = 0;
- // offset relative to descriptors_addr
- uint64_t dex_descriptor_offset = 0;
+ // remote addr of jit descriptor
+ uint64_t jit_descriptor_addr = 0;
+ // remote addr of dex descriptor
+ uint64_t dex_descriptor_addr = 0;
// The state we know about the remote jit debug descriptor.
Descriptor last_jit_descriptor;
@@ -181,10 +178,9 @@ class JITDebugReader {
// The location of descriptors in libart.so.
struct DescriptorsLocation {
- uint64_t relative_addr = 0;
- uint64_t size = 0;
- uint64_t jit_descriptor_offset = 0;
- uint64_t dex_descriptor_offset = 0;
+ bool is_64bit = false;
+ uint64_t jit_descriptor_addr = 0;
+ uint64_t dex_descriptor_addr = 0;
};
bool ReadProcess(Process& process, std::vector<JITDebugInfo>* debug_info);
@@ -192,12 +188,14 @@ class JITDebugReader {
std::vector<JITDebugInfo>* debug_info);
bool IsDescriptorChanged(Process& process, Descriptor& old_descriptor);
bool InitializeProcess(Process& process);
- const DescriptorsLocation* GetDescriptorsLocation(const std::string& art_lib_path, bool is_64bit);
+ const DescriptorsLocation* GetDescriptorsLocation(const std::string& art_lib_path);
bool ReadRemoteMem(Process& process, uint64_t remote_addr, uint64_t size, void* data);
bool ReadDescriptors(Process& process, Descriptor* jit_descriptor, Descriptor* dex_descriptor);
- bool LoadDescriptor(bool is_64bit, const char* data, Descriptor* descriptor);
template <typename DescriptorT>
- bool LoadDescriptorImpl(const char* data, Descriptor* descriptor);
+ bool ReadDescriptorsImpl(Process& process, Descriptor* jit_descriptor,
+ Descriptor* dex_descriptor);
+ template <typename DescriptorT>
+ bool ParseDescriptor(const DescriptorT& raw_descriptor, Descriptor* descriptor);
bool ReadNewCodeEntries(Process& process, const Descriptor& descriptor,
uint64_t last_action_timestamp, uint32_t read_entry_limit,
@@ -226,7 +224,6 @@ class JITDebugReader {
// All monitored processes
std::unordered_map<pid_t, Process> processes_;
std::unordered_map<std::string, DescriptorsLocation> descriptors_location_cache_;
- std::vector<char> descriptors_buf_;
std::priority_queue<JITDebugInfo, std::vector<JITDebugInfo>, std::greater<JITDebugInfo>>
debug_info_q_;
diff --git a/simpleperf/OfflineUnwinder.cpp b/simpleperf/OfflineUnwinder.cpp
index f611ef4d..bbc488b4 100644
--- a/simpleperf/OfflineUnwinder.cpp
+++ b/simpleperf/OfflineUnwinder.cpp
@@ -66,6 +66,9 @@ CHECK_ERROR_CODE(ERROR_INVALID_ELF);
CHECK_ERROR_CODE(ERROR_THREAD_DOES_NOT_EXIST);
CHECK_ERROR_CODE(ERROR_THREAD_TIMEOUT);
CHECK_ERROR_CODE(ERROR_SYSTEM_CALL);
+CHECK_ERROR_CODE(ERROR_BAD_ARCH);
+CHECK_ERROR_CODE(ERROR_MAPS_PARSE);
+CHECK_ERROR_CODE(ERROR_INVALID_PARAMETER);
CHECK_ERROR_CODE(ERROR_MAX);
// Max frames seen so far is 463, in http://b/110923759.
diff --git a/simpleperf/OfflineUnwinder.h b/simpleperf/OfflineUnwinder.h
index b6445577..9eb9e8ff 100644
--- a/simpleperf/OfflineUnwinder.h
+++ b/simpleperf/OfflineUnwinder.h
@@ -39,7 +39,10 @@ enum UnwindStackErrorCode : uint8_t {
// not exist.
ERROR_THREAD_TIMEOUT, // Timeout trying to unwind a local thread.
ERROR_SYSTEM_CALL, // System call failed while unwinding.
- ERROR_MAX = ERROR_SYSTEM_CALL,
+ ERROR_BAD_ARCH, // Arch invalid (none, or mismatched).
+ ERROR_MAPS_PARSE, // Failed to parse maps data.
+ ERROR_INVALID_PARAMETER, // Invalid parameter passed to function.
+ ERROR_MAX = ERROR_INVALID_PARAMETER,
};
struct UnwindingResult {
diff --git a/simpleperf/cmd_monitor.cpp b/simpleperf/cmd_monitor.cpp
index ed9edfac..d81ccfea 100644
--- a/simpleperf/cmd_monitor.cpp
+++ b/simpleperf/cmd_monitor.cpp
@@ -266,21 +266,22 @@ bool MonitorCommand::PrepareMonitoring() {
// 5. Add read/signal/periodic Events.
IOEventLoop* loop = event_selection_set_.GetIOEventLoop();
auto exit_loop_callback = [loop]() { return loop->ExitLoop(); };
- if (!loop->AddSignalEvents({SIGCHLD, SIGINT, SIGTERM}, exit_loop_callback)) {
+ if (!loop->AddSignalEvents({SIGCHLD, SIGINT, SIGTERM}, exit_loop_callback, IOEventHighPriority)) {
return false;
}
// Only add an event for SIGHUP if we didn't inherit SIG_IGN (e.g. from
// nohup).
if (!SignalIsIgnored(SIGHUP)) {
- if (!loop->AddSignalEvent(SIGHUP, exit_loop_callback)) {
+ if (!loop->AddSignalEvent(SIGHUP, exit_loop_callback, IOEventHighPriority)) {
return false;
}
}
if (duration_in_sec_ != 0) {
- if (!loop->AddPeriodicEvent(SecondToTimeval(duration_in_sec_),
- [loop]() { return loop->ExitLoop(); })) {
+ if (!loop->AddPeriodicEvent(
+ SecondToTimeval(duration_in_sec_), [loop]() { return loop->ExitLoop(); },
+ IOEventHighPriority)) {
return false;
}
}
@@ -291,6 +292,10 @@ bool MonitorCommand::DoMonitoring() {
if (!event_selection_set_.GetIOEventLoop()->RunLoop()) {
return false;
}
+ if (!event_selection_set_.SyncKernelBuffer()) {
+ return false;
+ }
+ event_selection_set_.CloseEventFiles();
if (!event_selection_set_.FinishReadMmapEventData()) {
return false;
}
diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp
index b84bcfab..4b79eedc 100644
--- a/simpleperf/cmd_record.cpp
+++ b/simpleperf/cmd_record.cpp
@@ -629,25 +629,26 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
}
IOEventLoop* loop = event_selection_set_.GetIOEventLoop();
auto exit_loop_callback = [loop]() { return loop->ExitLoop(); };
- if (!loop->AddSignalEvents({SIGCHLD, SIGINT, SIGTERM}, exit_loop_callback)) {
+ if (!loop->AddSignalEvents({SIGCHLD, SIGINT, SIGTERM}, exit_loop_callback, IOEventHighPriority)) {
return false;
}
// Only add an event for SIGHUP if we didn't inherit SIG_IGN (e.g. from nohup).
if (!SignalIsIgnored(SIGHUP)) {
- if (!loop->AddSignalEvent(SIGHUP, exit_loop_callback)) {
+ if (!loop->AddSignalEvent(SIGHUP, exit_loop_callback, IOEventHighPriority)) {
return false;
}
}
if (stop_signal_fd_ != -1) {
- if (!loop->AddReadEvent(stop_signal_fd_, exit_loop_callback)) {
+ if (!loop->AddReadEvent(stop_signal_fd_, exit_loop_callback, IOEventHighPriority)) {
return false;
}
}
if (duration_in_sec_ != 0) {
- if (!loop->AddPeriodicEvent(SecondToTimeval(duration_in_sec_),
- [loop]() { return loop->ExitLoop(); })) {
+ if (!loop->AddPeriodicEvent(
+ SecondToTimeval(duration_in_sec_), [loop]() { return loop->ExitLoop(); },
+ IOEventHighPriority)) {
return false;
}
}
@@ -718,9 +719,10 @@ bool RecordCommand::DoRecording(Workload* workload) {
return false;
}
time_stat_.stop_recording_time = GetSystemClock();
- if (!event_selection_set_.FinishReadMmapEventData()) {
+ if (!event_selection_set_.SyncKernelBuffer()) {
return false;
}
+ event_selection_set_.CloseEventFiles();
time_stat_.finish_recording_time = GetSystemClock();
uint64_t recording_time = time_stat_.finish_recording_time - time_stat_.start_recording_time;
LOG(INFO) << "Recorded for " << recording_time / 1e9 << " seconds. Start post processing.";
@@ -754,26 +756,31 @@ static bool WriteRecordDataToOutFd(const std::string& in_filename,
}
bool RecordCommand::PostProcessRecording(const std::vector<std::string>& args) {
- // 1. Merge map records dumped while recording by map record thread.
+ // 1. Read records left in the buffer.
+ if (!event_selection_set_.FinishReadMmapEventData()) {
+ return false;
+ }
+
+ // 2. Merge map records dumped while recording by map record thread.
if (map_record_thread_) {
if (!map_record_thread_->Join() || !MergeMapRecords()) {
return false;
}
}
- // 2. Post unwind dwarf callchain.
+ // 3. Post unwind dwarf callchain.
if (unwind_dwarf_callchain_ && post_unwind_) {
if (!PostUnwindRecords()) {
return false;
}
}
- // 3. Optionally join Callchains.
+ // 4. Optionally join Callchains.
if (callchain_joiner_) {
JoinCallChains();
}
- // 4. Dump additional features, and close record file.
+ // 5. Dump additional features, and close record file.
if (!DumpAdditionalFeatures(args)) {
return false;
}
@@ -785,7 +792,7 @@ bool RecordCommand::PostProcessRecording(const std::vector<std::string>& args) {
}
time_stat_.post_process_time = GetSystemClock();
- // 4. Show brief record result.
+ // 6. Show brief record result.
auto record_stat = event_selection_set_.GetRecordStat();
if (event_selection_set_.HasAuxTrace()) {
LOG(INFO) << "Aux data traced: " << record_stat.aux_data_size;
@@ -1278,8 +1285,9 @@ bool RecordCommand::CreateAndInitRecordFile() {
return false;
}
// 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],
+ dumping_attr_id_ = event_selection_set_.GetEventAttrWithId()[0];
+ CHECK(!dumping_attr_id_.ids.empty());
+ 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); });
@@ -1503,14 +1511,13 @@ bool RecordCommand::SaveRecordWithoutUnwinding(Record* record) {
bool RecordCommand::ProcessJITDebugInfo(const std::vector<JITDebugInfo>& debug_info,
bool sync_kernel_records) {
- EventAttrWithId attr_id = event_selection_set_.GetEventAttrWithId()[0];
for (auto& info : debug_info) {
if (info.type == JITDebugInfo::JIT_DEBUG_JIT_CODE) {
uint64_t timestamp =
jit_debug_reader_->SyncWithRecords() ? info.timestamp : last_record_timestamp_;
- Mmap2Record record(*attr_id.attr, false, info.pid, info.pid, info.jit_code_addr,
+ 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, attr_id.ids[0], timestamp);
+ info.file_path, dumping_attr_id_.ids[0], timestamp);
if (!ProcessRecord(&record)) {
return false;
}
@@ -1519,8 +1526,9 @@ 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(*attr_id.attr, false, info.pid, info.pid, map.start_addr, map.len,
- map.pgoff, map.prot, map.name, attr_id.ids[0], timestamp);
+ 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)) {
return false;
}
diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp
index ef2c1b68..01861c60 100644
--- a/simpleperf/cmd_record_test.cpp
+++ b/simpleperf/cmd_record_test.cpp
@@ -565,10 +565,12 @@ TEST(record_cmd, exit_with_parent_option) {
TEST(record_cmd, use_cmd_exit_code_option) {
TemporaryFile tmpfile;
int exit_code;
- RecordCmd()->Run({"--use-cmd-exit-code", "-o", tmpfile.path, "ls", "."}, &exit_code);
- ASSERT_EQ(exit_code, 0);
- RecordCmd()->Run({"--use-cmd-exit-code", "-o", tmpfile.path, "ls", "/not_exist_path"},
+ RecordCmd()->Run({"-e", GetDefaultEvent(), "--use-cmd-exit-code", "-o", tmpfile.path, "ls", "."},
&exit_code);
+ ASSERT_EQ(exit_code, 0);
+ RecordCmd()->Run(
+ {"-e", GetDefaultEvent(), "--use-cmd-exit-code", "-o", tmpfile.path, "ls", "/not_exist_path"},
+ &exit_code);
ASSERT_NE(exit_code, 0);
}
diff --git a/simpleperf/doc/README.md b/simpleperf/doc/README.md
index 473a5c11..51ffc839 100644
--- a/simpleperf/doc/README.md
+++ b/simpleperf/doc/README.md
@@ -95,6 +95,9 @@ See [executable_commands_reference.md](./executable_commands_reference.md).
See [scripts_reference.md](./scripts_reference.md).
+## View the profile
+
+See [view_the_profile.md](./view_the_profile.md).
## Answers to common issues
diff --git a/simpleperf/doc/collect_etm_data_for_autofdo.md b/simpleperf/doc/collect_etm_data_for_autofdo.md
index f9f5a158..145c0adf 100644
--- a/simpleperf/doc/collect_etm_data_for_autofdo.md
+++ b/simpleperf/doc/collect_etm_data_for_autofdo.md
@@ -94,7 +94,7 @@ Then we can use a.prof for PGO during compilation, via `-fprofile-sample-use=a.p
## Collect ETM data with a daemon
Android also has a daemon collecting ETM data periodically. It only runs on userdebug and eng
-devices. The source code is in `<aosp-top>/system/extras/profcollectd`.
+devices. The source code is in https://android.googlesource.com/platform/system/extras/+/master/profcollectd/.
## Support ETM in the kernel
@@ -107,21 +107,37 @@ The Coresight driver can be enabled by below kernel configs:
CONFIG_CORESIGHT=y
CONFIG_CORESIGHT_LINK_AND_SINK_TMC=y
CONFIG_CORESIGHT_SOURCE_ETM4X=y
- CONFIG_CORESIGHT_DYNAMIC_REPLICATOR=y
```
-On Kernel 5.10+, we can build Coresight driver as kernel modules instead.
+On Kernel 5.10+, we recommend building Coresight driver as kernel modules. Because it works with
+GKI kernel.
-Android common kernel 5.10+ should have all the Coresight patches needed. And we have backported
-necessary Coresight patches to Android common kernel 4.14 and 4.19. Android common kernel 5.4
-misses a few patches. Please create an [ndk issue](https://github.com/android/ndk/issues) if you
-need ETM function on 5.4 kernel.
+```config
+ CONFIG_CORESIGHT=m
+ CONFIG_CORESIGHT_LINK_AND_SINK_TMC=m
+ CONFIG_CORESIGHT_SOURCE_ETM4X=m
+```
+
+Android common kernel 5.10+ should have all the Coresight patches needed to collect ETM data.
+Android common kernel 5.4 misses two patches. But by adding patches in
+https://android-review.googlesource.com/q/topic:test_etm_on_hikey960_5.4, we can collect ETM data
+on hikey960 with 5.4 kernel.
+For Android common kernel 4.14 and 4.19, we have backported all necessary Coresight patches.
Besides Coresight driver, we also need to add Coresight devices in device tree. An example is in
https://github.com/torvalds/linux/blob/master/arch/arm64/boot/dts/arm/juno-base.dtsi. There should
be a path flowing ETM data from ETM device through funnels, ETF and replicators, all the way to
ETR, which writes ETM data to system memory.
+One optional flag in ETM device tree is "arm,coresight-loses-context-with-cpu". It saves ETM
+registers when a CPU enters low power state. It may be needed to avoid
+"coresight_disclaim_device_unlocked" warning when doing system wide collection.
+
+One optional flag in ETR device tree is "arm,scatter-gather". Simpleperf requests 4M system memory
+for ETR to store ETM data. Without IOMMU, the memory needs to be contiguous. If the kernel can't
+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.
+
## 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/pictures/android_studio_profiler_flame_chart.png b/simpleperf/doc/pictures/android_studio_profiler_flame_chart.png
new file mode 100644
index 00000000..6fea4f77
--- /dev/null
+++ b/simpleperf/doc/pictures/android_studio_profiler_flame_chart.png
Binary files differ
diff --git a/simpleperf/doc/pictures/android_studio_profiler_open_perf_trace.png b/simpleperf/doc/pictures/android_studio_profiler_open_perf_trace.png
new file mode 100644
index 00000000..faa9b70d
--- /dev/null
+++ b/simpleperf/doc/pictures/android_studio_profiler_open_perf_trace.png
Binary files differ
diff --git a/simpleperf/doc/pictures/android_studio_profiler_select_process.png b/simpleperf/doc/pictures/android_studio_profiler_select_process.png
new file mode 100644
index 00000000..4004e171
--- /dev/null
+++ b/simpleperf/doc/pictures/android_studio_profiler_select_process.png
Binary files differ
diff --git a/simpleperf/doc/pictures/android_studio_profiler_select_recording_method.png b/simpleperf/doc/pictures/android_studio_profiler_select_recording_method.png
new file mode 100644
index 00000000..3813533a
--- /dev/null
+++ b/simpleperf/doc/pictures/android_studio_profiler_select_recording_method.png
Binary files differ
diff --git a/simpleperf/doc/pictures/continuous_pprof.png b/simpleperf/doc/pictures/continuous_pprof.png
new file mode 100644
index 00000000..f92f7b65
--- /dev/null
+++ b/simpleperf/doc/pictures/continuous_pprof.png
Binary files differ
diff --git a/simpleperf/doc/pictures/firefox_profiler.png b/simpleperf/doc/pictures/firefox_profiler.png
new file mode 100644
index 00000000..4019289b
--- /dev/null
+++ b/simpleperf/doc/pictures/firefox_profiler.png
Binary files differ
diff --git a/simpleperf/doc/pictures/flamescope.png b/simpleperf/doc/pictures/flamescope.png
new file mode 100644
index 00000000..2a19dad5
--- /dev/null
+++ b/simpleperf/doc/pictures/flamescope.png
Binary files differ
diff --git a/simpleperf/doc/pictures/flamescope_click.png b/simpleperf/doc/pictures/flamescope_click.png
new file mode 100644
index 00000000..a5a8a979
--- /dev/null
+++ b/simpleperf/doc/pictures/flamescope_click.png
Binary files differ
diff --git a/simpleperf/doc/pictures/flamescope_flamegraph.png b/simpleperf/doc/pictures/flamescope_flamegraph.png
new file mode 100644
index 00000000..8a0b4ef9
--- /dev/null
+++ b/simpleperf/doc/pictures/flamescope_flamegraph.png
Binary files differ
diff --git a/simpleperf/doc/pictures/report_command.png b/simpleperf/doc/pictures/report_command.png
new file mode 100644
index 00000000..c04419a8
--- /dev/null
+++ b/simpleperf/doc/pictures/report_command.png
Binary files differ
diff --git a/simpleperf/doc/pictures/report_html.png b/simpleperf/doc/pictures/report_html.png
new file mode 100644
index 00000000..358ef7b4
--- /dev/null
+++ b/simpleperf/doc/pictures/report_html.png
Binary files differ
diff --git a/simpleperf/doc/scripts_reference.md b/simpleperf/doc/scripts_reference.md
index 1a97ebe5..31dee02d 100644
--- a/simpleperf/doc/scripts_reference.md
+++ b/simpleperf/doc/scripts_reference.md
@@ -114,6 +114,9 @@ device. It is more convenient than running adb commands manually.
## Viewing the profile
+Scripts in this section are for viewing the profile or converting profile data into formats used by
+external UIs. For recommended UIs, see [view_the_profile.md](view_the_profile.md).
+
### report.py
report.py is a wrapper of the `report` command on the host. It accepts all options of the `report`
diff --git a/simpleperf/doc/view_the_profile.md b/simpleperf/doc/view_the_profile.md
new file mode 100644
index 00000000..8a0e07e7
--- /dev/null
+++ b/simpleperf/doc/view_the_profile.md
@@ -0,0 +1,342 @@
+# View the profile
+
+[TOC]
+
+## Introduction
+
+After using `simpleperf record` or `app_profiler.py`, we get a profile data file. The file contains
+a list of samples. Each sample has a timestamp, a thread id, a callstack, events (like cpu-cycles
+or cpu-clock) used in this sample, etc. We have many choices for viewing the profile. We can show
+samples in chronological order, or show aggregated flamegraphs. We can show reports in text format,
+or in some interactive UIs.
+
+Below shows some recommended UIs to view the profile. Google developers can find more examples in
+[go/gmm-profiling](go/gmm-profiling?polyglot=linux-workstation#viewing-the-profile).
+
+
+## Continuous PProf UI (great flamegraph UI, but only available internally)
+
+[PProf](https://github.com/google/pprof) is a mature profiling technology used extensively on
+Google servers, with a powerful flamegraph UI, with strong drilldown, search, pivot, profile diff,
+and graph visualisation.
+
+![Example](./pictures/continuous_pprof.png)
+
+We can use `pprof_proto_generator.py` to convert profiles into pprof.profile protobufs for use in
+pprof.
+
+```
+# Output all threads, broken down by threadpool.
+./pprof_proto_generator.py
+
+# Use proguard mapping.
+./pprof_proto_generator.py --proguard-mapping-file proguard.map
+
+# Just the main (UI) thread (query by thread name):
+./pprof_proto_generator.py --comm com.example.android.displayingbitmaps
+```
+
+This will print some debug logs about Failed to read symbols: this is usually OK, unless those
+symbols are hotspots.
+
+Upload pprof.profile to http://pprof/ UI:
+
+```
+# Upload all threads in profile, grouped by threadpool.
+# This is usually a good default, combining threads with similar names.
+pprof --flame --tagroot threadpool pprof.profile
+
+# Upload all threads in profile, grouped by individual thread name.
+pprof --flame --tagroot thread pprof.profile
+
+# Upload all threads in profile, without grouping by thread.
+pprof --flame pprof.profile
+This will output a URL, example: https://pprof.corp.google.com/?id=589a60852306144c880e36429e10b166
+```
+
+## Firefox Profiler (great chronological UI)
+
+We can view Android profiles using Firefox Profiler: https://profiler.firefox.com/. This does not
+require Firefox installation -- Firefox Profiler is just a website, you can open it in any browser.
+
+![Example](./pictures/firefox_profiler.png)
+
+Firefox Profiler has a great chronological view, as it doesn't pre-aggregate similar stack traces
+like pprof does.
+
+We can use `gecko_profile_generator.py` to convert raw perf.data files into a Firefox Profile, with
+Proguard deobfuscation.
+
+```
+# Create Gecko Profile
+./gecko_profile_generator.py | gzip > gecko_profile.json.gz
+
+# Create Gecko Profile using Proguard map
+./gecko_profile_generator.py --proguard-mapping-file proguard.map | gzip > gecko_profile.json.gz
+```
+
+Then drag-and-drop gecko_profile.json.gz into https://profiler.firefox.com/.
+
+Firefox Profiler supports:
+
+1. Aggregated Flamegraphs
+2. Chronological Stackcharts
+
+And allows filtering by:
+
+1. Individual threads
+2. Multiple threads (Ctrl+Click thread names to select many)
+3. Timeline period
+4. Stack frame text search
+
+## FlameScope (great jank-finding UI)
+
+[Netflix's FlameScope](https://github.com/Netflix/flamescope) is a rough, proof-of-concept UI that
+lets you spot repeating patterns of work by laying out the profile as a subsecond heatmap.
+
+Below, each vertical stripe is one second, and each cell is 10ms. Redder cells have more samples.
+See https://www.brendangregg.com/blog/2018-11-08/flamescope-pattern-recognition.html for how to
+spot patterns.
+
+This is an example of a 60s DisplayBitmaps app Startup Profile.
+
+![Example](./pictures/flamescope.png)
+
+You can see:
+
+ The thick red vertical line on the left is startup.
+ The long white vertical sections on the left shows the app is mostly idle, waiting for commands
+ from instrumented tests.
+ Then we see periodically red blocks, which shows the app is periodically busy handling commands
+ from instrumented tests.
+
+Click the start and end cells of a duration:
+
+![Example](./pictures/flamescope_click.png)
+
+To see a flamegraph for that duration:
+
+![Example](./pictures/flamescope_flamegraph.png)
+
+Install and run Flamescope:
+
+```
+git clone https://github.com/Netflix/flamescope ~/flamescope
+cd ~/flamescope
+pip install -r requirements.txt
+npm install
+npm run webpack
+python3 run.py
+```
+
+Then open FlameScope in-browser: http://localhost:5000/.
+
+FlameScope can read gzipped perf script format profiles. Convert simpleperf perf.data to this
+format with `report_sample.py`, and place it in Flamescope's examples directory:
+
+```
+# Create `Linux perf script` format profile.
+report_sample.py | gzip > ~/flamescope/examples/my_simpleperf_profile.gz
+
+# Create `Linux perf script` format profile using Proguard map.
+report_sample.py \
+ --proguard-mapping-file proguard.map \
+ | gzip > ~/flamescope/examples/my_simpleperf_profile.gz
+```
+
+Open the profile "as Linux Perf", and click start and end sections to get a flamegraph of that
+timespan.
+
+To investigate UI Thread Jank, filter to UI thread samples only:
+
+```
+report_sample.py \
+ --comm com.example.android.displayingbitmaps \ # UI Thread
+ | gzip > ~/flamescope/examples/uithread.gz
+```
+
+Once you've identified the timespan of interest, consider also zooming into that section with
+Firefox Profiler, which has a more powerful flamegraph viewer.
+
+## Differential FlameGraph
+
+See Brendan Gregg's [Differential Flame Graphs](https://www.brendangregg.com/blog/2014-11-09/differential-flame-graphs.html) blog.
+
+Use Simpleperf's `stackcollapse.py` to convert perf.data to Folded Stacks format for the FlameGraph
+toolkit.
+
+Consider diffing both directions: After minus Before, and Before minus After.
+
+If you've recorded before and after your optimisation as perf_before.data and perf_after.data, and
+you're only interested in the UI thread:
+
+```
+# Generate before and after folded stacks from perf.data files
+./stackcollapse.py --kernel --jit -i perf_before.data \
+ --proguard-mapping-file proguard_before.map \
+ --comm com.example.android.displayingbitmaps \
+ > perf_before.folded
+./stackcollapse.py --kernel --jit -i perf_after.data \
+ --proguard-mapping-file proguard_after.map \
+ --comm com.example.android.displayingbitmaps \
+ > perf_after.folded
+
+# Generate diff reports
+FlameGraph/difffolded.pl -n perf_before.folded perf_after.folded \
+ | FlameGraph/flamegraph.pl > diff1.svg
+FlameGraph/difffolded.pl -n --negate perf_after.folded perf_before.folded \
+ | FlameGraph/flamegraph.pl > diff2.svg
+```
+
+## Android Studio Profiler
+
+Android Studio Profiler supports recording and reporting profiles of app processes. It supports
+several recording methods, including one using simpleperf as backend. You can use Android Studio
+Profiler for both recording and reporting.
+
+In Android Studio:
+Open View -> Tool Windows -> Profiler
+Click + -> Your Device -> Profileable Processes -> Your App
+
+![Example](./pictures/android_studio_profiler_select_process.png)
+
+Click into "CPU" Chart
+
+Choose Callstack Sample Recording. Even if you're using Java, this provides better observability,
+into ART, malloc, and the kernel.
+
+![Example](./pictures/android_studio_profiler_select_recording_method.png)
+
+Click Record, run your test on the device, then Stop when you're done.
+
+Click on a thread track, and "Flame Chart" to see a chronological chart on the left, and an
+aggregated flamechart on the right:
+
+![Example](./pictures/android_studio_profiler_flame_chart.png)
+
+If you want more flexibility in recording options, or want to add proguard mapping file, you can
+record using simpleperf, and report using Android Studio Profiler.
+
+We can use `simpleperf report-sample` to convert perf.data to trace files for Android Studio
+Profiler.
+
+```
+# Convert perf.data to perf.trace for Android Studio Profiler.
+# If on Mac/Windows, use simpleperf host executable for those platforms instead.
+bin/linux/x86_64/simpleperf report-sample --show-callchain --protobuf -i perf.data -o perf.trace
+
+# Convert perf.data to perf.trace using proguard mapping file.
+bin/linux/x86_64/simpleperf report-sample --show-callchain --protobuf -i perf.data -o perf.trace \
+ --proguard-mapping-file proguard.map
+```
+
+In Android Studio: Open File -> Open -> Select perf.trace
+
+![Example](./pictures/android_studio_profiler_open_perf_trace.png)
+
+
+## Simpleperf HTML Report
+
+Simpleperf can generate its own HTML Profile, which is able to show Android-specific information
+and separate flamegraphs for all threads, with a much rougher flamegraph UI.
+
+![Example](./pictures/report_html.png)
+
+This UI is fairly rough; we recommend using the Continuous PProf UI or Firefox Profiler instead. But
+it's useful for a quick look at your data.
+
+Each of the following commands take as input ./perf.data and output ./report.html.
+
+```
+# Make an HTML report.
+./report_html.py
+
+# Make an HTML report with Proguard mapping.
+./report_html.py --proguard-mapping-file proguard.map
+```
+
+This will print some debug logs about Failed to read symbols: this is usually OK, unless those
+symbols are hotspots.
+
+See also [report_html.py's README](scripts_reference.md#report_htmlpy) and `report_html.py -h`.
+
+
+## PProf Interactive Command Line
+
+Unlike Continuous PProf UI, [PProf](https://github.com/google/pprof) command line is publicly
+available, and allows drilldown, pivoting and filtering.
+
+The below session demonstrates filtering to stack frames containing processBitmap.
+
+```
+$ pprof pprof.profile
+(pprof) show=processBitmap
+(pprof) top
+Active filters:
+ show=processBitmap
+Showing nodes accounting for 2.45s, 11.44% of 21.46s total
+ flat flat% sum% cum cum%
+ 2.45s 11.44% 11.44% 2.45s 11.44% com.example.android.displayingbitmaps.util.ImageFetcher.processBitmap
+```
+
+And then showing the tags of those frames, to tell what threads they are running on:
+
+```
+(pprof) tags
+ pid: Total 2.5s
+ 2.5s ( 100%): 31112
+
+ thread: Total 2.5s
+ 1.4s (57.21%): AsyncTask #3
+ 1.1s (42.79%): AsyncTask #4
+
+ threadpool: Total 2.5s
+ 2.5s ( 100%): AsyncTask #%d
+
+ tid: Total 2.5s
+ 1.4s (57.21%): 31174
+ 1.1s (42.79%): 31175
+```
+
+Contrast with another method:
+
+```
+(pprof) show=addBitmapToCache
+(pprof) top
+Active filters:
+ show=addBitmapToCache
+Showing nodes accounting for 1.05s, 4.88% of 21.46s total
+ flat flat% sum% cum cum%
+ 1.05s 4.88% 4.88% 1.05s 4.88% com.example.android.displayingbitmaps.util.ImageCache.addBitmapToCache
+```
+
+For more information, see the [pprof README](https://github.com/google/pprof/blob/master/doc/README.md#interactive-terminal-use).
+
+
+## Simpleperf Report Command Line
+
+The simpleperf report command reports profiles in text format.
+
+![Example](./pictures/report_command.png)
+
+You can call `simpleperf report` directly or call it via `report.py`.
+
+```
+# Report symbols in table format.
+$ ./report.py --children
+
+# Report call graph.
+$ bin/linux/x86_64/simpleperf report -g -i perf.data
+```
+
+See also [report command's README](executable_commands_reference.md#The-report-command) and
+`report.py -h`.
+
+
+## Custom Report Interface
+
+If the above View UIs can't fulfill your need, you can use `simpleperf_report_lib.py` to parse
+perf.data, extract sample information, and feed it to any views you like.
+
+See [simpleperf_report_lib.py's README](scripts_reference.md#simpleperf_report_libpy) for more
+details.
diff --git a/simpleperf/dso.cpp b/simpleperf/dso.cpp
index bd565b1c..6d559770 100644
--- a/simpleperf/dso.cpp
+++ b/simpleperf/dso.cpp
@@ -326,14 +326,13 @@ BuildId Dso::FindExpectedBuildIdForPath(const std::string& path) {
return BuildId();
}
-BuildId Dso::GetExpectedBuildId() {
+BuildId Dso::GetExpectedBuildId() const {
return FindExpectedBuildIdForPath(path_);
}
-Dso::Dso(DsoType type, const std::string& path, const std::string& debug_file_path)
+Dso::Dso(DsoType type, const std::string& path)
: type_(type),
path_(path),
- debug_file_path_(debug_file_path),
is_loaded_(false),
dump_id_(UINT_MAX),
symbol_dump_id_(0),
@@ -465,8 +464,7 @@ static void SortAndFixSymbols(std::vector<Symbol>& symbols) {
class DexFileDso : public Dso {
public:
- DexFileDso(const std::string& path, const std::string& debug_file_path)
- : Dso(DSO_DEX_FILE, path, debug_file_path) {}
+ DexFileDso(const std::string& path) : Dso(DSO_DEX_FILE, path) {}
void AddDexFileOffset(uint64_t dex_file_offset) override {
auto it = std::lower_bound(dex_file_offsets_.begin(), dex_file_offsets_.end(), dex_file_offset);
@@ -484,11 +482,12 @@ class DexFileDso : public Dso {
std::vector<Symbol> LoadSymbolsImpl() override {
std::vector<Symbol> symbols;
- auto tuple = SplitUrlInApk(debug_file_path_);
+ const std::string& debug_file_path = GetDebugFilePath();
+ auto tuple = SplitUrlInApk(debug_file_path);
// Symbols of dex files are collected on device. If the dex file doesn't exist, probably
// we are reporting on host, and there is no need to report warning of missing dex files.
- if (!IsRegularFile(std::get<0>(tuple) ? std::get<1>(tuple) : debug_file_path_)) {
- LOG(DEBUG) << "skip reading symbols from non-exist dex_file " << debug_file_path_;
+ if (!IsRegularFile(std::get<0>(tuple) ? std::get<1>(tuple) : debug_file_path)) {
+ LOG(DEBUG) << "skip reading symbols from non-exist dex_file " << debug_file_path;
return symbols;
}
bool status = false;
@@ -501,19 +500,19 @@ class DexFileDso : public Dso {
std::vector<uint8_t> data;
if (ahelper && ahelper->FindEntry(std::get<2>(tuple), &entry) &&
ahelper->GetEntryData(entry, &data)) {
- status = ReadSymbolsFromDexFileInMemory(data.data(), data.size(), debug_file_path_,
+ status = ReadSymbolsFromDexFileInMemory(data.data(), data.size(), debug_file_path,
dex_file_offsets_, symbol_callback);
}
} else {
- status = ReadSymbolsFromDexFile(debug_file_path_, dex_file_offsets_, symbol_callback);
+ status = ReadSymbolsFromDexFile(debug_file_path, dex_file_offsets_, symbol_callback);
}
if (!status) {
android::base::LogSeverity level =
symbols_.empty() ? android::base::WARNING : android::base::DEBUG;
- LOG(level) << "Failed to read symbols from dex_file " << debug_file_path_;
+ LOG(level) << "Failed to read symbols from dex_file " << debug_file_path;
return symbols;
}
- LOG(VERBOSE) << "Read symbols from dex_file " << debug_file_path_ << " successfully";
+ LOG(VERBOSE) << "Read symbols from dex_file " << debug_file_path << " successfully";
SortAndFixSymbols(symbols);
return symbols;
}
@@ -524,8 +523,8 @@ class DexFileDso : public Dso {
class ElfDso : public Dso {
public:
- ElfDso(const std::string& path, const std::string& debug_file_path)
- : Dso(DSO_ELF_FILE, path, debug_file_path) {}
+ ElfDso(const std::string& path, bool force_64bit)
+ : Dso(DSO_ELF_FILE, path), force_64bit_(force_64bit) {}
std::string_view GetReportPath() const override {
if (JITDebugReader::IsPathInJITSymFile(path_)) {
@@ -551,11 +550,11 @@ class ElfDso : public Dso {
BuildId build_id = GetExpectedBuildId();
ElfStatus status;
- auto elf = ElfFile::Open(debug_file_path_, &build_id, &status);
+ auto elf = ElfFile::Open(GetDebugFilePath(), &build_id, &status);
if (elf) {
min_vaddr_ = elf->ReadMinExecutableVaddr(&file_offset_of_min_vaddr_);
} else {
- LOG(WARNING) << "failed to read min virtual address of " << debug_file_path_ << ": "
+ LOG(WARNING) << "failed to read min virtual address of " << GetDebugFilePath() << ": "
<< status;
}
}
@@ -587,7 +586,7 @@ class ElfDso : public Dso {
// ELF_FILE Dso objects should actually be DEX_FILE, because they have dex file offsets.
// So here converts ELF_FILE Dso into DEX_FILE Dso.
type_ = DSO_DEX_FILE;
- dex_file_dso_.reset(new DexFileDso(path_, path_));
+ dex_file_dso_.reset(new DexFileDso(path_));
}
dex_file_dso_->AddDexFileOffset(dex_file_offset);
}
@@ -597,6 +596,11 @@ class ElfDso : public Dso {
}
protected:
+ std::string FindDebugFilePath() const override {
+ BuildId build_id = GetExpectedBuildId();
+ return debug_elf_file_finder_.FindDebugFile(path_, force_64bit_, build_id);
+ }
+
std::vector<Symbol> LoadSymbolsImpl() override {
if (dex_file_dso_) {
return dex_file_dso_->LoadSymbolsImpl();
@@ -609,11 +613,11 @@ class ElfDso : public Dso {
}
};
ElfStatus status;
- auto elf = ElfFile::Open(debug_file_path_, &build_id, &status);
+ auto elf = ElfFile::Open(GetDebugFilePath(), &build_id, &status);
if (elf) {
status = elf->ParseSymbols(symbol_callback);
}
- ReportReadElfSymbolResult(status, path_, debug_file_path_,
+ ReportReadElfSymbolResult(status, path_, GetDebugFilePath(),
symbols_.empty() ? android::base::WARNING : android::base::DEBUG);
SortAndFixSymbols(symbols);
return symbols;
@@ -622,6 +626,7 @@ class ElfDso : public Dso {
private:
static constexpr uint64_t uninitialized_value = std::numeric_limits<uint64_t>::max();
+ bool force_64bit_;
uint64_t min_vaddr_ = uninitialized_value;
uint64_t file_offset_of_min_vaddr_ = uninitialized_value;
std::unique_ptr<DexFileDso> dex_file_dso_;
@@ -629,8 +634,8 @@ class ElfDso : public Dso {
class KernelDso : public Dso {
public:
- KernelDso(const std::string& path, const std::string& debug_file_path)
- : Dso(DSO_KERNEL, path, debug_file_path) {
+ KernelDso(const std::string& path) : Dso(DSO_KERNEL, path) {
+ debug_file_path_ = FindDebugFilePath();
if (!vmlinux_.empty()) {
// Use vmlinux as the kernel debug file.
BuildId build_id = GetExpectedBuildId();
@@ -639,7 +644,7 @@ class KernelDso : public Dso {
debug_file_path_ = vmlinux_;
has_debug_file_ = true;
}
- } else if (IsRegularFile(debug_file_path_)) {
+ } else if (IsRegularFile(GetDebugFilePath())) {
has_debug_file_ = true;
}
}
@@ -663,6 +668,11 @@ class KernelDso : public Dso {
}
protected:
+ std::string FindDebugFilePath() const override {
+ BuildId build_id = GetExpectedBuildId();
+ return debug_elf_file_finder_.FindDebugFile(path_, false, build_id);
+ }
+
std::vector<Symbol> LoadSymbolsImpl() override {
std::vector<Symbol> symbols;
if (has_debug_file_) {
@@ -689,7 +699,7 @@ class KernelDso : public Dso {
if (!fix_kernel_address_randomization_) {
LOG(WARNING) << "Don't know how to fix addresses changed by kernel address randomization. So "
"symbols in "
- << debug_file_path_ << " are not used";
+ << GetDebugFilePath() << " are not used";
return;
}
// symbols_ are kernel symbols got from /proc/kallsyms while recording. Those symbols are
@@ -703,10 +713,10 @@ class KernelDso : public Dso {
}
};
ElfStatus status;
- if (auto elf = ElfFile::Open(debug_file_path_, &status); elf) {
+ if (auto elf = ElfFile::Open(GetDebugFilePath(), &status); elf) {
status = elf->ParseSymbols(symbol_callback);
}
- ReportReadElfSymbolResult(status, path_, debug_file_path_);
+ ReportReadElfSymbolResult(status, path_, GetDebugFilePath());
}
void ReadSymbolsFromKallsyms(std::string& kallsyms, std::vector<Symbol>* symbols) {
@@ -771,7 +781,7 @@ class KernelDso : public Dso {
kernel_start_file_offset_ = 0;
if (has_debug_file_) {
ElfStatus status;
- if (auto elf = ElfFile::Open(debug_file_path_, &status); elf) {
+ if (auto elf = ElfFile::Open(GetDebugFilePath(), &status); elf) {
for (const auto& section : elf->GetSectionHeader()) {
if (section.name == ".text") {
kernel_start_addr_ = section.vaddr;
@@ -791,9 +801,9 @@ class KernelDso : public Dso {
class KernelModuleDso : public Dso {
public:
- KernelModuleDso(const std::string& path, const std::string& debug_file_path,
- uint64_t memory_start, uint64_t memory_end, Dso* kernel_dso)
- : Dso(DSO_KERNEL_MODULE, path, debug_file_path),
+ KernelModuleDso(const std::string& path, uint64_t memory_start, uint64_t memory_end,
+ Dso* kernel_dso)
+ : Dso(DSO_KERNEL_MODULE, path),
memory_start_(memory_start),
memory_end_(memory_end),
kernel_dso_(kernel_dso) {}
@@ -819,6 +829,11 @@ class KernelModuleDso : public Dso {
}
protected:
+ std::string FindDebugFilePath() const override {
+ BuildId build_id = GetExpectedBuildId();
+ return debug_elf_file_finder_.FindDebugFile(path_, false, build_id);
+ }
+
std::vector<Symbol> LoadSymbolsImpl() override {
std::vector<Symbol> symbols;
BuildId build_id = GetExpectedBuildId();
@@ -829,11 +844,11 @@ class KernelModuleDso : public Dso {
}
};
ElfStatus status;
- auto elf = ElfFile::Open(debug_file_path_, &build_id, &status);
+ auto elf = ElfFile::Open(GetDebugFilePath(), &build_id, &status);
if (elf) {
status = elf->ParseSymbols(symbol_callback);
}
- ReportReadElfSymbolResult(status, path_, debug_file_path_,
+ ReportReadElfSymbolResult(status, path_, GetDebugFilePath(),
symbols_.empty() ? android::base::WARNING : android::base::DEBUG);
SortAndFixSymbols(symbols);
return symbols;
@@ -897,7 +912,7 @@ class KernelModuleDso : public Dso {
class SymbolMapFileDso : public Dso {
public:
- SymbolMapFileDso(const std::string& path) : Dso(DSO_SYMBOL_MAP_FILE, path, path) {}
+ SymbolMapFileDso(const std::string& path) : Dso(DSO_SYMBOL_MAP_FILE, path) {}
uint64_t IpToVaddrInFile(uint64_t ip, uint64_t, uint64_t) override { return ip; }
@@ -907,7 +922,7 @@ class SymbolMapFileDso : public Dso {
class UnknownDso : public Dso {
public:
- UnknownDso(const std::string& path) : Dso(DSO_UNKNOWN_FILE, path, path) {}
+ UnknownDso(const std::string& path) : Dso(DSO_UNKNOWN_FILE, path) {}
uint64_t IpToVaddrInFile(uint64_t ip, uint64_t, uint64_t) override { return ip; }
@@ -917,15 +932,13 @@ class UnknownDso : public Dso {
std::unique_ptr<Dso> Dso::CreateDso(DsoType dso_type, const std::string& dso_path,
bool force_64bit) {
- BuildId build_id = FindExpectedBuildIdForPath(dso_path);
- std::string debug_path = debug_elf_file_finder_.FindDebugFile(dso_path, force_64bit, build_id);
switch (dso_type) {
case DSO_ELF_FILE:
- return std::unique_ptr<Dso>(new ElfDso(dso_path, debug_path));
+ return std::unique_ptr<Dso>(new ElfDso(dso_path, force_64bit));
case DSO_KERNEL:
- return std::unique_ptr<Dso>(new KernelDso(dso_path, debug_path));
+ return std::unique_ptr<Dso>(new KernelDso(dso_path));
case DSO_DEX_FILE:
- return std::unique_ptr<Dso>(new DexFileDso(dso_path, dso_path));
+ return std::unique_ptr<Dso>(new DexFileDso(dso_path));
case DSO_SYMBOL_MAP_FILE:
return std::unique_ptr<Dso>(new SymbolMapFileDso(dso_path));
case DSO_UNKNOWN_FILE:
@@ -938,26 +951,28 @@ std::unique_ptr<Dso> Dso::CreateDso(DsoType dso_type, const std::string& dso_pat
std::unique_ptr<Dso> Dso::CreateDsoWithBuildId(DsoType dso_type, const std::string& dso_path,
BuildId& build_id) {
- std::string debug_path = debug_elf_file_finder_.FindDebugFile(dso_path, false, build_id);
+ std::unique_ptr<Dso> dso;
switch (dso_type) {
case DSO_ELF_FILE:
- return std::unique_ptr<Dso>(new ElfDso(dso_path, debug_path));
+ dso.reset(new ElfDso(dso_path, false));
+ break;
case DSO_KERNEL:
- return std::unique_ptr<Dso>(new KernelDso(dso_path, debug_path));
+ dso.reset(new KernelDso(dso_path));
+ break;
case DSO_KERNEL_MODULE:
- return std::unique_ptr<Dso>(new KernelModuleDso(dso_path, debug_path, 0, 0, nullptr));
+ dso.reset(new KernelModuleDso(dso_path, 0, 0, nullptr));
+ break;
default:
LOG(FATAL) << "Unexpected dso_type " << static_cast<int>(dso_type);
+ return nullptr;
}
- return nullptr;
+ dso->debug_file_path_ = debug_elf_file_finder_.FindDebugFile(dso_path, false, build_id);
+ return dso;
}
std::unique_ptr<Dso> Dso::CreateKernelModuleDso(const std::string& dso_path, uint64_t memory_start,
uint64_t memory_end, Dso* kernel_dso) {
- BuildId build_id = FindExpectedBuildIdForPath(dso_path);
- std::string debug_path = debug_elf_file_finder_.FindDebugFile(dso_path, false, build_id);
- return std::unique_ptr<Dso>(
- new KernelModuleDso(dso_path, debug_path, memory_start, memory_end, kernel_dso));
+ return std::unique_ptr<Dso>(new KernelModuleDso(dso_path, memory_start, memory_end, kernel_dso));
}
const char* DsoTypeToString(DsoType dso_type) {
diff --git a/simpleperf/dso.h b/simpleperf/dso.h
index 866aadfd..30427766 100644
--- a/simpleperf/dso.h
+++ b/simpleperf/dso.h
@@ -145,7 +145,13 @@ class Dso {
// Return the path recorded in perf.data.
const std::string& Path() const { return path_; }
// Return the path containing symbol table and debug information.
- const std::string& GetDebugFilePath() const { return debug_file_path_; }
+ const std::string& GetDebugFilePath() const {
+ if (!debug_file_path_.has_value()) {
+ debug_file_path_ = FindDebugFilePath();
+ }
+ return debug_file_path_.value();
+ }
+
// Return the path beautified for reporting.
virtual std::string_view GetReportPath() const { return Path(); }
// Return the file name without directory info.
@@ -195,9 +201,10 @@ class Dso {
static uint32_t g_dump_id_;
static simpleperf_dso_impl::DebugElfFileFinder debug_elf_file_finder_;
- Dso(DsoType type, const std::string& path, const std::string& debug_file_path);
- BuildId GetExpectedBuildId();
+ Dso(DsoType type, const std::string& path);
+ BuildId GetExpectedBuildId() const;
+ virtual std::string FindDebugFilePath() const { return path_; }
virtual std::vector<Symbol> LoadSymbolsImpl() = 0;
DsoType type_;
@@ -205,7 +212,7 @@ class Dso {
const std::string path_;
// path of the shared library having symbol table and debug information
// It is the same as path_, or has the same build id as path_.
- std::string debug_file_path_;
+ mutable std::optional<std::string> debug_file_path_;
// File name of the shared library, got by removing directories in path_.
std::string file_name_;
std::vector<Symbol> symbols_;
diff --git a/simpleperf/dso_test.cpp b/simpleperf/dso_test.cpp
index 1338c1c0..53f62a57 100644
--- a/simpleperf/dso_test.cpp
+++ b/simpleperf/dso_test.cpp
@@ -310,3 +310,15 @@ TEST(dso, FunctionName) {
symbol = Symbol("ctep.v", 0x0, 0x1);
ASSERT_EQ(symbol.FunctionName(), "ctep.v");
}
+
+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();
+}
diff --git a/simpleperf/environment.h b/simpleperf/environment.h
index 1dd6952e..34ce7fa5 100644
--- a/simpleperf/environment.h
+++ b/simpleperf/environment.h
@@ -144,7 +144,8 @@ static inline uint64_t GetSystemClock() {
#if defined(__ANDROID__)
bool IsInAppUid();
-#else
+#endif
+#if !defined(__ANDROID__) && !defined(ANDROID_HOST_MUSL)
static inline int gettid() {
return syscall(__NR_gettid);
}
diff --git a/simpleperf/event_selection_set.cpp b/simpleperf/event_selection_set.cpp
index d5bca318..58b4b956 100644
--- a/simpleperf/event_selection_set.cpp
+++ b/simpleperf/event_selection_set.cpp
@@ -840,14 +840,7 @@ bool EventSelectionSet::ReadMmapEventData(bool with_time_limit) {
}
bool EventSelectionSet::FinishReadMmapEventData() {
- // Stop the read thread, so we don't get more records beyond current time.
- if (!SyncKernelBuffer() || !record_read_thread_->StopReadThread()) {
- return false;
- }
- if (!ReadMmapEventData(false)) {
- return false;
- }
- return true;
+ return ReadMmapEventData(false);
}
void EventSelectionSet::CloseEventFiles() {
diff --git a/simpleperf/profcollect.cpp b/simpleperf/profcollect.cpp
index 39c04ab7..724111e8 100644
--- a/simpleperf/profcollect.cpp
+++ b/simpleperf/profcollect.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <wakelock/wakelock.h>
#include <include/simpleperf_profcollect.hpp>
#include "ETMRecorder.h"
@@ -42,6 +43,13 @@ bool HasDeviceSupport() {
}
bool Record(const char* event_name, const char* output, float duration) {
+ // 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.";
+ return false;
+ }
auto recordCmd = CreateCommandInstance("record");
std::vector<std::string> args;
args.push_back("-a");
diff --git a/simpleperf/report_lib_interface.cpp b/simpleperf/report_lib_interface.cpp
index c55209ab..1025c325 100644
--- a/simpleperf/report_lib_interface.cpp
+++ b/simpleperf/report_lib_interface.cpp
@@ -199,7 +199,7 @@ class ReportLib {
}
const char* GetSupportedTraceOffCpuModes();
bool SetTraceOffCpuMode(const char* mode);
- bool SetSampleFilter(const char* filter);
+ bool SetSampleFilter(const char** filters, int filters_len);
Sample* GetNextSample();
Event* GetEventOfCurrentSample() { return &current_event_; }
@@ -296,8 +296,11 @@ bool ReportLib::SetTraceOffCpuMode(const char* mode) {
return true;
}
-bool ReportLib::SetSampleFilter(const char* filter) {
- std::vector<std::string> args = android::base::Split(filter, " ");
+bool ReportLib::SetSampleFilter(const char** filters, int filters_len) {
+ std::vector<std::string> args;
+ for (int i = 0; i < filters_len; i++) {
+ args.emplace_back(filters[i]);
+ }
OptionFormatMap option_formats = GetRecordFilterOptionFormats(false);
OptionValueMap options;
std::vector<std::pair<OptionName, OptionValue>> ordered_options;
@@ -595,7 +598,7 @@ void MergeJavaMethods(ReportLib* report_lib, bool merge) EXPORT;
bool AddProguardMappingFile(ReportLib* report_lib, const char* mapping_file) EXPORT;
const char* GetSupportedTraceOffCpuModes(ReportLib* report_lib) EXPORT;
bool SetTraceOffCpuMode(ReportLib* report_lib, const char* mode) EXPORT;
-bool SetSampleFilter(ReportLib* report_lib, const char* filter) EXPORT;
+bool SetSampleFilter(ReportLib* report_lib, const char** filters, int filters_len) EXPORT;
Sample* GetNextSample(ReportLib* report_lib) EXPORT;
Event* GetEventOfCurrentSample(ReportLib* report_lib) EXPORT;
@@ -656,8 +659,8 @@ bool SetTraceOffCpuMode(ReportLib* report_lib, const char* mode) {
return report_lib->SetTraceOffCpuMode(mode);
}
-bool SetSampleFilter(ReportLib* report_lib, const char* filter) {
- return report_lib->SetSampleFilter(filter);
+bool SetSampleFilter(ReportLib* report_lib, const char** filters, int filters_len) {
+ return report_lib->SetSampleFilter(filters, filters_len);
}
Sample* GetNextSample(ReportLib* report_lib) {
diff --git a/simpleperf/scripts/annotate.py b/simpleperf/scripts/annotate.py
index 16af623e..7bf8fe92 100755
--- a/simpleperf/scripts/annotate.py
+++ b/simpleperf/scripts/annotate.py
@@ -146,7 +146,7 @@ class SourceFileAnnotator(object):
def __init__(self, config):
# check config variables
- config_names = ['perf_data_list', 'source_dirs', 'comm_filters', 'dso_filters', 'ndk_path']
+ config_names = ['perf_data_list', 'source_dirs', 'dso_filters', 'ndk_path']
for name in config_names:
if name not in config:
log_exit('config [%s] is missing' % name)
@@ -161,7 +161,6 @@ class SourceFileAnnotator(object):
self.config = config
self.symfs_dir = symfs_dir
self.kallsyms = kallsyms
- self.comm_filter = set(config['comm_filters']) if config.get('comm_filters') else None
self.dso_filter = set(config['dso_filters']) if config.get('dso_filters') else None
config['annotate_dest_dir'] = 'annotated_files'
@@ -193,15 +192,12 @@ class SourceFileAnnotator(object):
lib.SetSymfs(self.symfs_dir)
if self.kallsyms:
lib.SetKallsymsFile(self.kallsyms)
- if self.config.get('sample_filter'):
- lib.SetSampleFilter(self.config.get('sample_filter'))
+ lib.SetReportOptions(self.config['report_lib_options'])
while True:
sample = lib.GetNextSample()
if sample is None:
lib.Close()
break
- if not self._filter_sample(sample):
- continue
symbols = []
symbols.append(lib.GetSymbolOfCurrentSample())
callchain = lib.GetCallChainOfCurrentSample()
@@ -215,13 +211,6 @@ class SourceFileAnnotator(object):
self.addr2line.add_addr(symbol.dso_name, build_id, symbol.symbol_addr,
symbol.symbol_addr)
- def _filter_sample(self, sample):
- """Return true if the sample can be used."""
- if self.comm_filter:
- if sample.thread_comm not in self.comm_filter:
- return False
- return True
-
def _filter_symbol(self, symbol):
if not self.dso_filter or symbol.dso_name in self.dso_filter:
return True
@@ -241,15 +230,12 @@ class SourceFileAnnotator(object):
lib.SetSymfs(self.symfs_dir)
if self.kallsyms:
lib.SetKallsymsFile(self.kallsyms)
- if self.config.get('sample_filter'):
- lib.SetSampleFilter(self.config.get('sample_filter'))
+ lib.SetReportOptions(self.config['report_lib_options'])
while True:
sample = lib.GetNextSample()
if sample is None:
lib.Close()
break
- if not self._filter_sample(sample):
- continue
self._generate_periods_for_sample(lib, sample)
def _generate_periods_for_sample(self, lib, sample):
@@ -477,11 +463,9 @@ def main():
help='show raw period instead of percentage')
parser.add_argument('--summary-width', type=int, default=80, help='max width of summary file')
sample_filter_group = parser.add_argument_group('Sample filter options')
- parser.add_sample_filter_options(sample_filter_group)
- sample_filter_group.add_argument('--comm', nargs='+', action='append', help="""
- Use samples only in threads with selected names.""")
sample_filter_group.add_argument('--dso', nargs='+', action='append', help="""
Use samples only in selected binaries.""")
+ parser.add_report_lib_options(sample_filter_group=sample_filter_group)
args = parser.parse_args()
config = {}
@@ -489,12 +473,11 @@ def main():
if not config['perf_data_list']:
config['perf_data_list'].append('perf.data')
config['source_dirs'] = flatten_arg_list(args.source_dirs)
- config['comm_filters'] = flatten_arg_list(args.comm)
config['dso_filters'] = flatten_arg_list(args.dso)
config['ndk_path'] = args.ndk_path
config['raw_period'] = args.raw_period
config['summary_width'] = args.summary_width
- config['sample_filter'] = args.sample_filter
+ config['report_lib_options'] = args.report_lib_options
annotator = SourceFileAnnotator(config)
annotator.annotate()
diff --git a/simpleperf/scripts/bin/android/arm/simpleperf b/simpleperf/scripts/bin/android/arm/simpleperf
index 5ccbd4f1..db2fde7f 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 862e4d93..785b6e33 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 a78e0514..97a168d5 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 8872cf4a..c250c450 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 32631f67..191e51f8 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 c4daaf2b..1a6a8875 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 6f148a8e..ca444ec9 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 f2968884..dca7b30d 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 074bff34..b49e8f8f 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 fc98293a..9838e771 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/gecko_profile_generator.py b/simpleperf/scripts/gecko_profile_generator.py
index e7108ee6..396c9a58 100755
--- a/simpleperf/scripts/gecko_profile_generator.py
+++ b/simpleperf/scripts/gecko_profile_generator.py
@@ -30,7 +30,7 @@ import sys
from dataclasses import dataclass, field
from simpleperf_report_lib import ReportLib
-from simpleperf_utils import BaseArgumentParser, flatten_arg_list
+from simpleperf_utils import BaseArgumentParser, flatten_arg_list, ReportLibOptions
from typing import List, Dict, Optional, NamedTuple, Set, Tuple
@@ -295,22 +295,17 @@ def _gecko_profile(
record_file: str,
symfs_dir: Optional[str],
kallsyms_file: Optional[str],
- proguard_mapping_file: List[str],
- comm_filter: Set[str],
- sample_filter: Optional[str]) -> GeckoProfile:
+ report_lib_options: ReportLibOptions) -> GeckoProfile:
"""convert a simpleperf profile to gecko format"""
lib = ReportLib()
lib.ShowIpForUnknownSymbol()
- for file_path in proguard_mapping_file:
- lib.AddProguardMappingFile(file_path)
if symfs_dir is not None:
lib.SetSymfs(symfs_dir)
lib.SetRecordFile(record_file)
if kallsyms_file is not None:
lib.SetKallsymsFile(kallsyms_file)
- if sample_filter:
- lib.SetSampleFilter(sample_filter)
+ lib.SetReportOptions(report_lib_options)
arch = lib.GetArch()
meta_info = lib.MetaInfo()
@@ -324,9 +319,6 @@ def _gecko_profile(
if sample is None:
lib.Close()
break
- if comm_filter:
- if sample.thread_comm not in comm_filter:
- continue
event = lib.GetEventOfCurrentSample()
symbol = lib.GetSymbolOfCurrentSample()
callchain = lib.GetCallChainOfCurrentSample()
@@ -405,22 +397,13 @@ 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(
- '--proguard-mapping-file', nargs='+',
- help='Add proguard mapping file to de-obfuscate symbols',
- default=[])
- sample_filter_group = parser.add_argument_group('Sample filter options')
- parser.add_sample_filter_options(sample_filter_group)
- sample_filter_group.add_argument('--comm', nargs='+', action='append', help="""
- Use samples only in threads with selected names.""")
+ 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,
- proguard_mapping_file=args.proguard_mapping_file,
- comm_filter=set(flatten_arg_list(args.comm)),
- sample_filter=args.sample_filter)
+ report_lib_options=args.report_lib_options)
json.dump(profile, sys.stdout, sort_keys=True)
diff --git a/simpleperf/scripts/inferno/inferno.py b/simpleperf/scripts/inferno/inferno.py
index 512b1dc9..ccf1b6b6 100755
--- a/simpleperf/scripts/inferno/inferno.py
+++ b/simpleperf/scripts/inferno/inferno.py
@@ -111,14 +111,7 @@ def parse_samples(process, args, sample_filter_fn):
lib.SetRecordFile(record_file)
if kallsyms_file:
lib.SetKallsymsFile(kallsyms_file)
- if args.show_art_frames:
- lib.ShowArtFrames(True)
- for file_path in args.proguard_mapping_file or []:
- lib.AddProguardMappingFile(file_path)
- if args.trace_offcpu:
- lib.SetTraceOffCpuMode(args.trace_offcpu)
- if args.sample_filter:
- lib.SetSampleFilter(args.sample_filter)
+ lib.SetReportOptions(args.report_lib_options)
process.cmd = lib.GetRecordCmd()
product_props = lib.MetaInfo().get("product_props")
if product_props:
@@ -313,12 +306,8 @@ def main():
report_group.add_argument('--symfs', help="""Set the path to find binaries with symbols and
debug info.""")
report_group.add_argument('--title', help='Show a title in the report.')
- report_group.add_argument('--show_art_frames', action='store_true',
- help='Show frames of internal methods in the ART Java interpreter.')
- report_group.add_argument('--proguard-mapping-file', nargs='+',
- help='Add proguard mapping file to de-obfuscate symbols')
- parser.add_trace_offcpu_option(report_group)
- parser.add_sample_filter_options(report_group, False)
+ parser.add_report_lib_options(
+ report_group, sample_filter_group=report_group, sample_filter_with_pid_shortcut=False)
debug_group = parser.add_argument_group('Debug options')
debug_group.add_argument('--disable_adb_root', action='store_true', help="""Force adb to run
diff --git a/simpleperf/scripts/pprof_proto_generator.py b/simpleperf/scripts/pprof_proto_generator.py
index 2e807d95..57c988b9 100755
--- a/simpleperf/scripts/pprof_proto_generator.py
+++ b/simpleperf/scripts/pprof_proto_generator.py
@@ -270,7 +270,6 @@ class PprofProfileGenerator(object):
config['binary_cache_dir'] = 'binary_cache'
if not os.path.isdir(config['binary_cache_dir']):
config['binary_cache_dir'] = None
- self.comm_filter = set(config['comm_filters']) if config.get('comm_filters') else None
self.dso_filter = set(config['dso_filters']) if config.get('dso_filters') else None
self.max_chain_length = config['max_chain_length']
self.profile = profile_pb2.Profile()
@@ -303,10 +302,7 @@ class PprofProfileGenerator(object):
if self.config.get('show_art_frames'):
self.lib.ShowArtFrames()
- for file_path in self.config['proguard_mapping_file'] or []:
- self.lib.AddProguardMappingFile(file_path)
- if self.config.get('sample_filter'):
- self.lib.SetSampleFilter(self.config['sample_filter'])
+ self.lib.SetReportOptions(self.config['report_lib_options'])
comments = [
"Simpleperf Record Command:\n" + self.lib.GetRecordCmd(),
@@ -329,9 +325,6 @@ class PprofProfileGenerator(object):
symbol = self.lib.GetSymbolOfCurrentSample()
callchain = self.lib.GetCallChainOfCurrentSample()
- if not self._filter_report_sample(report_sample):
- continue
-
sample_type_id = self.get_sample_type_id(event.name)
sample = Sample()
sample.add_value(sample_type_id, 1)
@@ -379,13 +372,6 @@ class PprofProfileGenerator(object):
return self.profile
- def _filter_report_sample(self, sample):
- """Return true if the sample can be used."""
- if self.comm_filter:
- if sample.thread_comm not in self.comm_filter:
- return False
- return True
-
def _filter_symbol(self, symbol):
if not self.dso_filter or symbol.dso_name in self.dso_filter:
return True
@@ -633,20 +619,13 @@ def main():
parser.add_argument('--max_chain_length', type=int, default=1000000000, help="""
Maximum depth of samples to be converted.""") # Large value as infinity standin.
parser.add_argument('--ndk_path', type=extant_dir, help='Set the path of a ndk release.')
- parser.add_argument('--show_art_frames', action='store_true',
- help='Show frames of internal methods in the ART Java interpreter.')
- parser.add_argument(
- '--proguard-mapping-file', nargs='+',
- help='Add proguard mapping file to de-obfuscate symbols')
parser.add_argument(
'-j', '--jobs', type=int, default=os.cpu_count(),
help='Use multithreading to speed up source code annotation.')
sample_filter_group = parser.add_argument_group('Sample filter options')
- parser.add_sample_filter_options(sample_filter_group)
- sample_filter_group.add_argument('--comm', nargs='+', action='append', help="""
- Use samples only in threads with selected names.""")
sample_filter_group.add_argument('--dso', nargs='+', action='append', help="""
Use samples only in selected binaries.""")
+ parser.add_report_lib_options(sample_filter_group=sample_filter_group)
args = parser.parse_args()
if args.show:
@@ -658,18 +637,16 @@ def main():
config = {}
config['output_file'] = args.output_file
- config['comm_filters'] = flatten_arg_list(args.comm)
config['dso_filters'] = flatten_arg_list(args.dso)
config['ndk_path'] = args.ndk_path
- config['show_art_frames'] = args.show_art_frames
config['max_chain_length'] = args.max_chain_length
- config['proguard_mapping_file'] = args.proguard_mapping_file
- config['sample_filter'] = args.sample_filter
+ config['report_lib_options'] = args.report_lib_options
generator = PprofProfileGenerator(config)
for record_file in args.record_file:
generator.load_record_file(record_file)
profile = generator.gen(args.jobs)
store_pprof_profile(config['output_file'], profile)
+ logging.info("Report is generated at '%s' successfully." % config['output_file'])
if __name__ == '__main__':
diff --git a/simpleperf/scripts/purgatorio/purgatorio.py b/simpleperf/scripts/purgatorio/purgatorio.py
index 0f072547..4e2560a2 100755
--- a/simpleperf/scripts/purgatorio/purgatorio.py
+++ b/simpleperf/scripts/purgatorio/purgatorio.py
@@ -43,6 +43,7 @@ from functools import cmp_to_key
simpleperf_path = Path(__file__).absolute().parents[1]
sys.path.insert(0, str(simpleperf_path))
import simpleperf_report_lib as sp
+from simpleperf_utils import BaseArgumentParser
# fmt: on
@@ -169,11 +170,7 @@ def generate_datasource(args):
if args.ksyms:
lib.SetKallsymsFile(args.ksyms)
- if not args.not_art:
- lib.ShowArtFrames(True)
-
- for file_path in args.proguard_mapping_file or []:
- lib.AddProguardMappingFile(file_path)
+ lib.SetReportOptions(args.report_lib_options)
product = lib.MetaInfo().get('product_props')
@@ -268,21 +265,19 @@ def generate_datasource(args):
def main():
- parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+ parser = BaseArgumentParser()
parser.add_argument('-i', '--input_file', type=str, required=True, help='input file')
parser.add_argument('--title', '-t', type=str, help='document title')
parser.add_argument('--ksyms', '-k', type=str, help='path to kernel symbols (kallsyms)')
parser.add_argument('--usyms', '-u', type=str, help='path to tree with user space symbols')
- parser.add_argument('--not_art', '-a', action='store_true', help='Don\'t show ART symbols')
parser.add_argument('--output', '-o', type=str, help='output file')
parser.add_argument('--dont_open', '-d', action='store_true', help='Don\'t open output file')
parser.add_argument('--include_dso_names', '-n', action='store_true',
help='Include dso names in backtraces')
parser.add_argument('--include_symbols_addr', '-s', action='store_true',
help='Include addresses of symbols in backtraces')
- parser.add_argument(
- '--proguard-mapping-file', nargs='+',
- help='Add proguard mapping file to de-obfuscate symbols')
+ parser.add_report_lib_options(default_show_art_frames=True)
+
args = parser.parse_args()
# TODO test hierarchical ranges too
diff --git a/simpleperf/scripts/report_html.py b/simpleperf/scripts/report_html.py
index 9de2f98b..56f8dae5 100755
--- a/simpleperf/scripts/report_html.py
+++ b/simpleperf/scripts/report_html.py
@@ -31,7 +31,7 @@ from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Tuple, Un
from simpleperf_report_lib import ReportLib, SymbolStruct
from simpleperf_utils import (
Addr2Nearestline, BaseArgumentParser, BinaryFinder, get_script_dir, log_exit, Objdump,
- open_report_in_browser, ReadElf, SourceFileSearcher)
+ open_report_in_browser, ReadElf, ReportLibOptions, SourceFileSearcher)
MAX_CALLSTACK_LENGTH = 750
@@ -605,13 +605,10 @@ class RecordData(object):
def __init__(
self, binary_cache_path: Optional[str],
ndk_path: Optional[str],
- build_addr_hit_map: bool, proguard_mapping_files: Optional[List[str]],
- trace_offcpu: Optional[str]):
+ build_addr_hit_map: bool):
self.binary_cache_path = binary_cache_path
self.ndk_path = ndk_path
self.build_addr_hit_map = build_addr_hit_map
- self.proguard_mapping_files = proguard_mapping_files
- self.trace_offcpu = trace_offcpu
self.meta_info: Optional[Dict[str, str]] = None
self.cmdline: Optional[str] = None
self.arch: Optional[str] = None
@@ -623,23 +620,15 @@ class RecordData(object):
self.gen_addr_hit_map_in_record_info = False
self.binary_finder = BinaryFinder(binary_cache_path, ReadElf(ndk_path))
- def load_record_file(
- self, record_file: str, show_art_frames: bool, sample_filter: Optional[str]):
+ def load_record_file(self, record_file: str, report_lib_options: ReportLibOptions):
lib = ReportLib()
lib.SetRecordFile(record_file)
# If not showing ip for unknown symbols, the percent of the unknown symbol may be
# accumulated to very big, and ranks first in the sample table.
lib.ShowIpForUnknownSymbol()
- if show_art_frames:
- lib.ShowArtFrames()
if self.binary_cache_path:
lib.SetSymfs(self.binary_cache_path)
- for file_path in self.proguard_mapping_files or []:
- lib.AddProguardMappingFile(file_path)
- if self.trace_offcpu:
- lib.SetTraceOffCpuMode(self.trace_offcpu)
- if sample_filter:
- lib.SetSampleFilter(sample_filter)
+ lib.SetReportOptions(report_lib_options)
self.meta_info = lib.MetaInfo()
self.cmdline = lib.GetRecordCmd()
self.arch = lib.GetArch()
@@ -987,16 +976,10 @@ def get_args() -> argparse.Namespace:
help='Use multithreading to speed up disassembly and source code annotation.')
parser.add_argument('--ndk_path', nargs=1, help='Find tools in the ndk path.')
parser.add_argument('--no_browser', action='store_true', help="Don't open report in browser.")
- parser.add_argument('--show_art_frames', action='store_true',
- help='Show frames of internal methods in the ART Java interpreter.')
parser.add_argument('--aggregate-by-thread-name', action='store_true', help="""aggregate
samples by thread name instead of thread id. This is useful for
showing multiple perf.data generated for the same app.""")
- parser.add_argument(
- '--proguard-mapping-file', nargs='+',
- help='Add proguard mapping file to de-obfuscate symbols')
- parser.add_trace_offcpu_option()
- parser.add_sample_filter_options()
+ parser.add_report_lib_options()
return parser.parse_args()
@@ -1022,10 +1005,9 @@ def main():
log_exit('Invalid --jobs option.')
# 2. Produce record data.
- record_data = RecordData(binary_cache_path, ndk_path, build_addr_hit_map,
- args.proguard_mapping_file, args.trace_offcpu)
+ record_data = RecordData(binary_cache_path, ndk_path, build_addr_hit_map)
for record_file in args.record_file:
- record_data.load_record_file(record_file, args.show_art_frames, args.sample_filter)
+ record_data.load_record_file(record_file, args.report_lib_options)
if args.aggregate_by_thread_name:
record_data.aggregate_by_thread_name()
record_data.limit_percents(args.min_func_percent, args.min_callchain_percent)
diff --git a/simpleperf/scripts/report_sample.py b/simpleperf/scripts/report_sample.py
index 7388442a..dc5c4e2b 100755
--- a/simpleperf/scripts/report_sample.py
+++ b/simpleperf/scripts/report_sample.py
@@ -19,7 +19,7 @@
"""
from simpleperf_report_lib import ReportLib
-from simpleperf_utils import BaseArgumentParser, flatten_arg_list
+from simpleperf_utils import BaseArgumentParser, flatten_arg_list, ReportLibOptions
from typing import List, Set, Optional
@@ -28,27 +28,19 @@ def report_sample(
symfs_dir: str,
kallsyms_file: str,
show_tracing_data: bool,
- proguard_mapping_file: List[str],
header: bool,
- comm_filter: Set[str],
- trace_offcpu: Optional[str],
- sample_filter: Optional[str]):
+ report_lib_options: ReportLibOptions):
""" read record_file, and print each sample"""
lib = ReportLib()
lib.ShowIpForUnknownSymbol()
- for file_path in proguard_mapping_file:
- lib.AddProguardMappingFile(file_path)
if symfs_dir is not None:
lib.SetSymfs(symfs_dir)
if record_file is not None:
lib.SetRecordFile(record_file)
if kallsyms_file is not None:
lib.SetKallsymsFile(kallsyms_file)
- if trace_offcpu:
- lib.SetTraceOffCpuMode(trace_offcpu)
- if sample_filter:
- lib.SetSampleFilter(sample_filter)
+ lib.SetReportOptions(report_lib_options)
if header:
print("# ========")
@@ -64,9 +56,6 @@ def report_sample(
if sample is None:
lib.Close()
break
- if comm_filter:
- if sample.thread_comm not in comm_filter:
- continue
event = lib.GetEventOfCurrentSample()
symbol = lib.GetSymbolOfCurrentSample()
callchain = lib.GetCallChainOfCurrentSample()
@@ -97,27 +86,17 @@ def main():
parser.add_argument('-i', '--record_file', nargs='?', default='perf.data',
help='Default is perf.data.')
parser.add_argument('--show_tracing_data', action='store_true', help='print tracing data.')
- parser.add_argument(
- '--proguard-mapping-file', nargs='+',
- help='Add proguard mapping file to de-obfuscate symbols',
- default=[])
parser.add_argument('--header', action='store_true',
help='Show metadata header, like perf script --header')
- parser.add_argument('--comm', nargs='+', action='append', help="""
- Use samples only in threads with selected names.""")
- parser.add_trace_offcpu_option()
- parser.add_sample_filter_options()
+ parser.add_report_lib_options()
args = parser.parse_args()
report_sample(
record_file=args.record_file,
symfs_dir=args.symfs,
kallsyms_file=args.kallsyms,
show_tracing_data=args.show_tracing_data,
- proguard_mapping_file=args.proguard_mapping_file,
header=args.header,
- comm_filter=set(flatten_arg_list(args.comm)),
- trace_offcpu=args.trace_offcpu,
- sample_filter=args.sample_filter)
+ report_lib_options=args.report_lib_options)
if __name__ == '__main__':
diff --git a/simpleperf/scripts/run_simpleperf_without_usb_connection.py b/simpleperf/scripts/run_simpleperf_without_usb_connection.py
index 8beddcd9..405deb4f 100755
--- a/simpleperf/scripts/run_simpleperf_without_usb_connection.py
+++ b/simpleperf/scripts/run_simpleperf_without_usb_connection.py
@@ -45,6 +45,8 @@ def start_recording(args):
shell_cmd = 'cd /data/local/tmp && nohup ./simpleperf record ' + args.record_options
if args.app:
shell_cmd += ' --app ' + args.app
+ if args.pid:
+ shell_cmd += ' -p ' + args.pid
if args.size_limit:
shell_cmd += ' --size-limit ' + args.size_limit
shell_cmd += ' >/data/local/tmp/simpleperf_output 2>&1'
@@ -85,6 +87,8 @@ def main():
Default is `-e task-clock:u -g`.""")
start_parser.add_argument('-p', '--app', help="""Profile an Android app, given the package
name. Like `-p com.example.android.myapp`.""")
+ start_parser.add_argument('--pid', help="""Profile an Android app, given the process id.
+ Like `--pid 918`.""")
start_parser.add_argument('--size_limit', type=str,
help="""Stop profiling when recording data reaches
[size_limit][K|M|G] bytes. Like `--size_limit 1M`.""")
diff --git a/simpleperf/scripts/simpleperf_report_lib.py b/simpleperf/scripts/simpleperf_report_lib.py
index 5c2893c1..48b31199 100644
--- a/simpleperf/scripts/simpleperf_report_lib.py
+++ b/simpleperf/scripts/simpleperf_report_lib.py
@@ -26,7 +26,8 @@ from pathlib import Path
import struct
from typing import Any, Dict, List, Optional, Union
-from simpleperf_utils import bytes_to_str, get_host_binary_path, is_windows, str_to_bytes
+from simpleperf_utils import (bytes_to_str, get_host_binary_path, is_windows, str_to_bytes,
+ ReportLibOptions)
def _is_null(p: Optional[ct._Pointer]) -> bool:
@@ -297,6 +298,18 @@ class ReportLib(object):
self._DestroyReportLibFunc(self._instance)
self._instance = None
+ def SetReportOptions(self, options: ReportLibOptions):
+ """ Set report options in one call. """
+ if options.proguard_mapping_files:
+ for file_path in options.proguard_mapping_files:
+ self.AddProguardMappingFile(file_path)
+ if options.show_art_frames:
+ self.ShowArtFrames(True)
+ if options.trace_offcpu:
+ self.SetTraceOffCpuMode(options.trace_offcpu)
+ if options.sample_filters:
+ self.SetSampleFilter(options.sample_filters)
+
def SetLogSeverity(self, log_level: str = 'info'):
""" Set log severity of native lib, can be verbose,debug,info,error,fatal."""
cond: bool = self._SetLogSeverityFunc(self.getInstance(), _char_pt(log_level))
@@ -362,7 +375,7 @@ class ReportLib(object):
res: bool = self._SetTraceOffCpuModeFunc(self.getInstance(), _char_pt(mode))
_check(res, f'Failed to call SetTraceOffCpuMode({mode})')
- def SetSampleFilter(self, filter: str):
+ def SetSampleFilter(self, filters: List[str]):
""" Set options used to filter samples. Available options are:
--exclude-pid pid1,pid2,... Exclude samples for selected processes.
--exclude-tid tid1,tid2,... Exclude samples for selected threads.
@@ -381,8 +394,10 @@ class ReportLib(object):
The filter argument should be a concatenation of options.
"""
- res: bool = self._SetSampleFilterFunc(self.getInstance(), _char_pt(filter))
- _check(res, f'Failed to call SetSampleFilter({filter})')
+ filter_array = (ct.c_char_p * len(filters))()
+ filter_array[:] = [_char_pt(f) for f in filters]
+ res: bool = self._SetSampleFilterFunc(self.getInstance(), filter_array, len(filters))
+ _check(res, f'Failed to call SetSampleFilter({filters})')
def GetNextSample(self) -> Optional[SampleStruct]:
""" Return the next sample. If no more samples, return None. """
diff --git a/simpleperf/scripts/simpleperf_utils.py b/simpleperf/scripts/simpleperf_utils.py
index 755919c0..2a7dfd35 100644
--- a/simpleperf/scripts/simpleperf_utils.py
+++ b/simpleperf/scripts/simpleperf_utils.py
@@ -21,6 +21,7 @@
from __future__ import annotations
import argparse
from concurrent.futures import Future, ThreadPoolExecutor
+from dataclasses import dataclass
import logging
import os
import os.path
@@ -999,14 +1000,33 @@ class ArgParseFormatter(
pass
+@dataclass
+class ReportLibOptions:
+ show_art_frames: bool
+ trace_offcpu: str
+ proguard_mapping_files: List[str]
+ sample_filters: List[str]
+
+
class BaseArgumentParser(argparse.ArgumentParser):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs, formatter_class=ArgParseFormatter)
self.has_sample_filter_options = False
self.sample_filter_with_pid_shortcut = False
-
- def add_trace_offcpu_option(self, subparser: Optional[Any] = None):
- parser = subparser if subparser else self
+ self.has_report_lib_options = False
+
+ def add_report_lib_options(self, group: Optional[Any] = None,
+ default_show_art_frames: bool = False,
+ sample_filter_group: Optional[Any] = None,
+ sample_filter_with_pid_shortcut: bool = True):
+ self.has_report_lib_options = True
+ parser = group if group else self
+ parser.add_argument(
+ '--proguard-mapping-file', nargs='+',
+ help='Add proguard mapping file to de-obfuscate symbols')
+ parser.add_argument('--show-art-frames', '--show_art_frames',
+ action=argparse.BooleanOptionalAction, default=default_show_art_frames,
+ help='Show frames of internal methods in the ART Java interpreter.')
parser.add_argument(
'--trace-offcpu', choices=['on-cpu', 'off-cpu', 'on-off-cpu', 'mixed-on-off-cpu'],
help="""Set report mode for profiles recorded with --trace-offcpu option. All possible
@@ -1015,8 +1035,9 @@ class BaseArgumentParser(argparse.ArgumentParser):
mixed-on-off-cpu (on-cpu and off-cpu samples using the same event name).
If not set, mixed-on-off-cpu mode is used.
""")
+ self._add_sample_filter_options(sample_filter_group, sample_filter_with_pid_shortcut)
- def add_sample_filter_options(
+ def _add_sample_filter_options(
self, group: Optional[Any] = None, with_pid_shortcut: bool = True):
if not group:
group = self.add_argument_group('Sample filter options')
@@ -1044,7 +1065,8 @@ class BaseArgumentParser(argparse.ArgumentParser):
'--include-process-name', metavar='process_name_regex', nargs='+',
help='only include samples for processes with name containing the regular expression')
group.add_argument(
- '--include-thread-name', metavar='thread_name_regex', nargs='+',
+ '--comm', '--include-thread-name', metavar='thread_name_regex',
+ dest='include_thread_name', nargs='+',
help='only include samples for threads with name containing the regular expression')
group.add_argument(
'--filter-file', metavar='file',
@@ -1053,40 +1075,38 @@ class BaseArgumentParser(argparse.ArgumentParser):
self.has_sample_filter_options = True
self.sample_filter_with_pid_shortcut = with_pid_shortcut
- def _build_sample_filter(self, args: argparse.Namespace) -> Optional[str]:
- """ Convert sample filter options into a sample filter string, which can be passed to
- ReportLib.SetSampleFilter().
- """
+ def _build_sample_filter(self, args: argparse.Namespace) -> List[str]:
+ """ Build sample filters, which can be passed to ReportLib.SetSampleFilter(). """
filters = []
if args.exclude_pid:
- filters.append('--exclude-pid ' + ','.join(str(pid) for pid in args.exclude_pid))
+ filters.extend(['--exclude-pid', ','.join(str(pid) for pid in args.exclude_pid)])
if args.exclude_tid:
- filters.append('--exclude-tid ' + ','.join(str(tid) for tid in args.exclude_tid))
+ filters.extend(['--exclude-tid', ','.join(str(tid) for tid in args.exclude_tid)])
if args.exclude_process_name:
for name in args.exclude_process_name:
- filters.append('--exclude-process-name ' + name)
+ filters.extend(['--exclude-process-name', name])
if args.exclude_thread_name:
for name in args.exclude_thread_name:
- filters.append('--exclude-thread-name ' + name)
+ filters.extend(['--exclude-thread-name', name])
if args.include_pid:
- filters.append('--include-pid ' + ','.join(str(pid) for pid in args.include_pid))
+ filters.extend(['--include-pid', ','.join(str(pid) for pid in args.include_pid)])
if args.include_tid:
- filters.append('--include-tid ' + ','.join(str(tid) for tid in args.include_tid))
+ filters.extend(['--include-tid', ','.join(str(tid) for tid in args.include_tid)])
if self.sample_filter_with_pid_shortcut:
if args.pid:
- filters.append('--include-pid ' + ','.join(str(pid) for pid in args.pid))
+ filters.extend(['--include-pid', ','.join(str(pid) for pid in args.pid)])
if args.tid:
- filters.append('--include-tid ' + ','.join(str(pid) for pid in args.tid))
+ filters.extend(['--include-tid', ','.join(str(pid) for pid in args.tid)])
if args.include_process_name:
for name in args.include_process_name:
- filters.append('--include-process-name ' + name)
+ filters.extend(['--include-process-name', name])
if args.include_thread_name:
for name in args.include_thread_name:
- filters.append('--include-thread-name ' + name)
+ filters.extend(['--include-thread-name', name])
if args.filter_file:
- filters.append('--filter-file ' + args.filter_file)
- return ' '.join(filters)
+ filters.extend(['--filter-file', args.filter_file])
+ return filters
def parse_known_args(self, *args, **kwargs):
self.add_argument(
@@ -1094,8 +1114,12 @@ class BaseArgumentParser(argparse.ArgumentParser):
default='info', help='set log level')
namespace, left_args = super().parse_known_args(*args, **kwargs)
- if self.has_sample_filter_options:
- setattr(namespace, 'sample_filter', self._build_sample_filter(namespace))
+ if self.has_report_lib_options:
+ sample_filters = self._build_sample_filter(namespace)
+ report_lib_options = ReportLibOptions(
+ namespace.show_art_frames, namespace.trace_offcpu, namespace.proguard_mapping_file,
+ sample_filters)
+ setattr(namespace, 'report_lib_options', report_lib_options)
if not Log.initialized:
Log.init(namespace.log)
diff --git a/simpleperf/scripts/stackcollapse.py b/simpleperf/scripts/stackcollapse.py
index 2574843a..e0e1d86f 100755
--- a/simpleperf/scripts/stackcollapse.py
+++ b/simpleperf/scripts/stackcollapse.py
@@ -26,7 +26,7 @@
from collections import defaultdict
from simpleperf_report_lib import ReportLib
-from simpleperf_utils import BaseArgumentParser, flatten_arg_list
+from simpleperf_utils import BaseArgumentParser, flatten_arg_list, ReportLibOptions
from typing import DefaultDict, List, Optional, Set
import logging
@@ -37,30 +37,25 @@ def collapse_stacks(
record_file: str,
symfs_dir: str,
kallsyms_file: str,
- proguard_mapping_file: List[str],
event_filter: str,
include_pid: bool,
include_tid: bool,
annotate_kernel: bool,
annotate_jit: bool,
include_addrs: bool,
- comm_filter: Set[str],
- sample_filter: Optional[str]):
+ report_lib_options: ReportLibOptions):
"""read record_file, aggregate per-stack and print totals per-stack"""
lib = ReportLib()
if include_addrs:
lib.ShowIpForUnknownSymbol()
- for file_path in proguard_mapping_file:
- lib.AddProguardMappingFile(file_path)
if symfs_dir is not None:
lib.SetSymfs(symfs_dir)
if record_file is not None:
lib.SetRecordFile(record_file)
if kallsyms_file is not None:
lib.SetKallsymsFile(kallsyms_file)
- if sample_filter:
- lib.SetSampleFilter(sample_filter)
+ lib.SetReportOptions(report_lib_options)
stacks: DefaultDict[str, int] = defaultdict(int)
event_defaulted = False
@@ -70,9 +65,6 @@ def collapse_stacks(
if sample is None:
lib.Close()
break
- if comm_filter:
- if sample.thread_comm not in comm_filter:
- continue
event = lib.GetEventOfCurrentSample()
symbol = lib.GetSymbolOfCurrentSample()
callchain = lib.GetCallChainOfCurrentSample()
@@ -123,30 +115,23 @@ def main():
parser.add_argument('--jit', action='store_true', help='Annotate JIT functions with a _[j]')
parser.add_argument('--addrs', action='store_true',
help='include raw addresses where symbols can\'t be found')
- parser.add_argument(
- '--proguard-mapping-file', nargs='+',
- help='Add proguard mapping file to de-obfuscate symbols',
- default=[])
sample_filter_group = parser.add_argument_group('Sample filter options')
- parser.add_sample_filter_options(sample_filter_group, False)
sample_filter_group.add_argument('--event-filter', nargs='?', default='',
help='Event type filter e.g. "cpu-cycles" or "instructions"')
- sample_filter_group.add_argument('--comm', nargs='+', action='append', help="""
- Use samples only in threads with selected names.""")
+ parser.add_report_lib_options(sample_filter_group=sample_filter_group,
+ sample_filter_with_pid_shortcut=False)
args = parser.parse_args()
collapse_stacks(
record_file=args.record_file,
symfs_dir=args.symfs,
kallsyms_file=args.kallsyms,
- proguard_mapping_file=args.proguard_mapping_file,
event_filter=args.event_filter,
include_pid=args.pid,
include_tid=args.tid,
annotate_kernel=args.kernel,
annotate_jit=args.jit,
include_addrs=args.addrs,
- comm_filter=set(flatten_arg_list(args.comm)),
- sample_filter=args.sample_filter)
+ report_lib_options=args.report_lib_options)
if __name__ == '__main__':
diff --git a/simpleperf/scripts/test/annotate_test.py b/simpleperf/scripts/test/annotate_test.py
index eef5133a..ce419f66 100644
--- a/simpleperf/scripts/test/annotate_test.py
+++ b/simpleperf/scripts/test/annotate_test.py
@@ -74,3 +74,10 @@ class TestAnnotate(TestBase):
filter_file.flush()
get_report('--filter-file ' + filter_file.name)
os.unlink(filter_file.name)
+
+ def test_show_art_frames(self):
+ self.run_cmd(
+ ['annotate.py', '-i', TestHelper.testdata_path('perf_with_interpreter_frames.data'),
+ '--show-art-frames'])
+ summary = Path('annotated_files') / 'summary'
+ self.check_strings_in_file(summary, 'total period: 9800649')
diff --git a/simpleperf/scripts/test/do_test.py b/simpleperf/scripts/test/do_test.py
index 2c67bb78..b95a1fef 100755
--- a/simpleperf/scripts/test/do_test.py
+++ b/simpleperf/scripts/test/do_test.py
@@ -32,6 +32,7 @@ import multiprocessing as mp
import os
from pathlib import Path
import re
+import subprocess
import sys
import time
from tqdm import tqdm
@@ -39,7 +40,7 @@ import types
from typing import List, Optional
import unittest
-from simpleperf_utils import BaseArgumentParser, extant_dir, log_exit, remove
+from simpleperf_utils import BaseArgumentParser, extant_dir, log_exit, remove, is_darwin
from . api_profiler_test import *
from . annotate_test import *
@@ -517,6 +518,15 @@ 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()
@@ -532,4 +542,5 @@ 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 54bde746..5a0d45ec 100644
--- a/simpleperf/scripts/test/gecko_profile_generator_test.py
+++ b/simpleperf/scripts/test/gecko_profile_generator_test.py
@@ -18,20 +18,22 @@ import json
import os
import re
import tempfile
-from typing import Set
+from typing import List, Optional, Set
from . test_utils import TestBase, TestHelper
class TestGeckoProfileGenerator(TestBase):
- def run_generator(self, testdata_file):
+ def run_generator(self, testdata_file: str, options: Optional[List[str]] = None) -> str:
testdata_path = TestHelper.testdata_path(testdata_file)
- gecko_profile_json = self.run_cmd(
- ['gecko_profile_generator.py', '-i', testdata_path], return_output=True)
- return json.loads(gecko_profile_json)
+ args = ['gecko_profile_generator.py', '-i', testdata_path]
+ if options:
+ args.extend(options)
+ return self.run_cmd(args, return_output=True)
def test_golden(self):
- got = self.run_generator('perf_with_interpreter_frames.data')
+ output = self.run_generator('perf_with_interpreter_frames.data')
+ 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)
@@ -41,8 +43,7 @@ class TestGeckoProfileGenerator(TestBase):
def test_sample_filters(self):
def get_threads_for_filter(filter: str) -> Set[int]:
- report = self.run_cmd(['gecko_profile_generator.py', '-i', TestHelper.testdata_path(
- 'perf_display_bitmaps.data')] + filter.split(), return_output=True)
+ report = self.run_generator('perf_display_bitmaps.data', filter.split())
pattern = re.compile(r'"tid":\s+(\d+),')
threads = set()
for m in re.finditer(pattern, report):
@@ -71,3 +72,10 @@ class TestGeckoProfileGenerator(TestBase):
self.assertIn(31881, threads)
self.assertNotIn(31850, threads)
os.unlink(filter_file.name)
+
+ def test_show_art_frames(self):
+ art_frame_str = 'art::interpreter::DoCall'
+ report = self.run_generator('perf_with_interpreter_frames.data')
+ self.assertNotIn(art_frame_str, report)
+ report = self.run_generator('perf_with_interpreter_frames.data', ['--show-art-frames'])
+ self.assertIn(art_frame_str, report)
diff --git a/simpleperf/scripts/test/inferno_test.py b/simpleperf/scripts/test/inferno_test.py
index bddb2200..83fc7cf8 100644
--- a/simpleperf/scripts/test/inferno_test.py
+++ b/simpleperf/scripts/test/inferno_test.py
@@ -81,3 +81,12 @@ class TestInferno(TestBase):
self.assertIn(31881, threads)
self.assertNotIn(31850, threads)
os.unlink(filter_file.name)
+
+ def test_show_art_frames(self):
+ art_frame_str = 'art::interpreter::DoCall'
+ options = ['--record_file',
+ TestHelper.testdata_path('perf_with_interpreter_frames.data'), '-sc']
+ report = self.get_report(options)
+ self.assertNotIn(art_frame_str, report)
+ report = self.get_report(options + ['--show-art-frames'])
+ self.assertIn(art_frame_str, report)
diff --git a/simpleperf/scripts/test/pprof_proto_generator_test.py b/simpleperf/scripts/test/pprof_proto_generator_test.py
index 0d964841..cbeb8d6a 100644
--- a/simpleperf/scripts/test/pprof_proto_generator_test.py
+++ b/simpleperf/scripts/test/pprof_proto_generator_test.py
@@ -24,6 +24,7 @@ from typing import List, Optional, Set
from binary_cache_builder import BinaryCacheBuilder
from pprof_proto_generator import load_pprof_profile, PprofProfileGenerator
from . test_utils import TestBase, TestHelper
+from simpleperf_utils import ReportLibOptions
class TestPprofProtoGenerator(TestBase):
@@ -216,7 +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, 'proguard_mapping_file': None}
+ config = {'ndk_path': None, 'max_chain_length': 1000000,
+ 'report_lib_options': ReportLibOptions(False, '', None, None)}
generator = PprofProfileGenerator(config)
generator.load_record_file(testdata_file)
diff --git a/simpleperf/scripts/test/purgatorio_test.py b/simpleperf/scripts/test/purgatorio_test.py
index e814c42d..ff862f4f 100644
--- a/simpleperf/scripts/test/purgatorio_test.py
+++ b/simpleperf/scripts/test/purgatorio_test.py
@@ -42,3 +42,11 @@ class TestPurgatorio(TestBase):
# Show original method name with proguard mapping file.
self.assertIn(original_methodname, self.get_report(
['--proguard-mapping-file', proguard_mapping_file, '-i', testdata_file]))
+
+ def test_show_art_frames(self):
+ art_frame_str = 'art::interpreter::DoCall'
+ options = ['-i', TestHelper.testdata_path('perf_with_interpreter_frames.data')]
+ report = self.get_report(options)
+ self.assertIn(art_frame_str, report)
+ report = self.get_report(options + ['--no-show-art-frames'])
+ self.assertNotIn(art_frame_str, report)
diff --git a/simpleperf/scripts/test/report_html_test.py b/simpleperf/scripts/test/report_html_test.py
index 0ae99e95..250ad033 100644
--- a/simpleperf/scripts/test/report_html_test.py
+++ b/simpleperf/scripts/test/report_html_test.py
@@ -84,6 +84,10 @@ class TestReportHtml(TestBase):
self.assertIn(original_methodname, json.dumps(record_data))
def get_record_data(self, options: List[str]) -> Dict[str, Any]:
+ json_data = self.get_record_data_string(options)
+ return json.loads(json_data)
+
+ def get_record_data_string(self, options: List[str]) -> str:
args = ['report_html.py'] + options
if TestHelper.ndk_path:
args += ['--ndk_path', TestHelper.ndk_path]
@@ -99,8 +103,7 @@ class TestReportHtml(TestBase):
start_pos += 1
end_pos = data.find(end_str, start_pos)
self.assertNotEqual(end_pos, -1)
- json_data = data[start_pos:end_pos]
- return json.loads(json_data)
+ return data[start_pos:end_pos]
def test_add_source_code(self):
""" Test --add_source_code option. """
@@ -243,3 +246,11 @@ class TestReportHtml(TestBase):
self.assertIn(31881, threads)
self.assertNotIn(31850, threads)
os.unlink(filter_file.name)
+
+ def test_show_art_frames(self):
+ art_frame_str = 'art::interpreter::DoCall'
+ options = ['-i', TestHelper.testdata_path('perf_with_interpreter_frames.data')]
+ report = self.get_record_data_string(options)
+ self.assertNotIn(art_frame_str, report)
+ report = self.get_record_data_string(options + ['--show-art-frames'])
+ self.assertIn(art_frame_str, report)
diff --git a/simpleperf/scripts/test/report_lib_test.py b/simpleperf/scripts/test/report_lib_test.py
index ba7ae466..1447c5ae 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 Set
+from typing import List, Set
from simpleperf_report_lib import ReportLib
from . test_utils import TestBase, TestHelper
@@ -275,34 +275,39 @@ class TestReportLib(TestBase):
def test_set_sample_filter(self):
""" Test using ReportLib.SetSampleFilter(). """
- def get_threads_for_filter(filter: str) -> Set[int]:
+ def get_threads_for_filter(filters: List[str]) -> Set[int]:
self.report_lib.Close()
self.report_lib = ReportLib()
self.report_lib.SetRecordFile(TestHelper.testdata_path('perf_display_bitmaps.data'))
- self.report_lib.SetSampleFilter(filter)
+ self.report_lib.SetSampleFilter(filters)
threads = set()
while self.report_lib.GetNextSample():
sample = self.report_lib.GetCurrentSample()
threads.add(sample.tid)
return threads
- self.assertNotIn(31850, get_threads_for_filter('--exclude-pid 31850'))
- self.assertIn(31850, get_threads_for_filter('--include-pid 31850'))
- self.assertNotIn(31881, get_threads_for_filter('--exclude-tid 31881'))
- self.assertIn(31881, get_threads_for_filter('--include-tid 31881'))
+ self.assertNotIn(31850, get_threads_for_filter(['--exclude-pid', '31850']))
+ self.assertIn(31850, get_threads_for_filter(['--include-pid', '31850']))
+ self.assertNotIn(31881, get_threads_for_filter(['--exclude-tid', '31881']))
+ self.assertIn(31881, get_threads_for_filter(['--include-tid', '31881']))
self.assertNotIn(31881, get_threads_for_filter(
- '--exclude-process-name com.example.android.displayingbitmaps'))
+ ['--exclude-process-name', 'com.example.android.displayingbitmaps']))
self.assertIn(31881, get_threads_for_filter(
- '--include-process-name com.example.android.displayingbitmaps'))
+ ['--include-process-name', 'com.example.android.displayingbitmaps']))
self.assertNotIn(31850, get_threads_for_filter(
- '--exclude-thread-name com.example.android.displayingbitmaps'))
+ ['--exclude-thread-name', 'com.example.android.displayingbitmaps']))
self.assertIn(31850, get_threads_for_filter(
- '--include-thread-name com.example.android.displayingbitmaps'))
+ ['--include-thread-name', 'com.example.android.displayingbitmaps']))
+
+ # Check that thread name can have space.
+ self.assertNotIn(31856, get_threads_for_filter(
+ ['--exclude-thread-name', 'Jit thread pool']))
+ self.assertIn(31856, get_threads_for_filter(['--include-thread-name', 'Jit thread pool']))
with tempfile.NamedTemporaryFile('w', delete=False) as filter_file:
filter_file.write('GLOBAL_BEGIN 684943449406175\nGLOBAL_END 684943449406176')
filter_file.flush()
- threads = get_threads_for_filter('--filter-file ' + filter_file.name)
+ threads = get_threads_for_filter(['--filter-file', filter_file.name])
self.assertIn(31881, threads)
self.assertNotIn(31850, threads)
os.unlink(filter_file.name)
diff --git a/simpleperf/scripts/test/report_sample_test.py b/simpleperf/scripts/test/report_sample_test.py
index 4b09f82f..5b6681cb 100644
--- a/simpleperf/scripts/test/report_sample_test.py
+++ b/simpleperf/scripts/test/report_sample_test.py
@@ -17,32 +17,27 @@
import os
import re
import tempfile
-from typing import Set
+from typing import List, Optional, Set
from . test_utils import TestBase, TestHelper
class TestReportSample(TestBase):
+ def get_record_data_string(self, record_file: str, options: Optional[List[str]] = None):
+ args = ['report_sample.py', '-i', TestHelper.testdata_path(record_file)]
+ if options:
+ args += options
+ report = self.run_cmd(args, return_output=True)
+ return report.replace('\r', '')
def test_no_flags(self):
- got = self.run_cmd(
- ['report_sample.py',
- '-i',
- TestHelper.testdata_path('perf_display_bitmaps.data')],
- return_output=True)
- got = got.replace('\r', '')
+ got = self.get_record_data_string('perf_display_bitmaps.data')
with open(TestHelper.testdata_path('perf_display_bitmaps.perf-script')) as f:
want = f.read()
self.assertEqual(got, want)
def test_comm_filter_to_renderthread(self):
- got = self.run_cmd(
- ['report_sample.py',
- '-i',
- TestHelper.testdata_path('perf_display_bitmaps.data'),
- '--comm', 'RenderThread'],
- return_output=True)
- got = got.replace('\r', '')
+ got = self.get_record_data_string('perf_display_bitmaps.data', ['--comm', 'RenderThread'])
self.assertIn('RenderThread', got)
self.assertNotIn('com.example.android.displayingbitmaps', got)
@@ -51,13 +46,8 @@ class TestReportSample(TestBase):
self.assertEqual(got, want)
def test_comm_filter_to_ui_thread(self):
- got = self.run_cmd(
- ['report_sample.py',
- '-i',
- TestHelper.testdata_path('perf_display_bitmaps.data'),
- '--comm', 'com.example.android.displayingbitmaps'],
- return_output=True)
- got = got.replace('\r', '')
+ got = self.get_record_data_string('perf_display_bitmaps.data', [
+ '--comm', 'com.example.android.displayingbitmaps'])
self.assertIn('com.example.android.displayingbitmaps', got)
self.assertNotIn('RenderThread', got)
with open(TestHelper.testdata_path('perf_display_bitmaps.UiThread.perf-script')) as f:
@@ -65,29 +55,20 @@ class TestReportSample(TestBase):
self.assertEqual(got, want)
def test_header(self):
- got = self.run_cmd(
- ['report_sample.py',
- '-i',
- TestHelper.testdata_path('perf_display_bitmaps.data'),
- '--header'],
- return_output=True)
- got = got.replace('\r', '')
+ got = self.get_record_data_string('perf_display_bitmaps.data', ['--header'])
with open(TestHelper.testdata_path('perf_display_bitmaps.header.perf-script')) as f:
want = f.read()
self.assertEqual(got, want)
def test_trace_offcpu(self):
- got = self.run_cmd(
- ['report_sample.py', '-i', TestHelper.testdata_path('perf_with_trace_offcpu_v2.data'),
- '--trace-offcpu', 'on-cpu'], return_output=True)
+ got = self.get_record_data_string('perf_with_trace_offcpu_v2.data', [
+ '--trace-offcpu', 'on-cpu'])
self.assertIn('cpu-clock:u', got)
self.assertNotIn('sched:sched_switch', got)
def test_sample_filters(self):
def get_threads_for_filter(filter: str) -> Set[int]:
- report = self.run_cmd(
- ['report_sample.py', '-i', TestHelper.testdata_path('perf_display_bitmaps.data')] +
- filter.split(), return_output=True)
+ report = self.get_record_data_string('perf_display_bitmaps.data', filter.split())
pattern = re.compile(r'\s+31850/(\d+)\s+')
threads = set()
for m in re.finditer(pattern, report):
@@ -116,3 +97,11 @@ class TestReportSample(TestBase):
self.assertIn(31881, threads)
self.assertNotIn(31850, threads)
os.unlink(filter_file.name)
+
+ def test_show_art_frames(self):
+ art_frame_str = 'art::interpreter::DoCall'
+ report = self.get_record_data_string('perf_with_interpreter_frames.data')
+ self.assertNotIn(art_frame_str, report)
+ report = self.get_record_data_string(
+ 'perf_with_interpreter_frames.data', ['--show-art-frames'])
+ self.assertIn(art_frame_str, report)
diff --git a/simpleperf/scripts/test/stackcollapse_test.py b/simpleperf/scripts/test/stackcollapse_test.py
index fee39222..4b9cdbed 100644
--- a/simpleperf/scripts/test/stackcollapse_test.py
+++ b/simpleperf/scripts/test/stackcollapse_test.py
@@ -19,90 +19,57 @@ import os
from pathlib import Path
import re
import tempfile
-from typing import Set
+from typing import List, Optional, Set
from . test_utils import TestBase, TestHelper
class TestStackCollapse(TestBase):
+ def get_report(self, testdata_file: str, options: Optional[List[str]] = None) -> str:
+ args = ['stackcollapse.py', '-i', TestHelper.testdata_path(testdata_file)]
+ if options:
+ args.extend(options)
+ report = self.run_cmd(args, return_output=True)
+ return report.replace('\r', '')
def test_jit_annotations(self):
- got = self.run_cmd([
- 'stackcollapse.py',
- '-i', TestHelper.testdata_path('perf_with_jit_symbol.data'),
- '--jit',
- ], return_output=True)
- got = got.replace('\r', '')
+ got = self.get_report('perf_with_jit_symbol.data', ['--jit'])
golden_path = TestHelper.testdata_path('perf_with_jit_symbol.foldedstack')
self.assertEqual(got, Path(golden_path).read_text())
def test_kernel_annotations(self):
- got = self.run_cmd([
- 'stackcollapse.py',
- '-i', TestHelper.testdata_path('perf_with_jit_symbol.data'),
- '--kernel',
- ], return_output=True)
- got = got.replace('\r', '')
+ got = self.get_report('perf_with_jit_symbol.data', ['--kernel'])
golden_path = TestHelper.testdata_path('perf_with_jit_symbol.foldedstack_with_kernel')
self.assertEqual(got, Path(golden_path).read_text())
def test_with_pid(self):
- got = self.run_cmd([
- 'stackcollapse.py',
- '-i', TestHelper.testdata_path('perf_with_jit_symbol.data'),
- '--jit',
- '--pid',
- ], return_output=True)
- got = got.replace('\r', '')
+ got = self.get_report('perf_with_jit_symbol.data', ['--jit', '--pid'])
golden_path = TestHelper.testdata_path('perf_with_jit_symbol.foldedstack_with_pid')
self.assertEqual(got, Path(golden_path).read_text())
def test_with_tid(self):
- got = self.run_cmd([
- 'stackcollapse.py',
- '-i', TestHelper.testdata_path('perf_with_jit_symbol.data'),
- '--jit',
- '--tid',
- ], return_output=True)
- got = got.replace('\r', '')
+ got = self.get_report('perf_with_jit_symbol.data', ['--jit', '--tid'])
golden_path = TestHelper.testdata_path('perf_with_jit_symbol.foldedstack_with_tid')
self.assertEqual(got, Path(golden_path).read_text())
def test_two_event_types_chooses_first(self):
- got = self.run_cmd([
- 'stackcollapse.py',
- '-i', TestHelper.testdata_path('perf_with_two_event_types.data'),
- ], return_output=True)
- got = got.replace('\r', '')
+ got = self.get_report('perf_with_two_event_types.data')
golden_path = TestHelper.testdata_path('perf_with_two_event_types.foldedstack')
self.assertEqual(got, Path(golden_path).read_text())
def test_two_event_types_chooses_with_event_filter(self):
- got = self.run_cmd([
- 'stackcollapse.py',
- '-i', TestHelper.testdata_path('perf_with_two_event_types.data'),
- '--event-filter', 'cpu-clock',
- ], return_output=True)
- got = got.replace('\r', '')
+ got = self.get_report('perf_with_two_event_types.data', ['--event-filter', 'cpu-clock'])
golden_path = TestHelper.testdata_path('perf_with_two_event_types.foldedstack_cpu_clock')
self.assertEqual(got, Path(golden_path).read_text())
def test_unknown_symbol_addrs(self):
- got = self.run_cmd([
- 'stackcollapse.py',
- '-i', TestHelper.testdata_path('perf_with_jit_symbol.data'),
- '--addrs',
- ], return_output=True)
- got = got.replace('\r', '')
+ got = self.get_report('perf_with_jit_symbol.data', ['--addrs'])
golden_path = TestHelper.testdata_path('perf_with_jit_symbol.foldedstack_addrs')
self.assertEqual(got, Path(golden_path).read_text())
def test_sample_filters(self):
def get_threads_for_filter(filter: str) -> Set[int]:
- report = self.run_cmd(
- ['stackcollapse.py', '-i', TestHelper.testdata_path('perf_display_bitmaps.data'),
- '--tid'] + filter.split(),
- return_output=True)
+ report = self.get_report('perf_display_bitmaps.data', ['--tid'] + filter.split())
pattern = re.compile(r'-31850/(\d+);')
threads = set()
for m in re.finditer(pattern, report):
@@ -129,3 +96,10 @@ class TestStackCollapse(TestBase):
self.assertIn(31881, threads)
self.assertNotIn(31850, threads)
os.unlink(filter_file.name)
+
+ def test_show_art_frames(self):
+ art_frame_str = 'art::interpreter::DoCall'
+ report = self.get_report('perf_with_interpreter_frames.data')
+ self.assertNotIn(art_frame_str, report)
+ report = self.get_report('perf_with_interpreter_frames.data', ['--show-art-frames'])
+ self.assertIn(art_frame_str, report)
diff --git a/tests/kernel.config/AndroidTest.xml b/tests/kernel.config/AndroidTest.xml
index 19da90af..78452e1c 100644
--- a/tests/kernel.config/AndroidTest.xml
+++ b/tests/kernel.config/AndroidTest.xml
@@ -26,7 +26,7 @@
</target_preparer>
<!-- Make sure there is some data in the pstore then reboot -->
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <option name="run-command" value="echo HELLOWORLD >/dev/pmsg0" />
+ <option name="run-command" value="if [ -e /dev/pmsg0 ] ; then echo HELLOWORLD > /dev/pmsg0; fi" />
<option name="throw-if-cmd-fail" value="true" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RebootTargetPreparer" />
diff --git a/toolchain-extras/Android.bp b/toolchain-extras/Android.bp
index 220f3e34..d48f081f 100644
--- a/toolchain-extras/Android.bp
+++ b/toolchain-extras/Android.bp
@@ -53,18 +53,18 @@ cc_defaults {
sanitize: {
blocklist: "libprofile_clang_extras_blocklist.txt",
},
-}
-
-cc_library_static {
- name: "libprofile-clang-extras",
- defaults: ["libprofile-clang-defaults"],
-
native_bridge_supported: true,
vendor_available: true,
product_available: true,
vndk: {
enabled: true,
},
+}
+
+cc_defaults {
+ name: "libprofile-clang-platform-defaults",
+ defaults: ["libprofile-clang-defaults"],
+
ramdisk_available: true,
vendor_ramdisk_available: true,
recovery_available: true,
@@ -74,36 +74,70 @@ cc_library_static {
header_libs: ["libc_headers"],
}
+// -----------------------------------------------------------------------------
+// libprofile-clang libraries for regular coverage. They also install a signal
+// handler that writes coverage data to disk.
+// -----------------------------------------------------------------------------
+
+cc_library_static {
+ name: "libprofile-clang-extras",
+ defaults: ["libprofile-clang-platform-defaults"],
+}
+
cc_library_static {
name: "libprofile-clang-extras_ndk",
defaults: ["libprofile-clang-defaults"],
- native_bridge_supported: true,
- vendor_available: true,
- product_available: true,
- vndk: {
- enabled: true,
- },
sdk_version: "minimum",
}
cc_library_static {
name: "libprofile-clang-extras_cfi_support",
- defaults: ["libprofile-clang-defaults"],
+ defaults: ["libprofile-clang-platform-defaults"],
- native_bridge_supported: true,
- vendor_available: true,
- product_available: true,
- vndk: {
- enabled: true,
+ sanitize: {
+ cfi: true,
+ config: {
+ cfi_assembly_support: true,
+ },
},
- ramdisk_available: true,
- vendor_ramdisk_available: true,
- recovery_available: true,
+}
+
+// -----------------------------------------------------------------------------
+// libprofile-clang libraries for continuous coverage. They install a no-op
+// signal handler.
+// -----------------------------------------------------------------------------
+
+cc_defaults {
+ name: "profile-extras-continuous-mode",
+ cflags: ["-D__CONTINUOUS_COVERAGE_MODE__"],
+}
+
+cc_library_static {
+ name: "libprofile-clang-extras_continuous",
+ defaults: [
+ "libprofile-clang-platform-defaults",
+ "profile-extras-continuous-mode",
+ ],
+}
+
+cc_library_static {
+ name: "libprofile-clang-extras_ndk_continuous",
+ defaults: [
+ "libprofile-clang-platform-defaults",
+ "profile-extras-continuous-mode",
+ ],
+
+ sdk_version: "minimum",
+}
+
+cc_library_static {
+ name: "libprofile-clang-extras_cfi_support_continuous",
+ defaults: [
+ "libprofile-clang-platform-defaults",
+ "profile-extras-continuous-mode",
+ ],
- stl: "none",
- system_shared_libs: [],
- header_libs: ["libc_headers"],
sanitize: {
cfi: true,
config: {
@@ -112,6 +146,9 @@ cc_library_static {
},
}
+// -----------------------------------------------------------------------------
+// tests
+// -----------------------------------------------------------------------------
cc_test {
name: "libprofile-extras-test",
srcs: [
diff --git a/toolchain-extras/profile-clang-extras.cpp b/toolchain-extras/profile-clang-extras.cpp
index c45f9b39..6a7766ef 100644
--- a/toolchain-extras/profile-clang-extras.cpp
+++ b/toolchain-extras/profile-clang-extras.cpp
@@ -24,10 +24,16 @@ extern "C" {
static sighandler_t chained_signal_handler = SIG_ERR;
+#ifndef __CONTINUOUS_COVERAGE_MODE__
int __llvm_profile_write_file(void);
+#endif // __CONTINUOUS_COVERAGE_MODE__
static void llvm_signal_handler(__unused int signum) {
+ // TODO(pirama) Only disable __llvm_profile_write_file call to begin with.
+ // After continuous mode is stable, stop registering the signal handler.
+#ifndef __CONTINUOUS_COVERAGE_MODE__
__llvm_profile_write_file();
+#endif // __CONTINUOUS_COVERAGE_MODE__
if (chained_signal_handler != SIG_ERR && chained_signal_handler != SIG_IGN &&
chained_signal_handler != SIG_DFL) {
diff --git a/verity/Android.bp b/verity/Android.bp
index fc2b827c..ef09528c 100644
--- a/verity/Android.bp
+++ b/verity/Android.bp
@@ -154,13 +154,8 @@ python_binary_host {
name: "build_verity_metadata",
srcs: ["build_verity_metadata.py"],
version: {
- py2: {
- enabled: true,
- embedded_launcher: true,
- },
py3: {
- enabled: false,
- embedded_launcher: false,
+ embedded_launcher: true,
},
},
}
diff --git a/verity/build_verity_metadata.py b/verity/build_verity_metadata.py
index 5a7d7d27..a428f270 100644
--- a/verity/build_verity_metadata.py
+++ b/verity/build_verity_metadata.py
@@ -1,4 +1,4 @@
-#! /usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2013 The Android Open Source Project
#
@@ -29,9 +29,9 @@ BLOCK_SIZE = 4096
METADATA_SIZE = BLOCK_SIZE * 8
def run(cmd):
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, text=True)
output, _ = p.communicate()
- print output
+ print(output)
if p.returncode:
exit(-1)
@@ -43,12 +43,12 @@ def build_metadata_block(verity_table, signature, verity_disable=False):
magic = MAGIC_DISABLE if verity_disable else MAGIC_NUMBER
block = struct.pack("II256sI", magic, VERSION, signature, table_len)
block += verity_table
- block = block.ljust(METADATA_SIZE, '\x00')
+ block = block.ljust(METADATA_SIZE, b'\x00')
return block
def sign_verity_table(table, signer_path, key_path, signer_args=None):
- with tempfile.NamedTemporaryFile(suffix='.table') as table_file:
- with tempfile.NamedTemporaryFile(suffix='.sig') as signature_file:
+ with tempfile.NamedTemporaryFile(mode='wb', suffix='.table') as table_file:
+ with tempfile.NamedTemporaryFile(mode='rb', suffix='.sig') as signature_file:
table_file.write(table)
table_file.flush()
if signer_args is None:
@@ -56,7 +56,7 @@ def sign_verity_table(table, signer_path, key_path, signer_args=None):
else:
args_list = shlex.split(signer_args)
cmd = [signer_path] + args_list + [table_file.name, key_path, signature_file.name]
- print cmd
+ print(cmd)
run(cmd)
return signature_file.read()
@@ -70,7 +70,7 @@ def build_verity_table(block_device, data_blocks, root_hash, salt):
data_blocks,
root_hash,
salt)
- return table
+ return table.encode()
def build_verity_metadata(data_blocks, metadata_image, root_hash, salt,
block_device, signer_path, signing_key, signer_args=None,
@@ -109,9 +109,9 @@ if __name__ == "__main__":
args = parser.parse_args()
if args.dest == 'size':
- print get_verity_metadata_size(args.partition_size)
+ print(get_verity_metadata_size(args.partition_size))
else:
- build_verity_metadata(args.blocks / 4096, args.metadata_image,
+ build_verity_metadata(args.blocks // 4096, args.metadata_image,
args.root_hash, args.salt, args.block_device,
args.signer_path, args.signing_key,
args.signer_args, args.verity_disable)
diff --git a/verity/fec/Android.bp b/verity/fec/Android.bp
index 9ca45600..ede5fd19 100644
--- a/verity/fec/Android.bp
+++ b/verity/fec/Android.bp
@@ -25,6 +25,11 @@ cc_binary_host {
misc_undefined: ["integer"],
},
},
+ linux_musl_x86_64: {
+ sanitize: {
+ misc_undefined: ["integer"],
+ },
+ },
linux: {
static_libs: [
"libavb",