<filename>heatmaps/heat_map.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2015 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Wrapper to generate heat maps for chrome."""

from __future__ import print_function

import argparse
import os
import shutil
import sys
import tempfile

from cros_utils import command_executer
from heatmaps import heatmap_generator


def IsARepoRoot(directory):
  """Returns True if directory is the root of a repo checkout."""
  return os.path.exists(
      os.path.join(os.path.realpath(os.path.expanduser(directory)), '.repo'))


class HeatMapProducer(object):
  """Class to produce heat map."""

  def __init__(self,
               chromeos_root,
               perf_data,
               hugepage,
               binary,
               title,
               logger=None):
    self.chromeos_root = os.path.realpath(os.path.expanduser(chromeos_root))
    self.perf_data = os.path.realpath(os.path.expanduser(perf_data))
    self.hugepage = hugepage
    self.dir = os.path.dirname(os.path.realpath(__file__))
    self.binary = binary
    self.ce = command_executer.GetCommandExecuter()
    self.temp_dir = ''
    self.temp_perf_inchroot = ''
    self.temp_dir_created = False
    self.perf_report = ''
    self.title = title
    self.logger = logger

  def _EnsureFileInChroot(self):
    chroot_prefix = os.path.join(self.chromeos_root, 'chroot')
    if self.perf_data.startswith(chroot_prefix):
      # If the path to perf_data starts with the same chromeos_root, assume
      # it's in the chromeos_root so no need for temporary directory and copy.
      self.temp_dir = self.perf_data.replace('perf.data', '')
      self.temp_perf_inchroot = self.temp_dir.replace(chroot_prefix, '')

    else:
      # Otherwise, create a temporary directory and copy perf.data into chroot.
      self.temp_dir = tempfile.mkdtemp(
          prefix=os.path.join(self.chromeos_root, 'src/'))
      temp_perf = os.path.join(self.temp_dir, 'perf.data')
      shutil.copy2(self.perf_data, temp_perf)
      self.temp_perf_inchroot = os.path.join('~/trunk/src',
                                             os.path.basename(self.temp_dir))
      self.temp_dir_created = True

  def _GeneratePerfReport(self):
    cmd = ('cd %s && perf report -D -i perf.data > perf_report.txt' %
           self.temp_perf_inchroot)
    retval = self.ce.ChrootRunCommand(self.chromeos_root, cmd)
    if retval:
      raise RuntimeError('Failed to generate perf report')
    self.perf_report = os.path.join(self.temp_dir, 'perf_report.txt')

  def _GetHeatMap(self, top_n_pages):
    generator = heatmap_generator.HeatmapGenerator(
        perf_report=self.perf_report,
        page_size=4096,
        hugepage=self.hugepage,
        title=self.title)
    generator.draw()
    # Analyze top N hottest symbols with the binary, if provided
    if self.binary:
      generator.analyze(self.binary, top_n_pages)

  def _RemoveFiles(self):
    files = [
        'out.txt', 'inst-histo.txt', 'inst-histo-hp.txt', 'inst-histo-sp.txt'
    ]
    for f in files:
      if os.path.exists(f):
        os.remove(f)

  def Run(self, top_n_pages):
    try:
      self._EnsureFileInChroot()
      self._GeneratePerfReport()
      self._GetHeatMap(top_n_pages)
    finally:
      self._RemoveFiles()
    msg = ('heat map and time histogram genereated in the current '
           'directory with name heat_map.png and timeline.png '
           'accordingly.')
    if self.binary:
      msg += ('\nThe hottest %d pages inside and outside hugepage '
              'is symbolized and saved to addr2symbol.txt' % top_n_pages)
    if self.logger:
      self.logger.LogOutput(msg)
    else:
      print(msg)


def main(argv):
  """Parse the options.

  Args:
    argv: The options with which this script was invoked.

  Returns:
    0 unless an exception is raised.
  """
  parser = argparse.ArgumentParser()

  parser.add_argument(
      '--chromeos_root',
      dest='chromeos_root',
      required=True,
      help='ChromeOS root to use for generate heatmaps.')
  parser.add_argument(
      '--perf_data',
      dest='perf_data',
      required=True,
      help='The raw perf data. Must be collected with -e instructions while '
      'disabling ASLR.')
  parser.add_argument(
      '--binary',
      dest='binary',
      help='The path to the Chrome binary. Only useful if want to print '
      'symbols on hottest pages',
      default=None)
  parser.add_argument(
      '--top_n',
      dest='top_n',
      type=int,
      default=10,
      help='Print out top N hottest pages within/outside huge page range. '
      'Must be used with --hugepage and --binary. (Default: %(default)s)')
  parser.add_argument(
      '--title', dest='title', help='Title of the heatmap', default='')
  parser.add_argument(
      '--hugepage',
      dest='hugepage',
      help='A range of addresses (start,end) where huge page starts and ends'
      ' in text section, separated by a comma.'
      ' Used to differentiate regions in heatmap.'
      ' Example: --hugepage=0,4096'
      ' If not specified, no effect on the heatmap.',
      default=None)

  options = parser.parse_args(argv)

  if not IsARepoRoot(options.chromeos_root):
    parser.error('%s does not contain .repo dir.' % options.chromeos_root)

  if not os.path.isfile(options.perf_data):
    parser.error('Cannot find perf_data: %s.' % options.perf_data)

  hugepage_range = None
  if options.hugepage:
    hugepage_range = options.hugepage.split(',')
    if len(hugepage_range) != 2 or \
     int(hugepage_range[0]) > int(hugepage_range[1]):
      parser.error('Wrong format of hugepage range: %s' % options.hugepage)
    hugepage_range = [int(x) for x in hugepage_range]

  heatmap_producer = HeatMapProducer(options.chromeos_root, options.perf_data,
                                     hugepage_range, options.binary,
                                     options.title)

  heatmap_producer.Run(options.top_n)


if __name__ == '__main__':
  sys.exit(main(sys.argv[1:]))

<filename>heatmaps/heat_map.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2015 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Wrapper to generate heat maps for chrome."""

