Skip to content

Commit 7c7ca37

Browse files
committed
collector: Add PerfettoCollector
Add a Collector for accessing Google's Perfetto tracing infrastructure. The Collector takes a path to an on-device config file, starts tracing in the background using the perfetto binary and then stops by killing the tracing process. Signed-off-by: Kajetan Puchalski <[email protected]>
1 parent 9379489 commit 7c7ca37

File tree

3 files changed

+119
-4
lines changed

3 files changed

+119
-4
lines changed

devlib/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
from devlib.derived.fps import DerivedGfxInfoStats, DerivedSurfaceFlingerStats
4747

4848
from devlib.collector.ftrace import FtraceCollector
49+
from devlib.collector.perfetto import PerfettoCollector
4950
from devlib.collector.perf import PerfCollector
5051
from devlib.collector.serial_trace import SerialTraceCollector
5152
from devlib.collector.dmesg import DmesgCollector

devlib/collector/perfetto.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Copyright 2023 ARM Limited
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
16+
import os
17+
import subprocess
18+
from shlex import quote
19+
20+
from devlib.host import PACKAGE_BIN_DIRECTORY
21+
from devlib.collector import (CollectorBase, CollectorOutput,
22+
CollectorOutputEntry)
23+
from devlib.exception import TargetStableError, HostError
24+
25+
OUTPUT_PERFETTO_TRACE = 'devlib-trace.perfetto-trace'
26+
27+
28+
class PerfettoCollector(CollectorBase):
29+
"""
30+
Perfetto is a production-grade open-source stack for performance instrumentation
31+
and trace analysis developed by Google. It offers services and libraries for
32+
recording system-level and app-level traces, native + java heap profiling,
33+
a library for analyzing traces using SQL and a web-based UI to visualize and
34+
explore multi-GB traces.
35+
36+
This collector takes a path to a perfetto config file saved on disk and passes
37+
it directly to the tool.
38+
39+
On Android platfroms Perfetto is included in the framework starting with Android 9.
40+
On Android 8 and below, follow the Linux instructions below to build and include
41+
the standalone tracebox binary.
42+
43+
On Linux platforms, either traced (Perfetto tracing daemon) needs to be running
44+
in the background or the tracebox binary needs to be built from source and placed
45+
in the Package Bin directory. The build instructions can be found here:
46+
47+
https://perfetto.dev/docs/contributing/build-instructions
48+
49+
After building the 'tracebox' binary should be copied to devlib/bin/<arch>/.
50+
51+
For more information consult the official documentation:
52+
https://perfetto.dev/docs/
53+
"""
54+
55+
def __init__(self, target, config=None):
56+
super().__init__(target)
57+
self.bg_cmd = None
58+
self.config = config
59+
target_output_path = self.target.working_directory
60+
61+
self.target_binary = 'perfetto'
62+
if target.os == 'android':
63+
# Android requires Perfetto to write to this directory
64+
target_output_path = '/data/misc/perfetto-traces'
65+
os_version = target.os_version['release']
66+
# Android 9 and 10 require traced to be enabled manually
67+
if os_version == '9' or os_version == '10':
68+
target.execute('setprop persist.traced.enable 1')
69+
70+
if target.os in ['linux', 'android'] and not target.is_running('traced'):
71+
self.target_binary = 'tracebox'
72+
if not self.target.get_installed(self.target_binary):
73+
host_executable = os.path.join(PACKAGE_BIN_DIRECTORY,
74+
self.target.abi, self.target_binary)
75+
if not os.path.exists(host_executable):
76+
raise HostError("{} not found on the host".format(self.target_binary))
77+
self.target.install(host_executable)
78+
79+
self.target_output_file = target.path.join(target_output_path, OUTPUT_PERFETTO_TRACE)
80+
81+
def start(self):
82+
cmd = "cat {} | {} --txt -c - -o {}".format(
83+
quote(self.config), quote(self.target_binary), quote(self.target_output_file)
84+
)
85+
# start tracing
86+
if self.bg_cmd is None:
87+
self.bg_cmd = self.target.background(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
88+
else:
89+
raise TargetStableError('Perfetto collector is not re-entrant')
90+
91+
def stop(self):
92+
# stop tracing
93+
self.bg_cmd.cancel()
94+
self.bg_cmd = None
95+
96+
def set_output(self, output_path):
97+
if os.path.isdir(output_path):
98+
output_path = os.path.join(output_path, os.path.basename(self.target_output_file))
99+
self.output_path = output_path
100+
101+
def get_data(self):
102+
if self.output_path is None:
103+
raise RuntimeError("Output path was not set.")
104+
if not self.target.file_exists(self.target_output_file):
105+
raise RuntimeError("Output file not found on the device")
106+
self.target.pull(self.target_output_file, self.output_path)
107+
output = CollectorOutput()
108+
if not os.path.isfile(self.output_path):
109+
self.logger.warning('Perfetto trace not pulled from device.')
110+
else:
111+
output.append(CollectorOutputEntry(self.output_path, 'file'))
112+
return output
113+

devlib/target.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -280,10 +280,11 @@ def shutils(self):
280280
self._setup_shutils()
281281
return self._shutils
282282

283-
def is_running(self, process):
284-
cmd = f'''{self.busybox} ps -A -o comm,stat | {self.busybox} awk '$1 == "{process}" && $2 != "Z"' '''
285-
result = self.execute(cmd, as_root=False)
286-
return bool(result)
283+
def is_running(self, comm):
284+
cmd_ps = f'''{self.busybox} ps -A -T -o stat,comm'''
285+
cmd_awk = f'''{self.busybox} awk 'BEGIN{{found=0}} {{state=$1; $1=""; if ($state != "Z" && $0 == " {comm}") {{found=1}}}} END {{print found}}' '''
286+
result = self.execute(f"{cmd_ps} | {cmd_awk}", as_root=False)
287+
return bool(int(result))
287288

288289
@tls_property
289290
def _conn(self):

0 commit comments

Comments
 (0)