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 devil
.android
import device_temp_file
13 from devil
.android
.perf
import perf_control
15 from profile_chrome
import controllers
16 from profile_chrome
import ui
18 from pylib
import constants
20 sys
.path
.append(os
.path
.join(constants
.DIR_SOURCE_ROOT
,
24 # pylint: disable=F0401
25 from telemetry
.internal
.platform
.profiler
import android_profiling_helper
26 from telemetry
.internal
.util
import binary_manager
28 android_profiling_helper
= 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 binary_manager
.InitDependencyManager(None)
115 return android_profiling_helper
.PrepareDeviceForPerf(device
)
118 def GetCategories(cls
, device
):
119 perf_binary
= cls
._PrepareDevice
(device
)
120 return device
.RunShellCommand('%s list' % perf_binary
)
122 def StartTracing(self
, _
):
123 self
._perf
_instance
= _PerfProfiler(self
._device
,
127 def StopTracing(self
):
128 if not self
._perf
_instance
:
130 self
._perf
_instance
.SignalAndWait()
133 def _GetInteractivePerfCommand(perfhost_path
, perf_profile
, symfs_dir
,
134 required_libs
, kallsyms
):
135 cmd
= '%s report -n -i %s --symfs %s --kallsyms %s' % (
136 os
.path
.relpath(perfhost_path
, '.'), perf_profile
, symfs_dir
, kallsyms
)
137 for lib
in required_libs
:
138 lib
= os
.path
.join(symfs_dir
, lib
[1:])
139 if not os
.path
.exists(lib
):
141 objdump_path
= android_profiling_helper
.GetToolchainBinaryPath(
144 cmd
+= ' --objdump %s' % os
.path
.relpath(objdump_path
, '.')
149 symfs_dir
= os
.path
.join(tempfile
.gettempdir(),
150 os
.path
.expandvars('$USER-perf-symfs'))
151 if not os
.path
.exists(symfs_dir
):
152 os
.makedirs(symfs_dir
)
153 required_libs
= set()
155 # Download the recorded perf profile.
156 perf_profile
= self
._perf
_instance
.PullResult(symfs_dir
)
158 android_profiling_helper
.GetRequiredLibrariesForPerfProfile(
160 if not required_libs
:
161 logging
.warning('No libraries required by perf trace. Most likely there '
162 'are no samples in the trace.')
164 # Build a symfs with all the necessary libraries.
165 kallsyms
= android_profiling_helper
.CreateSymFs(self
._device
,
169 perfhost_path
= binary_manager
.FetchPath(
170 android_profiling_helper
.GetPerfhostName(), 'x86_64', 'linux')
172 ui
.PrintMessage('\nNote: to view the profile in perf, run:')
173 ui
.PrintMessage(' ' + self
._GetInteractivePerfCommand
(perfhost_path
,
174 perf_profile
, symfs_dir
, required_libs
, kallsyms
))
176 # Convert the perf profile into JSON.
177 perf_script_path
= os
.path
.join(os
.path
.dirname(os
.path
.abspath(__file__
)),
178 'third_party', 'perf_to_tracing.py')
179 json_file_name
= os
.path
.basename(perf_profile
)
180 with
open(os
.devnull
, 'w') as dev_null
, \
181 open(json_file_name
, 'w') as json_file
:
182 cmd
= [perfhost_path
, 'script', '-s', perf_script_path
, '-i',
183 perf_profile
, '--symfs', symfs_dir
, '--kallsyms', kallsyms
]
184 if subprocess
.call(cmd
, stdout
=json_file
, stderr
=dev_null
):
185 logging
.warning('Perf data to JSON conversion failed. The result will '
186 'not contain any perf samples. You can still view the '
187 'perf data manually as shown above.')
190 return json_file_name