from __future__ import print_function

import argparse
import os
import shutil
import sys
import tempfile

from cros_utils import command_executer
from heatmaps import heatmap_generator


def IsARepoRoot(directory):
  """Returns True if directory is the root of a repo checkout."""
  return os.path.exists(
      os.path.join(os.path.realpath(os.path.expanduser(directory)), '.repo'))


class HeatMapProducer(object):
  """Class to produce heat map."""

  def __init__(self,
               chromeos_root,
               perf_data,
               hugepage,
               binary,
               title,
               logger=None):
    self.chromeos_root = os.path.realpath(os.path.expanduser(chromeos_root))
    self.perf_data = os.path.realpath(os.path.expanduser(perf_data))
    self.hugepage = hugepage
    self.dir = os.path.dirname(os.path.realpath(__file__))
    self.binary = binary
    self.ce = command_executer.GetCommandExecuter()
    self.temp_dir = ''
    self.temp_perf_inchroot = ''
    self.temp_dir_created = False
    self.perf_report = ''
    self.title = title
    self.logger = logger

  def _EnsureFileInChroot(self):
    chroot_prefix = os.path.join(self.chromeos_root, 'chroot')
    if self.perf_data.startswith(chroot_prefix):
      # If the path to perf_data starts with the same chromeos_root, assume
      # it's in the chromeos_root so no need for temporary directory and copy.
      self.temp_dir = self.perf_data.replace('perf.data', '')
      self.temp_perf_inchroot = self.temp_dir.replace(chroot_prefix, '')

    else:
      # Otherwise, create a temporary directory and copy perf.data into chroot.
      self.temp_dir = tempfile.mkdtemp(
          prefix=os.path.join(self.chromeos_root, 'src/'))
      temp_perf = os.path.join(self.temp_dir, 'perf.data')
      shutil.copy2(self.perf_data, temp_perf)
      self.temp_perf_inchroot = os.path.join('~/trunk/src',
                                             os.path.basename(self.temp_dir))
      self.temp_dir_created = True

  def _GeneratePerfReport(self):
    cmd = ('cd %s && perf report -D -i perf.data > perf_report.txt' %
           self.temp_perf_inchroot)
    retval = self.ce.ChrootRunCommand(self.chromeos_root, cmd)
    if retval:
      raise RuntimeError('Failed to generate perf report')
    self.perf_report = os.path.join(self.temp_dir, 'perf_report.txt')

  def _GetHeatMap(self, top_n_pages):
    generator = heatmap_generator.HeatmapGenerator(
        perf_report=self.perf_report,
        page_size=4096,
        hugepage=self.hugepage,
        title=self.title)
    generator.draw()
    # Analyze top N hottest symbols with the binary, if provided
    if self.binary:
      generator.analyze(self.binary, top_n_pages)

  def _RemoveFiles(self):
    files = [
        'out.txt', 'inst-histo.txt', 'inst-histo-hp.txt', 'inst-histo-sp.txt'
    ]
    for f in files:
      if os.path.exists(f):
        os.remove(f)

  def Run(self, top_n_pages):
    try:
      self._EnsureFileInChroot()
      self._GeneratePerfReport()
      self._GetHeatMap(top_n_pages)
    finally:
      self._RemoveFiles()
    msg = ('heat map and time histogram genereated in the current '
           'directory with name heat_map.png and timeline.png '
           'accordingly.')
    if self.binary:
      msg += ('\nThe hottest %d pages inside and outside hugepage '
              'is symbolized and saved to addr2symbol.txt' % top_n_pages)
    if self.logger:
      self.logger.LogOutput(msg)
    else:
      print(msg)


