146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)# Copyright 2014 The Chromium Authors. All rights reserved.
246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)# found in the LICENSE file.
446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)import logging
646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)import os
75f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import signal
846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)import subprocess
946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)import sys
1046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)import tempfile
1146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
125f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)from profile_chrome import controllers
135f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)from profile_chrome import ui
1446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
1546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)from pylib import android_commands
1646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)from pylib import constants
17f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)from pylib.perf import perf_control
1846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
1946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)sys.path.append(os.path.join(constants.DIR_SOURCE_ROOT,
2046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                             'tools',
2146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                             'telemetry'))
2246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)try:
2346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  # pylint: disable=F0401
2446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  from telemetry.core.platform.profiler import android_profiling_helper
2546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  from telemetry.util import support_binaries
2646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)except ImportError:
2746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  android_profiling_helper = None
2846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  support_binaries = None
2946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
3046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
3146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)_PERF_OPTIONS = [
3246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    # Sample across all processes and CPUs to so that the current CPU gets
3346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    # recorded to each sample.
3446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    '--all-cpus',
3546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    # In perf 3.13 --call-graph requires an argument, so use the -g short-hand
3646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    # which does not.
3746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    '-g',
3846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    # Increase priority to avoid dropping samples. Requires root.
3946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    '--realtime', '80',
4046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    # Record raw samples to get CPU information.
4146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    '--raw-samples',
4246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    # Increase sampling frequency for better coverage.
4346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    '--freq', '2000',
4446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)]
4546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
4646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
4746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)class _PerfProfiler(object):
4846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  def __init__(self, device, perf_binary, categories):
4946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    self._device = device
5046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    self._output_file = android_commands.DeviceTempFile(
5146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        self._device.old_interface, prefix='perf_output')
5246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    self._log_file = tempfile.TemporaryFile()
5346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # TODO(jbudorick) Look at providing a way to unhandroll this once the
555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    #                 adb rewrite has fully landed.
565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    device_param = (['-s', str(self._device)] if str(self._device) else [])
5746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    cmd = ['adb'] + device_param + \
5846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          ['shell', perf_binary, 'record',
5946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)           '--output', self._output_file.name] + _PERF_OPTIONS
6046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if categories:
6146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      cmd += ['--event', ','.join(categories)]
62f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    self._perf_control = perf_control.PerfControl(self._device)
636d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    self._perf_control.SetPerfProfilingMode()
6446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    self._perf_process = subprocess.Popen(cmd,
6546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                          stdout=self._log_file,
6646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                          stderr=subprocess.STDOUT)
6746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
6846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  def SignalAndWait(self):
69116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    self._device.KillAll('perf', signum=signal.SIGINT)
7046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    self._perf_process.wait()
716d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    self._perf_control.SetDefaultPerfMode()
7246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
7346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  def _FailWithLog(self, msg):
7446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    self._log_file.seek(0)
7546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    log = self._log_file.read()
7646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    raise RuntimeError('%s. Log output:\n%s' % (msg, log))
7746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
7846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  def PullResult(self, output_path):
79116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    if not self._device.FileExists(self._output_file.name):
8046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      self._FailWithLog('Perf recorded no data')
8146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
8246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    perf_profile = os.path.join(output_path,
8346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                os.path.basename(self._output_file.name))
84116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    self._device.PullFile(self._output_file.name, perf_profile)
8546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if not os.stat(perf_profile).st_size:
8646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      os.remove(perf_profile)
8746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      self._FailWithLog('Perf recorded a zero-sized file')
8846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
8946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    self._log_file.close()
9046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    self._output_file.close()
9146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return perf_profile
9246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
9346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
9446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)class PerfProfilerController(controllers.BaseController):
9546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  def __init__(self, device, categories):
9646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    controllers.BaseController.__init__(self)
9746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    self._device = device
9846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    self._categories = categories
9946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    self._perf_binary = self._PrepareDevice(device)
10046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    self._perf_instance = None
10146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
10246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  def __repr__(self):
10346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return 'perf profile'
10446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
10546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  @staticmethod
10646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  def IsSupported():
10746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return bool(android_profiling_helper)
10846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
10946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  @staticmethod
11046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  def _PrepareDevice(device):
11146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if not 'BUILDTYPE' in os.environ:
11246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      os.environ['BUILDTYPE'] = 'Release'
11346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return android_profiling_helper.PrepareDeviceForPerf(device)
11446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
11546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  @classmethod
11646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  def GetCategories(cls, device):
11746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    perf_binary = cls._PrepareDevice(device)
118f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    return device.RunShellCommand('%s list' % perf_binary)
11946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
12046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  def StartTracing(self, _):
12146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    self._perf_instance = _PerfProfiler(self._device,
12246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                        self._perf_binary,
12346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                        self._categories)
12446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
12546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  def StopTracing(self):
12646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if not self._perf_instance:
12746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      return
12846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    self._perf_instance.SignalAndWait()
12946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
130f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  @staticmethod
131f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  def _GetInteractivePerfCommand(perfhost_path, perf_profile, symfs_dir,
132f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)                                 required_libs, kallsyms):
133f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    cmd = '%s report -n -i %s --symfs %s --kallsyms %s' % (
134f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)        os.path.relpath(perfhost_path, '.'), perf_profile, symfs_dir, kallsyms)
135f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    for lib in required_libs:
136f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      lib = os.path.join(symfs_dir, lib[1:])
137f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      if not os.path.exists(lib):
138f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)        continue
139f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      objdump_path = android_profiling_helper.GetToolchainBinaryPath(
140f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)          lib, 'objdump')
141f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      if objdump_path:
142f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)        cmd += ' --objdump %s' % os.path.relpath(objdump_path, '.')
143f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)        break
144f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    return cmd
145f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
14646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  def PullTrace(self):
14746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    symfs_dir = os.path.join(tempfile.gettempdir(),
14846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                             os.path.expandvars('$USER-perf-symfs'))
14946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if not os.path.exists(symfs_dir):
15046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      os.makedirs(symfs_dir)
15146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    required_libs = set()
15246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
15346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    # Download the recorded perf profile.
15446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    perf_profile = self._perf_instance.PullResult(symfs_dir)
15546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    required_libs = \
15646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        android_profiling_helper.GetRequiredLibrariesForPerfProfile(
15746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)            perf_profile)
15846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if not required_libs:
15946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      logging.warning('No libraries required by perf trace. Most likely there '
16046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                      'are no samples in the trace.')
16146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
16246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    # Build a symfs with all the necessary libraries.
16346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    kallsyms = android_profiling_helper.CreateSymFs(self._device,
16446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                                    symfs_dir,
16546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                                    required_libs,
16646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                                    use_symlinks=False)
1671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    perfhost_path = support_binaries.FindPath(
1681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        android_profiling_helper.GetPerfhostName(), 'linux')
169f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
170f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    ui.PrintMessage('\nNote: to view the profile in perf, run:')
171f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    ui.PrintMessage('  ' + self._GetInteractivePerfCommand(perfhost_path,
172f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)        perf_profile, symfs_dir, required_libs, kallsyms))
173f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
174f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    # Convert the perf profile into JSON.
1755f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    perf_script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
1765f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                    'third_party', 'perf_to_tracing.py')
17746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    json_file_name = os.path.basename(perf_profile)
17846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    with open(os.devnull, 'w') as dev_null, \
17946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        open(json_file_name, 'w') as json_file:
18046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      cmd = [perfhost_path, 'script', '-s', perf_script_path, '-i',
18146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)             perf_profile, '--symfs', symfs_dir, '--kallsyms', kallsyms]
182f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      if subprocess.call(cmd, stdout=json_file, stderr=dev_null):
183f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)        logging.warning('Perf data to JSON conversion failed. The result will '
184f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)                        'not contain any perf samples. You can still view the '
185f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)                        'perf data manually as shown above.')
186f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)        return None
187f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
18846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return json_file_name
189