1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# Copyright 2015 The ChromiumOS Authors 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7"""Wrapper to generate heat maps for chrome.""" 8 9 10import argparse 11import os 12import shutil 13import sys 14import tempfile 15 16from cros_utils import command_executer 17from heatmaps import heatmap_generator 18 19 20def IsARepoRoot(directory): 21 """Returns True if directory is the root of a repo checkout.""" 22 return os.path.exists( 23 os.path.join(os.path.realpath(os.path.expanduser(directory)), ".repo") 24 ) 25 26 27class HeatMapProducer(object): 28 """Class to produce heat map.""" 29 30 def __init__( 31 self, chromeos_root, perf_data, hugepage, binary, title, logger=None 32 ): 33 self.chromeos_root = os.path.realpath(os.path.expanduser(chromeos_root)) 34 self.perf_data = os.path.realpath(os.path.expanduser(perf_data)) 35 self.hugepage = hugepage 36 self.dir = os.path.dirname(os.path.realpath(__file__)) 37 self.binary = binary 38 self.ce = command_executer.GetCommandExecuter() 39 self.temp_dir = "" 40 self.temp_perf_inchroot = "" 41 self.temp_dir_created = False 42 self.perf_report = "" 43 self.title = title 44 self.logger = logger 45 46 def _EnsureFileInChroot(self): 47 chroot_prefix = os.path.join(self.chromeos_root, "chroot") 48 if self.perf_data.startswith(chroot_prefix): 49 # If the path to perf_data starts with the same chromeos_root, assume 50 # it's in the chromeos_root so no need for temporary directory and copy. 51 self.temp_dir = self.perf_data.replace("perf.data", "") 52 self.temp_perf_inchroot = self.temp_dir.replace(chroot_prefix, "") 53 54 else: 55 # Otherwise, create a temporary directory and copy perf.data into chroot. 56 self.temp_dir = tempfile.mkdtemp( 57 prefix=os.path.join(self.chromeos_root, "src/") 58 ) 59 temp_perf = os.path.join(self.temp_dir, "perf.data") 60 shutil.copy2(self.perf_data, temp_perf) 61 self.temp_perf_inchroot = os.path.join( 62 "~/trunk/src", os.path.basename(self.temp_dir) 63 ) 64 self.temp_dir_created = True 65 66 def _GeneratePerfReport(self): 67 cmd = ( 68 "cd %s && perf report -D -i perf.data > perf_report.txt" 69 % self.temp_perf_inchroot 70 ) 71 retval = self.ce.ChrootRunCommand(self.chromeos_root, cmd) 72 if retval: 73 raise RuntimeError("Failed to generate perf report") 74 self.perf_report = os.path.join(self.temp_dir, "perf_report.txt") 75 76 def _GetHeatMap(self, top_n_pages): 77 generator = heatmap_generator.HeatmapGenerator( 78 perf_report=self.perf_report, 79 page_size=4096, 80 hugepage=self.hugepage, 81 title=self.title, 82 ) 83 generator.draw() 84 # Analyze top N hottest symbols with the binary, if provided 85 if self.binary: 86 generator.analyze(self.binary, top_n_pages) 87 88 def _RemoveFiles(self): 89 files = [ 90 "out.txt", 91 "inst-histo.txt", 92 "inst-histo-hp.txt", 93 "inst-histo-sp.txt", 94 ] 95 for f in files: 96 if os.path.exists(f): 97 os.remove(f) 98 99 def Run(self, top_n_pages): 100 try: 101 self._EnsureFileInChroot() 102 self._GeneratePerfReport() 103 self._GetHeatMap(top_n_pages) 104 finally: 105 self._RemoveFiles() 106 msg = ( 107 "heat map and time histogram genereated in the current " 108 "directory with name heat_map.png and timeline.png " 109 "accordingly." 110 ) 111 if self.binary: 112 msg += ( 113 "\nThe hottest %d pages inside and outside hugepage " 114 "is symbolized and saved to addr2symbol.txt" % top_n_pages 115 ) 116 if self.logger: 117 self.logger.LogOutput(msg) 118 else: 119 print(msg) 120 121 122def main(argv): 123 """Parse the options. 124 125 Args: 126 argv: The options with which this script was invoked. 127 128 Returns: 129 0 unless an exception is raised. 130 """ 131 parser = argparse.ArgumentParser() 132 133 parser.add_argument( 134 "--chromeos_root", 135 dest="chromeos_root", 136 required=True, 137 help="ChromeOS root to use for generate heatmaps.", 138 ) 139 parser.add_argument( 140 "--perf_data", 141 dest="perf_data", 142 required=True, 143 help="The raw perf data. Must be collected with -e instructions while " 144 "disabling ASLR.", 145 ) 146 parser.add_argument( 147 "--binary", 148 dest="binary", 149 help="The path to the Chrome binary. Only useful if want to print " 150 "symbols on hottest pages", 151 default=None, 152 ) 153 parser.add_argument( 154 "--top_n", 155 dest="top_n", 156 type=int, 157 default=10, 158 help="Print out top N hottest pages within/outside huge page range. " 159 "Must be used with --hugepage and --binary. (Default: %(default)s)", 160 ) 161 parser.add_argument( 162 "--title", dest="title", help="Title of the heatmap", default="" 163 ) 164 parser.add_argument( 165 "--hugepage", 166 dest="hugepage", 167 help="A range of addresses (start,end) where huge page starts and ends" 168 " in text section, separated by a comma." 169 " Used to differentiate regions in heatmap." 170 " Example: --hugepage=0,4096" 171 " If not specified, no effect on the heatmap.", 172 default=None, 173 ) 174 175 options = parser.parse_args(argv) 176 177 if not IsARepoRoot(options.chromeos_root): 178 parser.error("%s does not contain .repo dir." % options.chromeos_root) 179 180 if not os.path.isfile(options.perf_data): 181 parser.error("Cannot find perf_data: %s." % options.perf_data) 182 183 hugepage_range = None 184 if options.hugepage: 185 hugepage_range = options.hugepage.split(",") 186 if len(hugepage_range) != 2 or int(hugepage_range[0]) > int( 187 hugepage_range[1] 188 ): 189 parser.error( 190 "Wrong format of hugepage range: %s" % options.hugepage 191 ) 192 hugepage_range = [int(x) for x in hugepage_range] 193 194 heatmap_producer = HeatMapProducer( 195 options.chromeos_root, 196 options.perf_data, 197 hugepage_range, 198 options.binary, 199 options.title, 200 ) 201 202 heatmap_producer.Run(options.top_n) 203 204 205if __name__ == "__main__": 206 sys.exit(main(sys.argv[1:])) 207