def main(argv):
  """Parse the options.

  Args:
    argv: The options with which this script was invoked.

  Returns:
    0 unless an exception is raised.
  """
  parser = argparse.ArgumentParser()

  parser.add_argument(
      '--chromeos_root',
      dest='chromeos_root',
      required=True,
      help='ChromeOS root to use for generate heatmaps.')
  parser.add_argument(
      '--perf_data',
      dest='perf_data',
      required=True,
      help='The raw perf data. Must be collected with -e instructions while '
      'disabling ASLR.')
  parser.add_argument(
      '--binary',
      dest='binary',
      help='The path to the Chrome binary. Only useful if want to print '
      'symbols on hottest pages',
      default=None)
  parser.add_argument(
      '--top_n',
      dest='top_n',
      type=int,
      default=10,
      help='Print out top N hottest pages within/outside huge page range. '
      'Must be used with --hugepage and --binary. (Default: %(default)s)')
  parser.add_argument(
      '--title', dest='title', help='Title of the heatmap', default='')
  parser.add_argument(
      '--hugepage',
      dest='hugepage',
      help='A range of addresses (start,end) where huge page starts and ends'
      ' in text section, separated by a comma.'
      ' Used to differentiate regions in heatmap.'
      ' Example: --hugepage=0,4096'
      ' If not specified, no effect on the heatmap.',
      default=None)

  options = parser.parse_args(argv)

  if not IsARepoRoot(options.chromeos_root):
    parser.error('%s does not contain .repo dir.' % options.chromeos_root)

  if not os.path.isfile(options.perf_data):
    parser.error('Cannot find perf_data: %s.' % options.perf_data)

  hugepage_range = None
  if options.hugepage:
    hugepage_range = options.hugepage.split(',')
    if len(hugepage_range) != 2 or \
     int(hugepage_range[0]) > int(hugepage_range[1]):
      parser.error('Wrong format of hugepage range: %s' % options.hugepage)
    hugepage_range = [int(x) for x in hugepage_range]

  heatmap_producer = HeatMapProducer(options.chromeos_root, options.perf_data,
                                     hugepage_range, options.binary,
                                     options.title)

  heatmap_producer.Run(options.top_n)


if __name__ == '__main__':
  sys.exit(main(sys.argv[1:]))

#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright 2015 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. Wrapper to generate heat maps for chrome. Returns True if directory is the root of a repo checkout. Class to produce heat map. # If the path to perf_data starts with the same chromeos_root, assume # it's in the chromeos_root so no need for temporary directory and copy. # Otherwise, create a temporary directory and copy perf.data into chroot. # Analyze top N hottest symbols with the binary, if provided Parse the options.    Args:     argv: The options with which this script was invoked.    Returns:     0 unless an exception is raised.