1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
12 from profile_chrome
import controllers
13 from profile_chrome
import ui
15 from pylib
import android_commands
16 from pylib
import constants
17 from pylib
.perf
import perf_control
18 from pylib
.utils
import device_temp_file
20 sys
.path
.append(os
.path
.join(constants
.DIR_SOURCE_ROOT
,
24 # pylint: disable=F0401
25 from telemetry
.core
.platform
.profiler
import android_profiling_helper
26 from telemetry
.util
import support_binaries
28 android_profiling_helper
= None
29 support_binaries
= None
33 # Sample across all processes and CPUs to so that the current CPU gets
34 # recorded to each sample.
36 # In perf 3.13 --call-graph requires an argument, so use the -g short-hand
39 # Increase priority to avoid dropping samples. Requires root.
41 # Record raw samples to get CPU information.
43 # Increase sampling frequency for better coverage.
48 class _PerfProfiler(object):
49 def __init__(self
, device
, perf_binary
, categories
):
51 self
._output
_file
= device_temp_file
.DeviceTempFile(
52 self
._device
.adb
, prefix
='perf_output')
53 self
._log
_file
= tempfile
.TemporaryFile()
55 # TODO(jbudorick) Look at providing a way to unhandroll this once the
56 # adb rewrite has fully landed.
57 device_param
= (['-s', str(self
._device
)] if str(self
._device
) else [])
58 cmd
= ['adb'] + device_param
+ \
59 ['shell', perf_binary
, 'record',
60 '--output', self
._output
_file
.name
] + _PERF_OPTIONS
62 cmd
+= ['--event', ','.join(categories
)]
63 self
._perf
_control
= perf_control
.PerfControl(self
._device
)
64 self
._perf
_control
.SetPerfProfilingMode()
65 self
._perf
_process
= subprocess
.Popen(cmd
,
66 stdout
=self
._log
_file
,
67 stderr
=subprocess
.STDOUT
)
69 def SignalAndWait(self
):
70 self
._device
.KillAll('perf', signum
=signal
.SIGINT
)
71 self
._perf
_process
.wait()
72 self
._perf
_control
.SetDefaultPerfMode()
74 def _FailWithLog(self
, msg
):
75 self
._log
_file
.seek(0)
76 log
= self
._log
_file
.read()
77 raise RuntimeError('%s. Log output:\n%s' % (msg
, log
))
79 def PullResult(self
, output_path
):
80 if not self
._device
.FileExists(self
._output
_file
.name
):
81 self
._FailWithLog
('Perf recorded no data')
83 perf_profile
= os
.path
.join(output_path
,
84 os
.path
.basename(self
._output
_file
.name
))
85 self
._device
.PullFile(self
._output
_file
.name
, perf_profile
)
86 if not os
.stat(perf_profile
).st_size
:
87 os
.remove(perf_profile
)
88 self
._FailWithLog
('Perf recorded a zero-sized file')
90 self
._log
_file
.close()
91 self
._output
_file
.close()
95 class PerfProfilerController(controllers
.BaseController
):
96 def __init__(self
, device
, categories
):
97 controllers
.BaseController
.__init
__(self
)
99 self
._categories
= categories
100 self
._perf
_binary
= self
._PrepareDevice
(device
)
101 self
._perf
_instance
= None
104 return 'perf profile'
108 return bool(android_profiling_helper
)
111 def _PrepareDevice(device
):
112 if not 'BUILDTYPE' in os
.environ
:
113 os
.environ
['BUILDTYPE'] = 'Release'
114 return android_profiling_helper
.PrepareDeviceForPerf(device
)
117 def GetCategories(cls
, device
):
118 perf_binary
= cls
._PrepareDevice
(device
)
119 return device
.RunShellCommand('%s list' % perf_binary
)
121 def StartTracing(self
, _
):
122 self
._perf
_instance
= _PerfProfiler(self
._device
,
126 def StopTracing(self
):
127 if not self
._perf
_instance
:
129 self
._perf
_instance
.SignalAndWait()
132 def _GetInteractivePerfCommand(perfhost_path
, perf_profile
, symfs_dir
,
133 required_libs
, kallsyms
):
134 cmd
= '%s report -n -i %s --symfs %s --kallsyms %s' % (
135 os
.path
.relpath(perfhost_path
, '.'), perf_profile
, symfs_dir
, kallsyms
)
136 for lib
in required_libs
:
137 lib
= os
.path
.join(symfs_dir
, lib
[1:])
138 if not os
.path
.exists(lib
):
140 objdump_path
= android_profiling_helper
.GetToolchainBinaryPath(
143 cmd
+= ' --objdump %s' % os
.path
.relpath(objdump_path
, '.')
148 symfs_dir
= os
.path
.join(tempfile
.gettempdir(),
149 os
.path
.expandvars('$USER-perf-symfs'))
150 if not os
.path
.exists(symfs_dir
):
151 os
.makedirs(symfs_dir
)
152 required_libs
= set()
154 # Download the recorded perf profile.
155 perf_profile
= self
._perf
_instance
.PullResult(symfs_dir
)
157 android_profiling_helper
.GetRequiredLibrariesForPerfProfile(
159 if not required_libs
:
160 logging
.warning('No libraries required by perf trace. Most likely there '
161 'are no samples in the trace.')
163 # Build a symfs with all the necessary libraries.
164 kallsyms
= android_profiling_helper
.CreateSymFs(self
._device
,
168 perfhost_path
= support_binaries
.FindPath(
169 android_profiling_helper
.GetPerfhostName(), 'x86_64', 'linux')
171 ui
.PrintMessage('\nNote: to view the profile in perf, run:')
172 ui
.PrintMessage(' ' + self
._GetInteractivePerfCommand
(perfhost_path
,
173 perf_profile
, symfs_dir
, required_libs
, kallsyms
))
175 # Convert the perf profile into JSON.
176 perf_script_path
= os
.path
.join(os
.path
.dirname(os
.path
.abspath(__file__
)),
177 'third_party', 'perf_to_tracing.py')
178 json_file_name
= os
.path
.basename(perf_profile
)
179 with
open(os
.devnull
, 'w') as dev_null
, \
180 open(json_file_name
, 'w') as json_file
:
181 cmd
= [perfhost_path
, 'script', '-s', perf_script_path
, '-i',
182 perf_profile
, '--symfs', symfs_dir
, '--kallsyms', kallsyms
]
183 if subprocess
.call(cmd
, stdout
=json_file
, stderr
=dev_null
):
184 logging
.warning('Perf data to JSON conversion failed. The result will '
185 'not contain any perf samples. You can still view the '
186 'perf data manually as shown above.')
189 return json_file_name