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 constants
16 from pylib
.perf
import perf_control
17 from pylib
.utils
import device_temp_file
19 sys
.path
.append(os
.path
.join(constants
.DIR_SOURCE_ROOT
,
23 # pylint: disable=F0401
24 from telemetry
.internal
.platform
.profiler
import android_profiling_helper
25 from telemetry
.internal
.util
import binary_manager
27 android_profiling_helper
= None
32 # Sample across all processes and CPUs to so that the current CPU gets
33 # recorded to each sample.
35 # In perf 3.13 --call-graph requires an argument, so use the -g short-hand
38 # Increase priority to avoid dropping samples. Requires root.
40 # Record raw samples to get CPU information.
42 # Increase sampling frequency for better coverage.
47 class _PerfProfiler(object):
48 def __init__(self
, device
, perf_binary
, categories
):
50 self
._output
_file
= device_temp_file
.DeviceTempFile(
51 self
._device
.adb
, prefix
='perf_output')
52 self
._log
_file
= tempfile
.TemporaryFile()
54 # TODO(jbudorick) Look at providing a way to unhandroll this once the
55 # adb rewrite has fully landed.
56 device_param
= (['-s', str(self
._device
)] if str(self
._device
) else [])
57 cmd
= ['adb'] + device_param
+ \
58 ['shell', perf_binary
, 'record',
59 '--output', self
._output
_file
.name
] + _PERF_OPTIONS
61 cmd
+= ['--event', ','.join(categories
)]
62 self
._perf
_control
= perf_control
.PerfControl(self
._device
)
63 self
._perf
_control
.SetPerfProfilingMode()
64 self
._perf
_process
= subprocess
.Popen(cmd
,
65 stdout
=self
._log
_file
,
66 stderr
=subprocess
.STDOUT
)
68 def SignalAndWait(self
):
69 self
._device
.KillAll('perf', signum
=signal
.SIGINT
)
70 self
._perf
_process
.wait()
71 self
._perf
_control
.SetDefaultPerfMode()
73 def _FailWithLog(self
, msg
):
74 self
._log
_file
.seek(0)
75 log
= self
._log
_file
.read()
76 raise RuntimeError('%s. Log output:\n%s' % (msg
, log
))
78 def PullResult(self
, output_path
):
79 if not self
._device
.FileExists(self
._output
_file
.name
):
80 self
._FailWithLog
('Perf recorded no data')
82 perf_profile
= os
.path
.join(output_path
,
83 os
.path
.basename(self
._output
_file
.name
))
84 self
._device
.PullFile(self
._output
_file
.name
, perf_profile
)
85 if not os
.stat(perf_profile
).st_size
:
86 os
.remove(perf_profile
)
87 self
._FailWithLog
('Perf recorded a zero-sized file')
89 self
._log
_file
.close()
90 self
._output
_file
.close()
94 class PerfProfilerController(controllers
.BaseController
):
95 def __init__(self
, device
, categories
):
96 controllers
.BaseController
.__init
__(self
)
98 self
._categories
= categories
99 self
._perf
_binary
= self
._PrepareDevice
(device
)
100 self
._perf
_instance
= None
103 return 'perf profile'
107 return bool(android_profiling_helper
)
110 def _PrepareDevice(device
):
111 if not 'BUILDTYPE' in os
.environ
:
112 os
.environ
['BUILDTYPE'] = 'Release'
113 return android_profiling_helper
.PrepareDeviceForPerf(device
)
116 def GetCategories(cls
, device
):
117 perf_binary
= cls
._PrepareDevice
(device
)
118 return device
.RunShellCommand('%s list' % perf_binary
)
120 def StartTracing(self
, _
):
121 self
._perf
_instance
= _PerfProfiler(self
._device
,
125 def StopTracing(self
):
126 if not self
._perf
_instance
:
128 self
._perf
_instance
.SignalAndWait()
131 def _GetInteractivePerfCommand(perfhost_path
, perf_profile
, symfs_dir
,
132 required_libs
, kallsyms
):
133 cmd
= '%s report -n -i %s --symfs %s --kallsyms %s' % (
134 os
.path
.relpath(perfhost_path
, '.'), perf_profile
, symfs_dir
, kallsyms
)
135 for lib
in required_libs
:
136 lib
= os
.path
.join(symfs_dir
, lib
[1:])
137 if not os
.path
.exists(lib
):
139 objdump_path
= android_profiling_helper
.GetToolchainBinaryPath(
142 cmd
+= ' --objdump %s' % os
.path
.relpath(objdump_path
, '.')
147 symfs_dir
= os
.path
.join(tempfile
.gettempdir(),
148 os
.path
.expandvars('$USER-perf-symfs'))
149 if not os
.path
.exists(symfs_dir
):
150 os
.makedirs(symfs_dir
)
151 required_libs
= set()
153 # Download the recorded perf profile.
154 perf_profile
= self
._perf
_instance
.PullResult(symfs_dir
)
156 android_profiling_helper
.GetRequiredLibrariesForPerfProfile(
158 if not required_libs
:
159 logging
.warning('No libraries required by perf trace. Most likely there '
160 'are no samples in the trace.')
162 # Build a symfs with all the necessary libraries.
163 kallsyms
= android_profiling_helper
.CreateSymFs(self
._device
,
167 perfhost_path
= binary_manager
.FetchPath(
168 android_profiling_helper
.GetPerfhostName(), 'x86_64', 'linux')
170 ui
.PrintMessage('\nNote: to view the profile in perf, run:')
171 ui
.PrintMessage(' ' + self
._GetInteractivePerfCommand
(perfhost_path
,
172 perf_profile
, symfs_dir
, required_libs
, kallsyms
))
174 # Convert the perf profile into JSON.
175 perf_script_path
= os
.path
.join(os
.path
.dirname(os
.path
.abspath(__file__
)),
176 'third_party', 'perf_to_tracing.py')
177 json_file_name
= os
.path
.basename(perf_profile
)
178 with
open(os
.devnull
, 'w') as dev_null
, \
179 open(json_file_name
, 'w') as json_file
:
180 cmd
= [perfhost_path
, 'script', '-s', perf_script_path
, '-i',
181 perf_profile
, '--symfs', symfs_dir
, '--kallsyms', kallsyms
]
182 if subprocess
.call(cmd
, stdout
=json_file
, stderr
=dev_null
):
183 logging
.warning('Perf data to JSON conversion failed. The result will '
184 'not contain any perf samples. You can still view the '
185 'perf data manually as shown above.')
188 return json_file_name