aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorge Burgess IV <gbiv@google.com>2023-07-13 14:40:35 -0600
committerChromeos LUCI <chromeos-scoped@luci-project-accounts.iam.gserviceaccount.com>2023-07-13 22:34:13 +0000
commit99e441aedcd071ecfb72f14a86b9566ee52bbda9 (patch)
treeaefcf21d012d07c8f023e703f8706b5f4f04c1a7
parentb3e3219b54f42c5322f30d486b2ebea60e430d77 (diff)
downloadtoolchain-utils-99e441aedcd071ecfb72f14a86b9566ee52bbda9.tar.gz
heatmaps: remove
This tool was used primarily to turn perf.data profiles into pictures representing where in the binary %ip was over time. It almost certainly hasn't been used in many years, and as LLVM has gotten better at promoting code locality (especially through things like ThinLTO), it seems like it's increasingly unlikely for this to be relevant to us in the future. BUG=b:286091080 TEST=None Change-Id: Id1da1485f40b4aa974125167b06215431867373d Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/4683721 Reviewed-by: Jordan Abrahams-Whitehead <ajordanr@google.com> Commit-Queue: George Burgess <gbiv@chromium.org> Tested-by: George Burgess <gbiv@chromium.org>
-rwxr-xr-xheatmaps/heat_map.py206
-rwxr-xr-xheatmaps/heat_map_test.py179
-rw-r--r--heatmaps/heatmap_generator.py524
-rwxr-xr-xheatmaps/heatmap_generator_test.py326
-rwxr-xr-xheatmaps/perf-to-inst-page.sh68
5 files changed, 0 insertions, 1303 deletions
diff --git a/heatmaps/heat_map.py b/heatmaps/heat_map.py
deleted file mode 100755
index a3c52369..00000000
--- a/heatmaps/heat_map.py
+++ /dev/null
@@ -1,206 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-# Copyright 2015 The ChromiumOS Authors
-# 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."""
-
-
-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:]))
diff --git a/heatmaps/heat_map_test.py b/heatmaps/heat_map_test.py
deleted file mode 100755
index 96300bb4..00000000
--- a/heatmaps/heat_map_test.py
+++ /dev/null
@@ -1,179 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#
-# Copyright 2019 The ChromiumOS Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Tests for heat_map.py."""
-
-
-import os
-import unittest
-import unittest.mock as mock
-
-from cros_utils import command_executer
-from heatmaps import heat_map
-from heatmaps import heatmap_generator
-
-
-def make_heatmap(
- chromeos_root="/path/to/fake/chromeos_root/",
- perf_data="/any_path/perf.data",
-):
- return heat_map.HeatMapProducer(chromeos_root, perf_data, None, None, "")
-
-
-def fake_mkdtemp(prefix):
- """Mock tempfile.mkdtemp() by just create a pathname."""
- return prefix + "random_dir"
-
-
-def fake_parser_error(_, msg):
- """Redirect parser.error() to exception."""
- raise Exception(msg)
-
-
-def fake_generate_perf_report_exception(_):
- raise Exception
-
-
-class HeatmapTest(unittest.TestCase):
- """All of our tests for heat_map."""
-
- # pylint: disable=protected-access
- @mock.patch("shutil.copy2")
- @mock.patch("tempfile.mkdtemp")
- def test_EnsureFileInChrootAlreadyInside(self, mock_mkdtemp, mock_copy):
- perf_data_inchroot = (
- "/path/to/fake/chromeos_root/chroot/inchroot_path/perf.data"
- )
- heatmap = make_heatmap(perf_data=perf_data_inchroot)
- heatmap._EnsureFileInChroot()
- self.assertFalse(heatmap.temp_dir_created)
- self.assertEqual(
- heatmap.temp_dir,
- "/path/to/fake/chromeos_root/chroot/inchroot_path/",
- )
- self.assertEqual(heatmap.temp_perf_inchroot, "/inchroot_path/")
- mock_mkdtemp.assert_not_called()
- mock_copy.assert_not_called()
-
- @mock.patch("shutil.copy2")
- @mock.patch("tempfile.mkdtemp", fake_mkdtemp)
- def test_EnsureFileInChrootOutsideNeedCopy(self, mock_copy):
- heatmap = make_heatmap()
- heatmap._EnsureFileInChroot()
- self.assertTrue(heatmap.temp_dir_created)
- self.assertEqual(mock_copy.call_count, 1)
- self.assertEqual(
- heatmap.temp_dir, "/path/to/fake/chromeos_root/src/random_dir"
- )
- self.assertEqual(heatmap.temp_perf_inchroot, "~/trunk/src/random_dir")
-
- @mock.patch.object(command_executer.CommandExecuter, "ChrootRunCommand")
- def test_GeneratePerfReport(self, mock_ChrootRunCommand):
- heatmap = make_heatmap()
- heatmap.temp_dir = "/fake/chroot/inchroot_path/"
- heatmap.temp_perf_inchroot = "/inchroot_path/"
- mock_ChrootRunCommand.return_value = 0
- heatmap._GeneratePerfReport()
- cmd = (
- "cd %s && perf report -D -i perf.data > perf_report.txt"
- % heatmap.temp_perf_inchroot
- )
- mock_ChrootRunCommand.assert_called_with(heatmap.chromeos_root, cmd)
- self.assertEqual(mock_ChrootRunCommand.call_count, 1)
- self.assertEqual(
- heatmap.perf_report, "/fake/chroot/inchroot_path/perf_report.txt"
- )
-
- @mock.patch.object(heatmap_generator, "HeatmapGenerator")
- def test_GetHeatMap(self, mock_heatmap_generator):
- heatmap = make_heatmap()
- heatmap._GetHeatMap(10)
- self.assertTrue(mock_heatmap_generator.called)
-
- @mock.patch.object(heat_map.HeatMapProducer, "_EnsureFileInChroot")
- @mock.patch.object(heat_map.HeatMapProducer, "_GeneratePerfReport")
- @mock.patch.object(heat_map.HeatMapProducer, "_GetHeatMap")
- @mock.patch.object(heat_map.HeatMapProducer, "_RemoveFiles")
- def test_Run(
- self,
- mock_remove_files,
- mock_get_heatmap,
- mock_generate_perf_report,
- mock_ensure_file_in_chroot,
- ):
- heatmap = make_heatmap()
- heatmap.Run(10)
- mock_ensure_file_in_chroot.assert_called_once_with()
- mock_generate_perf_report.assert_called_once_with()
- mock_get_heatmap.assert_called_once_with(10)
- mock_remove_files.assert_called_once_with()
-
- @mock.patch.object(heat_map.HeatMapProducer, "_EnsureFileInChroot")
- @mock.patch.object(
- heat_map.HeatMapProducer,
- "_GeneratePerfReport",
- new=fake_generate_perf_report_exception,
- )
- @mock.patch.object(heat_map.HeatMapProducer, "_GetHeatMap")
- @mock.patch.object(heat_map.HeatMapProducer, "_RemoveFiles")
- @mock.patch("builtins.print")
- def test_Run_with_exception(
- self,
- mock_print,
- mock_remove_files,
- mock_get_heatmap,
- mock_ensure_file_in_chroot,
- ):
- heatmap = make_heatmap()
- with self.assertRaises(Exception):
- heatmap.Run(10)
- mock_ensure_file_in_chroot.assert_called_once_with()
- mock_get_heatmap.assert_not_called()
- mock_remove_files.assert_called_once_with()
- mock_print.assert_not_called()
-
- @mock.patch("argparse.ArgumentParser.error", fake_parser_error)
- @mock.patch.object(os.path, "isfile")
- @mock.patch.object(heat_map, "IsARepoRoot")
- def test_main_arg_format(self, mock_IsARepoRoot, mock_isfile):
- """Test wrong arg format are detected."""
- args = [
- "--chromeos_root=/fake/chroot/",
- "--perf_data=/path/to/perf.data",
- ]
-
- # Test --chromeos_root format
- mock_IsARepoRoot.return_value = False
- with self.assertRaises(Exception) as msg:
- heat_map.main(args)
- self.assertIn("does not contain .repo dir.", str(msg.exception))
-
- # Test --perf_data format
- mock_IsARepoRoot.return_value = True
- mock_isfile.return_value = False
- with self.assertRaises(Exception) as msg:
- heat_map.main(args)
- self.assertIn("Cannot find perf_data", str(msg.exception))
-
- # Test --hugepage format
- mock_isfile.return_value = True
- args.append("--hugepage=0")
- with self.assertRaises(Exception) as msg:
- heat_map.main(args)
- self.assertIn("Wrong format of hugepage range", str(msg.exception))
-
- # Test --hugepage parse
- args[-1] = "--hugepage=0,4096"
- heat_map.HeatMapProducer = mock.MagicMock()
- heat_map.main(args)
- heat_map.HeatMapProducer.assert_called_with(
- "/fake/chroot/", "/path/to/perf.data", [0, 4096], None, ""
- )
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/heatmaps/heatmap_generator.py b/heatmaps/heatmap_generator.py
deleted file mode 100644
index 703c37d4..00000000
--- a/heatmaps/heatmap_generator.py
+++ /dev/null
@@ -1,524 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright 2018 The ChromiumOS Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Python module to draw heat map for Chrome
-
-heat map is a histogram used to analyze the locality of function layout.
-
-This module is used by heat_map.py. HeatmapGenerator is a class to
-generate data for drawing heat maps (the actual drawing of heat maps is
-performed by another script perf-to-inst-page.sh). It can also analyze
-the symbol names in hot pages.
-"""
-
-
-import bisect
-import collections
-import os
-import pipes
-import subprocess
-
-from cros_utils import command_executer
-
-
-HugepageRange = collections.namedtuple("HugepageRange", ["start", "end"])
-
-
-class MMap(object):
- """Class to store mmap information in perf report.
-
- We assume ASLR is disabled, so MMap for all Chrome is assumed to be
- the same. This class deals with the case hugepage creates several
- mmaps for Chrome but should be merged together. In these case, we
- assume the first MMAP is not affected by the bug and use the MMAP.
- """
-
- def __init__(self, addr, size, offset):
- self.start_address = addr
- self.size = size
- self.offset = offset
-
- def __str__(self):
- return "(%x, %x, %x)" % (self.start_address, self.size, self.offset)
-
- def merge(self, mmap):
- # This function should not be needed, since we should only have
- # one MMAP on Chrome of each process. This function only deals with
- # images that is affected by http://crbug.com/931465.
-
- # This function is only checking a few conditions to make sure
- # the bug is within our expectation.
-
- if self.start_address == mmap.start_address:
- assert (
- self.size >= mmap.size
- ), "Original MMAP size(%x) is smaller than the forked process(%x)." % (
- self.size,
- mmap.size,
- )
- # The case that the MMAP is forked from the previous process
- # No need to do anything, OR
- # The case where hugepage causes a small Chrome mmap.
- # In this case, we use the prior MMAP for the whole Chrome
- return
-
- assert self.start_address < mmap.start_address, (
- "Original MMAP starting address(%x) is larger than the forked"
- "process(%x)." % (self.start_address, mmap.start_address)
- )
-
- assert (
- self.start_address + self.size >= mmap.start_address + mmap.size
- ), "MMAP of the forked process exceeds the end of original MMAP."
-
-
-class HeatmapGenerator(object):
- """Class to generate heat map with a perf report, containing mmaps and
-
- samples. This class contains two interfaces with other modules:
- draw() and analyze().
-
- draw() draws a heatmap with the sample information given in the perf report
- analyze() prints out the symbol names in hottest pages with the given
- chrome binary
- """
-
- def __init__(
- self, perf_report, page_size, hugepage, title, log_level="verbose"
- ):
- self.perf_report = perf_report
- # Pick 1G as a relatively large number. All addresses less than it will
- # be recorded. The actual heatmap will show up to a boundary of the
- # largest address in text segment.
- self.max_addr = 1024 * 1024 * 1024
- self.ce = command_executer.GetCommandExecuter(log_level=log_level)
- self.dir = os.path.dirname(os.path.realpath(__file__))
- with open(perf_report, "r", encoding="utf-8") as f:
- self.perf_report_contents = f.readlines()
- # Write histogram results to a text file, in order to use gnu plot to draw
- self.hist_temp_output = open("out.txt", "w", encoding="utf-8")
- self.processes = {}
- self.deleted_processes = {}
- self.count = 0
- if hugepage:
- self.hugepage = HugepageRange(start=hugepage[0], end=hugepage[1])
- else:
- self.hugepage = None
- self.title = title
- self.symbol_addresses = []
- self.symbol_names = []
- self.page_size = page_size
-
- def _parse_perf_sample(self, line):
- # In a perf report, generated with -D, a PERF_RECORD_SAMPLE command should
- # look like this: TODO: some arguments are unknown
- #
- # cpuid cycle unknown [unknown]: PERF_RECORD_SAMPLE(IP, 0x2): pid/tid:
- # 0xaddr period: period addr: addr
- # ... thread: threadname:tid
- # ...... dso: process
- #
- # This is an example:
- # 1 136712833349 0x6a558 [0x30]: PERF_RECORD_SAMPLE(IP, 0x2): 5227/5227:
- # 0x55555683b810 period: 372151 addr: 0
- # ... thread: chrome:5227
- # ...... dso: /opt/google/chrome/chrome
- #
- # For this function, the 7th argument (args[6]) after spltting with spaces
- # is pid/tid. We use the combination of the two as the pid.
- # Also, we add an assertion here to check the tid in the 7th argument(
- # args[6]) and the 15th argument(arg[14]) are the same
- #
- # The function returns the ((pid,tid), address) pair if the sampling
- # is on Chrome. Otherwise, return (None, None) pair.
-
- if (
- "thread: chrome" not in line
- or "dso: /opt/google/chrome/chrome" not in line
- ):
- return None, None
- args = line.split(" ")
- pid_raw = args[6].split("/")
- assert (
- pid_raw[1][:-1] == args[14].split(":")[1][:-1]
- ), "TID in %s of sample is not the same: %s/%s" % (
- line[:-1],
- pid_raw[1][:-1],
- args[14].split(":")[1][:-1],
- )
- key = (int(pid_raw[0]), int(pid_raw[1][:-1]))
- address = int(args[7], base=16)
- return key, address
-
- def _parse_perf_record(self, line):
- # In a perf report, generated with -D, a PERF_RECORD_MMAP2 command should
- # look like this: TODO: some arguments are unknown
- #
- # cpuid cycle unknown [unknown]: PERF_RECORD_MMAP2 pid/tid:
- # [0xaddr(0xlength) @ pageoffset maj:min ino ino_generation]:
- # permission process
- #
- # This is an example.
- # 2 136690556823 0xa6898 [0x80]: PERF_RECORD_MMAP2 5227/5227:
- # [0x555556496000(0x8d1b000) @ 0xf42000 b3:03 92844 1892514370]:
- # r-xp /opt/google/chrome/chrome
- #
- # For this function, the 6th argument (args[5]) after spltting with spaces
- # is pid/tid. We use the combination of the two as the pid.
- # The 7th argument (args[6]) is the [0xaddr(0xlength). We can peel the
- # string to get the address and size of the mmap.
- # The 9th argument (args[8]) is the page offset.
- # The function returns the ((pid,tid), mmap) pair if the mmap is for Chrome
- # is on Chrome. Otherwise, return (None, None) pair.
-
- if "chrome/chrome" not in line:
- return None, None
- args = line.split(" ")
- pid_raw = args[5].split("/")
- assert (
- pid_raw[0] == pid_raw[1][:-1]
- ), "PID in %s of mmap is not the same: %s/%s" % (
- line[:-1],
- pid_raw[0],
- pid_raw[1],
- )
- pid = (int(pid_raw[0]), int(pid_raw[1][:-1]))
- address_raw = args[6].split("(")
- start_address = int(address_raw[0][1:], base=16)
- size = int(address_raw[1][:-1], base=16)
- offset = int(args[8], base=16)
- # Return an mmap object instead of only starting address,
- # in case there are many mmaps for the sample PID
- return pid, MMap(start_address, size, offset)
-
- def _parse_pair_event(self, arg):
- # This function is called by the _parse_* functions that has a pattern of
- # pids like: (pid:tid):(pid:tid), i.e.
- # PERF_RECORD_FORK and PERF_RECORD_COMM
- _, remain = arg.split("(", 1)
- pid1, remain = remain.split(":", 1)
- pid2, remain = remain.split(")", 1)
- _, remain = remain.split("(", 1)
- pid3, remain = remain.split(":", 1)
- pid4, remain = remain.split(")", 1)
- return (int(pid1), int(pid2)), (int(pid3), int(pid4))
-
- def _process_perf_record(self, line):
- # This function calls _parse_perf_record() to get information from
- # PERF_RECORD_MMAP2. It records the mmap object for each pid (a pair of
- # pid,tid), into a dictionary.
- pid, mmap = self._parse_perf_record(line)
- if pid is None:
- # PID = None meaning the mmap is not for chrome
- return
- if pid in self.processes:
- # This should never happen for a correct profiling result, as we
- # should only have one MMAP for Chrome for each process.
- # If it happens, see http://crbug.com/931465
- self.processes[pid].merge(mmap)
- else:
- self.processes[pid] = mmap
-
- def _process_perf_fork(self, line):
- # In a perf report, generated with -D, a PERF_RECORD_FORK command should
- # look like this:
- #
- # cpuid cycle unknown [unknown]:
- # PERF_RECORD_FORK(pid_to:tid_to):(pid_from:tid_from)
- #
- # This is an example.
- # 0 0 0x22a8 [0x38]: PERF_RECORD_FORK(1:1):(0:0)
- #
- # In this function, we need to peel the information of pid:tid pairs
- # So we get the last argument and send it to function _parse_pair_event()
- # for analysis.
- # We use (pid, tid) as the pid.
- args = line.split(" ")
- pid_to, pid_from = self._parse_pair_event(args[-1])
- if pid_from in self.processes:
- assert pid_to not in self.processes
- self.processes[pid_to] = MMap(
- self.processes[pid_from].start_address,
- self.processes[pid_from].size,
- self.processes[pid_from].offset,
- )
-
- def _process_perf_exit(self, line):
- # In a perf report, generated with -D, a PERF_RECORD_EXIT command should
- # look like this:
- #
- # cpuid cycle unknown [unknown]:
- # PERF_RECORD_EXIT(pid1:tid1):(pid2:tid2)
- #
- # This is an example.
- # 1 136082505621 0x30810 [0x38]: PERF_RECORD_EXIT(3851:3851):(3851:3851)
- #
- # In this function, we need to peel the information of pid:tid pairs
- # So we get the last argument and send it to function _parse_pair_event()
- # for analysis.
- # We use (pid, tid) as the pid.
- args = line.split(" ")
- pid_to, pid_from = self._parse_pair_event(args[-1])
- assert pid_to == pid_from, "(%d, %d) (%d, %d)" % (
- pid_to[0],
- pid_to[1],
- pid_from[0],
- pid_from[1],
- )
- if pid_to in self.processes:
- # Don't delete the process yet
- self.deleted_processes[pid_from] = self.processes[pid_from]
-
- def _process_perf_sample(self, line):
- # This function calls _parse_perf_sample() to get information from
- # the perf report.
- # It needs to check the starting address of allocated mmap from
- # the dictionary (self.processes) to calculate the offset within
- # the text section of the sampling.
- # The offset is calculated into pages (4KB or 2MB) and writes into
- # out.txt together with the total counts, which will be used to
- # calculate histogram.
- pid, addr = self._parse_perf_sample(line)
- if pid is None:
- return
-
- assert (
- pid in self.processes and pid not in self.deleted_processes
- ), "PID %d not found mmap and not forked from another process"
-
- start_address = self.processes[pid].start_address
- address = addr - start_address
- assert (
- address >= 0
- and "addresses accessed in PERF_RECORD_SAMPLE should be larger than"
- " the starting address of Chrome"
- )
- if address < self.max_addr:
- self.count += 1
- line = "%d/%d: %d %d" % (
- pid[0],
- pid[1],
- self.count,
- address // self.page_size * self.page_size,
- )
- if self.hugepage:
- if self.hugepage.start <= address < self.hugepage.end:
- line += " hugepage"
- else:
- line += " smallpage"
- print(line, file=self.hist_temp_output)
-
- def _read_perf_report(self):
- # Serve as main function to read perf report, generated by -D
- lines = iter(self.perf_report_contents)
- for line in lines:
- if "PERF_RECORD_MMAP" in line:
- self._process_perf_record(line)
- elif "PERF_RECORD_FORK" in line:
- self._process_perf_fork(line)
- elif "PERF_RECORD_EXIT" in line:
- self._process_perf_exit(line)
- elif "PERF_RECORD_SAMPLE" in line:
- # Perf sample is multi-line
- self._process_perf_sample(line + next(lines) + next(lines))
- self.hist_temp_output.close()
-
- def _draw_heat_map(self):
- # Calls a script (perf-to-inst-page.sh) to calculate histogram
- # of results written in out.txt and also generate pngs for
- # heat maps.
- heatmap_script = os.path.join(self.dir, "perf-to-inst-page.sh")
- if self.hugepage:
- hp_arg = "hugepage"
- else:
- hp_arg = "none"
-
- cmd = "{0} {1} {2}".format(
- heatmap_script, pipes.quote(self.title), hp_arg
- )
- retval = self.ce.RunCommand(cmd)
- if retval:
- raise RuntimeError("Failed to run script to generate heatmap")
-
- def _restore_histogram(self):
- # When hugepage is used, there are two files inst-histo-{hp,sp}.txt
- # So we need to read in all the files.
- names = [
- x for x in os.listdir(".") if "inst-histo" in x and ".txt" in x
- ]
- hist = {}
- for n in names:
- with open(n, encoding="utf-8") as f:
- for l in f.readlines():
- num, addr = l.strip().split(" ")
- assert int(addr) not in hist
- hist[int(addr)] = int(num)
- return hist
-
- def _read_symbols_from_binary(self, binary):
- # FIXME: We are using nm to read symbol names from Chrome binary
- # for now. Can we get perf to hand us symbol names, instead of
- # using nm in the future?
- #
- # Get all the symbols (and their starting addresses) that fall into
- # the page. Will be used to print out information of hot pages
- # Each line shows the information of a symbol:
- # [symbol value (0xaddr)] [symbol type] [symbol name]
- # For some symbols, the [symbol name] field might be missing.
- # e.g.
- # 0000000001129da0 t Builtins_LdaNamedPropertyHandler
-
- # Generate a list of symbols from nm tool and check each line
- # to extract symbols names
- text_section_start = 0
- for l in subprocess.check_output(["nm", "-n", binary]).split("\n"):
- args = l.strip().split(" ")
- if len(args) < 3:
- # No name field
- continue
- addr_raw, symbol_type, name = args
- addr = int(addr_raw, base=16)
- if "t" not in symbol_type and "T" not in symbol_type:
- # Filter out symbols not in text sections
- continue
- if not self.symbol_addresses:
- # The first symbol in text sections
- text_section_start = addr
- self.symbol_addresses.append(0)
- self.symbol_names.append(name)
- else:
- assert (
- text_section_start != 0
- ), "The starting address of text section has not been found"
- if addr == self.symbol_addresses[-1]:
- # if the same address has multiple symbols, put them together
- # and separate symbol names with '/'
- self.symbol_names[-1] += "/" + name
- else:
- # The output of nm -n command is already sorted by address
- # Insert to the end will result in a sorted array for bisect
- self.symbol_addresses.append(addr - text_section_start)
- self.symbol_names.append(name)
-
- def _map_addr_to_symbol(self, addr):
- # Find out the symbol name
- assert self.symbol_addresses
- index = bisect.bisect(self.symbol_addresses, addr)
- assert (
- 0 < index <= len(self.symbol_names)
- ), "Failed to find an index (%d) in the list (len=%d)" % (
- index,
- len(self.symbol_names),
- )
- return self.symbol_names[index - 1]
-
- def _print_symbols_in_hot_pages(self, fp, pages_to_show):
- # Print symbols in all the pages of interest
- for page_num, sample_num in pages_to_show:
- print(
- "----------------------------------------------------------",
- file=fp,
- )
- print(
- "Page Offset: %d MB, Count: %d"
- % (page_num // 1024 // 1024, sample_num),
- file=fp,
- )
-
- symbol_counts = collections.Counter()
- # Read Sample File and find out the occurance of symbols in the page
- lines = iter(self.perf_report_contents)
- for line in lines:
- if "PERF_RECORD_SAMPLE" in line:
- pid, addr = self._parse_perf_sample(
- line + next(lines) + next(lines)
- )
- if pid is None:
- # The sampling is not on Chrome
- continue
- if (
- addr // self.page_size
- != (self.processes[pid].start_address + page_num)
- // self.page_size
- ):
- # Sampling not in the current page
- continue
-
- name = self._map_addr_to_symbol(
- addr - self.processes[pid].start_address
- )
- assert name, "Failed to find symbol name of addr %x" % addr
- symbol_counts[name] += 1
-
- assert (
- sum(symbol_counts.values()) == sample_num
- ), "Symbol name matching missing for some addresses: %d vs %d" % (
- sum(symbol_counts.values()),
- sample_num,
- )
-
- # Print out the symbol names sorted by the number of samples in
- # the page
- for name, count in sorted(
- symbol_counts.items(), key=lambda kv: kv[1], reverse=True
- ):
- if count == 0:
- break
- print("> %s : %d" % (name, count), file=fp)
- print("\n\n", file=fp)
-
- def draw(self):
- # First read perf report to process information and save histogram
- # into a text file
- self._read_perf_report()
- # Then use gnu plot to draw heat map
- self._draw_heat_map()
-
- def analyze(self, binary, top_n):
- # Read histogram from histo.txt
- hist = self._restore_histogram()
- # Sort the pages in histogram
- sorted_hist = sorted(
- hist.items(), key=lambda value: value[1], reverse=True
- )
-
- # Generate symbolizations
- self._read_symbols_from_binary(binary)
-
- # Write hottest pages
- with open("addr2symbol.txt", "w", encoding="utf-8") as fp:
- if self.hugepage:
- # Print hugepage region first
- print(
- "Hugepage top %d hot pages (%d MB - %d MB):"
- % (
- top_n,
- self.hugepage.start // 1024 // 1024,
- self.hugepage.end // 1024 // 1024,
- ),
- file=fp,
- )
- pages_to_print = [
- (k, v)
- for k, v in sorted_hist
- if self.hugepage.start <= k < self.hugepage.end
- ][:top_n]
- self._print_symbols_in_hot_pages(fp, pages_to_print)
- print("==========================================", file=fp)
- print(
- "Top %d hot pages landed outside of hugepage:" % top_n,
- file=fp,
- )
- # Then print outside pages
- pages_to_print = [
- (k, v)
- for k, v in sorted_hist
- if k < self.hugepage.start or k >= self.hugepage.end
- ][:top_n]
- self._print_symbols_in_hot_pages(fp, pages_to_print)
- else:
- # Print top_n hottest pages.
- pages_to_print = sorted_hist[:top_n]
- self._print_symbols_in_hot_pages(fp, pages_to_print)
diff --git a/heatmaps/heatmap_generator_test.py b/heatmaps/heatmap_generator_test.py
deleted file mode 100755
index 898c7370..00000000
--- a/heatmaps/heatmap_generator_test.py
+++ /dev/null
@@ -1,326 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-# Copyright 2018 The ChromiumOS Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Tests for heatmap_generator.py."""
-
-
-import os
-import unittest
-import unittest.mock as mock
-
-from heatmaps import heatmap_generator
-
-
-def _write_perf_mmap(pid, tid, addr, size, fp):
- print(
- "0 0 0 0 PERF_RECORD_MMAP2 %d/%d: "
- "[%x(%x) @ 0x0 0:0 0 0] "
- "r-xp /opt/google/chrome/chrome\n" % (pid, tid, addr, size),
- file=fp,
- )
-
-
-def _write_perf_fork(pid_from, tid_from, pid_to, tid_to, fp):
- print(
- "0 0 0 0 PERF_RECORD_FORK(%d:%d):(%d:%d)\n"
- % (pid_to, tid_to, pid_from, tid_from),
- file=fp,
- )
-
-
-def _write_perf_exit(pid_from, tid_from, pid_to, tid_to, fp):
- print(
- "0 0 0 0 PERF_RECORD_EXIT(%d:%d):(%d:%d)\n"
- % (pid_to, tid_to, pid_from, tid_from),
- file=fp,
- )
-
-
-def _write_perf_sample(pid, tid, addr, fp):
- print(
- "0 0 0 0 PERF_RECORD_SAMPLE(IP, 0x2): "
- "%d/%d: %x period: 100000 addr: 0" % (pid, tid, addr),
- file=fp,
- )
- print(" ... thread: chrome:%d" % tid, file=fp)
- print(" ...... dso: /opt/google/chrome/chrome\n", file=fp)
-
-
-def _heatmap(file_name, page_size=4096, hugepage=None, analyze=False, top_n=10):
- generator = heatmap_generator.HeatmapGenerator(
- file_name, page_size, hugepage, "", log_level="none"
- ) # Don't log to stdout
- generator.draw()
- if analyze:
- generator.analyze("/path/to/chrome", top_n)
-
-
-def _cleanup(file_name):
- files = [
- file_name,
- "out.txt",
- "inst-histo.txt",
- "inst-histo-hp.txt",
- "inst-histo-sp.txt",
- "heat_map.png",
- "timeline.png",
- "addr2symbol.txt",
- ]
- for f in files:
- if os.path.exists(f):
- os.remove(f)
-
-
-class HeatmapGeneratorDrawTests(unittest.TestCase):
- """All of our tests for heatmap_generator.draw() and related."""
-
- def test_with_one_mmap_one_sample(self):
- """Tests one perf record and one sample."""
- fname = "test.txt"
- with open(fname, "w") as f:
- _write_perf_mmap(101, 101, 0xABCD000, 0x100, f)
- _write_perf_sample(101, 101, 0xABCD101, f)
- self.addCleanup(_cleanup, fname)
- _heatmap(fname)
- self.assertIn("out.txt", os.listdir("."))
- with open("out.txt") as f:
- lines = f.readlines()
- self.assertEqual(len(lines), 1)
- self.assertIn("101/101: 1 0", lines[0])
-
- def test_with_one_mmap_multiple_samples(self):
- """Tests one perf record and three samples."""
- fname = "test.txt"
- with open(fname, "w") as f:
- _write_perf_mmap(101, 101, 0xABCD000, 0x100, f)
- _write_perf_sample(101, 101, 0xABCD101, f)
- _write_perf_sample(101, 101, 0xABCD102, f)
- _write_perf_sample(101, 101, 0xABCE102, f)
- self.addCleanup(_cleanup, fname)
- _heatmap(fname)
- self.assertIn("out.txt", os.listdir("."))
- with open("out.txt") as f:
- lines = f.readlines()
- self.assertEqual(len(lines), 3)
- self.assertIn("101/101: 1 0", lines[0])
- self.assertIn("101/101: 2 0", lines[1])
- self.assertIn("101/101: 3 4096", lines[2])
-
- def test_with_fork_and_exit(self):
- """Tests perf fork and perf exit."""
- fname = "test_fork.txt"
- with open(fname, "w") as f:
- _write_perf_mmap(101, 101, 0xABCD000, 0x100, f)
- _write_perf_fork(101, 101, 202, 202, f)
- _write_perf_sample(101, 101, 0xABCD101, f)
- _write_perf_sample(202, 202, 0xABCE101, f)
- _write_perf_exit(202, 202, 202, 202, f)
- self.addCleanup(_cleanup, fname)
- _heatmap(fname)
- self.assertIn("out.txt", os.listdir("."))
- with open("out.txt") as f:
- lines = f.readlines()
- self.assertEqual(len(lines), 2)
- self.assertIn("101/101: 1 0", lines[0])
- self.assertIn("202/202: 2 4096", lines[1])
-
- def test_hugepage_creates_two_chrome_mmaps(self):
- """Test two chrome mmaps for the same process."""
- fname = "test_hugepage.txt"
- with open(fname, "w") as f:
- _write_perf_mmap(101, 101, 0xABCD000, 0x1000, f)
- _write_perf_fork(101, 101, 202, 202, f)
- _write_perf_mmap(202, 202, 0xABCD000, 0x100, f)
- _write_perf_mmap(202, 202, 0xABCD300, 0xD00, f)
- _write_perf_sample(101, 101, 0xABCD102, f)
- _write_perf_sample(202, 202, 0xABCD102, f)
- self.addCleanup(_cleanup, fname)
- _heatmap(fname)
- self.assertIn("out.txt", os.listdir("."))
- with open("out.txt") as f:
- lines = f.readlines()
- self.assertEqual(len(lines), 2)
- self.assertIn("101/101: 1 0", lines[0])
- self.assertIn("202/202: 2 0", lines[1])
-
- def test_hugepage_creates_two_chrome_mmaps_fail(self):
- """Test two chrome mmaps for the same process."""
- fname = "test_hugepage.txt"
- # Cases where first_mmap.size < second_mmap.size
- with open(fname, "w") as f:
- _write_perf_mmap(101, 101, 0xABCD000, 0x1000, f)
- _write_perf_fork(101, 101, 202, 202, f)
- _write_perf_mmap(202, 202, 0xABCD000, 0x10000, f)
- self.addCleanup(_cleanup, fname)
- with self.assertRaises(AssertionError) as msg:
- _heatmap(fname)
- self.assertIn("Original MMAP size", str(msg.exception))
-
- # Cases where first_mmap.address > second_mmap.address
- with open(fname, "w") as f:
- _write_perf_mmap(101, 101, 0xABCD000, 0x1000, f)
- _write_perf_fork(101, 101, 202, 202, f)
- _write_perf_mmap(202, 202, 0xABCC000, 0x10000, f)
- with self.assertRaises(AssertionError) as msg:
- _heatmap(fname)
- self.assertIn("Original MMAP starting address", str(msg.exception))
-
- # Cases where first_mmap.address + size <
- # second_mmap.address + second_mmap.size
- with open(fname, "w") as f:
- _write_perf_mmap(101, 101, 0xABCD000, 0x1000, f)
- _write_perf_fork(101, 101, 202, 202, f)
- _write_perf_mmap(202, 202, 0xABCD100, 0x10000, f)
- with self.assertRaises(AssertionError) as msg:
- _heatmap(fname)
- self.assertIn("exceeds the end of original MMAP", str(msg.exception))
-
- def test_histogram(self):
- """Tests if the tool can generate correct histogram.
-
- In the tool, histogram is generated from statistics
- of perf samples (saved to out.txt). The histogram is
- generated by perf-to-inst-page.sh and saved to
- inst-histo.txt. It will be used to draw heat maps.
- """
- fname = "test_histo.txt"
- with open(fname, "w") as f:
- _write_perf_mmap(101, 101, 0xABCD000, 0x100, f)
- for i in range(100):
- _write_perf_sample(101, 101, 0xABCD000 + i, f)
- _write_perf_sample(101, 101, 0xABCE000 + i, f)
- _write_perf_sample(101, 101, 0xABFD000 + i, f)
- _write_perf_sample(101, 101, 0xAFCD000 + i, f)
- self.addCleanup(_cleanup, fname)
- _heatmap(fname)
- self.assertIn("inst-histo.txt", os.listdir("."))
- with open("inst-histo.txt") as f:
- lines = f.readlines()
- self.assertEqual(len(lines), 4)
- self.assertIn("100 0", lines[0])
- self.assertIn("100 4096", lines[1])
- self.assertIn("100 196608", lines[2])
- self.assertIn("100 4194304", lines[3])
-
- def test_histogram_two_mb_page(self):
- """Tests handling of 2MB page."""
- fname = "test_histo.txt"
- with open(fname, "w") as f:
- _write_perf_mmap(101, 101, 0xABCD000, 0x100, f)
- for i in range(100):
- _write_perf_sample(101, 101, 0xABCD000 + i, f)
- _write_perf_sample(101, 101, 0xABCE000 + i, f)
- _write_perf_sample(101, 101, 0xABFD000 + i, f)
- _write_perf_sample(101, 101, 0xAFCD000 + i, f)
- self.addCleanup(_cleanup, fname)
- _heatmap(fname, page_size=2 * 1024 * 1024)
- self.assertIn("inst-histo.txt", os.listdir("."))
- with open("inst-histo.txt") as f:
- lines = f.readlines()
- self.assertEqual(len(lines), 2)
- self.assertIn("300 0", lines[0])
- self.assertIn("100 4194304", lines[1])
-
- def test_histogram_in_and_out_hugepage(self):
- """Tests handling the case of separating samples in and out huge page."""
- fname = "test_histo.txt"
- with open(fname, "w") as f:
- _write_perf_mmap(101, 101, 0xABCD000, 0x100, f)
- for i in range(100):
- _write_perf_sample(101, 101, 0xABCD000 + i, f)
- _write_perf_sample(101, 101, 0xABCE000 + i, f)
- _write_perf_sample(101, 101, 0xABFD000 + i, f)
- _write_perf_sample(101, 101, 0xAFCD000 + i, f)
- self.addCleanup(_cleanup, fname)
- _heatmap(fname, hugepage=[0, 8192])
- file_list = os.listdir(".")
- self.assertNotIn("inst-histo.txt", file_list)
- self.assertIn("inst-histo-hp.txt", file_list)
- self.assertIn("inst-histo-sp.txt", file_list)
- with open("inst-histo-hp.txt") as f:
- lines = f.readlines()
- self.assertEqual(len(lines), 2)
- self.assertIn("100 0", lines[0])
- self.assertIn("100 4096", lines[1])
- with open("inst-histo-sp.txt") as f:
- lines = f.readlines()
- self.assertEqual(len(lines), 2)
- self.assertIn("100 196608", lines[0])
- self.assertIn("100 4194304", lines[1])
-
-
-class HeatmapGeneratorAnalyzeTests(unittest.TestCase):
- """All of our tests for heatmap_generator.analyze() and related."""
-
- def setUp(self):
- # Use the same perf report for testing
- self.fname = "test_histo.txt"
- with open(self.fname, "w") as f:
- _write_perf_mmap(101, 101, 0xABCD000, 0x100, f)
- for i in range(10):
- _write_perf_sample(101, 101, 0xABCD000 + i, f)
- _write_perf_sample(101, 101, 0xABCE000 + i, f)
- _write_perf_sample(101, 101, 0xABFD000 + i, f)
- self.nm = (
- "000000000abcd000 t Func1@Page1\n"
- "000000000abcd001 t Func2@Page1\n"
- "000000000abcd0a0 t Func3@Page1andFunc1@Page2\n"
- "000000000abce010 t Func2@Page2\n"
- "000000000abfd000 t Func1@Page3\n"
- )
-
- def tearDown(self):
- _cleanup(self.fname)
-
- @mock.patch("subprocess.check_output")
- def test_analyze_hot_pages_with_hp_top(self, mock_nm):
- """Test if the analyze() can print the top page with hugepage."""
- mock_nm.return_value = self.nm
- _heatmap(self.fname, hugepage=[0, 8192], analyze=True, top_n=1)
- file_list = os.listdir(".")
- self.assertIn("addr2symbol.txt", file_list)
- with open("addr2symbol.txt") as f:
- contents = f.read()
- self.assertIn("Func2@Page1 : 9", contents)
- self.assertIn("Func1@Page1 : 1", contents)
- self.assertIn("Func1@Page3 : 10", contents)
- # Only displaying one page in hugepage
- self.assertNotIn("Func3@Page1andFunc1@Page2 : 10", contents)
-
- @mock.patch("subprocess.check_output")
- def test_analyze_hot_pages_without_hp_top(self, mock_nm):
- """Test if the analyze() can print the top page without hugepage."""
- mock_nm.return_value = self.nm
- _heatmap(self.fname, analyze=True, top_n=1)
- file_list = os.listdir(".")
- self.assertIn("addr2symbol.txt", file_list)
- with open("addr2symbol.txt") as f:
- contents = f.read()
- self.assertIn("Func2@Page1 : 9", contents)
- self.assertIn("Func1@Page1 : 1", contents)
- # Only displaying one page
- self.assertNotIn("Func3@Page1andFunc1@Page2 : 10", contents)
- self.assertNotIn("Func1@Page3 : 10", contents)
-
- @mock.patch("subprocess.check_output")
- def test_analyze_hot_pages_with_hp_top10(self, mock_nm):
- """Test if the analyze() can print with default top 10."""
- mock_nm.return_value = self.nm
- _heatmap(self.fname, analyze=True)
- # Make sure nm command is called correctly.
- mock_nm.assert_called_with(["nm", "-n", "/path/to/chrome"])
- file_list = os.listdir(".")
- self.assertIn("addr2symbol.txt", file_list)
- with open("addr2symbol.txt") as f:
- contents = f.read()
- self.assertIn("Func2@Page1 : 9", contents)
- self.assertIn("Func1@Page1 : 1", contents)
- self.assertIn("Func3@Page1andFunc1@Page2 : 10", contents)
- self.assertIn("Func1@Page3 : 10", contents)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/heatmaps/perf-to-inst-page.sh b/heatmaps/perf-to-inst-page.sh
deleted file mode 100755
index 5be1a2b9..00000000
--- a/heatmaps/perf-to-inst-page.sh
+++ /dev/null
@@ -1,68 +0,0 @@
-#! /bin/bash -u
-# Copyright 2015 The ChromiumOS Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This script takes the out.txt, generated by heatmap_generator.py
-# and sorted into a heatmap data file (inst-histo.txt) and then
-# call gnu plot to draw the heat map and the time map.
-# A heat map shows the overall hotness of instructions being executed
-# while the time map shows the hotness of instruction at different time.
-#
-# Usage:
-# ./perf-to-inst-page.sh HEATMAP_TITLE
-# HEATMAP_TITLE: the title to display on the heatmap
-
-HEAT_PNG="heat_map.png"
-TIMELINE_PNG="timeline.png"
-HEATMAP_TITLE=$1
-ENABLE_HUGEPAGE=$2
-
-heatmap_command="
-set terminal png size 600,450
-set xlabel \"Instruction Virtual Address (MB)\"
-set ylabel \"Sample Occurance\"
-set grid
-
-set output \"${HEAT_PNG}\"
-set title \"${HEATMAP_TITLE}\"
-"
-
-if [[ "${ENABLE_HUGEPAGE}" = "hugepage" ]]; then
- hugepage_hist="inst-histo-hp.txt"
- smallpage_hist="inst-histo-sp.txt"
- cat out.txt | grep hugepage | awk '{print $3}' \
- | sort -n | uniq -c > "${hugepage_hist}"
- cat out.txt | grep smallpage | awk '{print $3}' \
- | sort -n | uniq -c > "${smallpage_hist}"
- # generate inst heat map
- heatmap_in_hugepage=("'${hugepage_hist}' using \
-(\$2/1024/1024):1 with impulses notitle lt rgb 'red'")
- heatmap_outside_hugepage=("'${smallpage_hist}' using \
-(\$2/1024/1024):1 with impulses notitle lt rgb 'blue'")
- echo "
- ${heatmap_command}
- plot ${heatmap_in_hugepage}, ${heatmap_outside_hugepage}
- " | gnuplot
-else
- awk '{print $3}' out.txt | sort -n | uniq -c > inst-histo.txt
- # generate inst heat map
- echo "
- ${heatmap_command}
- plot 'inst-histo.txt' using (\$2/1024/1024):1 with impulses notitle
- " | gnuplot
-fi
-
-# generate instruction page access timeline
-num=$(awk 'END {print NR+1}' out.txt)
-
-echo "
-set terminal png size 600,450
-set xlabel \"time (sec)\"
-set ylabel \"Instruction Virtual Address (MB)\"
-
-set output \"${TIMELINE_PNG}\"
-set title \"instruction page accessd timeline\"
-
-plot 'out.txt' using (\$0/$num*10):(\$3/1024/1024) with dots notitle
-" | gnuplot