• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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