├── .dir-locals.el ├── .gitignore ├── LICENSE ├── README.md ├── doc ├── 00 - Quick start.ipynb ├── BarPlotter.ipynb ├── BareTrace class.ipynb ├── Displaying results.ipynb ├── Dynamic traces.ipynb ├── FTrace class.ipynb ├── InteractivePlotter.ipynb ├── Plotter.ipynb ├── Stats.ipynb ├── api_reference │ ├── .gitignore │ ├── Makefile │ ├── conf.py │ └── index.rst ├── compare runs.ipynb ├── sample_trace.csv ├── summary plots.ipynb ├── trace.txt ├── trace_dynamic.txt └── trace_stats.dat ├── hooks └── pre-commit ├── pyproject.toml ├── scripts └── publish_interactive_plots.py ├── setup.cfg ├── tests ├── WA_sysfs_extract.tar.xz ├── constrained.csv ├── raw_trace.dat ├── raw_trace.txt ├── results.csv ├── test_baretrace.py ├── test_base.py ├── test_caching.py ├── test_common_clk.py ├── test_constraint.py ├── test_copyright.py ├── test_cpu_power.py ├── test_devfreq.py ├── test_duplicates.py ├── test_dynamic.py ├── test_fallback.py ├── test_filesystem.py ├── test_ftrace.py ├── test_idle.py ├── test_pid.py ├── test_plot_utils.py ├── test_plotter.py ├── test_results.py ├── test_sched.py ├── test_sort.py ├── test_stats.py ├── test_stats_grammar.py ├── test_systrace.py ├── test_thermal.py ├── test_trappy.py ├── test_utils.py ├── test_wa_sysfs_extractor.py ├── trace.dat ├── trace_common_clk.txt ├── trace_empty.txt ├── trace_equals.txt ├── trace_failed_to_parse.txt ├── trace_filesystem.txt ├── trace_idle.dat ├── trace_idle_unsorted.txt ├── trace_legacy_systrace.html ├── trace_no_trailing_line.txt ├── trace_sched.txt ├── trace_sched.txt.cache │ ├── CpuIdle.csv │ ├── CpuInPower.csv │ ├── CpuOutPower.csv │ ├── DevfreqInPower.csv │ ├── DevfreqOutPower.csv │ ├── PIDController.csv │ ├── SchedContribScaleFactor.csv │ ├── SchedCpuCapacity.csv │ ├── SchedCpuFrequency.csv │ ├── SchedLoadAvgCpu.csv │ ├── SchedLoadAvgSchedGroup.csv │ ├── SchedLoadAvgTask.csv │ ├── SchedMigrateTask.csv │ ├── SchedSwitch.csv │ ├── SchedWakeup.csv │ ├── SchedWakeupNew.csv │ ├── Thermal.csv │ ├── ThermalGovernor.csv │ └── metadata.json ├── trace_surfaceflinger.html ├── trace_systrace.html ├── trappy ├── unconstrained.csv └── utils_tests.py └── trappy ├── __init__.py ├── bare_trace.py ├── base.py ├── common_clk.py ├── compare_runs.py ├── cpu_power.py ├── devfreq_power.py ├── dynamic.py ├── exception.py ├── fallback.py ├── filesystem.py ├── ftrace.py ├── function.py ├── function_graph.py ├── idle.py ├── nbexport ├── __init__.py └── exporter.py ├── pid_controller.py ├── plot_utils.py ├── plotter ├── AbstractDataPlotter.py ├── AttrConf.py ├── BarPlot.py ├── ColorMap.py ├── Constraint.py ├── EventPlot.py ├── ILinePlot.py ├── ILinePlotGen.py ├── IPythonConf.py ├── LinePlot.py ├── PlotLayout.py ├── StaticPlot.py ├── Utils.py ├── __init__.py ├── css │ ├── EventPlot.css │ └── EventPlot_help.jpg └── js │ ├── EventPlot.js │ └── ILinePlot.js ├── raw_syscalls.py ├── sched.py ├── stats ├── Aggregator.py ├── Correlator.py ├── Indexer.py ├── StatConf.py ├── Topology.py ├── Trigger.py ├── __init__.py └── grammar.py ├── systrace.py ├── thermal.py ├── utils.py └── wa ├── __init__.py ├── results.py └── sysfs_extractor.py /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((nil . ((eval . (progn 2 | (require 'projectile) 3 | (puthash (projectile-project-root) 4 | "nosetests" 5 | projectile-test-cmd-map)))))) 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | doc/.ipynb_checkpoints 3 | /dist/ 4 | /build/ 5 | /TRAPpy.egg-info/ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | THIS REPOSITORY HAS MOVED TO https://gitlab.arm.com/tooling/trappy 2 | 3 | TRAPpy [![Version](https://img.shields.io/pypi/v/trappy.svg)](https://pypi.python.org/pypi/trappy) 4 | ====== 5 | 6 | TRAPpy (Trace Analysis and Plotting in Python) is a visualization tool to help 7 | analyze data generated on a device. It parses ftrace-like logs and creates 8 | in-memory data structures to be used for plotting and data analysis. 9 | 10 | # Installation 11 | 12 | The following instructions are for Ubuntu 14.04 LTS but they should 13 | also work with Debian jessie. Older versions of Ubuntu or Debian 14 | (e.g. Ubuntu 12.04 or Debian wheezy) will likely require to install 15 | more packages from pip as the ones present in Ubuntu 12.04 or Debian 16 | wheezy will probably be too old. 17 | 18 | ## Required dependencies 19 | 20 | ##### Install additional tools required for some tests and functionalities 21 | 22 | $ sudo apt install trace-cmd kernelshark 23 | 24 | ##### Install the Python package manager 25 | 26 | $ sudo apt install python-pip python-dev 27 | 28 | ##### Install required python packages 29 | 30 | $ sudo apt install libfreetype6-dev libpng12-dev python-nose 31 | $ sudo pip install numpy matplotlib pandas ipython[all] 32 | 33 | ##### Install TRAPpy 34 | 35 | $ sudo pip install --upgrade trappy 36 | 37 | # Quickstart 38 | 39 | Now launch the ipython notebook server: 40 | 41 | $ ipython notebook 42 | 43 | This should pop up a browser. If it doesn't, open a web browser and go 44 | to http://localhost:8888/tree/ 45 | 46 | In the `doc/` folder there's a `00 - Quick start` which describes how to 47 | run TRAPpy. Other notebooks in that directory describe other functions 48 | of TRAPpy. 49 | 50 | # Documentation 51 | 52 | API reference can be found in https://pythonhosted.org/TRAPpy/ 53 | 54 | # For developers 55 | 56 | ## Clone the repository 57 | 58 | The code of the TRAPpy toolkit with all the supported tests and 59 | Notebooks can be cloned from the official GitHub repository with this 60 | command: 61 | 62 | $ git clone https://github.com/ARM-software/trappy.git 63 | 64 | ## Testing your installation 65 | 66 | An easy way to test your installation is to use the `nosetests` command from 67 | TRAPpy's home directory: 68 | 69 | $ nosetests 70 | 71 | If the installation is correct all tests will succeed. 72 | -------------------------------------------------------------------------------- /doc/BareTrace class.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "", 4 | "signature": "sha256:07509ec03cc71263d49f2d8bb69f21bd039d5ddeeb3f3c81573002794049a1aa" 5 | }, 6 | "nbformat": 3, 7 | "nbformat_minor": 0, 8 | "worksheets": [ 9 | { 10 | "cells": [ 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "The `BareTrace` class lets you create a trace class that has been parsed outside of trappy. This lets you leverage trappy's and bart's facilities with any type of trace." 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "metadata": {}, 21 | "source": [ 22 | "Let's assume that we have events available in a csv file. We can import them into a trace object by creating a `BareTrace` object and adding them using `add_parsed_event`." 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "collapsed": false, 28 | "input": [ 29 | "import pandas as pd\n", 30 | "import trappy" 31 | ], 32 | "language": "python", 33 | "metadata": {}, 34 | "outputs": [], 35 | "prompt_number": 1 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "metadata": {}, 40 | "source": [ 41 | "First we import the parsed events into a pandas DataFrame" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "collapsed": false, 47 | "input": [ 48 | "event = pd.DataFrame.from_csv(\"sample_trace.csv\")" 49 | ], 50 | "language": "python", 51 | "metadata": {}, 52 | "outputs": [], 53 | "prompt_number": 2 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "Now we create an trappy trace object and add our event to it. " 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "collapsed": false, 65 | "input": [ 66 | "trace = trappy.BareTrace()\n", 67 | "trace.add_parsed_event(\"http_access\", event)" 68 | ], 69 | "language": "python", 70 | "metadata": {}, 71 | "outputs": [], 72 | "prompt_number": 3 73 | }, 74 | { 75 | "cell_type": "markdown", 76 | "metadata": {}, 77 | "source": [ 78 | "We can add more events if we had collected them from different sources. Just call `.add_parsed_event()` for each of them" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "collapsed": false, 84 | "input": [ 85 | "trappy.ILinePlot(trace, trace.http_access, column=[\"response_time\"]).view()" 86 | ], 87 | "language": "python", 88 | "metadata": {}, 89 | "outputs": [], 90 | "prompt_number": null 91 | } 92 | ], 93 | "metadata": {} 94 | } 95 | ] 96 | } -------------------------------------------------------------------------------- /doc/api_reference/.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | *.rst 3 | !index.rst 4 | -------------------------------------------------------------------------------- /doc/api_reference/index.rst: -------------------------------------------------------------------------------- 1 | .. TRAPpy documentation master file, created by 2 | sphinx-quickstart on Fri Sep 4 12:40:17 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to TRAPpy's documentation! 7 | ================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 4 13 | 14 | trappy 15 | 16 | 17 | Indices and tables 18 | ================== 19 | 20 | * :ref:`genindex` 21 | * :ref:`modindex` 22 | * :ref:`search` 23 | -------------------------------------------------------------------------------- /doc/sample_trace.csv: -------------------------------------------------------------------------------- 1 | Time,host,type,file,protocol,response_code,response_time 2 | 0.5536,62.242.88.10,GET,/index.html,HTTP/1.0,200,945 3 | 0.748,62.242.88.10,GET,/robots.txt,HTTP/1.0,200,567 4 | 0.956,67.131.107.5,GET,/admin.html,HTTP/1.0,401,340 5 | 1.5455,217.140.96.140,GET,/razor.html,HTTP/1.1,404,120 6 | 1.7304,216.139.185.45,GET,/mailman/admindb/ppwc,HTTP/1.1,200,1012 7 | 2.0729,216.139.185.45,POST,/mailman/admindb/ppwc,HTTP/1.1,200,1103 8 | -------------------------------------------------------------------------------- /doc/trace_stats.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARM-software/trappy/bba21c4de5cbe380d375bdf3f5666c7880235287/doc/trace_stats.dat -------------------------------------------------------------------------------- /hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright 2015-2015 ARM Limited 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # 17 | # git pre-commit hook. There's no way to automate them when cloning a 18 | # repository. You need to run this by hand in the root of the 19 | # repository to activate it: 20 | # 21 | # ln -s ../../hooks/pre-commit .git/hooks/ 22 | 23 | set -e 24 | 25 | # Fail if there's whitespace errors 26 | git diff-index --check --cached HEAD -- 27 | 28 | # Run testsuite 29 | exec nosetests 30 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "wheel" 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | -------------------------------------------------------------------------------- /scripts/publish_interactive_plots.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2015-2017 ARM Limited 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | 18 | """This is a script to publish a notebook containing Ipython graphs 19 | The static data is published as an anonymous gist. GitHub does not 20 | allow easy deletions of anonymous gists. 21 | """ 22 | from __future__ import unicode_literals 23 | from __future__ import division 24 | from __future__ import print_function 25 | 26 | import os 27 | import argparse 28 | from IPython.nbformat.sign import TrustNotebookApp 29 | from argparse import RawTextHelpFormatter 30 | 31 | # Logging Configuration 32 | import logging 33 | from trappy.plotter import IPythonConf 34 | 35 | logging.basicConfig(level=logging.INFO) 36 | 37 | 38 | def change_resource_paths(txt): 39 | """Change the resource paths from local to 40 | Web URLs 41 | """ 42 | 43 | # Replace the path for d3-tip 44 | txt = txt.replace( 45 | IPythonConf.add_web_base("plotter_scripts/EventPlot/d3.tip.v0.6.3"), 46 | IPythonConf.D3_TIP_URL) 47 | txt = txt.replace( 48 | IPythonConf.add_web_base("plotter_scripts/EventPlot/d3.v3.min"), 49 | IPythonConf.D3_PLOTTER_URL) 50 | txt = txt.replace( 51 | IPythonConf.add_web_base("plotter_scripts/EventPlot/EventPlot"), 52 | "https://rawgit.com/sinkap/7f89de3e558856b81f10/raw/46144f8f8c5da670c54f826f0c634762107afc66/EventPlot") 53 | txt = txt.replace( 54 | IPythonConf.add_web_base("plotter_scripts/ILinePlot/synchronizer"), 55 | IPythonConf.DYGRAPH_SYNC_URL) 56 | txt = txt.replace( 57 | IPythonConf.add_web_base("plotter_scripts/ILinePlot/dygraph-combined"), 58 | IPythonConf.DYGRAPH_COMBINED_URL) 59 | txt = txt.replace( 60 | IPythonConf.add_web_base("plotter_scripts/ILinePlot/ILinePlot"), 61 | "https://rawgit.com/sinkap/648927dfd6985d4540a9/raw/69d6f1f9031ae3624c15707315ce04be1a9d1ac3/ILinePlot") 62 | txt = txt.replace( 63 | IPythonConf.add_web_base("plotter_scripts/ILinePlot/underscore-min"), 64 | IPythonConf.UNDERSCORE_URL) 65 | 66 | logging.info("Updated Library Paths...") 67 | return txt 68 | 69 | 70 | def publish(source, target): 71 | """Publish the notebook for globally viewable interactive 72 | plots 73 | """ 74 | 75 | txt = "" 76 | 77 | with open(source, 'r') as file_fh: 78 | txt = change_resource_paths(file_fh.read()) 79 | 80 | with open(target, 'w') as file_fh: 81 | file_fh.write(txt) 82 | 83 | trust = TrustNotebookApp() 84 | trust.sign_notebook(target) 85 | logging.info("Signed and Saved: %s", target) 86 | 87 | def main(): 88 | """Command Line Invocation Routine""" 89 | 90 | parser = argparse.ArgumentParser(description=""" 91 | The data for the interactive plots is stored in the ipython profile. 92 | In order to make it accessible when the notebook is published or shared, 93 | a github gist of the data is created and the links in the notebook are 94 | updated. The library links are also updated to their corresponding publicly 95 | accessible URLs. 96 | """, 97 | prog="publish_interactive_plots.py", formatter_class=RawTextHelpFormatter) 98 | 99 | parser.add_argument( 100 | "-p", 101 | "--profile", 102 | help="ipython profile", 103 | default="default", 104 | type=str) 105 | 106 | parser.add_argument( 107 | "-o", 108 | "--outfile", 109 | help="name of the output notebook", 110 | default="", 111 | type=str) 112 | 113 | parser.add_argument("notebook") 114 | args = parser.parse_args() 115 | 116 | notebook = args.notebook 117 | outfile = args.outfile 118 | 119 | if outfile == "": 120 | outfile = "published_" + os.path.basename(notebook) 121 | logging.info("Setting outfile as %s", outfile) 122 | 123 | elif not outfile.endswith(".ipynb"): 124 | outfile += ".ipynb" 125 | 126 | publish(notebook, outfile) 127 | 128 | if __name__ == "__main__": 129 | main() 130 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = TRAPpy 3 | version = 6.0.1 4 | author = ARM-TRAPPY 5 | author_email = trappy@arm.com 6 | description = Trace Analysis and Plotting 7 | long_description = file: README.md 8 | long_description_content_type = text/markdown 9 | url = https://github.com/ARM-software/trappy 10 | classifiers = 11 | Development Status :: 5 - Production/Stable 12 | Environment :: Web Environment 13 | Environment :: Console 14 | License :: OSI Approved :: Apache Software License 15 | Operating System :: POSIX :: Linux 16 | Programming Language :: Python :: 2.7 17 | Programming Language :: Python :: 3 18 | Topic :: System :: Operating System Kernels :: Linux 19 | Topic :: Scientific/Engineering :: Visualization 20 | 21 | [options] 22 | packages = find: 23 | python_requires = >=3.6 24 | scripts = 25 | scripts/publish_interactive_plots.py 26 | install_requires = 27 | numpy 28 | pyparsing 29 | pandas>=0.15.0 30 | future 31 | 32 | [options.package_data] 33 | trappy.plotter = 34 | js/EventPlot.js 35 | js/ILinePlot.js 36 | css/EventPlot.css 37 | css/EventPlot_help.jpg 38 | 39 | [options.extras_require] 40 | notebook = 41 | matplotlib>=1.3.1 42 | ipython>=3.0.0 43 | jupyter>=1.0.0 44 | 45 | [upload_sphinx] 46 | upload-dir = doc/api_reference/_build/html 47 | 48 | [nosetests] 49 | processes = -1 50 | process-timeout = 60 51 | -------------------------------------------------------------------------------- /tests/WA_sysfs_extract.tar.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARM-software/trappy/bba21c4de5cbe380d375bdf3f5666c7880235287/tests/WA_sysfs_extract.tar.xz -------------------------------------------------------------------------------- /tests/raw_trace.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARM-software/trappy/bba21c4de5cbe380d375bdf3f5666c7880235287/tests/raw_trace.dat -------------------------------------------------------------------------------- /tests/raw_trace.txt: -------------------------------------------------------------------------------- 1 | version = 6 2 | CPU 3 is empty 3 | CPU 4 is empty 4 | cpus=6 5 | ls-4734 [002] 106439.675591: sched_switch: prev_comm=trace-cmd prev_pid=4734 prev_prio=120 prev_state=1024 next_comm=migration/2 next_pid=18 next_prio=0 6 | migration/2-18 [002] 106439.675613: sched_switch: prev_comm=migration/2 prev_pid=18 prev_prio=0 prev_state=1 next_comm=trace-cmd next_pid=4732 next_prio=120 7 | trace-cmd-4730 [001] 106439.675718: sched_switch: prev_comm=trace-cmd prev_pid=4730 prev_prio=120 prev_state=1 next_comm=trace-cmd next_pid=4729 next_prio=120 8 | -------------------------------------------------------------------------------- /tests/test_baretrace.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | import pandas as pd 20 | import trappy 21 | import unittest 22 | 23 | class TestBareTrace(unittest.TestCase): 24 | def __init__(self, *args, **kwargs): 25 | super(TestBareTrace, self).__init__(*args, **kwargs) 26 | dfr0 = pd.DataFrame({"l1_misses": [24, 535, 41], 27 | "l2_misses": [155, 11, 200], 28 | "cpu": [ 0, 1, 0]}, 29 | index=pd.Series([1.020, 1.342, 1.451], name="Time")) 30 | 31 | dfr1 = pd.DataFrame({"load": [ 35, 16, 21, 28], 32 | "util": [279, 831, 554, 843]}, 33 | index=pd.Series([1.279, 1.718, 2.243, 2.465], name="Time")) 34 | 35 | self.dfr = [dfr0, dfr1] 36 | 37 | def test_bare_trace_accepts_name(self): 38 | """The BareTrace() accepts a name parameter""" 39 | 40 | trace = trappy.BareTrace(name="foo") 41 | 42 | self.assertEqual(trace.name, "foo") 43 | 44 | def test_bare_trace_can_add_parsed_event(self): 45 | """The BareTrace() class can add parsed events to its collection of trace events""" 46 | trace = trappy.BareTrace() 47 | trace.add_parsed_event("pmu_counters", self.dfr[0]) 48 | 49 | self.assertEqual(len(trace.pmu_counters.data_frame), 3) 50 | self.assertEqual(trace.pmu_counters.data_frame["l1_misses"].iloc[0], 24) 51 | 52 | trace.add_parsed_event("pivoted_counters", self.dfr[0], pivot="cpu") 53 | self.assertEqual(trace.pivoted_counters.pivot, "cpu") 54 | 55 | def test_bare_trace_get_duration(self): 56 | """BareTrace.get_duration() works for a simple case""" 57 | 58 | trace = trappy.BareTrace() 59 | trace.add_parsed_event("pmu_counter", self.dfr[0]) 60 | trace.add_parsed_event("load_event", self.dfr[1]) 61 | 62 | self.assertEqual(trace.get_duration(), self.dfr[1].index[-1] - self.dfr[0].index[0]) 63 | 64 | def test_bare_trace_get_duration_normalized(self): 65 | """BareTrace.get_duration() works if the trace has been normalized""" 66 | 67 | trace = trappy.BareTrace() 68 | trace.add_parsed_event("pmu_counter", self.dfr[0].copy()) 69 | trace.add_parsed_event("load_event", self.dfr[1].copy()) 70 | 71 | basetime = self.dfr[0].index[0] 72 | trace._normalize_time(basetime) 73 | 74 | expected_duration = self.dfr[1].index[-1] - basetime 75 | self.assertEqual(trace.get_duration(), expected_duration) 76 | 77 | def test_bare_trace_normalize_time_accepts_basetime(self): 78 | """BareTrace().normalize_time() accepts an arbitrary basetime""" 79 | 80 | trace = trappy.BareTrace() 81 | trace.add_parsed_event("pmu_counter", self.dfr[0].copy()) 82 | 83 | prev_first_time = trace.pmu_counter.data_frame.index[0] 84 | basetime = 3 85 | 86 | trace._normalize_time(basetime) 87 | 88 | self.assertEqual(trace.basetime, basetime) 89 | 90 | exp_first_time = prev_first_time - basetime 91 | self.assertEqual(round(trace.pmu_counter.data_frame.index[0] - exp_first_time, 7), 0) 92 | -------------------------------------------------------------------------------- /tests/test_common_clk.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 ARM Limited, Google 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | import os 20 | import sys 21 | 22 | import utils_tests 23 | import trappy 24 | 25 | sys.path.append(os.path.join(utils_tests.TESTS_DIRECTORY, "..", "trappy")) 26 | 27 | class TestCommonClk(utils_tests.SetupDirectory): 28 | def __init__(self, *args, **kwargs): 29 | super(TestCommonClk, self).__init__( 30 | [("trace_common_clk.txt", "trace_common_clk.txt"),], 31 | *args, 32 | **kwargs) 33 | 34 | def test_common_clk_set_rate_can_be_parsed(self): 35 | """TestCommonClk: test that clock__set_rate events can be parsed""" 36 | trace = trappy.FTrace("trace_common_clk.txt", events=['clock_set_rate']) 37 | df = trace.clock_set_rate.data_frame 38 | self.assertSetEqual(set(df.columns), 39 | set(["__comm", "__cpu", "__line", "__pid", "cpu_id", "clk_name", "rate"])) 40 | 41 | def test_common_clk_enable_can_be_parsed(self): 42 | """TestCommonClk: test that clock_enable events can be parsed""" 43 | trace = trappy.FTrace("trace_common_clk.txt", events=['clock_enable']) 44 | df = trace.clock_enable.data_frame 45 | self.assertSetEqual(set(df.columns), 46 | set(["__comm", "__cpu", "__line", "__pid", "cpu_id", "clk_name", "state"])) 47 | 48 | def test_common_clk_disable_can_be_parsed(self): 49 | """TestCommonClk: test that clock_disable events can be parsed""" 50 | trace = trappy.FTrace("trace_common_clk.txt", events=['clock_disable']) 51 | df = trace.clock_disable.data_frame 52 | self.assertSetEqual(set(df.columns), 53 | set(["__comm", "__cpu", "__line", "__pid", "cpu_id", "clk_name", "state"])) 54 | -------------------------------------------------------------------------------- /tests/test_copyright.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | from builtins import map 20 | from datetime import date 21 | from glob import glob 22 | import os 23 | import re 24 | import unittest 25 | 26 | 27 | def copyright_is_valid(fname): 28 | """Return True if fname has a valid copyright""" 29 | with open(fname) as fin: 30 | # Read the first 2K of the file. If the copyright is not there, you 31 | # are probably doing something wrong 32 | lines = fin.readlines(2048) 33 | 34 | # Either the first or the second line must have a "Copyright:" line 35 | first_line = re.compile(r"(#| \*) Copyright") 36 | try: 37 | if not first_line.search(lines[0]): 38 | if first_line.search(lines[1]): 39 | # Drop the first line to align the copyright to lines[0] 40 | lines = lines[1:] 41 | else: 42 | return False 43 | except IndexError: 44 | return False 45 | 46 | # The copyright mentions ARM/Arm Limited 47 | if not any([name in lines[0] for name in ["ARM Limited", "Arm Limited"]]): 48 | return False 49 | 50 | apache_line = 6 51 | if "Google Inc" in lines[1]: 52 | apache_line += 1 53 | 54 | # The Copyright includes valid years 55 | current_year = date.today().year 56 | years = list(map(int, re.findall(r"[-\s](?P\d+)", lines[0]))) 57 | 58 | if not years: 59 | return False 60 | 61 | for year in years: 62 | if year < 1970 or year > current_year: 63 | return False 64 | 65 | # It's the apache license 66 | if "http://www.apache.org/licenses/LICENSE-2.0" not in lines[apache_line]: 67 | return False 68 | 69 | return True 70 | 71 | 72 | class TestCopyRight(unittest.TestCase): 73 | def test_copyrights(self): 74 | """Check that all files have valid copyrights""" 75 | 76 | tests_dir = os.path.dirname(os.path.abspath(__file__)) 77 | base_dir = os.path.dirname(tests_dir) 78 | patterns_to_ignore = {} 79 | 80 | for root, dirs, files in os.walk(base_dir): 81 | if ".gitignore" in files: 82 | fname = os.path.join(root, ".gitignore") 83 | with open(fname) as fin: 84 | lines = fin.readlines() 85 | 86 | patterns_to_ignore[root] = [l.strip() for l in lines] 87 | 88 | files_to_ignore = [] 89 | for directory, patterns in patterns_to_ignore.items(): 90 | if root.startswith(directory): 91 | for pat in patterns: 92 | pat = os.path.join(root, pat) 93 | files_to_ignore.extend(glob(pat)) 94 | 95 | for dirname in dirs: 96 | full_dirname = os.path.join(root, dirname) 97 | if full_dirname in files_to_ignore: 98 | dirs.remove(dirname) 99 | 100 | 101 | for fname in files: 102 | fname = os.path.join(root, fname) 103 | if fname in files_to_ignore: 104 | continue 105 | 106 | extension = os.path.splitext(fname)[1] 107 | if extension in [".py", ".js", ".css"]: 108 | if not copyright_is_valid(fname): 109 | print("Invalid copyright in {}".format(fname)) 110 | self.fail() 111 | 112 | if '.git' in dirs: 113 | dirs.remove('.git') 114 | -------------------------------------------------------------------------------- /tests/test_devfreq.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | import pandas as pd 20 | 21 | import trappy 22 | from test_thermal import BaseTestThermal 23 | 24 | class TestDevfreqPower(BaseTestThermal): 25 | """Tests for the DevfreqInPower and DevfreqOutPower classes""" 26 | 27 | def test_devfreq_inp_dataframe(self): 28 | """Test that DevfreqInPower creates proper data frames""" 29 | devfreq_in_power = trappy.FTrace().devfreq_in_power 30 | 31 | self.assertTrue("freq" in devfreq_in_power.data_frame.columns) 32 | 33 | def test_devfreq_outp_dataframe(self): 34 | """Test that DevfreqOutPower creates proper data frames""" 35 | devfreq_out_power = trappy.FTrace().devfreq_out_power 36 | 37 | self.assertTrue("freq" in devfreq_out_power.data_frame.columns) 38 | 39 | def test_get_inp_all_freqs(self): 40 | """Test that DevfreqInPower get_all_freqs() work""" 41 | 42 | all_freqs = trappy.FTrace().devfreq_in_power.get_all_freqs() 43 | self.assertTrue(isinstance(all_freqs, pd.DataFrame)) 44 | 45 | self.assertEqual(all_freqs["freq"].iloc[0], 525) 46 | 47 | def test_get_outp_all_freqs(self): 48 | """Test that DevfreqOutPower get_all_freqs() work""" 49 | 50 | all_freqs = trappy.FTrace().devfreq_out_power.get_all_freqs() 51 | self.assertTrue(isinstance(all_freqs, pd.DataFrame)) 52 | 53 | self.assertEqual(all_freqs["freq"].iloc[0], 525) 54 | -------------------------------------------------------------------------------- /tests/test_duplicates.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | import unittest 20 | import matplotlib 21 | import pandas as pd 22 | import utils_tests 23 | import trappy 24 | import shutil 25 | 26 | from test_thermal import BaseTestThermal 27 | 28 | 29 | class TestPlotterDupVals(BaseTestThermal): 30 | 31 | """Test Duplicate Entries in plotter""" 32 | 33 | def __init__(self, *args, **kwargs): 34 | super(TestPlotterDupVals, self).__init__(*args, **kwargs) 35 | 36 | def test_plotter_duplicates(self): 37 | """Test that plotter handles duplicates fine""" 38 | with open("trace.txt", "w") as fout: 39 | fout.write("""version = 6 40 | cpus=6 41 | rcuos/2-22 [001] 0000.018510: sched_load_avg_sg: cpus=00000001 load=0 utilization=0 42 | rcuos/2-22 [001] 6550.018611: sched_load_avg_sg: cpus=00000002 load=1 utilization=1 43 | rcuos/2-22 [001] 6550.018611: sched_load_avg_sg: cpus=00000004 load=2 utilization=2 44 | rcuos/2-22 [001] 6550.018612: sched_load_avg_sg: cpus=00000001 load=2 utilization=3 45 | rcuos/2-22 [001] 6550.018624: sched_load_avg_sg: cpus=00000002 load=1 utilization=4 46 | rcuos/2-22 [001] 6550.018625: sched_load_avg_sg: cpus=00000002 load=2 utilization=5 47 | rcuos/2-22 [001] 6550.018626: sched_load_avg_sg: cpus=00000002 load=3 utilization=6 48 | rcuos/2-22 [001] 6550.018627: sched_load_avg_sg: cpus=00000002 load=1 utilization=7 49 | rcuos/2-22 [001] 6550.018628: sched_load_avg_sg: cpus=00000004 load=2 utilization=8\n""") 50 | fout.close() 51 | trace1 = trappy.FTrace(name="first") 52 | l = trappy.LinePlot( 53 | trace1, 54 | trappy.sched.SchedLoadAvgSchedGroup, 55 | column=['utilization'], 56 | filters={ 57 | "load": [ 58 | 1, 59 | 2]}, 60 | pivot="cpus", 61 | marker='o', 62 | linestyle='none', 63 | per_line=3) 64 | l.view(test=True) 65 | 66 | def test_plotter_triplicates(self): 67 | 68 | """Test that plotter handles triplicates fine""" 69 | 70 | with open("trace.txt", "w") as fout: 71 | fout.write("""version = 6 72 | cpus=6 73 | rcuos/2-22 [001] 0000.018510: sched_load_avg_sg: cpus=00000001 load=0 utilization=0 74 | rcuos/2-22 [001] 6550.018611: sched_load_avg_sg: cpus=00000002 load=1 utilization=1 75 | rcuos/2-22 [001] 6550.018611: sched_load_avg_sg: cpus=00000004 load=2 utilization=2 76 | rcuos/2-22 [001] 6550.018611: sched_load_avg_sg: cpus=00000004 load=2 utilization=2 77 | rcuos/2-22 [001] 6550.018612: sched_load_avg_sg: cpus=00000001 load=2 utilization=3 78 | rcuos/2-22 [001] 6550.018624: sched_load_avg_sg: cpus=00000002 load=1 utilization=4 79 | rcuos/2-22 [001] 6550.018625: sched_load_avg_sg: cpus=00000002 load=2 utilization=5 80 | rcuos/2-22 [001] 6550.018626: sched_load_avg_sg: cpus=00000002 load=3 utilization=6 81 | rcuos/2-22 [001] 6550.018627: sched_load_avg_sg: cpus=00000002 load=1 utilization=7 82 | rcuos/2-22 [001] 6550.018628: sched_load_avg_sg: cpus=00000004 load=2 utilization=8\n""") 83 | fout.close() 84 | 85 | trace1 = trappy.FTrace(name="first") 86 | l = trappy.LinePlot( 87 | trace1, 88 | trappy.sched.SchedLoadAvgSchedGroup, 89 | column=['utilization'], 90 | filters={ 91 | "load": [ 92 | 1, 93 | 2]}, 94 | pivot="cpus", 95 | marker='o', 96 | linestyle='none', 97 | per_line=3) 98 | l.view(test=True) 99 | -------------------------------------------------------------------------------- /tests/test_dynamic.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | import unittest 20 | import matplotlib 21 | from test_sched import BaseTestSched 22 | from trappy.base import Base 23 | import trappy 24 | 25 | 26 | class DynamicEvent(Base): 27 | 28 | """Test the ability to register 29 | specific classes to trappy""" 30 | 31 | unique_word = "dynamic_test_key" 32 | name = "dynamic_event" 33 | 34 | 35 | class TestDynamicEvents(BaseTestSched): 36 | 37 | def __init__(self, *args, **kwargs): 38 | super(TestDynamicEvents, self).__init__(*args, **kwargs) 39 | 40 | def test_dynamic_data_frame(self): 41 | """ 42 | Test if the dynamic events are populated 43 | in the data frame 44 | """ 45 | parse_class = trappy.register_dynamic_ftrace("DynamicEvent", "dynamic_test_key") 46 | t = trappy.FTrace(name="first") 47 | self.assertTrue(len(t.dynamic_event.data_frame) == 1) 48 | 49 | trappy.unregister_dynamic_ftrace(parse_class) 50 | 51 | def test_dynamic_class_attr(self): 52 | """ 53 | Test the attibutes of the dynamically 54 | generated class 55 | """ 56 | cls = trappy.register_dynamic_ftrace("DynamicEvent", "dynamic_test_key", 57 | pivot="test_pivot") 58 | self.assertEqual(cls.__name__, "DynamicEvent") 59 | self.assertEqual(cls.name, "dynamic_event") 60 | self.assertEqual(cls.unique_word, "dynamic_test_key") 61 | self.assertEqual(cls.pivot, "test_pivot") 62 | 63 | trappy.unregister_dynamic_ftrace(cls) 64 | 65 | def test_dynamic_event_plot(self): 66 | """Test if plotter can accept a dynamic class 67 | for a template argument""" 68 | 69 | cls = trappy.register_dynamic_ftrace("DynamicEvent", "dynamic_test_key") 70 | t = trappy.FTrace(name="first") 71 | l = trappy.LinePlot(t, cls, column="load") 72 | l.view(test=True) 73 | 74 | trappy.unregister_dynamic_ftrace(cls) 75 | 76 | def test_dynamic_event_scope(self): 77 | """Test the case when an "all" scope class is 78 | registered. it should appear in both thermal and sched 79 | ftrace class definitions when scoped ftrace objects are created 80 | """ 81 | cls = trappy.register_dynamic_ftrace("DynamicEvent", "dynamic_test_key") 82 | t1 = trappy.FTrace(name="first") 83 | self.assertTrue(cls.name in t1.class_definitions) 84 | 85 | trappy.unregister_dynamic_ftrace(cls) 86 | 87 | def test_register_ftrace_parser(self): 88 | trappy.register_ftrace_parser(DynamicEvent) 89 | t = trappy.FTrace(name="first") 90 | self.assertTrue(len(t.dynamic_event.data_frame) == 1) 91 | 92 | trappy.unregister_ftrace_parser(DynamicEvent) 93 | 94 | def test_no_none_pivot(self): 95 | """register_dynamic_ftrace() with default value for pivot doesn't create a class with a pivot=None""" 96 | cls = trappy.register_dynamic_ftrace("MyEvent", "my_dyn_test_key") 97 | self.assertFalse(hasattr(cls, "pivot")) 98 | 99 | trappy.unregister_dynamic_ftrace(cls) 100 | 101 | def test_unregister_dynamic_ftrace(self): 102 | """Test that dynamic events can be unregistered""" 103 | dyn_event = trappy.register_dynamic_ftrace("DynamicEvent", 104 | "dynamic_test_key") 105 | trace = trappy.FTrace(name="first") 106 | self.assertTrue(len(trace.dynamic_event.data_frame) == 1) 107 | 108 | trappy.unregister_dynamic_ftrace(dyn_event) 109 | trace = trappy.FTrace(name="first") 110 | 111 | self.assertFalse(hasattr(trace, "dynamic_event")) 112 | 113 | dyn_event = trappy.register_dynamic_ftrace("DynamicEvent", 114 | "dynamic_test_key", 115 | scope="sched") 116 | trace = trappy.FTrace(name="first") 117 | self.assertTrue(len(trace.dynamic_event.data_frame) == 1) 118 | 119 | trappy.unregister_dynamic_ftrace(dyn_event) 120 | trace = trappy.FTrace(name="first") 121 | 122 | self.assertFalse(hasattr(trace, "dynamic_event")) 123 | 124 | def test_unregister_ftrace_parser(self): 125 | """unregister_ftrace_parser() works""" 126 | trappy.register_ftrace_parser(DynamicEvent) 127 | trappy.unregister_ftrace_parser(DynamicEvent) 128 | trace = trappy.FTrace() 129 | self.assertFalse(hasattr(trace, "dynamic_event")) 130 | -------------------------------------------------------------------------------- /tests/test_fallback.py: -------------------------------------------------------------------------------- 1 | 2 | # Copyright 2018 ARM Limited, Google and contributors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | import trappy 18 | from utils_tests import SetupDirectory 19 | 20 | TEST_DATA = """ 21 | bash-7842 [002] 354.414778: print: tracing_mark_write: TRACE_MARKER_START 22 | shutils-7843 [001] 354.590131: print: tracing_mark_write: cpu_frequency_devlib: state=950000 cpu_id=0 23 | shutils-7843 [001] 354.590253: print: tracing_mark_write: cpu_frequency_devlib: state=1200000 cpu_id=1 24 | shutils-7843 [001] 354.590369: print: tracing_mark_write: cpu_frequency_devlib: state=1200000 cpu_id=2 25 | shutils-7843 [001] 354.590477: print: tracing_mark_write: cpu_frequency_devlib: state=950000 cpu_id=3 26 | shutils-7843 [001] 354.590584: print: tracing_mark_write: cpu_frequency_devlib: state=950000 cpu_id=4 27 | shutils-7843 [001] 354.590691: print: tracing_mark_write: cpu_frequency_devlib: state=950000 cpu_id=5 28 | shutils-7868 [001] 357.732485: print: tracing_mark_write: cpu_frequency_devlib: state=950000 cpu_id=0 29 | shutils-7868 [001] 357.732607: print: tracing_mark_write: cpu_frequency_devlib: state=1200000 cpu_id=1 30 | shutils-7868 [001] 357.732726: print: tracing_mark_write: cpu_frequency_devlib: state=1200000 cpu_id=2 31 | shutils-7868 [001] 357.732833: print: tracing_mark_write: cpu_frequency_devlib: state=950000 cpu_id=3 32 | shutils-7868 [001] 357.732939: print: tracing_mark_write: cpu_frequency_devlib: state=950000 cpu_id=4 33 | shutils-7868 [001] 357.733077: print: tracing_mark_write: cpu_frequency_devlib: state=950000 cpu_id=5 34 | bash-7870 [002] 357.892659: print: tracing_mark_write: TRACE_MARKER_STOP 35 | """ 36 | 37 | class TestFallback(SetupDirectory): 38 | def __init__(self, *args, **kwargs): 39 | super(TestFallback, self).__init__([], *args, **kwargs) 40 | 41 | def test_tracing_mark_write(self): 42 | with open("trace.txt", "w") as fout: 43 | fout.write(TEST_DATA) 44 | 45 | trace = trappy.FTrace(events=['cpu_frequency_devlib', 'tracing_mark_write']) 46 | 47 | self.assertEqual(len(trace.cpu_frequency_devlib.data_frame), 12) 48 | self.assertEqual(len(trace.tracing_mark_write.data_frame), 2) 49 | 50 | -------------------------------------------------------------------------------- /tests/test_filesystem.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 ARM Limited, Google 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | import os 20 | import sys 21 | 22 | import utils_tests 23 | import trappy 24 | 25 | sys.path.append(os.path.join(utils_tests.TESTS_DIRECTORY, "..", "trappy")) 26 | 27 | class TestFilesystem(utils_tests.SetupDirectory): 28 | def __init__(self, *args, **kwargs): 29 | super(TestFilesystem, self).__init__( 30 | [("trace_filesystem.txt", "trace_filesystem.txt"),], 31 | *args, 32 | **kwargs) 33 | 34 | #ext4 tests 35 | def test_filesystem_ext_da_write_begin_can_be_parsed(self): 36 | """TestFilesystem: test that ext4_da_write_begin events can be parsed""" 37 | trace = trappy.FTrace("trace_filesystem.txt", events=['ext4_da_write_begin']) 38 | df = trace.ext4_da_write_begin.data_frame 39 | self.assertSetEqual(set(df.columns), 40 | set(["__comm", "__cpu", "__line", "__pid", "dev", "inode", "pos", "len", "flags"])) 41 | 42 | def test_filesystem_ext_da_write_end_can_be_parsed(self): 43 | """TestFilesystem: test that ext4_da_write_end events can be parsed""" 44 | trace = trappy.FTrace("trace_filesystem.txt", events=['ext4_da_write_end']) 45 | df = trace.ext4_da_write_end.data_frame 46 | self.assertSetEqual(set(df.columns), 47 | set(["__comm", "__cpu", "__line", "__pid", "dev", "inode", "pos", "len", "copied"])) 48 | 49 | def test_filesystem_ext_sync_file_enter_can_be_parsed(self): 50 | """TestFilesystem: test that ext4_sync_file_enter events can be parsed""" 51 | trace = trappy.FTrace("trace_filesystem.txt", events=['ext4_sync_file_enter']) 52 | df = trace.ext4_sync_file_enter.data_frame 53 | self.assertSetEqual(set(df.columns), 54 | set(["__comm", "__cpu", "__line", "__pid", "dev", "inode", "parent", "datasync"])) 55 | 56 | def test_filesystem_ext_sync_file_exit_can_be_parsed(self): 57 | """TestFilesystem: test that ext4_sync_file_exit events can be parsed""" 58 | trace = trappy.FTrace("trace_filesystem.txt", events=['ext4_sync_file_exit']) 59 | df = trace.ext4_sync_file_exit.data_frame 60 | self.assertSetEqual(set(df.columns), 61 | set(["__comm", "__cpu", "__line", "__pid", "dev", "inode", "ret"])) 62 | 63 | #f2fs tests 64 | def test_filesystem_f2fs_write_begin_can_be_parsed(self): 65 | """TestFilesystem: test that f2fs_write_begin events can be parsed""" 66 | trace = trappy.FTrace("trace_filesystem.txt", events=['f2fs_write_begin']) 67 | df = trace.f2fs_write_begin.data_frame 68 | self.assertSetEqual(set(df.columns), 69 | set(["__comm", "__cpu", "__line", "__pid", "dev", "inode", "pos", "len", "flags"])) 70 | 71 | def test_filesystem_f2fs_write_end_can_be_parsed(self): 72 | """TestFilesystem: test that f2fs_write_end events can be parsed""" 73 | trace = trappy.FTrace("trace_filesystem.txt", events=['f2fs_write_end']) 74 | df = trace.f2fs_write_end.data_frame 75 | self.assertSetEqual(set(df.columns), 76 | set(["__comm", "__cpu", "__line", "__pid", "dev", "inode", "pos", "len", "copied"])) 77 | 78 | def test_filesystem_f2fs_sync_file_enter_can_be_parsed(self): 79 | """TestFilesystem: test that f2fs_sync_file_enter events can be parsed""" 80 | trace = trappy.FTrace("trace_filesystem.txt", events=['f2fs_sync_file_enter']) 81 | df = trace.f2fs_sync_file_enter.data_frame 82 | self.assertSetEqual(set(df.columns), 83 | set(["__comm", "__cpu", "__line", "__pid", "dev", 84 | "inode", "pino", "i_mode", "i_size", "i_nlink", "i_blocks", "i_advise"])) 85 | 86 | def test_filesystem_f2fs_sync_file_exit_can_be_parsed(self): 87 | """TestFilesystem: test that f2fs_sync_file_exit events can be parsed""" 88 | trace = trappy.FTrace("trace_filesystem.txt", events=['f2fs_sync_file_exit']) 89 | df = trace.f2fs_sync_file_exit.data_frame 90 | self.assertSetEqual(set(df.columns), 91 | set(["__comm", "__cpu", "__line", "__pid", "dev", "inode", "checkpoint", "datasync", "ret"])) 92 | -------------------------------------------------------------------------------- /tests/test_idle.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | import os 20 | import sys 21 | import unittest 22 | 23 | import pandas as pd 24 | from pandas.util.testing import assert_series_equal 25 | 26 | import utils_tests 27 | import trappy 28 | 29 | @unittest.skipUnless(utils_tests.trace_cmd_installed(), 30 | "trace-cmd not installed") 31 | class TestCpuIdle(utils_tests.SetupDirectory): 32 | def __init__(self, *args, **kwargs): 33 | super(TestCpuIdle, self).__init__( 34 | [("trace_idle.dat", "trace.dat")], 35 | *args, 36 | **kwargs) 37 | 38 | def test_get_dataframe(self): 39 | """Test that CpuIdle creates a proper data_frame""" 40 | 41 | df = trappy.FTrace(normalize_time=False).cpu_idle.data_frame 42 | 43 | exp_index = pd.Float64Index([ 44 | 162534.2157642, 45 | 162534.21600068, 46 | 162534.216552, 47 | 162534.21656774, 48 | 162534.21740058, 49 | 162534.2175208, 50 | 162534.21765486, 51 | 162534.2190772, 52 | 162534.21925174, 53 | 162534.21926752, 54 | 162534.21932854, 55 | 162534.21933622, 56 | 162534.21958702, 57 | 162534.2197626, 58 | 162534.21985288, 59 | 162534.22094658, 60 | 162534.22094704, 61 | ], name="Time") 62 | 63 | exp_states = pd.Series([ 64 | 2, -1, 2, -1, -1, -1, 2, -1, 2, -1, 0, 0, 2, -1, 2, -1, -1 65 | ], index=exp_index, name="state") 66 | exp_cpus = pd.Series([ 67 | 5, 2, 2, 1, 3, 0, 0, 0, 0, 0, 1, 3, 0, 0, 0, 3, 1 68 | ], index=exp_index, name="cpu_id") 69 | 70 | assert_series_equal(df["state"], exp_states, check_exact=True) 71 | assert_series_equal(df["cpu_id"], exp_cpus, check_exact=True) 72 | -------------------------------------------------------------------------------- /tests/test_pid.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | import matplotlib 20 | 21 | from test_thermal import BaseTestThermal 22 | import trappy 23 | 24 | class TestPIDController(BaseTestThermal): 25 | def test_dataframe(self): 26 | """Test that PIDController() generates a valid data_frame""" 27 | pid = trappy.FTrace().pid_controller 28 | 29 | self.assertTrue(len(pid.data_frame) > 0) 30 | self.assertTrue("err_integral" in pid.data_frame.columns) 31 | self.assertEqual(pid.data_frame["err"].iloc[0], 3225) 32 | 33 | def test_plot_controller(self): 34 | """Test PIDController.plot_controller() 35 | 36 | As it happens with all plot functions, just test that it doesn't explode""" 37 | pid = trappy.FTrace().pid_controller 38 | 39 | pid.plot_controller() 40 | matplotlib.pyplot.close('all') 41 | 42 | pid.plot_controller(title="Antutu", width=20, height=5) 43 | matplotlib.pyplot.close('all') 44 | 45 | _, ax = matplotlib.pyplot.subplots() 46 | pid.plot_controller(ax=ax) 47 | matplotlib.pyplot.close('all') 48 | -------------------------------------------------------------------------------- /tests/test_results.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | import os, sys 20 | import shutil 21 | import tempfile 22 | import matplotlib 23 | import pandas as pd 24 | 25 | import utils_tests 26 | sys.path.append(os.path.join(utils_tests.TESTS_DIRECTORY, "..", "trappy")) 27 | from trappy.wa import Result, get_results, combine_results 28 | 29 | class TestResults(utils_tests.SetupDirectory): 30 | def __init__(self, *args, **kwargs): 31 | super(TestResults, self).__init__( 32 | [("results.csv", "results.csv")], 33 | *args, **kwargs) 34 | 35 | def test_get_results(self): 36 | results_frame = get_results() 37 | 38 | self.assertEqual(type(results_frame), Result) 39 | self.assertEqual(type(results_frame.columns), pd.core.index.MultiIndex) 40 | self.assertEqual(results_frame["antutu"]["power_allocator"][0], 5) 41 | self.assertEqual(results_frame["antutu"]["step_wise"][1], 9) 42 | self.assertEqual(results_frame["antutu"]["step_wise"][2], 7) 43 | self.assertEqual(results_frame["t-rex_offscreen"]["power_allocator"][0], 1777) 44 | self.assertEqual(results_frame["geekbench"]["step_wise"][0], 8) 45 | self.assertEqual(results_frame["geekbench"]["power_allocator"][1], 1) 46 | self.assertAlmostEquals(results_frame["thechase"]["step_wise"][0], 242.0522258138) 47 | 48 | def test_get_results_path(self): 49 | """get_results() can be given a directory for the results.csv""" 50 | 51 | other_random_dir = tempfile.mkdtemp() 52 | os.chdir(other_random_dir) 53 | 54 | results_frame = get_results(self.out_dir) 55 | 56 | self.assertEqual(len(results_frame.columns), 10) 57 | 58 | def test_get_results_filename(self): 59 | """get_results() can be given a specific filename""" 60 | 61 | old_path = os.path.join(self.out_dir, "results.csv") 62 | new_path = os.path.join(self.out_dir, "new_results.csv") 63 | os.rename(old_path, new_path) 64 | 65 | results_frame = get_results(new_path) 66 | 67 | self.assertEqual(len(results_frame.columns), 10) 68 | 69 | def test_get_results_name(self): 70 | """get_results() optional name argument overrides the one in the results file""" 71 | res = get_results(name="malkovich") 72 | self.assertIsNotNone(res["antutu"]["malkovich"]) 73 | 74 | def test_combine_results(self): 75 | res1 = get_results() 76 | res2 = get_results() 77 | 78 | # First split them 79 | res1.drop('step_wise', axis=1, level=1, inplace=True) 80 | res2.drop('power_allocator', axis=1, level=1, inplace=True) 81 | 82 | # Now combine them again 83 | combined = combine_results([res1, res2]) 84 | 85 | self.assertEqual(type(combined), Result) 86 | self.assertEqual(combined["antutu"]["step_wise"][0], 4) 87 | self.assertEqual(combined["antutu"]["power_allocator"][0], 5) 88 | self.assertEqual(combined["geekbench"]["power_allocator"][1], 1) 89 | self.assertEqual(combined["t-rex_offscreen"]["step_wise"][2], 424) 90 | 91 | def test_plot_results_benchmark(self): 92 | """Test Result.plot_results_benchmark() 93 | 94 | Can't test it, so just check that it doens't bomb 95 | """ 96 | 97 | res = get_results() 98 | 99 | res.plot_results_benchmark("antutu") 100 | res.plot_results_benchmark("t-rex_offscreen", title="Glbench TRex") 101 | 102 | (_, _, y_min, y_max) = matplotlib.pyplot.axis() 103 | 104 | trex_data = pd.concat(res["t-rex_offscreen"][s] for s in res["t-rex_offscreen"]) 105 | data_min = min(trex_data) 106 | data_max = max(trex_data) 107 | 108 | # Fail if the axes are within the limits of the data. 109 | self.assertTrue(data_min > y_min) 110 | self.assertTrue(data_max < y_max) 111 | matplotlib.pyplot.close('all') 112 | 113 | def test_get_run_number(self): 114 | from trappy.wa.results import get_run_number 115 | 116 | self.assertEqual(get_run_number("score_2"), (True, 2)) 117 | self.assertEqual(get_run_number("score"), (True, 0)) 118 | self.assertEqual(get_run_number("score 3"), (True, 3)) 119 | self.assertEqual(get_run_number("FPS_1"), (True, 1)) 120 | self.assertEqual(get_run_number("Overall_Score"), (True, 0)) 121 | self.assertEqual(get_run_number("Overall_Score_2"), (True, 1)) 122 | self.assertEqual(get_run_number("Memory_score")[0], False) 123 | 124 | def test_plot_results(self): 125 | """Test Result.plot_results() 126 | 127 | Can't test it, so just check that it doens't bomb 128 | """ 129 | 130 | res = get_results() 131 | 132 | res.plot_results() 133 | matplotlib.pyplot.close('all') 134 | 135 | def test_init_fig(self): 136 | r1 = get_results() 137 | r1.init_fig() 138 | -------------------------------------------------------------------------------- /tests/test_sort.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Arm Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | import os 20 | import sys 21 | import unittest 22 | 23 | import pandas as pd 24 | from pandas.util.testing import assert_series_equal 25 | 26 | import utils_tests 27 | import trappy 28 | 29 | @unittest.skipUnless(utils_tests.trace_cmd_installed(), 30 | "trace-cmd not installed") 31 | class TestCpuIdle(utils_tests.SetupDirectory): 32 | def __init__(self, *args, **kwargs): 33 | super(TestCpuIdle, self).__init__( 34 | [("trace_idle_unsorted.txt", "trace.txt")], 35 | *args, 36 | **kwargs) 37 | 38 | def test_get_dataframe(self): 39 | """Test that unsorted events are handled correctly""" 40 | 41 | df = trappy.FTrace(normalize_time=False).cpu_idle.data_frame 42 | self.assertEqual(df.index.size, 8) 43 | -------------------------------------------------------------------------------- /tests/test_systrace.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | import utils_tests 20 | import trappy 21 | import numpy as np 22 | 23 | class TestSystrace(utils_tests.SetupDirectory): 24 | 25 | def __init__(self, *args, **kwargs): 26 | super(TestSystrace, self).__init__( 27 | [("trace_systrace.html", "trace.html"), 28 | ("trace_surfaceflinger.html", "trace_sf.html")], 29 | *args, 30 | **kwargs) 31 | 32 | def test_systrace_html(self): 33 | """Tests parsing of a systrace embedded textual trace """ 34 | 35 | events = ["sched_switch", "sched_wakeup", "trace_event_clock_sync"] 36 | trace = trappy.SysTrace("trace.html", events=events) 37 | 38 | self.assertTrue(hasattr(trace, "sched_switch")) 39 | self.assertEqual(len(trace.sched_switch.data_frame), 4) 40 | self.assertTrue("prev_comm" in trace.sched_switch.data_frame.columns) 41 | 42 | self.assertTrue(hasattr(trace, "sched_wakeup")) 43 | self.assertEqual(len(trace.sched_wakeup.data_frame), 4) 44 | self.assertTrue("target_cpu" in trace.sched_wakeup.data_frame.columns) 45 | 46 | self.assertTrue(hasattr(trace, "trace_event_clock_sync")) 47 | self.assertEqual(len(trace.trace_event_clock_sync.data_frame), 1) 48 | self.assertTrue("realtime_ts" in trace.trace_event_clock_sync.data_frame.columns) 49 | 50 | def test_cpu_counting(self): 51 | """SysTrace traces know the number of cpus""" 52 | 53 | trace = trappy.SysTrace("trace.html") 54 | 55 | self.assertTrue(hasattr(trace, "_cpus")) 56 | self.assertEqual(trace._cpus, 3) 57 | 58 | def test_systrace_userspace(self): 59 | """Test parsing of userspace events""" 60 | 61 | # Test a 'B' event (begin) 62 | trace = trappy.SysTrace("trace_sf.html") 63 | dfr = trace.tracing_mark_write.data_frame 64 | self.assertEqual(dfr['__pid'].iloc[2], 7591) 65 | self.assertEqual(dfr['__comm'].iloc[2], 'RenderThread') 66 | self.assertEqual(dfr['pid'].iloc[2], 7459) 67 | self.assertEqual(dfr['event'].iloc[2], 'B') 68 | self.assertEqual(dfr['func'].iloc[2], 'notifyFramePending') 69 | self.assertEqual(dfr['data'].iloc[2], None) 70 | 71 | # Test a 'C' event (count) 72 | self.assertEqual(dfr['__pid'].iloc[-2], 612) 73 | self.assertEqual(dfr['__comm'].iloc[-2], 'HwBinder:594_1') 74 | self.assertEqual(dfr['pid'].iloc[-2], 594) 75 | self.assertEqual(dfr['func'].iloc[-2], 'HW_VSYNC_0') 76 | self.assertEqual(dfr['event'].iloc[-2], 'C') 77 | self.assertEqual(dfr['data'].iloc[-2], '0') 78 | 79 | # Test an 'E' event (end) 80 | edfr = dfr[dfr['event'] == 'E'] 81 | self.assertEqual(edfr['__pid'].iloc[0], 7591) 82 | self.assertEqual(edfr['__comm'].iloc[0], 'RenderThread') 83 | self.assertTrue(np.isnan(edfr['pid'].iloc[0])) 84 | self.assertEqual(edfr['func'].iloc[0], None) 85 | self.assertEqual(edfr['event'].iloc[0], 'E') 86 | self.assertEqual(edfr['data'].iloc[0], None) 87 | 88 | def test_systrace_line_num(self): 89 | """Test for line numbers in a systrace""" 90 | trace = trappy.SysTrace("trace_sf.html") 91 | dfr = trace.sched_switch.data_frame 92 | self.assertEqual(trace.lines, 2506) 93 | self.assertEqual(dfr['__line'].iloc[0], 0) 94 | self.assertEqual(dfr['__line'].iloc[1], 6) 95 | self.assertEqual(dfr['__line'].iloc[-1], 2505) 96 | 97 | def test_parse_tracing_mark_write_events(self): 98 | """Check that tracing_mark_write events are parsed without errors""" 99 | events = ['tracing_mark_write'] 100 | try: 101 | trace = trappy.SysTrace("trace.html", events=events) 102 | except TypeError as e: 103 | self.fail("tracing_mark_write parsing failed with {} exception"\ 104 | .format(e.message)) 105 | 106 | 107 | class TestLegacySystrace(utils_tests.SetupDirectory): 108 | 109 | def __init__(self, *args, **kwargs): 110 | super(TestLegacySystrace, self).__init__( 111 | [("trace_legacy_systrace.html", "trace.html")], 112 | *args, 113 | **kwargs) 114 | 115 | def test_systrace_html(self): 116 | """Tests parsing of a legacy systrace embedded textual trace """ 117 | 118 | events = ["sched_switch", "sched_wakeup", "sched_contrib_scale_f"] 119 | trace = trappy.SysTrace("trace.html", events=events) 120 | 121 | self.assertTrue(hasattr(trace, "sched_switch")) 122 | self.assertEqual(len(trace.sched_switch.data_frame), 3) 123 | self.assertTrue("prev_comm" in trace.sched_switch.data_frame.columns) 124 | 125 | self.assertTrue(hasattr(trace, "sched_wakeup")) 126 | self.assertEqual(len(trace.sched_wakeup.data_frame), 2) 127 | self.assertTrue("target_cpu" in trace.sched_wakeup.data_frame.columns) 128 | 129 | self.assertTrue(hasattr(trace, "sched_contrib_scale_factor")) 130 | self.assertEqual(len(trace.sched_contrib_scale_factor.data_frame), 2) 131 | self.assertTrue("freq_scale_factor" in trace.sched_contrib_scale_factor.data_frame.columns) 132 | 133 | def test_cpu_counting(self): 134 | """In a legacy SysTrace trace, trappy gets the number of cpus""" 135 | 136 | trace = trappy.SysTrace("trace.html") 137 | 138 | self.assertTrue(hasattr(trace, "_cpus")) 139 | self.assertEqual(trace._cpus, 8) 140 | -------------------------------------------------------------------------------- /tests/test_trappy.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | from builtins import str 20 | import os 21 | import re 22 | import matplotlib, tempfile 23 | 24 | import trappy 25 | from test_thermal import BaseTestThermal 26 | 27 | class TestTrappy(BaseTestThermal): 28 | def __init__(self, *args, **kwargs): 29 | super(TestTrappy, self).__init__(*args, **kwargs) 30 | self.map_label = {"00000000,00000039": "A53", "00000000,00000006": "A57"} 31 | self.actor_order = ["GPU", "A57", "A53"] 32 | 33 | def test_summary_plots(self): 34 | """Test summary_plots() 35 | 36 | Can't check that the graphs are ok, so just see that the method doesn't blow up""" 37 | 38 | trappy.summary_plots(self.actor_order, self.map_label) 39 | matplotlib.pyplot.close('all') 40 | 41 | trappy.summary_plots(self.actor_order, self.map_label, width=14, 42 | title="Foo") 43 | matplotlib.pyplot.close('all') 44 | 45 | def test_summary_plots_bad_parameters(self): 46 | """When summary_plots() receives bad parameters, it offers an understandable error""" 47 | 48 | self.assertRaises(TypeError, trappy.summary_plots, 49 | (self.map_label, self.actor_order)) 50 | 51 | try: 52 | trappy.summary_plots(self.map_label, self.actor_order) 53 | except TypeError as exception: 54 | self.assertTrue("actor_order" in str(exception)) 55 | else: 56 | self.fail() 57 | 58 | try: 59 | trappy.summary_plots(self.actor_order, self.actor_order) 60 | except TypeError as exception: 61 | self.assertTrue("map_label" in str(exception)) 62 | else: 63 | self.fail() 64 | 65 | def test_summary_other_dir(self): 66 | """Test summary_plots() with another directory""" 67 | 68 | other_random_dir = tempfile.mkdtemp() 69 | os.chdir(other_random_dir) 70 | 71 | trappy.summary_plots(self.actor_order, self.map_label, path=self.out_dir) 72 | matplotlib.pyplot.close('all') 73 | 74 | # Sanity check that the test actually ran from another directory 75 | self.assertEqual(os.getcwd(), other_random_dir) 76 | 77 | def test_summary_plots_only_power_allocator_trace(self): 78 | """Test that summary_plots() work if there is only power allocator 79 | trace""" 80 | 81 | # Strip out "thermal_temperature" from the trace 82 | trace_out = "" 83 | with open("trace.txt") as fin: 84 | for line in fin: 85 | if not re.search("thermal_temperature:", line): 86 | trace_out += line 87 | 88 | with open("trace.txt", "w") as fout: 89 | fout.write(trace_out) 90 | 91 | trappy.summary_plots(self.actor_order, self.map_label) 92 | matplotlib.pyplot.close('all') 93 | 94 | def test_summary_plots_no_gpu(self): 95 | """summary_plots() works if there is no GPU trace""" 96 | 97 | # Strip out devfreq traces 98 | trace_out = "" 99 | with open("trace.txt") as fin: 100 | for line in fin: 101 | if ("thermal_power_devfreq_get_power:" not in line) and \ 102 | ("thermal_power_devfreq_limit:" not in line): 103 | trace_out += line 104 | 105 | with open("trace.txt", "w") as fout: 106 | fout.write(trace_out) 107 | 108 | trappy.summary_plots(self.actor_order, self.map_label) 109 | matplotlib.pyplot.close('all') 110 | 111 | def test_summary_plots_one_actor(self): 112 | """summary_plots() works if there is only one actor""" 113 | 114 | # Strip out devfreq and little traces 115 | trace_out = "" 116 | with open("trace.txt") as fin: 117 | for line in fin: 118 | if ("thermal_power_devfreq_get_power:" not in line) and \ 119 | ("thermal_power_devfreq_limit:" not in line) and \ 120 | ("thermal_power_cpu_get_power: cpus=00000000,00000039" not in line) and \ 121 | ("thermal_power_cpu_limit: cpus=00000000,00000039" not in line): 122 | trace_out += line 123 | 124 | with open("trace.txt", "w") as fout: 125 | fout.write(trace_out) 126 | 127 | map_label = {"00000000,00000006": "A57"} 128 | trappy.summary_plots(self.actor_order, map_label) 129 | matplotlib.pyplot.close('all') 130 | 131 | def test_compare_runs(self): 132 | """Basic compare_runs() functionality""" 133 | 134 | trappy.compare_runs(self.actor_order, self.map_label, 135 | runs=[("new", "."), ("old", self.out_dir)]) 136 | matplotlib.pyplot.close('all') 137 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | import unittest 20 | from trappy import utils 21 | import pandas 22 | from pandas.util.testing import assert_series_equal 23 | 24 | 25 | class TestUtils(unittest.TestCase): 26 | 27 | def test_handle_duplicate_index(self): 28 | """Test Util Function: handle_duplicate_index 29 | """ 30 | 31 | # Refer to the example in the function doc string 32 | values = [0, 1, 2, 3, 4] 33 | index = [0.0, 1.0, 1.0, 6.0, 7.0] 34 | series = pandas.Series(values, index=index) 35 | new_index = [0.0, 1.0, 2.0, 3.0, 4.0, 6.0, 7.0] 36 | 37 | with self.assertRaises(ValueError): 38 | series.reindex(new_index) 39 | 40 | max_delta = 0.001 41 | expected_index = [0.0, 1.0, 1 + max_delta, 6.0, 7.0] 42 | expected_series = pandas.Series(values, index=expected_index) 43 | series = utils.handle_duplicate_index(series, max_delta) 44 | assert_series_equal(series, expected_series) 45 | 46 | # Make sure that the reindex doesn't raise ValueError any more 47 | series.reindex(new_index) 48 | 49 | def test_handle_duplicate_index_duplicate_end(self): 50 | """handle_duplicate_index copes with duplicates at the end of the series""" 51 | 52 | max_delta = 0.001 53 | values = [0, 1, 2, 3, 4] 54 | index = [0.0, 1.0, 2.0, 6.0, 6.0] 55 | expected_index = index[:] 56 | expected_index[-1] += max_delta 57 | series = pandas.Series(values, index=index) 58 | expected_series = pandas.Series(values, index=expected_index) 59 | 60 | series = utils.handle_duplicate_index(series, max_delta) 61 | assert_series_equal(series, expected_series) 62 | -------------------------------------------------------------------------------- /tests/test_wa_sysfs_extractor.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | import os 20 | import subprocess 21 | import unittest 22 | 23 | import utils_tests 24 | 25 | import trappy.wa 26 | 27 | class TestWASysfsExtractor(utils_tests.SetupDirectory): 28 | """Test the WA specific interface to get parameters from a sysfs extractor""" 29 | def __init__(self, *args, **kwargs): 30 | self.wa_sysfs_fname = "WA_sysfs_extract.tar.xz" 31 | super(TestWASysfsExtractor, self).__init__( 32 | [(self.wa_sysfs_fname, self.wa_sysfs_fname)], 33 | *args, **kwargs) 34 | 35 | def setUp(self): 36 | super(TestWASysfsExtractor, self).setUp() 37 | subprocess.check_call(["tar", "xf", self.wa_sysfs_fname]) 38 | 39 | def test_get_parameters(self): 40 | """Test that we can get the parameters of a sysfs extractor output""" 41 | 42 | os.chdir("..") 43 | thermal_params = trappy.wa.SysfsExtractor(self.out_dir).get_parameters() 44 | self.assertEqual(thermal_params["cdev0_weight"], 1024) 45 | self.assertEqual(thermal_params["cdev1_weight"], 768) 46 | self.assertEqual(thermal_params["trip_point_0_temp"], 72000) 47 | self.assertEqual(thermal_params["policy"], "power_allocator") 48 | 49 | def test_print_thermal_params(self): 50 | """Test that printing the thermal params doesn't bomb""" 51 | 52 | trappy.wa.SysfsExtractor(".").pretty_print_in_ipython() 53 | 54 | class TestWASysfsExtractorFailMode(unittest.TestCase): 55 | """Test the failure modes of the Workload Automation sysfs extractor""" 56 | 57 | def test_get_params_invalid_directory(self): 58 | """An invalid directory for trappy.wa.SysfsExtractor doesn't bomb""" 59 | 60 | sysfs_extractor = trappy.wa.SysfsExtractor(".") 61 | self.assertEqual(sysfs_extractor.get_parameters(), {}) 62 | 63 | sysfs_extractor.pretty_print_in_ipython() 64 | -------------------------------------------------------------------------------- /tests/trace.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARM-software/trappy/bba21c4de5cbe380d375bdf3f5666c7880235287/tests/trace.dat -------------------------------------------------------------------------------- /tests/trace_common_clk.txt: -------------------------------------------------------------------------------- 1 | VideoDecMsgThr-32342 [007] 85636.208531: clock_set_rate: video_subcore0_clk_src state=200000000 cpu_id=7 2 | VideoDecMsgThr-32342 [007] 85636.208637: clock_set_rate: mmss_video_subcore1_clk state=200000000 cpu_id=7 3 | VideoDecMsgThr-32342 [007] 85636.208640: clock_set_rate: video_subcore1_clk_src state=200000000 cpu_id=7 4 | writer-14965 [001] 85636.216529: clock_enable: blsp2_qup1_i2c_apps_clk_src state=1 cpu_id=1 5 | writer-14965 [001] 85636.216531: clock_enable: gcc_blsp2_qup1_i2c_apps_clk state=1 cpu_id=1 6 | writer-14965 [000] 85636.216771: clock_disable: gcc_blsp2_qup1_i2c_apps_clk state=0 cpu_id=0 7 | writer-14965 [000] 85636.216774: clock_disable: blsp2_qup1_i2c_apps_clk_src state=0 cpu_id=0 8 | writer-14965 [000] 85636.216801: clock_enable: blsp2_qup1_i2c_apps_clk_src state=1 cpu_id=0 9 | -------------------------------------------------------------------------------- /tests/trace_empty.txt: -------------------------------------------------------------------------------- 1 | version = 6 2 | cpus=5 3 | trace-cmd-2461 [000] 34.896920: sched_stat_runtime: comm=trace-cmd pid=2461 runtime=7778999 [ns] vruntime=6087430069 [ns] 4 | trace-cmd-2461 [000] 34.896941: sched_stat_sleep: comm=sshd pid=2450 delay=1802051076 [ns] 5 | trace-cmd-2461 [000] 34.897022: sched_stat_wait: comm=sshd pid=2450 delay=0 [ns] 6 | -0 [001] 34.897147: sched_stat_wait: comm=rcuos/0 pid=9 delay=0 [ns] 7 | rcuos/0-9 [001] 34.897169: sched_stat_runtime: comm=sshd pid=2450 runtime=258292 [ns] vruntime=6078688361 [ns] 8 | rcuos/0-9 [001] 34.897175: sched_stat_sleep: comm=rcu_sched pid=7 delay=26617125 [ns] 9 | rcuos/0-9 [001] 34.897186: sched_stat_runtime: comm=rcuos/0 pid=9 runtime=445750 [ns] vruntime=6730976621 [ns] 10 | sshd-2450 [000] 34.897303: sched_stat_runtime: comm=sshd pid=2450 runtime=134042 [ns] vruntime=6078822403 [ns] 11 | sshd-2450 [000] 34.897313: sched_stat_wait: comm=rcu_sched pid=7 delay=134042 [ns] 12 | rcu_sched-7 [000] 34.897329: sched_stat_runtime: comm=rcu_sched pid=7 runtime=25916 [ns] vruntime=6078455985 [ns] 13 | rcu_sched-7 [000] 34.897338: sched_stat_wait: comm=trace-cmd pid=2461 delay=418250 [ns] 14 | trace-cmd-2461 [000] 34.897641: sched_process_exit: comm=trace-cmd pid=2461 prio=120 15 | trace-cmd-2461 [000] 34.897665: sched_stat_runtime: comm=trace-cmd pid=2461 runtime=335875 [ns] vruntime=6087765944 [ns] 16 | trace-cmd-2461 [000] 34.897675: sched_stat_runtime: comm=trace-cmd pid=2461 runtime=10459 [ns] vruntime=6087776403 [ns] 17 | trace-cmd-2461 [000] 34.897697: sched_stat_sleep: comm=bash pid=2459 delay=696151164 [ns] 18 | trace-cmd-2461 [000] 34.897708: sched_stat_runtime: comm=trace-cmd pid=2461 runtime=33500 [ns] vruntime=6087809903 [ns] 19 | -0 [001] 34.898076: sched_stat_wait: comm=bash pid=2459 delay=0 [ns] 20 | bash-2459 [001] 34.898188: sched_process_wait: comm=bash pid=0 prio=120 21 | bash-2459 [001] 34.898653: sched_process_exit: comm=bash pid=2459 prio=120 22 | bash-2459 [001] 34.898702: sched_stat_sleep: comm=sshd pid=2450 delay=1394541 [ns] 23 | -0 [000] 34.898720: sched_stat_wait: comm=sshd pid=2450 delay=0 [ns] 24 | bash-2459 [001] 34.898721: sched_stat_runtime: comm=bash pid=2459 runtime=1027542 [ns] vruntime=6723004163 [ns] 25 | bash-2459 [001] 34.898732: sched_stat_runtime: comm=bash pid=2459 runtime=11000 [ns] vruntime=6723015163 [ns] 26 | bash-2459 [001] 34.898745: sched_stat_runtime: comm=bash pid=2459 runtime=13458 [ns] vruntime=6723028621 [ns] 27 | sshd-2450 [000] 34.898825: sched_process_wait: comm=sshd pid=0 prio=120 28 | sshd-2450 [000] 34.898927: sched_process_wait: comm=sshd pid=0 prio=120 29 | sshd-2450 [000] 34.898983: sched_stat_runtime: comm=sshd pid=2450 runtime=285875 [ns] vruntime=6079108278 [ns] 30 | -0 [000] 34.899144: sched_stat_sleep: comm=rcu_sched pid=7 delay=1810584 [ns] 31 | -0 [000] 34.899175: sched_stat_wait: comm=rcu_sched pid=7 delay=0 [ns] 32 | rcu_sched-7 [000] 34.899194: sched_stat_sleep: comm=rcuos/0 pid=9 delay=2004042 [ns] 33 | rcu_sched-7 [000] 34.899205: sched_stat_runtime: comm=rcu_sched pid=7 runtime=65375 [ns] vruntime=6078875278 [ns] 34 | -0 [001] 34.899620: sched_stat_wait: comm=rcuos/0 pid=9 delay=0 [ns] 35 | rcuos/0-9 [001] 34.899770: sched_stat_sleep: comm=rcu_sched pid=7 delay=538708 [ns] 36 | rcuos/0-9 [001] 34.899848: sched_stat_runtime: comm=rcuos/0 pid=9 runtime=653458 [ns] vruntime=6731630079 [ns] 37 | -0 [000] 34.899988: sched_stat_sleep: comm=sshd pid=2450 delay=1003959 [ns] 38 | -0 [000] 34.900052: sched_stat_wait: comm=rcu_sched pid=7 delay=0 [ns] 39 | rcu_sched-7 [000] 34.900069: sched_stat_runtime: comm=rcu_sched pid=7 runtime=325042 [ns] vruntime=6079200320 [ns] 40 | -0 [001] 34.900452: sched_stat_wait: comm=sshd pid=2450 delay=0 [ns] 41 | sshd-2450 [001] 34.900756: sched_stat_sleep: comm=in:imuxsock pid=1767 delay=1926798326 [ns] 42 | -0 [000] 34.901148: sched_stat_wait: comm=in:imuxsock pid=1767 delay=0 [ns] 43 | in:imuxsock-1767 [000] 34.901243: sched_stat_runtime: comm=in:imuxsock pid=1767 runtime=496084 [ns] vruntime=6079305987 [ns] 44 | in:imuxsock-1767 [000] 34.901250: sched_stat_sleep: comm=rs:main Q:Reg pid=1769 delay=1925639660 [ns] 45 | in:imuxsock-1767 [000] 34.901271: sched_stat_runtime: comm=in:imuxsock pid=1767 runtime=28500 [ns] vruntime=6079334487 [ns] 46 | in:imuxsock-1767 [000] 34.901282: sched_stat_wait: comm=rs:main Q:Reg pid=1769 delay=28500 [ns] 47 | sshd-2450 [001] 34.901431: sched_stat_runtime: comm=rs:main Q:Reg pid=1769 runtime=160500 [ns] vruntime=6078970403 [ns] 48 | sshd-2450 [001] 34.901437: sched_stat_sleep: comm=in:imuxsock pid=1767 delay=160500 [ns] 49 | rs:main-1769 [000] 34.901447: sched_stat_runtime: comm=rs:main Q:Reg pid=1769 runtime=15833 [ns] vruntime=6078986236 [ns] 50 | rs:main-1769 [000] 34.901457: sched_stat_wait: comm=in:imuxsock pid=1767 delay=15833 [ns] 51 | in:imuxsock-1767 [000] 34.901490: sched_stat_runtime: comm=in:imuxsock pid=1767 runtime=43125 [ns] vruntime=6079377612 [ns] 52 | in:imuxsock-1767 [000] 34.901495: sched_stat_sleep: comm=rs:main Q:Reg pid=1769 delay=43125 [ns] 53 | in:imuxsock-1767 [000] 34.901511: sched_stat_runtime: comm=in:imuxsock pid=1767 runtime=21333 [ns] vruntime=6079398945 [ns] 54 | -------------------------------------------------------------------------------- /tests/trace_equals.txt: -------------------------------------------------------------------------------- 1 | systemd-journal-1662 [003] 653.065292: bputs: 0xffff0000080feeb8s: equals_event: my_field=foo 2 | systemd-journal-1662 [003] 653.065293: bputs: 0xffff0000080feeb8s: equals_event: my_field=foo=bar 3 | systemd-journal-1662 [003] 653.065294: bputs: 0xffff0000080feeb8s: equals_event: my_field=foo=bar=baz 4 | systemd-journal-1662 [003] 653.065295: bputs: 0xffff0000080feeb8s: equals_event: my_field=1 5 | systemd-journal-1662 [003] 653.065296: bputs: 0xffff0000080feeb8s: equals_event: my_field=1=2 6 | systemd-journal-1662 [003] 653.065297: bputs: 0xffff0000080feeb8s: equals_event: my_field=1=foo 7 | systemd-journal-1662 [003] 653.065298: bputs: 0xffff0000080feeb8s: equals_event: my_field=1foo=2 8 | -------------------------------------------------------------------------------- /tests/trace_failed_to_parse.txt: -------------------------------------------------------------------------------- 1 | kworker/5:2-1497 [005] 454.414591: thermal_temperature: thermal_zone=cls0 id=0 temp_prev=62357 temp=79553 2 | kworker/5:2-1497 [005] 454.414603: thermal_power_cpu_get_power: [FAILED TO PARSE] cpumask=ARRAY[0f, 00, 00, 00, 00, 00, 00, 00] freq=0x82208 load=ARRAY[02, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00] load_len=4 dynamic_power=0 static_power=0 3 | kworker/5:2-1497 [005] 454.414607: thermal_power_cpu_get_power: [FAILED TO PARSE] cpumask=ARRAY[f0, 00, 00, 00, 00, 00, 00, 00] freq=0x240a90 load=ARRAY[02, 00, 00, 00, 02, 00, 00, 00, 02, 00, 00, 00, 02, 00, 00, 00] load_len=4 dynamic_power=125 static_power=0 4 | kworker/5:2-1497 [005] 454.414610: thermal_power_allocator_pid: thermal_zone_id=0 err=-4553 err_integral=-4553 p=-2046 i=-45 d=0 output=2410 5 | kworker/5:2-1497 [005] 454.414615: thermal_power_cpu_limit: cpus=00000000,0000000f freq=533000 cdev_state=4 power=0 6 | kworker/5:2-1497 [005] 454.414623: thermal_power_cpu_limit: cpus=00000000,000000f0 freq=2362000 cdev_state=0 power=2410 7 | kworker/5:2-1497 [005] 454.414625: thermal_power_allocator: [FAILED TO PARSE] tz_id=0 req_power=ARRAY[00, 00, 00, 00, 7d, 00, 00, 00] total_req_power=125 granted_power=ARRAY[00, 00, 00, 00, 6a, 09, 00, 00] total_granted_power=2410 num_actors=2 power_range=2410 max_allocatable_power=7264 current_temp=79553 delta_temp=-4553 8 | kworker/5:2-1497 [005] 454.414629: sched_load_avg_cpu: cpu=5 load_avg=253 util_avg=1007 util_avg_pelt=1007 util_avg_walt=0 9 | kworker/5:2-1497 [005] 454.414630: sched_load_avg_task: comm=kworker/5:2 pid=1497 cpu=5 load_avg=0 util_avg=0 util_avg_pelt=0 util_avg_walt=0 load_sum=47104 util_sum=47058 period_contrib=602 10 | kworker/5:2-1497 [005] 454.414630: sched_tune_tasks_update: pid=1497 comm=kworker/5:2 cpu=5 tasks=1 idx=0 boost=0 max_boost=0 11 | kworker/5:2-1497 [005] 454.414632: sched_load_avg_task: comm=big-max5 pid=5274 cpu=5 load_avg=1021 util_avg=1020 util_avg_pelt=1020 util_avg_walt=0 load_sum=48813227 util_sum=48718500 period_contrib=919 12 | -------------------------------------------------------------------------------- /tests/trace_idle.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARM-software/trappy/bba21c4de5cbe380d375bdf3f5666c7880235287/tests/trace_idle.dat -------------------------------------------------------------------------------- /tests/trace_idle_unsorted.txt: -------------------------------------------------------------------------------- 1 | version = 6 2 | cpus=8 3 | <...>-15430 [006] 7115.449594: sched_switch: prev_comm=trace-cmd prev_pid=15430 prev_prio=120 prev_state=64 next_comm=swapper/6 next_pid=0 next_prio=120 4 | -0 [006] 7115.449607: sched_switch: prev_comm=swapper/6 prev_pid=0 prev_prio=120 prev_state=1 next_comm=kworker/6:2 next_pid=11630 next_prio=120 5 | kworker/6:2-11630 [006] 7115.449621: sched_switch: prev_comm=kworker/6:2 prev_pid=11630 prev_prio=120 prev_state=256 next_comm=swapper/6 next_pid=0 next_prio=120 6 | -0 [006] 7115.449624: cpu_idle: state=0 cpu_id=6 7 | -0 [005] 7115.449770: cpu_idle: state=4294967295 cpu_id=5 8 | -0 [005] 7115.449781: sched_switch: prev_comm=swapper/5 prev_pid=0 prev_prio=120 prev_state=1 next_comm=bash next_pid=14840 next_prio=120 9 | -0 [006] 7115.449811: cpu_idle: state=4294967295 cpu_id=6 10 | -0 [006] 7115.449809: cpu_idle: state=1 cpu_id=6 11 | -0 [000] 7115.450009: cpu_idle: state=4294967295 cpu_id=0 12 | -0 [002] 7115.450021: cpu_idle: state=4294967295 cpu_id=2 13 | -0 [002] 7115.450043: sched_switch: prev_comm=swapper/2 prev_pid=0 prev_prio=120 prev_state=1 next_comm=rcu_preempt next_pid=9 next_prio=120 14 | rcu_preempt-9 [002] 7115.450059: sched_switch: prev_comm=rcu_preempt prev_pid=9 prev_prio=120 prev_state=256 next_comm=swapper/2 next_pid=0 next_prio=120 15 | -0 [000] 7115.450063: cpu_idle: state=1 cpu_id=0 16 | -0 [002] 7115.450065: cpu_idle: state=2 cpu_id=2 17 | bash-14840 [005] 7115.450093: sched_switch: prev_comm=bash prev_pid=14840 18 | -------------------------------------------------------------------------------- /tests/trace_legacy_systrace.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Android System Trace 5 | # 6 | # Remove HTML content... 7 | # 8 | 18 | 19 | 20 |
21 |
22 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /tests/trace_no_trailing_line.txt: -------------------------------------------------------------------------------- 1 | -0 [000] d..2 1136.097253: cpu_idle: state=0 cpu_id=0 2 | -0 [000] d..2 1136.097346: cpu_idle: state=4294967295 cpu_id=0 3 | chrome-2804 [003] d..3 1136.097350: sched_switch: prev_comm=chrome prev_pid=2804 prev_prio=112 prev_state=S ==> next_comm=swapper/3 next_pid=0 next_prio=120 4 | -0 [003] d..2 1136.097366: cpu_idle: state=0 cpu_id=3 5 | -0 [000] d..2 1136.097378: cpu_idle: state=0 cpu_id=0 6 | -0 [000] d..2 1136.097471: cpu_idle: state=4294967295 cpu_id=0 7 | -0 [000] d..2 1136.097503: cpu_idle: state=0 cpu_id=0 8 | -0 [000] d..2 1136.097596: cpu_idle: state=4294967295 cpu_id=0 9 | -0 [000] d..2 1136.097629: cpu_idle: state=0 cpu_id=0 10 | -0 [000] d..2 1136.097721: cpu_idle: state=4294967295 cpu_id=0 -------------------------------------------------------------------------------- /tests/trace_sched.txt: -------------------------------------------------------------------------------- 1 | version = 6 2 | cpus=6 3 | rcuos/2-22 [001] 6550.018511: sched_load_avg_sg: cpus=00000002 load=0 utilization=0 4 | trace-cmd-2971 [004] 6550.018512: sched_load_avg_task: comm=sshd pid=2962 load=0 utilization=0 runnable_avg_sum=0 running_avg_sum=0 avg_period=48595 5 | sshd-2962 [000] 6550.018513: sched_load_avg_cpu: cpu=0 load=13 utilization=18 6 | sshd-2962 [000] 6550.018514: dynamic_test_key: cpu=0 load=13 utilization=18 7 | <...>-19427 [000] 6550.018664: sched_wakeup_new: comm=shutils pid=19428 prio=120 success=1 target_cpu=2 8 | -0 [000] 6550.018679: sched_contrib_scale_f: cpu=0 freq_scale_factor=426 cpu_scale_factor=1024 9 | trace-cmd-3519 [003] 6550.018805: sched_cpu_capacity: cpu=3 capacity=430 rt_capacity=1024 10 | kworker/0:0-3410 [000] 6550.056870: cpu_frequency: state=600000 cpu_id=0 11 | -0 [001] 6550.100000: sched_wakeup: comm=rcu_preempt pid=7 prio=120 success=1 target_cpu=1 12 | <...>-19427 [000] 6551.993884: sched_wakeup_new: comm=shutils pid=19428 prio=120 success=1 target_cpu=2 13 | -0 [001] 6552.000002: sched_wakeup: comm=rcu_preempt pid=7 prio=120 success=1 target_cpu=1 14 | -------------------------------------------------------------------------------- /tests/trace_sched.txt.cache/CpuIdle.csv: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /tests/trace_sched.txt.cache/CpuInPower.csv: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /tests/trace_sched.txt.cache/CpuOutPower.csv: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /tests/trace_sched.txt.cache/DevfreqInPower.csv: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /tests/trace_sched.txt.cache/DevfreqOutPower.csv: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /tests/trace_sched.txt.cache/PIDController.csv: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /tests/trace_sched.txt.cache/SchedContribScaleFactor.csv: -------------------------------------------------------------------------------- 1 | Time,__comm,__cpu,__pid,cpu,cpu_scale_factor,freq_scale_factor 2 | 0.000167999999576,,0,0,0,1024,426 3 | -------------------------------------------------------------------------------- /tests/trace_sched.txt.cache/SchedCpuCapacity.csv: -------------------------------------------------------------------------------- 1 | Time,__comm,__cpu,__pid,capacity,cpu,rt_capacity 2 | 0.000293999999485,trace-cmd,3,3519,430,3,1024 3 | -------------------------------------------------------------------------------- /tests/trace_sched.txt.cache/SchedCpuFrequency.csv: -------------------------------------------------------------------------------- 1 | Time,__comm,__cpu,__pid,cpu,frequency 2 | 0.0383590000001,kworker/0:0,0,3410,0,600000 3 | -------------------------------------------------------------------------------- /tests/trace_sched.txt.cache/SchedLoadAvgCpu.csv: -------------------------------------------------------------------------------- 1 | Time,__comm,__cpu,__pid,cpu,load,utilization 2 | 1.99999976758e-06,sshd,0,2962,0,13,18 3 | -------------------------------------------------------------------------------- /tests/trace_sched.txt.cache/SchedLoadAvgSchedGroup.csv: -------------------------------------------------------------------------------- 1 | Time,__comm,__cpu,__pid,cpus,load,utilization 2 | 0.0,rcuos/2,1,22,00000002,0,0 3 | -------------------------------------------------------------------------------- /tests/trace_sched.txt.cache/SchedLoadAvgTask.csv: -------------------------------------------------------------------------------- 1 | Time,__comm,__cpu,__pid,avg_period,comm,load,pid,runnable_avg_sum,running_avg_sum,utilization 2 | 9.99999429041e-07,trace-cmd,4,2971,48595,sshd,0,2962,0,0,0 3 | -------------------------------------------------------------------------------- /tests/trace_sched.txt.cache/SchedMigrateTask.csv: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /tests/trace_sched.txt.cache/SchedSwitch.csv: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /tests/trace_sched.txt.cache/SchedWakeup.csv: -------------------------------------------------------------------------------- 1 | Time,__comm,__cpu,__pid,comm,pid,prio,success,target_cpu 2 | 0.0814890000001,,1,0,rcu_preempt,7,120,1,1 3 | 1.981491,,1,0,rcu_preempt,7,120,1,1 4 | -------------------------------------------------------------------------------- /tests/trace_sched.txt.cache/SchedWakeupNew.csv: -------------------------------------------------------------------------------- 1 | Time,__comm,__cpu,__pid,comm,pid,prio,success,target_cpu 2 | 0.000152999999955,<...>,0,19427,shutils,19428,120,1,2 3 | 1.975373,<...>,0,19427,shutils,19428,120,1,2 4 | -------------------------------------------------------------------------------- /tests/trace_sched.txt.cache/Thermal.csv: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /tests/trace_sched.txt.cache/ThermalGovernor.csv: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /tests/trace_sched.txt.cache/metadata.json: -------------------------------------------------------------------------------- 1 | {"params": {"window": [1, 5], "abs_window": [0, null]}, "md5sum": "47be9ccdd36fa0c3646b0d9b0f649da4", "basetime": 6550.018511} 2 | -------------------------------------------------------------------------------- /tests/trace_systrace.html: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Remove HTML content... 4 | # 5 | overlay.textContent = tr.b.normalizeException(err).message; 6 | overlay.title = 'Import error'; 7 | overlay.visible = true; 8 | }); 9 | } 10 | window.addEventListener('load', onLoad); 11 | 12 | 13 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /tests/trappy: -------------------------------------------------------------------------------- 1 | ../trappy -------------------------------------------------------------------------------- /tests/utils_tests.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | import unittest 20 | import os 21 | import shutil 22 | import subprocess 23 | import tempfile 24 | import trappy 25 | from trappy.ftrace import GenericFTrace 26 | 27 | TESTS_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) 28 | 29 | def trace_cmd_installed(): 30 | """Return true if trace-cmd is installed, false otherwise""" 31 | with open(os.devnull) as devnull: 32 | try: 33 | subprocess.check_call(["trace-cmd", "options"], stdout=devnull) 34 | except OSError: 35 | return False 36 | 37 | return True 38 | 39 | class SetupDirectory(unittest.TestCase): 40 | def __init__(self, files_to_copy, *args, **kwargs): 41 | self.files_to_copy = files_to_copy 42 | super(SetupDirectory, self).__init__(*args, **kwargs) 43 | GenericFTrace.disable_cache = True 44 | 45 | def setUp(self): 46 | self.previous_dir = os.getcwd() 47 | 48 | self.out_dir = tempfile.mkdtemp() 49 | os.chdir(self.out_dir) 50 | 51 | for src_fname, dst_fname in self.files_to_copy: 52 | src_fname = os.path.join(TESTS_DIRECTORY, src_fname) 53 | shutil.copy(src_fname, os.path.join(self.out_dir, dst_fname)) 54 | 55 | def tearDown(self): 56 | os.chdir(self.previous_dir) 57 | shutil.rmtree(self.out_dir) 58 | -------------------------------------------------------------------------------- /trappy/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | from builtins import object 20 | import warnings 21 | from trappy.bare_trace import BareTrace 22 | from trappy.compare_runs import summary_plots, compare_runs 23 | from trappy.exception import TrappyParseError 24 | from trappy.ftrace import FTrace 25 | from trappy.systrace import SysTrace 26 | try: 27 | from trappy.plotter.LinePlot import LinePlot 28 | except ImportError as exc: 29 | class LinePlot(object): 30 | def __init__(self, *args, **kwargs): 31 | raise exc 32 | try: 33 | from trappy.plotter.ILinePlot import ILinePlot 34 | from trappy.plotter.EventPlot import EventPlot 35 | from trappy.plotter.BarPlot import BarPlot 36 | except ImportError: 37 | pass 38 | from trappy.dynamic import register_dynamic_ftrace, register_ftrace_parser, \ 39 | unregister_ftrace_parser 40 | import trappy.nbexport 41 | 42 | # We define unregister_dynamic_ftrace() because it undoes what 43 | # register_dynamic_ftrace(). Internally it does exactly the same as 44 | # unregister_ftrace_parser() though but with these two names the API 45 | # makes more sense: register with register_dynamic_ftrace(), 46 | # unregister with unregister_dynamic_ftrace() 47 | unregister_dynamic_ftrace = unregister_ftrace_parser 48 | 49 | # Load all the modules to make sure all classes are registered with FTrace 50 | import os 51 | for fname in os.listdir(os.path.dirname(__file__)): 52 | import_name, extension = os.path.splitext(fname) 53 | if (extension == ".py") and (fname != "__init__.py") and \ 54 | (fname != "plot_utils.py"): 55 | __import__("trappy.{}".format(import_name)) 56 | 57 | del fname, import_name, extension 58 | -------------------------------------------------------------------------------- /trappy/bare_trace.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | from builtins import object 20 | import re 21 | 22 | class BareTrace(object): 23 | """A wrapper class that holds dataframes for all the events in a trace. 24 | 25 | BareTrace doesn't parse any file so it's a class that should 26 | either be (a) subclassed to parse a particular trace (like FTrace) 27 | or (b) be instantiated and the events added with add_parsed_event() 28 | 29 | :param name: is a string describing the trace. 30 | :type name: str 31 | 32 | """ 33 | 34 | def __init__(self, name=""): 35 | self.name = name 36 | self.normalized_time = False 37 | self.class_definitions = {} 38 | self.trace_classes = [] 39 | self.basetime = 0 40 | self.endtime = 0 41 | 42 | def get_duration(self): 43 | """Returns the largest time value of all classes, 44 | returns 0 if the data frames of all classes are empty""" 45 | max_durations = [] 46 | min_durations = [] 47 | 48 | for trace_class in self.trace_classes: 49 | try: 50 | max_durations.append(trace_class.data_frame.index[-1]) 51 | min_durations.append(trace_class.data_frame.index[0]) 52 | except IndexError: 53 | pass 54 | 55 | if len(min_durations) == 0 or len(max_durations) == 0: 56 | return 0 57 | 58 | return max(max_durations) - min(min_durations) 59 | 60 | def get_filters(self, key=""): 61 | """Returns an array with the available filters. 62 | 63 | :param key: If specified, returns a subset of the available filters 64 | that contain 'key' in their name (e.g., :code:`key="sched"` returns 65 | only the :code:`"sched"` related filters).""" 66 | filters = [] 67 | 68 | for cls in self.class_definitions: 69 | if re.search(key, cls): 70 | filters.append(cls) 71 | 72 | return filters 73 | 74 | def _normalize_time(self, basetime=None): 75 | """Normalize the time of all the trace classes 76 | 77 | :param basetime: The offset which needs to be subtracted from 78 | the time index 79 | :type basetime: float 80 | """ 81 | 82 | if basetime is not None: 83 | self.basetime = basetime 84 | 85 | for trace_class in self.trace_classes: 86 | trace_class.normalize_time(self.basetime) 87 | 88 | self.normalized_time = True 89 | 90 | def add_parsed_event(self, name, dfr, pivot=None): 91 | """Add a dataframe to the events in this trace 92 | 93 | This function lets you add other events that have been parsed 94 | by other tools to the collection of events in this instance. For 95 | example, assuming you have some events in a csv, you could add 96 | them to a trace instance like this: 97 | 98 | >>> trace = trappy.BareTrace() 99 | >>> counters_dfr = pd.DataFrame.from_csv("counters.csv") 100 | >>> trace.add_parsed_event("pmu_counters", counters_dfr) 101 | 102 | Now you can access :code:`trace.pmu_counters` as you would with any 103 | other trace event and other trappy classes can interact with 104 | them. 105 | 106 | :param name: The attribute name in this trace instance. As in the example above, if :code:`name` is "pmu_counters", the parsed event will be accessible using :code:`trace.pmu_counters`. 107 | :type name: str 108 | 109 | :param dfr: :mod:`pandas.DataFrame` containing the events. Its index should be time in seconds. Its columns are the events. 110 | :type dfr: :mod:`pandas.DataFrame` 111 | 112 | :param pivot: The data column about which the data can be grouped 113 | :type pivot: str 114 | 115 | """ 116 | from trappy.base import Base 117 | from trappy.dynamic import DynamicTypeFactory, default_init 118 | 119 | if hasattr(self, name): 120 | raise ValueError("event {} already present".format(name)) 121 | 122 | kwords = { 123 | "__init__": default_init, 124 | "unique_word": name + ":", 125 | "name": name, 126 | } 127 | 128 | trace_class = DynamicTypeFactory(name, (Base,), kwords) 129 | self.class_definitions[name] = trace_class 130 | 131 | event = trace_class() 132 | self.trace_classes.append(event) 133 | event.data_frame = dfr 134 | if pivot: 135 | event.pivot = pivot 136 | 137 | setattr(self, name, event) 138 | 139 | def finalize_objects(self): 140 | for trace_class in self.trace_classes: 141 | # If cached, don't need to do any other DF operation 142 | if trace_class.cached: 143 | continue 144 | trace_class.tracer = self 145 | trace_class.create_dataframe() 146 | trace_class.finalize_object() 147 | 148 | def generate_data_dict(self, data_str): 149 | return None 150 | -------------------------------------------------------------------------------- /trappy/common_clk.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Google, ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """ 18 | Definitions of common_clk (CONFIG_COMMON_CLK) trace parsers 19 | registered by the FTrace class 20 | """ 21 | from __future__ import unicode_literals 22 | from __future__ import division 23 | from __future__ import print_function 24 | 25 | from trappy.base import Base 26 | from trappy.dynamic import register_ftrace_parser, register_dynamic_ftrace 27 | 28 | class CommonClkBase(Base): 29 | #clock traces are of the form "clk_name field0=x field1=y ..." 30 | def generate_data_dict(self, data_str): 31 | clk_name, fields = data_str.split(' ', 1) 32 | ret = super(CommonClkBase, self).generate_data_dict(fields) 33 | ret['clk_name'] = clk_name 34 | return ret 35 | 36 | class CommonClkEnable(CommonClkBase): 37 | """Corresponds to Linux kernel trace event clock_enable""" 38 | 39 | unique_word = "clock_enable:" 40 | """The unique word that will be matched in a trace line""" 41 | 42 | register_ftrace_parser(CommonClkEnable) 43 | 44 | class CommonClkDisable(CommonClkBase): 45 | """Corresponds to Linux kernel trace event clock_disable""" 46 | 47 | unique_word = "clock_disable:" 48 | """The unique word that will be matched in a trace line""" 49 | 50 | register_ftrace_parser(CommonClkDisable) 51 | 52 | class CommonClkSetRate(CommonClkBase): 53 | """Corresponds to Linux kernel trace event clock_set_rate""" 54 | 55 | unique_word = "clock_set_rate:" 56 | """The unique word that will be matched in a trace line""" 57 | 58 | def finalize_object(self): 59 | self.data_frame.rename(columns={'state':'rate'}, inplace=True) 60 | 61 | register_ftrace_parser(CommonClkSetRate) 62 | -------------------------------------------------------------------------------- /trappy/compare_runs.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | import trappy.ftrace 20 | 21 | def compare_runs(actor_order, map_label, runs, **kwords): 22 | """A side by side comparison of multiple runs 23 | 24 | Plots include temperature, utilization, frequencies, PID 25 | controller and power. 26 | 27 | :param actor_order: An array showing the order in which the actors 28 | were registered. The array values are the labels that 29 | will be used in the input and output power plots. 30 | 31 | For Example: 32 | :: 33 | 34 | ["GPU", "A15", "A7"] 35 | 36 | :param map_label: A dict that matches cpumasks (as found in the 37 | trace) with their proper name. This "proper name" will be used as 38 | a label for the load and allfreqs plots. It's recommended that 39 | the names of the cpus matches those in actor_order. 40 | 41 | For Example: 42 | :: 43 | 44 | {"0000000f": "A7", "000000f0": "A15"} 45 | 46 | :param runs: An array of tuples consisting of a name and the path to 47 | the directory where the trace.dat is. 48 | 49 | For example: 50 | :: 51 | 52 | [("experiment1", "wa_output/antutu_antutu_1"), 53 | ("known good", "good/antutu_antutu_1")] 54 | 55 | :param tz_id: thermal zone id as it appears in the id field of the 56 | thermal_temperature trace event 57 | 58 | :type actor_order: list 59 | :type map_label: dict 60 | :type runs: list 61 | :type tz_id: int 62 | 63 | """ 64 | import trappy.plot_utils 65 | import trappy.wa 66 | 67 | if not isinstance(actor_order, list): 68 | raise TypeError("actor_order has to be an array") 69 | 70 | if not isinstance(map_label, dict): 71 | raise TypeError("map_label has to be a dict") 72 | 73 | if "width" not in kwords: 74 | kwords["width"] = 20 75 | if "height" not in kwords: 76 | kwords["height"] = 5 77 | 78 | run_data = [] 79 | for name, path in runs: 80 | run_data.append(trappy.FTrace(name=name, path=path, scope="thermal")) 81 | trappy.wa.SysfsExtractor(path).pretty_print_in_ipython() 82 | 83 | trappy.plot_utils.plot_temperature(run_data, **kwords) 84 | if "tz_id" in kwords: 85 | del kwords["tz_id"] 86 | 87 | try: 88 | trappy.plot_utils.plot_load(run_data, map_label, **kwords) 89 | except IndexError: 90 | raise ValueError("No power allocator traces found. Was IPA active (temp above switch on temperature) and FTrace configured to collect all thermal events?") 91 | trappy.plot_utils.plot_allfreqs(run_data, map_label, **kwords) 92 | trappy.plot_utils.plot_controller(run_data, **kwords) 93 | trappy.plot_utils.plot_input_power(run_data, actor_order, **kwords) 94 | trappy.plot_utils.plot_output_power(run_data, actor_order, **kwords) 95 | trappy.plot_utils.plot_freq_hists(run_data, map_label) 96 | trappy.plot_utils.plot_temperature_hist(run_data) 97 | 98 | def summary_plots(actor_order, map_label, **kwords): 99 | """A summary of plots for a given run 100 | 101 | .. warning:: 102 | 103 | This is a wrapper around compare_runs(). Use that instead. 104 | """ 105 | 106 | path = kwords.pop("path", ".") 107 | title = kwords.pop("title", "") 108 | 109 | return compare_runs(actor_order, map_label, [(title, path)], **kwords) 110 | -------------------------------------------------------------------------------- /trappy/devfreq_power.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Process the output of the devfreq_cooling devices in the current 18 | directory's trace.dat""" 19 | from __future__ import division 20 | from __future__ import unicode_literals 21 | 22 | import pandas as pd 23 | 24 | from trappy.base import Base 25 | from trappy.dynamic import register_ftrace_parser 26 | 27 | 28 | class DevfreqInPower(Base): 29 | """Process de devfreq cooling device data regarding get_power in an 30 | FTrace dump""" 31 | 32 | name = "devfreq_in_power" 33 | """The name of the :mod:`pandas.DataFrame` member that will be created in a 34 | :mod:`trappy.ftrace.FTrace` object""" 35 | 36 | unique_word="thermal_power_devfreq_get_power:" 37 | """The event name in the trace""" 38 | 39 | def get_all_freqs(self): 40 | """Return a :mod:`pandas.DataFrame` with 41 | the frequencies for the devfreq device 42 | 43 | The format should be the same as the one for 44 | :code:`CpuInPower().get_all_freqs()`. 45 | 46 | .. note:: Frequencies are in MHz. 47 | """ 48 | 49 | return pd.DataFrame(self.data_frame["freq"] / 1000000) 50 | 51 | register_ftrace_parser(DevfreqInPower, "thermal") 52 | 53 | 54 | class DevfreqOutPower(Base): 55 | """Process de devfreq cooling device data regarding power2state in an 56 | ftrace dump""" 57 | 58 | name = "devfreq_out_power" 59 | """The name of the :mod:`pandas.DataFrame` member that will be created in a 60 | :mod:`trappy.ftrace.FTrace` object""" 61 | 62 | unique_word="thermal_power_devfreq_limit:" 63 | """The event name in the trace""" 64 | 65 | def get_all_freqs(self): 66 | """Return a :mod:`pandas.DataFrame` with 67 | the output frequencies for the devfreq device 68 | 69 | The format should be the same as the one for 70 | :code:`CpuOutPower().get_all_freqs()`. 71 | 72 | .. note:: Frequencies are in MHz. 73 | """ 74 | 75 | return pd.DataFrame(self.data_frame["freq"] / 1000000) 76 | 77 | register_ftrace_parser(DevfreqOutPower, "thermal") 78 | -------------------------------------------------------------------------------- /trappy/dynamic.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """The idea is to create a wrapper class that 18 | returns a Type of a Class dynamically created based 19 | on the input parameters. Similar to a factory design 20 | pattern 21 | """ 22 | from __future__ import unicode_literals 23 | from __future__ import division 24 | from __future__ import print_function 25 | 26 | from trappy.base import Base 27 | import re 28 | from trappy.ftrace import GenericFTrace 29 | 30 | 31 | def default_init(self): 32 | """Default Constructor for the 33 | Dynamic MetaClass. This is used for 34 | the dynamic object creation in 35 | :mod:`trappy.dynamic.DynamicTypeFactory` 36 | """ 37 | 38 | kwords = {} 39 | 40 | try: 41 | kwords["parse_raw"] = self.parse_raw 42 | except AttributeError: 43 | pass 44 | 45 | super(type(self), self).__init__(**kwords) 46 | 47 | 48 | class DynamicTypeFactory(type): 49 | 50 | """Override the type class to create 51 | a dynamic type on the fly. This Factory 52 | class is used internally by 53 | :mod:`trappy.dynamic.register_dynamic_ftrace` 54 | """ 55 | 56 | def __new__(mcs, name, bases, dct): 57 | """Override the new method""" 58 | return type.__new__(mcs, str(name), bases, dct) 59 | 60 | def __init__(cls, name, bases, dct): 61 | """Override the constructor""" 62 | super(DynamicTypeFactory, cls).__init__(name, bases, dct) 63 | 64 | 65 | def _get_name(name): 66 | """Internal Method to Change camelcase to 67 | underscores. CamelCase -> camel_case 68 | """ 69 | return re.sub('(?!^)([A-Z]+)', r'_\1', name).lower() 70 | 71 | 72 | def register_dynamic_ftrace(class_name, unique_word, scope="all", 73 | parse_raw=False, pivot=None): 74 | """Create a Dynamic FTrace parser and register it with any FTrace parsing classes 75 | 76 | :param class_name: The name of the class to be registered 77 | (Should be in CamelCase) 78 | :type class_name: str 79 | 80 | :param unique_word: The unique_word to be matched in the 81 | trace 82 | :type unique_word: str 83 | 84 | :param scope: Registry Scope (Can be used to constrain 85 | the parsing of events and group them together) 86 | :type scope: str 87 | 88 | :param parse_raw: If, true, raw trace output (-r flag) 89 | will be used 90 | :type parse_raw: bool 91 | 92 | :param pivot: The data column about which the data can be grouped 93 | :type pivot: str 94 | 95 | For example if a new unique word :code:`my_unique_word` has 96 | to be registered with TRAPpy: 97 | :: 98 | 99 | import trappy 100 | custom_class = trappy.register_dynamic_ftrace("MyEvent", "my_unique_word") 101 | trace = trappy.FTrace("/path/to/trace_file") 102 | 103 | # New data member created in the ftrace object 104 | trace.my_event 105 | 106 | .. note:: The name of the member is :code:`my_event` from **MyEvent** 107 | 108 | 109 | :return: A class object of type :mod:`trappy.base.Base` 110 | """ 111 | 112 | kwords = { 113 | "__init__": default_init, 114 | "unique_word": unique_word, 115 | "name": _get_name(class_name), 116 | "parse_raw" : parse_raw, 117 | } 118 | 119 | if pivot: 120 | kwords["pivot"] = pivot 121 | 122 | dyn_class = DynamicTypeFactory(class_name, (Base,), kwords) 123 | GenericFTrace.register_parser(dyn_class, scope) 124 | return dyn_class 125 | 126 | 127 | def register_ftrace_parser(cls, scope="all"): 128 | """Register a new FTrace parser class implementation 129 | 130 | Should be used when the class has complex helper methods and does 131 | not expect to use the default constructor. 132 | 133 | :param cls: The class to be registered for 134 | enabling the parsing of an event in trace 135 | :type cls: :mod:`trappy.base.Base` 136 | 137 | :param scope: scope of this parser class. The scope can be used 138 | to restrict the parsing done on an individual file. Currently 139 | the only scopes available are "sched", "thermal" or "all" 140 | :type scope: string 141 | 142 | """ 143 | 144 | # Check the argspec of the class 145 | GenericFTrace.register_parser(cls, scope) 146 | 147 | def unregister_ftrace_parser(ftrace_parser): 148 | """Unregister an ftrace parser 149 | 150 | :param ftrace_parser: An ftrace parser class that was registered 151 | with register_ftrace_parser() or register_dynamic_ftrace(). 152 | If done with the latter, the cls parameter is the return value 153 | of register_dynamic_ftrace() 154 | :type ftrace_parser: class derived from :mod:`trappy.base.Base` 155 | 156 | """ 157 | GenericFTrace.unregister_parser(ftrace_parser) 158 | -------------------------------------------------------------------------------- /trappy/exception.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | # Copyright 2017 ARM Limited 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | class TrappyParseError(Exception): 18 | pass 19 | -------------------------------------------------------------------------------- /trappy/fallback.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 ARM Limited, Google and contributors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """ 17 | This module contains the class for representing fallback events used for ftrace 18 | events injected from userspace, which are free-form and could contain any 19 | string. 20 | """ 21 | from __future__ import unicode_literals 22 | from __future__ import division 23 | from __future__ import print_function 24 | 25 | from trappy.base import Base 26 | from trappy.dynamic import register_ftrace_parser 27 | 28 | class FallbackEvent(Base): 29 | """ 30 | Parse free-form events that couldn't be matched with more specific unique 31 | words. This class is always used as a fallback if nothing more specific 32 | could match the particular event. 33 | """ 34 | 35 | def generate_data_dict(self, data_str): 36 | if self.tracer: 37 | data_dict = self.tracer.generate_data_dict(data_str) 38 | if data_dict: 39 | return data_dict 40 | 41 | return { 'string': data_str } 42 | 43 | 44 | def __init__(self): 45 | super(FallbackEvent, self).__init__(fallback=True) 46 | 47 | class TracingMarkWrite(FallbackEvent): 48 | unique_word = "tracing_mark_write:" 49 | 50 | register_ftrace_parser(TracingMarkWrite) 51 | -------------------------------------------------------------------------------- /trappy/filesystem.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Google, ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """ 18 | Definitions of filesystem (ext4) trace parsers 19 | registered by the FTrace class 20 | """ 21 | from __future__ import unicode_literals 22 | from __future__ import division 23 | from __future__ import print_function 24 | 25 | from builtins import zip 26 | from trappy.base import Base 27 | from trappy.dynamic import register_ftrace_parser, register_dynamic_ftrace 28 | 29 | class FilesystemExt4Base(Base): 30 | def generate_data_dict(self, data_str): 31 | #filesystem traces are space delimited in the form: 32 | #fieldA valueA fieldB valueB ... 33 | data = data_str.split(' ') 34 | return list(zip(data[0::2], data[1::2])) 35 | 36 | def finalize_object(self): 37 | self.data_frame.rename(columns={'ino':'inode'}, inplace=True) 38 | 39 | 40 | class FilesystemExt4DaWriteBegin(FilesystemExt4Base): 41 | """Corresponds to Linux kernel trace event ext4_da_write_begin""" 42 | 43 | unique_word = "ext4_da_write_begin:" 44 | """The unique word that will be matched in a trace line""" 45 | 46 | 47 | register_ftrace_parser(FilesystemExt4DaWriteBegin) 48 | 49 | class FilesystemExt4DaWriteEnd(FilesystemExt4Base): 50 | """Corresponds to Linux kernel trace event ext4_da_write_end""" 51 | 52 | unique_word = "ext4_da_write_end:" 53 | """The unique word that will be matched in a trace line""" 54 | 55 | register_ftrace_parser(FilesystemExt4DaWriteEnd) 56 | 57 | class FilesystemExt4SyncFileEnter(FilesystemExt4Base): 58 | """Corresponds to Linux kernel trace event ext4_sync_file_enter""" 59 | 60 | unique_word = "ext4_sync_file_enter:" 61 | """The unique word that will be matched in a trace line""" 62 | 63 | register_ftrace_parser(FilesystemExt4SyncFileEnter) 64 | 65 | class FilesystemExt4SyncFileExit(FilesystemExt4Base): 66 | """Corresponds to Linux kernel trace event ext4_sync_file_exit""" 67 | 68 | unique_word = "ext4_sync_file_exit:" 69 | """The unique word that will be matched in a trace line""" 70 | 71 | register_ftrace_parser(FilesystemExt4SyncFileExit) 72 | 73 | class FilesystemF2FSBase(Base): 74 | def generate_data_dict(self, data_str): 75 | data_str = data_str.replace(" = ", "=") 76 | data_str = data_str.replace(",", " ") 77 | return super(FilesystemF2FSBase, self).generate_data_dict(data_str) 78 | 79 | def finalize_object(self): 80 | self.data_frame.rename(columns={'ino':'inode'}, inplace=True) 81 | 82 | class FilesystemF2FSWriteBegin(FilesystemF2FSBase): 83 | """Corresponds to Linux kernel trace event f2fs_write_begin""" 84 | 85 | unique_word = "f2fs_write_begin:" 86 | """The unique word that will be matched in a trace line""" 87 | 88 | register_ftrace_parser(FilesystemF2FSWriteBegin) 89 | 90 | class FilesystemF2FSWriteEnd(FilesystemF2FSBase): 91 | """Corresponds to Linux kernel trace event f2fs_write_end""" 92 | 93 | unique_word = "f2fs_write_end:" 94 | """The unique word that will be matched in a trace line""" 95 | 96 | register_ftrace_parser(FilesystemF2FSWriteEnd) 97 | 98 | class FilesystemF2FSSyncFileEnter(FilesystemF2FSBase): 99 | """Corresponds to Linux kernel trace event f2fs_sync_file_enter""" 100 | 101 | unique_word = "f2fs_sync_file_enter:" 102 | """The unique word that will be matched in a trace line""" 103 | 104 | register_ftrace_parser(FilesystemF2FSSyncFileEnter) 105 | 106 | class FilesystemF2FSSyncFileExit(FilesystemF2FSBase): 107 | """Corresponds to Linux kernel trace event f2fs_sync_file_exit""" 108 | 109 | unique_word = "f2fs_sync_file_exit:" 110 | """The unique word that will be matched in a trace line""" 111 | 112 | def generate_data_dict(self, data_str): 113 | data_str = data_str.replace("checkpoint is ", "checkpoint = ") 114 | return super(FilesystemF2FSSyncFileExit, self).generate_data_dict(data_str) 115 | 116 | register_ftrace_parser(FilesystemF2FSSyncFileExit) 117 | -------------------------------------------------------------------------------- /trappy/function.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Arm Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | 17 | from trappy.base import Base 18 | from trappy.dynamic import register_ftrace_parser 19 | 20 | class FunctionTracer(Base): 21 | """Parse 'function' events ('function' tracer)""" 22 | unique_word = "function" 23 | parse_raw = True 24 | 25 | def __init__(self): 26 | super().__init__(parse_raw=self.parse_raw) 27 | 28 | register_ftrace_parser(FunctionTracer) 29 | -------------------------------------------------------------------------------- /trappy/function_graph.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | 17 | from trappy.base import Base 18 | from trappy.dynamic import register_ftrace_parser 19 | 20 | class FuncgraphEntry(Base): 21 | """Parse funcgraph_entry""" 22 | unique_word = "funcgraph_entry" 23 | parse_raw = True 24 | def __init__(self): 25 | super().__init__(parse_raw=self.parse_raw) 26 | 27 | register_ftrace_parser(FuncgraphEntry) 28 | 29 | class FuncgraphExit(Base): 30 | """Parse funcgraph_exit""" 31 | unique_word = "funcgraph_exit" 32 | parse_raw = True 33 | def __init__(self): 34 | super().__init__(parse_raw=self.parse_raw) 35 | 36 | register_ftrace_parser(FuncgraphExit) 37 | -------------------------------------------------------------------------------- /trappy/idle.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | from trappy.base import Base 20 | from trappy.dynamic import register_ftrace_parser 21 | 22 | class CpuIdle(Base): 23 | """Parse cpu_idle""" 24 | 25 | unique_word = "cpu_idle" 26 | pivot = "cpu_id" 27 | 28 | def finalize_object(self): 29 | # The trace contains "4294967295" instead of "-1" when exiting an idle 30 | # state. 31 | uint32_max = (2 ** 32) - 1 32 | self.data_frame.replace(uint32_max, -1, inplace=True) 33 | super(CpuIdle, self).finalize_object() 34 | 35 | register_ftrace_parser(CpuIdle) 36 | -------------------------------------------------------------------------------- /trappy/nbexport/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # Copyright 2016 Google Inc. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | """HTML Exporter for TRAPPY plotter data. This allows 17 | * Custom Preprocessing 18 | """ 19 | from __future__ import unicode_literals 20 | from __future__ import division 21 | from __future__ import print_function 22 | 23 | try: 24 | from trappy.nbexport.exporter import HTML 25 | except ImportError: 26 | # Avoid testsuite errors when the testsuite is run in an environment without 27 | # ipython 28 | HTML = object 29 | -------------------------------------------------------------------------------- /trappy/nbexport/exporter.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # Copyright 2016 Google Inc. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | """Preprocessor to remove Marked Lines from IPython Output Cells""" 17 | from __future__ import unicode_literals 18 | from __future__ import division 19 | from __future__ import print_function 20 | 21 | from nbconvert.exporters.html import HTMLExporter 22 | from nbconvert.preprocessors import Preprocessor 23 | import os 24 | import re 25 | 26 | REMOVE_START = '/* TRAPPY_PUBLISH_REMOVE_START */' 27 | REMOVE_STOP = '/* TRAPPY_PUBLISH_REMOVE_STOP */' 28 | REMOVE_LINE = '/* TRAPPY_PUBLISH_REMOVE_LINE */' 29 | IMPORT_SCRIPT = r'/\* TRAPPY_PUBLISH_IMPORT = "([^"]+)" \*/' 30 | SOURCE_LIB = r'' 31 | 32 | 33 | class HTML(HTMLExporter): 34 | """HTML Exporter class for TRAPpy notebooks""" 35 | 36 | def __init__(self, **kwargs): 37 | super(HTML, self).__init__(**kwargs) 38 | self.register_preprocessor(TrappyPlotterPreprocessor, enabled=True) 39 | 40 | 41 | class TrappyPlotterPreprocessor(Preprocessor): 42 | """Preprocessor to remove Marked Lines from IPython Output Cells""" 43 | 44 | def __init__(self, *args, **kwargs): 45 | super(Preprocessor, self).__init__(*args, **kwargs) 46 | self.inlined_files = [] 47 | self.sourced_libs = [] 48 | 49 | def preprocess_cell(self, cell, resources, cell_index): 50 | """Check if cell has text/html output and filter it""" 51 | 52 | if cell.cell_type == 'code' and hasattr(cell, "outputs"): 53 | for output in cell.outputs: 54 | if output.output_type == "display_data" and \ 55 | hasattr( output.data, "text/html"): 56 | filtered = self.filter_output(output.data["text/html"]) 57 | output.data["text/html"] = filtered 58 | return cell, resources 59 | 60 | def filter_output(self, output): 61 | """Function to remove marked lines""" 62 | 63 | lines = output.split('\n') 64 | 65 | final_lines = [] 66 | multi_line_remove = False 67 | for line in lines: 68 | if REMOVE_START in line: 69 | multi_line_remove = True 70 | continue 71 | if REMOVE_STOP in line: 72 | multi_line_remove = False 73 | continue 74 | if multi_line_remove or REMOVE_LINE in line: 75 | continue 76 | 77 | import_match = re.search(IMPORT_SCRIPT, line) 78 | if import_match: 79 | trappy_base = os.path.dirname(os.path.dirname(__file__)) 80 | import_file = os.path.join(trappy_base, import_match.group(1)) 81 | if import_file in self.inlined_files: 82 | continue 83 | 84 | with open(import_file) as fin: 85 | final_lines.extend([l[:-1] for l in fin.readlines()]) 86 | 87 | self.inlined_files.append(import_file) 88 | continue 89 | 90 | source_match = re.search(SOURCE_LIB, line) 91 | if source_match: 92 | lib_url = source_match.group(1) 93 | if lib_url in self.sourced_libs: 94 | continue 95 | 96 | scl = ''.\ 97 | format(lib_url) 98 | final_lines.append(scl) 99 | 100 | self.sourced_libs.append(lib_url) 101 | continue 102 | 103 | final_lines.append(line) 104 | 105 | return '\n'.join(final_lines) 106 | -------------------------------------------------------------------------------- /trappy/pid_controller.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """Process the output of the power allocator's PID controller in the 17 | current directory's trace.dat""" 18 | from __future__ import unicode_literals 19 | from __future__ import division 20 | from __future__ import print_function 21 | 22 | from trappy.base import Base 23 | from trappy.dynamic import register_ftrace_parser 24 | 25 | class PIDController(Base): 26 | """Process the power allocator PID controller data in a FTrace dump""" 27 | 28 | name = "pid_controller" 29 | """The name of the :mod:`pandas.DataFrame` member that will be created in a 30 | :mod:`trappy.ftrace.FTrace` object""" 31 | 32 | pivot = "thermal_zone_id" 33 | """The Pivot along which the data is orthogonal""" 34 | 35 | unique_word="thermal_power_allocator_pid" 36 | """The event name in the trace""" 37 | 38 | def plot_controller(self, title="", width=None, height=None, ax=None): 39 | """Plot a summary of the controller data 40 | 41 | :param ax: Axis instance 42 | :type ax: :mod:`matplotlib.Axis` 43 | 44 | :param title: The title of the plot 45 | :type title: str 46 | 47 | :param width: The width of the plot 48 | :type width: int 49 | 50 | :param height: The height of the plot 51 | :type int: int 52 | """ 53 | import trappy.plot_utils 54 | 55 | title = trappy.plot_utils.normalize_title("PID", title) 56 | 57 | if not ax: 58 | ax = trappy.plot_utils.pre_plot_setup(width, height) 59 | 60 | self.data_frame[["output", "p", "i", "d"]].plot(ax=ax) 61 | trappy.plot_utils.post_plot_setup(ax, title=title) 62 | 63 | register_ftrace_parser(PIDController, "thermal") 64 | -------------------------------------------------------------------------------- /trappy/plotter/AbstractDataPlotter.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """This is the template class that all Plotters inherit""" 17 | from __future__ import unicode_literals 18 | from __future__ import division 19 | from __future__ import print_function 20 | 21 | from builtins import object 22 | from abc import abstractmethod, ABCMeta 23 | from pandas import DataFrame 24 | import re 25 | from trappy.utils import listify 26 | from functools import reduce 27 | from future.utils import with_metaclass 28 | # pylint: disable=R0921 29 | # pylint: disable=R0903 30 | 31 | 32 | class AbstractDataPlotter(with_metaclass(ABCMeta, object)): 33 | """This is an abstract data plotting Class defining an interface 34 | for the various Plotting Classes""" 35 | 36 | def __init__(self, traces=None, attr=None, templates=None): 37 | self._event_map = {} 38 | self._attr = attr if attr else {} 39 | self.traces = traces 40 | self.templates = templates 41 | 42 | @abstractmethod 43 | def view(self): 44 | """View the graph""" 45 | raise NotImplementedError("Method Not Implemented") 46 | 47 | @abstractmethod 48 | def savefig(self, path): 49 | """Save the image as a file 50 | 51 | :param path: Location of the Saved File 52 | :type path: str 53 | """ 54 | raise NotImplementedError("Method Not Implemented") 55 | 56 | def _check_data(self): 57 | """Internal function to check the received data""" 58 | 59 | data = listify(self.traces) 60 | 61 | if len(data): 62 | mask = [isinstance(x, DataFrame) for x in data] 63 | data_frame = reduce(lambda x, y: x and y, mask) 64 | sig_or_template = self.templates or "signals" in self._attr 65 | 66 | if not data_frame and not sig_or_template: 67 | raise ValueError( 68 | "Cannot understand data. Accepted DataFormats are pandas.DataFrame or trappy.FTrace/BareTrace/SysTrace (with templates)") 69 | elif data_frame and "column" not in self._attr: 70 | raise ValueError("Column not specified for DataFrame input") 71 | else: 72 | raise ValueError("Empty Data received") 73 | 74 | def _parse_value(self, signal_def): 75 | """Parse a signal definition into a (template, column) tuple 76 | 77 | :param signal_def: A signal definition. E.g. "trace_class:column" 78 | :type signal_def: str 79 | """ 80 | 81 | match = re.match(r"(?P[^:]+):(?P[^:]+)(?P:.+)?", 82 | signal_def) 83 | if not match: 84 | raise ValueError( 85 | 'Invalid signal definition "{}". ' 86 | 'Should have the form "trace_class:column" ' 87 | 'e.g. "cpu_frequency:frequency"'.format(signal_def)) 88 | event = match.group("event") 89 | column = match.group("column") 90 | color_match = match.group("color") 91 | if color_match: 92 | color_list = color_match[1:].split(",", 2) 93 | color = [int(n, 16) if n.startswith("0x") else int(n) for n in color_list] 94 | else: 95 | color = None 96 | 97 | try: 98 | return self._event_map[event], column, color 99 | except KeyError: 100 | for trace in listify(self.traces): 101 | 102 | if event in trace.class_definitions: 103 | self._event_map[event] = trace.class_definitions[event] 104 | return self._event_map[event], column, color 105 | 106 | raise ValueError( 107 | "Event: " + 108 | event + 109 | " not found in Trace Object") 110 | 111 | def _describe_signals(self): 112 | """Internal Function for populating templates and columns 113 | from signals 114 | """ 115 | 116 | if "column" in self._attr or self.templates: 117 | raise ValueError("column/templates specified with values") 118 | 119 | self._attr["column"] = [] 120 | self.templates = [] 121 | colors = [] 122 | 123 | for value in listify(self._attr["signals"]): 124 | template, column, color = self._parse_value(value) 125 | self.templates.append(template) 126 | self._attr["column"].append(column) 127 | colors.append(color) 128 | 129 | if any(colors): 130 | self._attr["colors"] = colors 131 | -------------------------------------------------------------------------------- /trappy/plotter/AttrConf.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """These are the default plotting Attributes""" 17 | from __future__ import unicode_literals 18 | from __future__ import division 19 | from __future__ import print_function 20 | 21 | WIDTH = 7 22 | """Default Width of a MatPlotlib Plot""" 23 | LENGTH = 7 24 | """Default Length of a MatPlotlib Plot""" 25 | PER_LINE = 2 26 | """Default Graphs per line""" 27 | CONCAT = False 28 | """Default value for concat in :mod:`trappy.plotter.LinePlot` 29 | and :mod:`trappy.plotter.ILinePlot` 30 | """ 31 | PIVOT = "__TRAPPY_PIVOT_DEFAULT" 32 | """Default pivot when None specified""" 33 | PIVOT_VAL = "__TRAPPY_DEFAULT_PIVOT_VAL" 34 | """Default PivotValue for the default pivot""" 35 | XLIM = None 36 | """Default value for xlimit""" 37 | YLIM = None 38 | """Default value for ylim""" 39 | FILL = False 40 | """Default value for "fill" in :mod:`trappy.plotter.LinePlot` 41 | and :mod:`trappy.plotter.ILinePlot`""" 42 | ALPHA = 0.75 43 | """Default value for the alpha channel""" 44 | TITLE = None 45 | """Default figure title (no title)""" 46 | TITLE_SIZE = 24 47 | """Default size for the figure title""" 48 | LEGEND_NCOL = 3 49 | """Default number of columns in the legend""" 50 | 51 | MPL_STYLE = { 52 | 'axes.axisbelow': True, 53 | 'axes.edgecolor': '#bcbcbc', 54 | 'axes.facecolor': 'white', 55 | 'axes.grid': True, 56 | 'axes.labelcolor': '#555555', 57 | 'axes.labelsize': 'large', 58 | 'axes.linewidth': 1.0, 59 | 'axes.titlesize': 'x-large', 60 | 'figure.edgecolor': 'white', 61 | 'figure.facecolor': 'white', 62 | 'figure.figsize': (6.0, 4.0), 63 | 'figure.subplot.hspace': 0.5, 64 | 'font.size': 10, 65 | 'interactive': True, 66 | 'keymap.all_axes': ['a'], 67 | 'keymap.back': ['left', 'c', 'backspace'], 68 | 'keymap.forward': ['right', 'v'], 69 | 'keymap.fullscreen': ['f'], 70 | 'keymap.grid': ['g'], 71 | 'keymap.home': ['h', 'r', 'home'], 72 | 'keymap.pan': ['p'], 73 | 'keymap.save': ['s'], 74 | 'keymap.xscale': ['L', 'k'], 75 | 'keymap.yscale': ['l'], 76 | 'keymap.zoom': ['o'], 77 | 'legend.fancybox': True, 78 | 'lines.antialiased': True, 79 | 'lines.linewidth': 1.0, 80 | 'patch.antialiased': True, 81 | 'patch.edgecolor': '#EEEEEE', 82 | 'patch.facecolor': '#348ABD', 83 | 'patch.linewidth': 0.5, 84 | 'toolbar': 'toolbar2', 85 | 'xtick.color': '#555555', 86 | 'xtick.direction': 'in', 87 | 'xtick.major.pad': 6.0, 88 | 'xtick.major.size': 0.0, 89 | 'xtick.minor.pad': 6.0, 90 | 'xtick.minor.size': 0.0, 91 | 'ytick.color': '#555555', 92 | 'ytick.direction': 'in', 93 | 'ytick.major.pad': 6.0, 94 | 'ytick.major.size': 0.0, 95 | 'ytick.minor.pad': 6.0, 96 | 'ytick.minor.size': 0.0 97 | } 98 | 99 | from distutils.version import LooseVersion 100 | import matplotlib 101 | 102 | colors = ['#348ABD', '#7A68A6', '#A60628', '#467821', '#CF4457', '#188487', 103 | '#E24A33'] 104 | if LooseVersion(matplotlib.__version__) < LooseVersion("1.5.1"): 105 | MPL_STYLE['axes.color_cycle'] = colors 106 | else: 107 | MPL_STYLE['axes.prop_cycle'] = matplotlib.cycler("color", colors) 108 | 109 | ARGS_TO_FORWARD = [ 110 | "marker", 111 | "markersize", 112 | "markevery", 113 | "linestyle", 114 | "linewidth", 115 | "drawstyle"] 116 | """kwargs that will be forwarded to matplotlib API calls 117 | """ 118 | HTML_HEIGHT = 400 119 | """Default height for HTML based plots""" 120 | DEFAULT_SYNC_ZOOM = False 121 | """Sync Graph zoom by default in 122 | :mod:`trappy.plotter.ILinePlot` graph groups 123 | """ 124 | EVENT_PLOT_STRIDE = False 125 | """Default value for stride which enables sampled 126 | EventPlots for :mod:`trappy.plotter.EventPlot` 127 | """ 128 | PLOT_SCATTER = False 129 | """Default value for creating Scatter Plots""" 130 | POINT_SIZE = 2 131 | """Default Point Size for plots (in pts)""" 132 | LINE_WIDTH = 1.0 133 | """Default Line Width for plotter""" 134 | -------------------------------------------------------------------------------- /trappy/plotter/BarPlot.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | """ 16 | This class sublclasses :mod:`trappy.plotter.StaticPlot.StaticPlot` to 17 | implement a bar plot. 18 | 19 | """ 20 | from __future__ import division 21 | from __future__ import unicode_literals 22 | from builtins import zip 23 | import numpy as np 24 | from trappy.plotter.StaticPlot import StaticPlot 25 | 26 | class BarPlot(StaticPlot): 27 | """BarPlot can plot data as vertical bars 28 | 29 | Values are plotted against their position in the list of data. 30 | 31 | :param traces: The input data 32 | :type traces: A single instance or a list of :mod:`trappy.trace.FTrace`, 33 | :mod:`trappy.trace.SysTrace`, :mod:`trappy.trace.BareTrace` or 34 | :mod:`pandas.DataFrame`. 35 | 36 | :param column: specifies the name of the column to be plotted. 37 | :type column: str or list(str) 38 | 39 | :param templates: TRAPpy events 40 | 41 | .. note:: 42 | 43 | This is not required if a :mod:`pandas.DataFrame` is 44 | used 45 | 46 | :type templates: :mod:`trappy.base.Base` 47 | 48 | :param signals: A string of the type event_name:column 49 | to indicate the value that needs to be plotted 50 | 51 | .. note:: 52 | 53 | - Only one of `signals` or both `templates` and 54 | `columns` should be specified 55 | - Signals format won't work for :mod:`pandas.DataFrame` 56 | input 57 | 58 | :type signals: str or list(string) 59 | 60 | :param title: A title describing the generated plots 61 | :type title: str 62 | 63 | :param stacked: The series are grouped by default. If you want a 64 | stacked plot, set stacked to True. 65 | :type stacked: bool 66 | 67 | :param spacing: A proportion of the size of each group which 68 | should be used as the spacing between the groups. e.g. 0.2 69 | (default) means that 1/5 of the groups total width is used as 70 | a spacing between groups. 71 | :type spacing: float 72 | """ 73 | 74 | def __init__(self, traces, templates=None, **kwargs): 75 | # Default keys, each can be overridden in kwargs 76 | 77 | super(BarPlot, self).__init__( 78 | traces=traces, 79 | templates=templates, 80 | **kwargs) 81 | 82 | def set_defaults(self): 83 | """Sets the default attrs""" 84 | super(BarPlot, self).set_defaults() 85 | self._attr["spacing"] = 0.2 86 | self._attr["stacked"] = False 87 | 88 | def plot_axis(self, axis, series_list, permute, concat, args_to_forward): 89 | """Internal Method called to plot data (series_list) on a given axis""" 90 | stacked = self._attr["stacked"] 91 | #Figure out how many bars per group 92 | bars_in_group = 1 if stacked else len(series_list) 93 | 94 | #Get the width of a group 95 | group_width = 1.0 - self._attr["spacing"] 96 | bar_width = group_width / bars_in_group 97 | 98 | #Keep a list of the tops of bars to plot stacks 99 | #Start with a list of 0s to put the first bars at the bottom 100 | value_list = [c.result[p].values for (c, p) in series_list] 101 | end_of_previous = [0] * max(len(x) for x in value_list) 102 | 103 | for i, (constraint, pivot) in enumerate(series_list): 104 | result = constraint.result 105 | bar_anchor = np.arange(len(result[pivot].values)) 106 | if not stacked: 107 | bar_anchor = bar_anchor + i * bar_width 108 | 109 | line_2d_list = axis.bar( 110 | bar_anchor, 111 | result[pivot].values, 112 | bottom=end_of_previous, 113 | width=bar_width, 114 | color=self._cmap.cmap(i), 115 | **args_to_forward 116 | ) 117 | 118 | if stacked: 119 | end_of_previous = [x + y for (x, y) in zip(end_of_previous, result[pivot].values)] 120 | 121 | axis.set_title(self.make_title(constraint, pivot, permute, concat)) 122 | 123 | self.add_to_legend(i, line_2d_list[0], constraint, pivot, concat, permute) 124 | -------------------------------------------------------------------------------- /trappy/plotter/ColorMap.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """Defines a generic indexable ColorMap Class""" 17 | from __future__ import division 18 | from __future__ import unicode_literals 19 | from builtins import str 20 | from builtins import object 21 | import matplotlib.colors as clrs 22 | import matplotlib.cm as cmx 23 | from matplotlib.colors import ListedColormap, Normalize 24 | 25 | 26 | def to_dygraph_colors(color_map): 27 | """Convert a color_map specified as a list of rgb tuples to the 28 | syntax that dygraphs expect: ["rgb(1, 2, 3)", "rgb(4, 5, 6)",...] 29 | 30 | :param color_map: a list of rgb tuples 31 | :type color_map: list of tuples 32 | """ 33 | 34 | rgb_list = ["rgb(" + ", ".join(str(i) for i in e) + ")" for e in color_map] 35 | 36 | return '["' + '", "'.join(rgb_list) + '"]' 37 | 38 | class ColorMap(object): 39 | 40 | """The Color Map Class to return a gradient method 41 | 42 | :param num_colors: Number or colors for which a gradient 43 | is needed 44 | :type num_colors: int 45 | """ 46 | 47 | def __init__(self, num_colors, cmap='hsv'): 48 | self.color_norm = clrs.Normalize(vmin=0, vmax=num_colors) 49 | self.scalar_map = cmx.ScalarMappable(norm=self.color_norm, cmap=cmap) 50 | self.num_colors = num_colors 51 | 52 | def cmap(self, index): 53 | """ 54 | :param index: Index for the gradient array 55 | :type index: int 56 | 57 | :return: The color at specified index 58 | """ 59 | return self.scalar_map.to_rgba(index) 60 | 61 | def cmap_inv(self, index): 62 | """ 63 | :param index: Index for the gradient array 64 | :type index: int 65 | 66 | :return: The color at :math:`N_{colors} - i` 67 | """ 68 | return self.cmap(self.num_colors - index) 69 | 70 | @classmethod 71 | def rgb_cmap(cls, rgb_list): 72 | """Constructor for a ColorMap from an rgb_list 73 | 74 | :param rgb_list: A list of rgb tuples for red, green and blue. 75 | The rgb values should be in the range 0-255. 76 | :type rgb_list: list of tuples 77 | """ 78 | 79 | rgb_list = [[x / 255.0 for x in rgb[:3]] for rgb in rgb_list] 80 | 81 | rgb_map = ListedColormap(rgb_list, name='default_color_map', N=None) 82 | num_colors = len(rgb_list) 83 | 84 | return cls(num_colors, cmap=rgb_map) 85 | -------------------------------------------------------------------------------- /trappy/plotter/PlotLayout.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | """This module implements functionality related to 16 | the arrangement of the plots on the underlying 17 | plotting backend. 18 | """ 19 | from __future__ import division 20 | from __future__ import unicode_literals 21 | 22 | from builtins import object 23 | import matplotlib.pyplot as plt 24 | from trappy.plotter import AttrConf 25 | 26 | 27 | class PlotLayout(object): 28 | 29 | """ 30 | :param cols: The number of columns to draw 31 | :type cols: int 32 | 33 | :param num_plots: The total number of plots 34 | :type num_plots: int 35 | 36 | The linear co-ordinate system :math:`[0, N_{plots}]` is 37 | mapped to a 2-D coordinate system with math:`N_{rows}` 38 | and :math:`N_{cols}` such that: 39 | 40 | .. math:: 41 | 42 | N_{rows} = \\frac{N_{cols}}{N_{plots}} 43 | """ 44 | 45 | def __init__(self, cols, num_plots, **kwargs): 46 | 47 | self.cols = cols 48 | self._attr = {} 49 | self.num_plots = num_plots 50 | self._single_plot = False 51 | if self.num_plots == 0: 52 | raise RuntimeError("No plots for the given constraints") 53 | 54 | if self.num_plots < self.cols: 55 | self.cols = self.num_plots 56 | self.rows = (self.num_plots // self.cols) 57 | # Avoid Extra Allocation (shows up in savefig!) 58 | if self.num_plots % self.cols != 0: 59 | self.rows += 1 60 | 61 | self.usecol = False 62 | self.userow = False 63 | self._set_defaults() 64 | 65 | for key in kwargs: 66 | self._attr[key] = kwargs[key] 67 | 68 | # Scale the plots if there is a single plot and 69 | # Set boolean variables 70 | if num_plots == 1: 71 | self._single_plot = True 72 | self._scale_plot() 73 | elif self.rows == 1: 74 | self.usecol = True 75 | elif self.cols == 1: 76 | self.userow = True 77 | self._scale_plot() 78 | 79 | self._attr["figure"], self._attr["axes"] = plt.subplots( 80 | self.rows, self.cols, figsize=( 81 | self._attr["width"] * self.cols, 82 | self._attr["length"] * self.rows)) 83 | 84 | if self._attr['title']: 85 | self._attr["figure"].suptitle( 86 | self._attr['title'], 87 | fontsize=AttrConf.TITLE_SIZE, 88 | horizontalalignment='center') 89 | 90 | def _scale_plot(self): 91 | """Scale the graph in one 92 | plot per line use case""" 93 | 94 | self._attr["width"] = int(self._attr["width"] * 2.5) 95 | self._attr["length"] = int(self._attr["length"] * 1.25) 96 | 97 | def _set_defaults(self): 98 | """set the default attrs""" 99 | self._attr["width"] = AttrConf.WIDTH 100 | self._attr["length"] = AttrConf.LENGTH 101 | 102 | def get_2d(self, linear_val): 103 | """Convert Linear to 2D coordinates 104 | 105 | :param linear_val: The value in 1-D 106 | co-ordinate 107 | :type linear_val: int 108 | 109 | :return: Converted 2-D tuple 110 | """ 111 | if self.usecol: 112 | return linear_val % self.cols 113 | 114 | if self.userow: 115 | return linear_val % self.rows 116 | 117 | val_x = linear_val % self.cols 118 | val_y = linear_val // self.cols 119 | return val_y, val_x 120 | 121 | def finish(self, plot_index): 122 | """Delete the empty cells 123 | 124 | :param plot_index: Linear index at which the 125 | last plot was created. This is used to 126 | delete the leftover empty plots that 127 | were generated. 128 | :type plot_index: int 129 | """ 130 | while plot_index < (self.rows * self.cols): 131 | self._attr["figure"].delaxes( 132 | self._attr["axes"][ 133 | self.get_2d(plot_index)]) 134 | plot_index += 1 135 | 136 | def get_axis(self, plot_index): 137 | """Get the axes for the plots 138 | 139 | :param plot_index: The index for 140 | which the axis is required. This 141 | internally is mapped to a 2-D co-ordinate 142 | 143 | :return: :mod:`matplotlib.axes.Axes` 144 | instance is returned 145 | """ 146 | if self._single_plot: 147 | return self._attr["axes"] 148 | else: 149 | return self._attr["axes"][self.get_2d(plot_index)] 150 | 151 | def get_fig(self): 152 | """Return the matplotlib figure object 153 | 154 | :return: :mod:`matplotlib.figure` 155 | """ 156 | return self._attr["figure"] 157 | -------------------------------------------------------------------------------- /trappy/plotter/Utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """Utils module has generic utils that will be used across 17 | objects 18 | """ 19 | from __future__ import unicode_literals 20 | from __future__ import division 21 | from __future__ import print_function 22 | 23 | import collections 24 | import warnings 25 | from trappy.utils import listify 26 | 27 | 28 | def normalize_list(val, lst): 29 | """Normalize a unitary list""" 30 | 31 | if len(lst) != 1: 32 | raise RuntimeError("Cannot Normalize a non-unitary list") 33 | 34 | return lst * val 35 | 36 | 37 | def decolonize(val): 38 | """Remove the colon at the end of the word 39 | This will be used by the unique word of 40 | template class to sanitize attr accesses 41 | """ 42 | 43 | return val.strip(":") 44 | 45 | 46 | def get_trace_event_data(trace, execnames=None, pids=None): 47 | """Create a list of objects that can be consumed by EventPlot to plot 48 | task residency like kernelshark 49 | 50 | """ 51 | 52 | if execnames: 53 | execnames = listify(execnames) 54 | 55 | if pids: 56 | pids = listify(pids) 57 | 58 | data = collections.defaultdict(list) 59 | pmap = {} 60 | 61 | data_frame = trace.sched_switch.data_frame 62 | start_idx = data_frame.index.values[0] 63 | end_idx = data_frame.index.values[-1] 64 | 65 | procs = set() 66 | 67 | for index, row in data_frame.iterrows(): 68 | prev_pid = row["prev_pid"] 69 | next_pid = row["next_pid"] 70 | next_comm = row["next_comm"] 71 | 72 | if prev_pid in pmap: 73 | name = pmap[prev_pid] 74 | data[name][-1][1] = index 75 | del pmap[prev_pid] 76 | 77 | name = "{}-{}".format(next_comm, next_pid) 78 | 79 | if next_pid in pmap: 80 | # Corrupted trace probably due to dropped events. We 81 | # don't know when the pid in pmap finished. We just 82 | # ignore it and don't plot it 83 | warn_str = "Corrupted trace (dropped events) for PID {} at time {}". \ 84 | format(next_pid, index) 85 | warnings.warn(warn_str) 86 | del pmap[next_pid] 87 | del data[name][-1] 88 | 89 | if next_pid != 0 and not next_comm.startswith("migration"): 90 | 91 | if execnames and next_comm not in execnames: 92 | continue 93 | 94 | if pids and next_pid not in pids: 95 | continue 96 | 97 | data[name].append([index, end_idx, row["__cpu"]]) 98 | pmap[next_pid] = name 99 | procs.add(name) 100 | 101 | return data, procs, [start_idx, end_idx] 102 | -------------------------------------------------------------------------------- /trappy/plotter/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """Init Module for the Plotter Code""" 17 | from __future__ import unicode_literals 18 | from __future__ import division 19 | from __future__ import print_function 20 | from __future__ import absolute_import 21 | 22 | import pandas as pd 23 | from . import LinePlot 24 | from . import AttrConf 25 | try: 26 | import trappy.plotter.EventPlot 27 | except ImportError: 28 | pass 29 | from . import Utils 30 | import trappy 31 | from . import IPythonConf 32 | 33 | def register_forwarding_arg(arg_name): 34 | """Allows the user to register args to 35 | be forwarded to matplotlib 36 | 37 | :param arg_name: The arg to register 38 | :type arg_name: str 39 | """ 40 | if arg_name not in AttrConf.ARGS_TO_FORWARD: 41 | AttrConf.ARGS_TO_FORWARD.append(arg_name) 42 | 43 | def unregister_forwarding_arg(arg_name): 44 | """Unregisters arg_name from being passed to 45 | plotter matplotlib calls 46 | 47 | :param arg_name: The arg to register 48 | :type arg_name: str 49 | """ 50 | try: 51 | AttrConf.ARGS_TO_FORWARD.remove(arg_name) 52 | except ValueError: 53 | pass 54 | 55 | def plot_trace(trace, 56 | execnames=None, 57 | pids=None): 58 | """Creates a kernelshark like plot of the trace file 59 | 60 | :param trace: The path to the trace or a trace object 61 | :type trace: str, :mod:`trappy.trace.FTrace`, :mod:`trappy.trace.SysTrace` 62 | or :mod:`trappy.trace.BareTrace`. 63 | 64 | :param execnames: List of execnames to be filtered. If not 65 | specified all execnames will be plotted 66 | :type execnames: list, str 67 | 68 | :param pids: List of pids to be filtered. If not specified 69 | all pids will be plotted 70 | :type pids: list, str 71 | """ 72 | 73 | if not IPythonConf.check_ipython(): 74 | raise RuntimeError("plot_trace needs ipython environment") 75 | 76 | if not isinstance(trace, trappy.BareTrace): 77 | if trace.endswith("html"): 78 | trace = trappy.SysTrace(trace) 79 | else: 80 | trace = trappy.FTrace(trace) 81 | 82 | data, procs, domain = Utils.get_trace_event_data(trace, execnames, pids) 83 | trace_graph = EventPlot.EventPlot(data, procs, domain, 84 | lane_prefix="CPU :", 85 | num_lanes=int(trace._cpus)) 86 | trace_graph.view() 87 | -------------------------------------------------------------------------------- /trappy/plotter/css/EventPlot.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2017 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | .d3-tip { 18 | line-height: 1; 19 | padding: 12px; 20 | background: rgba(0, 0, 0, 0.6); 21 | color: #fff; 22 | border-radius: 2px; 23 | position: absolute !important; 24 | z-index: 99999; 25 | } 26 | 27 | .d3-tip:after { 28 | box-sizing: border-box; 29 | pointer-events: none; 30 | display: inline; 31 | font-size: 10px; 32 | width: 100%; 33 | line-height: 1; 34 | color: rgba(0, 0, 0, 0.6); 35 | content: "\25BC"; 36 | position: absolute !important; 37 | z-index: 99999; 38 | text-align: center; 39 | } 40 | 41 | .d3-tip.n:after { 42 | margin: -1px 0 0 0; 43 | top: 100%; 44 | left: 0; 45 | } 46 | 47 | .contextRect { 48 | fill: lightgray; 49 | fill-opacity: 0.5; 50 | stroke: black; 51 | stroke-width: 1; 52 | stroke-opacity: 1; 53 | pointer-events: none; 54 | shape-rendering: crispEdges; 55 | } 56 | 57 | .chart { 58 | shape-rendering: crispEdges; 59 | } 60 | 61 | .mini text { 62 | font: 9px sans-serif; 63 | } 64 | 65 | .main text { 66 | font: 12px sans-serif; 67 | } 68 | 69 | .axis line, .axis path { 70 | stroke: black; 71 | } 72 | 73 | .miniItem { 74 | stroke-width: 8; 75 | } 76 | 77 | .brush .extent { 78 | 79 | stroke: #000; 80 | fill-opacity: .125; 81 | shape-rendering: crispEdges; 82 | } 83 | -------------------------------------------------------------------------------- /trappy/plotter/css/EventPlot_help.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARM-software/trappy/bba21c4de5cbe380d375bdf3f5666c7880235287/trappy/plotter/css/EventPlot_help.jpg -------------------------------------------------------------------------------- /trappy/plotter/js/ILinePlot.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2017 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var ILinePlot = ( function() { 18 | 19 | var graphs = new Array(); 20 | var syncObjs = new Array(); 21 | 22 | var purge = function() { 23 | for (var div_name in graphs) { 24 | if (document.getElementById(div_name) == null) { 25 | delete graphs[div_name]; 26 | } 27 | } 28 | }; 29 | 30 | var sync = function(group) { 31 | 32 | var syncGraphs = Array(); 33 | var xRange; 34 | var yRange; 35 | var syncZoom = true; 36 | 37 | for (var div_name in graphs) { 38 | 39 | if (graphs[div_name].group == group) { 40 | syncGraphs.push(graphs[div_name].graph); 41 | syncZoom = syncZoom & graphs[div_name].syncZoom; 42 | 43 | var xR = graphs[div_name].graph.xAxisRange(); 44 | var yR = graphs[div_name].graph.yAxisRange(); 45 | 46 | if (xRange != undefined) { 47 | if (xR[0] < xRange[0]) 48 | xRange[0] = xR[0]; 49 | if (xR[1] > xRange[1]) 50 | xRange[1] = xR[1]; 51 | } else 52 | xRange = xR; 53 | 54 | if (yRange != undefined) { 55 | if (yR[0] < yRange[0]) 56 | yRange[0] = yR[0]; 57 | if (yR[1] > yRange[1]) 58 | yRange[1] = yR[1]; 59 | } else 60 | yRange = yR; 61 | } 62 | } 63 | 64 | if (syncGraphs.length >= 2) { 65 | if (syncZoom) { 66 | if (syncObjs[group] != undefined) 67 | syncObjs[group].detach(); 68 | 69 | syncObjs[group] = Dygraph.synchronize(syncGraphs, { 70 | zoom: true, 71 | selection: false, 72 | range: true 73 | }); 74 | } 75 | 76 | $.each(syncGraphs, function(g) { 77 | var graph = syncGraphs[g]; 78 | 79 | graph.updateOptions({ 80 | valueRange: yRange, 81 | dateWindow: xRange 82 | }); 83 | 84 | if (graph.padFront_ == undefined) { 85 | graph.padFront_ = true; 86 | var _decoy_elem = new Array(graph.rawData_[0].length); 87 | graph.rawData_.unshift(_decoy_elem); 88 | } 89 | graph.rawData_[0][0] = xRange[0]; 90 | 91 | if (graph.padBack_ == undefined) { 92 | graph.padBack_ = true; 93 | var _decoy_elem = new Array(graph.rawData_[0].length); 94 | graph.rawData_.push(_decoy_elem); 95 | } 96 | graph.rawData_[graph.rawData_.length - 1][0] = xRange[1]; 97 | }); 98 | } 99 | }; 100 | 101 | var generate = function(data, colors) { 102 | create_graph(data, colors); 103 | purge(); 104 | if (data.syncGroup != undefined) 105 | sync(data.syncGroup); 106 | }; 107 | 108 | var create_graph = function(t_info, colors) { 109 | var tabular = t_info.data; 110 | 111 | var options = { 112 | legend: 'always', 113 | title: t_info.title, 114 | labels: tabular.labels, 115 | labelsDivStyles: { 116 | 'textAlign': 'right' 117 | }, 118 | rollPeriod: 1, 119 | animatedZooms: true, 120 | connectSeparatedPoints: true, 121 | showRangeSelector: t_info.rangesel, 122 | rangeSelectorHeight: 50, 123 | stepPlot: t_info.step_plot, 124 | logscale: t_info.logscale, 125 | fillGraph: t_info.fill_graph, 126 | labelsDiv: t_info.name + "_legend", 127 | errorBars: false, 128 | labelsSeparateLines: true, 129 | valueRange: t_info.valueRange, 130 | drawPoints: t_info.drawPoints, 131 | strokeWidth: t_info.strokeWidth, 132 | pointSize: t_info.pointSize, 133 | dateWindow: t_info.dateWindow 134 | }; 135 | 136 | if (typeof t_info.fill_alpha !== 'undefined') 137 | options.fillAlpha = t_info.fill_alpha; 138 | 139 | if (typeof colors !== 'undefined') 140 | options["colors"] = colors; 141 | 142 | var graph = new Dygraph(document.getElementById(t_info.name), tabular.data, options); 143 | 144 | var width = $("#" + t_info.name) 145 | .closest(".output_subarea").width() / t_info.per_line 146 | 147 | /* 148 | * Remove 3 pixels from width to avoid unnecessary horizontal scrollbar 149 | */ 150 | graph.resize(width - 3, t_info.height); 151 | 152 | $(window).on("resize." + t_info.name, function() { 153 | 154 | var width = $("#" + t_info.name) 155 | .closest(".output_subarea").width() / t_info.per_line 156 | 157 | graph.resize(width, t_info.height); 158 | }); 159 | 160 | graphs[t_info.name] = 161 | { 162 | graph: graph, 163 | group: t_info.syncGroup, 164 | syncZoom: t_info.syncZoom 165 | }; 166 | 167 | }; 168 | 169 | return { 170 | generate: generate 171 | }; 172 | 173 | }()); 174 | -------------------------------------------------------------------------------- /trappy/raw_syscalls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | 17 | import re 18 | from trappy.base import Base 19 | from trappy.dynamic import register_ftrace_parser 20 | 21 | class SysEnter(Base): 22 | """Parse sys enter""" 23 | 24 | unique_word = "sys_enter" 25 | pivot = "pid" 26 | parse_raw = False 27 | pat = re.compile(r"NR (\d+) \(([-+]?[\da-fA-F]+), ([-+]?[\da-fA-F]+), ([-+]?[\da-fA-F]+), ([-+]?[\da-fA-F]+), ([-+]?[\da-fA-F]+), ([-+]?[\da-fA-F]+)\)") 28 | 29 | def __init__(self): 30 | super(SysEnter, self).__init__(parse_raw=self.parse_raw) 31 | 32 | def explode(self, line): 33 | """trace-cmd-29016 [000] 99937.172691: sys_enter: NR 64 (1, 7764ee9000, 103, 0, 0, 4)""" 34 | 35 | match = None 36 | try: 37 | match = self.pat.search(line) 38 | num, *args = match.group(1,2,3,4,5,6,7) 39 | num = int(num) 40 | args = [int(x, 16) for x in args] 41 | ret = "nr={} arg0={} arg1={} arg2={} arg3={} arg4={} arg5={}".format(num, args[0], args[1], args[2], args[3], args[4], args[5]) 42 | return ret 43 | except Exception as e: 44 | raise ValueError("failed to parse line {}: {}".format(line, e)) 45 | 46 | def create_dataframe(self): 47 | self.data_array = [self.explode(line) for line in self.data_array] 48 | super(SysEnter, self).create_dataframe() 49 | 50 | register_ftrace_parser(SysEnter) 51 | 52 | 53 | 54 | class SysExit(Base): 55 | """Parse sys exit""" 56 | 57 | unique_word = "sys_exit" 58 | pivot = "pid" 59 | parse_raw = False 60 | pat = re.compile("NR ([-+]?\d+) = ([-+]?\d+)") 61 | 62 | def __init__(self): 63 | super(SysExit, self).__init__(parse_raw=self.parse_raw) 64 | 65 | def explode(self, line): 66 | """ trace-cmd-29016 [000] 99937.172659: sys_exit: NR 64 = 1 """ 67 | match = None 68 | try: 69 | match = self.pat.search(line) 70 | num, ret = match.group(1,2) 71 | num = int(num) 72 | ret = int(ret) 73 | foo = "nr={} ret={}".format(num, ret) 74 | return foo 75 | except Exception as e: 76 | raise ValueError("failed to parse line {}: {}".format(line, e)) 77 | 78 | def create_dataframe(self): 79 | self.data_array = [self.explode(line) for line in self.data_array] 80 | super(SysExit, self).create_dataframe() 81 | 82 | register_ftrace_parser(SysExit) 83 | -------------------------------------------------------------------------------- /trappy/sched.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Definitions of scheduler events registered by the FTrace class""" 18 | from __future__ import unicode_literals 19 | from __future__ import division 20 | from __future__ import print_function 21 | 22 | from trappy.base import Base 23 | from trappy.dynamic import register_ftrace_parser, register_dynamic_ftrace 24 | 25 | class SchedLoadAvgSchedGroup(Base): 26 | """Corresponds to Linux kernel trace event sched_load_avg_sched_group""" 27 | 28 | unique_word = "sched_load_avg_sg:" 29 | """The unique word that will be matched in a trace line""" 30 | 31 | _cpu_mask_column = "cpus" 32 | 33 | pivot = "cpus" 34 | """The Pivot along which the data is orthogonal""" 35 | 36 | def finalize_object(self): 37 | """This condition is necessary to force column 'cpus' to be printed 38 | as 8 digits w/ leading 0 39 | """ 40 | if self._cpu_mask_column in self.data_frame.columns: 41 | dfr = self.data_frame[self._cpu_mask_column].apply('{:0>8}'.format) 42 | self.data_frame[self._cpu_mask_column] = dfr 43 | 44 | register_ftrace_parser(SchedLoadAvgSchedGroup, "sched") 45 | 46 | class SchedLoadAvgTask(Base): 47 | """Corresponds to Linux kernel trace event sched_load_avg_task""" 48 | 49 | unique_word = "sched_load_avg_task:" 50 | """The unique word that will be matched in a trace line""" 51 | 52 | pivot = "pid" 53 | """The Pivot along which the data is orthogonal""" 54 | 55 | def get_pids(self, key=""): 56 | """Returns a list of (comm, pid) that contain 57 | 'key' in their 'comm'.""" 58 | dfr = self.data_frame.drop_duplicates(subset=['comm', 'pid']) 59 | dfr = dfr.ix[:, ['comm', 'pid']] 60 | 61 | return dfr[dfr['comm'].str.contains(key)].values.tolist() 62 | 63 | register_ftrace_parser(SchedLoadAvgTask, "sched") 64 | 65 | # pylint doesn't like globals that are not ALL_CAPS 66 | # pylint: disable=invalid-name 67 | SchedLoadAvgCpu = register_dynamic_ftrace("SchedLoadAvgCpu", 68 | "sched_load_avg_cpu:", "sched", 69 | pivot="cpu") 70 | """Load and Utilization Signals for CPUs""" 71 | 72 | SchedContribScaleFactor = register_dynamic_ftrace("SchedContribScaleFactor", 73 | "sched_contrib_scale_f:", 74 | "sched") 75 | """Event to register tracing of contrib factor""" 76 | 77 | class SchedCpuCapacity(Base): 78 | """Corresponds to Linux kernel trace event sched/cpu_capacity""" 79 | 80 | unique_word = "cpu_capacity:" 81 | """The unique word that will be matched in a trace line""" 82 | 83 | pivot = "cpu" 84 | """The Pivot along which the data is orthogonal""" 85 | 86 | def finalize_object(self): 87 | """This renaming is necessary because our cpu related pivot is 'cpu' 88 | and not 'cpu_id'. Otherwise you cannot 'mix and match' with other 89 | classes 90 | """ 91 | self.data_frame.rename(columns={'cpu_id':'cpu'}, inplace=True) 92 | self.data_frame.rename(columns={'state' :'capacity'}, inplace=True) 93 | 94 | register_ftrace_parser(SchedCpuCapacity, "sched") 95 | 96 | SchedWakeup = register_dynamic_ftrace("SchedWakeup", "sched_wakeup:", "sched", 97 | parse_raw=True) 98 | """Register SchedWakeup Event""" 99 | 100 | SchedWakeupNew = register_dynamic_ftrace("SchedWakeupNew", "sched_wakeup_new:", 101 | "sched", parse_raw=True) 102 | """Register SchedWakeupNew Event""" 103 | 104 | # pylint: enable=invalid-name 105 | 106 | class SchedSwitch(Base): 107 | """Parse sched_switch""" 108 | 109 | unique_word = "sched_switch:" 110 | parse_raw = True 111 | 112 | def __init__(self): 113 | super(SchedSwitch, self).__init__(parse_raw=self.parse_raw) 114 | 115 | def create_dataframe(self): 116 | self.data_array = [line.replace(" ==> ", " ", 1) 117 | for line in self.data_array] 118 | 119 | super(SchedSwitch, self).create_dataframe() 120 | 121 | register_ftrace_parser(SchedSwitch, "sched") 122 | 123 | class SchedCpuFrequency(Base): 124 | """Corresponds to Linux kernel trace event power/cpu_frequency""" 125 | 126 | unique_word = "cpu_frequency:" 127 | """The unique word that will be matched in a trace line""" 128 | 129 | pivot = "cpu" 130 | """The Pivot along which the data is orthogonal""" 131 | 132 | def finalize_object(self): 133 | """This renaming is necessary because our cpu related pivot is 'cpu' 134 | and not 'cpu_id'. Otherwise you cannot 'mix and match' with other 135 | classes 136 | """ 137 | self.data_frame.rename(columns={'cpu_id':'cpu'}, inplace=True) 138 | self.data_frame.rename(columns={'state' :'frequency'}, inplace=True) 139 | 140 | register_ftrace_parser(SchedCpuFrequency, "sched") 141 | 142 | register_dynamic_ftrace("SchedMigrateTask", "sched_migrate_task:", "sched") 143 | -------------------------------------------------------------------------------- /trappy/stats/Indexer.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """Indexers are responsible for providing indexes for 17 | aggregations and provide specific functions like 18 | unification and resampling. 19 | """ 20 | from __future__ import unicode_literals 21 | from __future__ import division 22 | from __future__ import print_function 23 | 24 | from builtins import object 25 | import pandas as pd 26 | import numpy as np 27 | from trappy.utils import listify 28 | from trappy.stats import StatConf 29 | 30 | class Indexer(object): 31 | """Indexer base class is an encapsulation 32 | around the pandas Index object with some 33 | special functionality 34 | 35 | :param index: Pandas index object. This can be 36 | non-unoform and non-unique 37 | :type index: :mod:`pandas.Index` 38 | 39 | :param traces: trappy FTrace list/singular object 40 | :type traces: :mod:`trappy.trace.FTrace` 41 | """ 42 | 43 | def __init__(self, index): 44 | self.index = index 45 | 46 | def series(self): 47 | """Returns an empty series with the initialized index 48 | """ 49 | return pd.Series(np.zeros(len(self.index)), index=self.index) 50 | 51 | def get_uniform(self, delta=StatConf.DELTA_DEFAULT): 52 | """ 53 | :param delta: Difference between two indices. This has a 54 | default value specified in StatConf.DELTA_DEFAULT 55 | :type delta: float 56 | 57 | :return: A uniformly spaced index. 58 | """ 59 | 60 | uniform_start = self.index.values[0] 61 | uniform_end = self.index.values[-1] 62 | new_index = np.arange(uniform_start, uniform_end, delta) 63 | return new_index 64 | 65 | def get_unified_indexer(indexers): 66 | """Unify the List of Indexers 67 | 68 | :param indexers: A list of indexers 69 | :type indexers: :mod:`trappy.stats.Indexer.Indexer` 70 | 71 | :return: A :mod:`pandas.Indexer.Indexer` 72 | with a unfied index 73 | """ 74 | 75 | new_index = indexers[0].index 76 | 77 | for idx in indexers[1:]: 78 | new_index = new_index.union(idx.index) 79 | 80 | return Indexer(new_index) 81 | 82 | class MultiTriggerIndexer(Indexer): 83 | """"The index unifies the indices of all trigger 84 | events. 85 | 86 | :param triggers: A (list or single) trigger 87 | :type triggers: :mod:`trappy.stats.Trigger.Trigger` 88 | """ 89 | 90 | def __init__(self, triggers): 91 | 92 | self._triggers = listify(triggers) 93 | super(MultiTriggerIndexer, self).__init__(self._unify()) 94 | 95 | def _unify(self): 96 | """Function to unify all the indices of each trigger 97 | """ 98 | 99 | idx = pd.Index([]) 100 | for trigger in self._triggers: 101 | trace = trigger.trace 102 | trappy_event = getattr(trace, trigger.template.name) 103 | idx = idx.union(trappy_event.data_frame.index) 104 | 105 | 106 | return pd.Index(np.unique(idx.values)) 107 | -------------------------------------------------------------------------------- /trappy/stats/StatConf.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """Config Parameters for the Statistics Framework""" 17 | from __future__ import unicode_literals 18 | from __future__ import division 19 | from __future__ import print_function 20 | 21 | # Default interval between a uniform time series 22 | DELTA_DEFAULT = 0.000025 23 | """The default delta for uniformly resampled data""" 24 | GRAMMAR_DEFAULT_PIVOT = "NO_PIVOT" 25 | """Default pivot value for :mod:`trappy.stats.grammar`""" 26 | REINDEX_METHOD_DEFAULT = "pad" 27 | """Default method for reindexing and filling up NaNs""" 28 | REINDEX_LIMIT_DEFAULT = None 29 | """Number or indices a value will be propagated forward when reindexing""" 30 | NAN_FILL_DEFAULT = True 31 | """Fill NaN values by default""" 32 | -------------------------------------------------------------------------------- /trappy/stats/Topology.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """A Topology can be defined as an arrangement of 17 | fundamental nodes, in various levels. Each topology 18 | has a default level "all" which has each node represented 19 | as a group. For example: 20 | 21 | +--------+---------------------------------------+ 22 | | level | groups | 23 | +========+=======================================+ 24 | | all | :code:`[[0, 1, 2, 3, 4]]` | 25 | +--------+---------------------------------------+ 26 | | cluster| :code:`[[0, 1], [2, 3, 4]]` | 27 | +--------+---------------------------------------+ 28 | | cpu | :code:`[[0], [1], [2], [3], [4], [5]]`| 29 | +--------+---------------------------------------+ 30 | 31 | """ 32 | from __future__ import unicode_literals 33 | from __future__ import division 34 | from __future__ import print_function 35 | 36 | from builtins import object 37 | class Topology(object): 38 | """Topology object allows grouping of 39 | pivot values (called nodes) at multiple levels. 40 | The implementation is targeted towards CPU topologies 41 | but can be used generically as well 42 | 43 | :param clusters: clusters can be defined as a 44 | list of groups which are again lists of nodes. 45 | 46 | .. note:: 47 | 48 | This is not a mandatory 49 | argument but can be used to quickly create typical 50 | CPU topologies. 51 | 52 | For Example: 53 | :: 54 | 55 | from trappy.stats.Topology import Topology 56 | 57 | CLUSTER_A = [0, 1] 58 | CLUTSER_B = [2, 3] 59 | 60 | clusters = [CLUSTER_A, CLUSTER_B] 61 | topology = Topology(clusters=clusters) 62 | 63 | :type clusters: list 64 | """ 65 | 66 | def __init__(self, clusters=[]): 67 | self._levels = {} 68 | self._nodes = set() 69 | 70 | if len(clusters): 71 | self.add_to_level("cluster", clusters) 72 | cpu_level = [] 73 | for node in self.flatten(): 74 | cpu_level.append([node]) 75 | self.add_to_level("cpu", cpu_level) 76 | 77 | def __repr__(self): 78 | repr_str = "" 79 | for level_name in self._levels: 80 | repr_str += level_name + " " + \ 81 | self.get_level(level_name).__repr__() + \ 82 | "\n" 83 | return repr_str 84 | 85 | def add_to_level(self, level_name, level_vals): 86 | """Add a group to a level 87 | 88 | This function allows to append a 89 | group of nodes to a level. If the level 90 | does not exist a new level is created 91 | 92 | :param level_name: The name of the level 93 | :type level_name: str 94 | 95 | :level_vals: groups containing nodes 96 | :type level_vals: list of lists: 97 | """ 98 | 99 | if level_name not in self._levels: 100 | self._levels[level_name] = [] 101 | 102 | self._levels[level_name] += level_vals 103 | 104 | for group in level_vals: 105 | self._nodes = self._nodes.union(set(group)) 106 | 107 | def get_level(self, level_name): 108 | """Returns the groups of nodes associated 109 | with a level 110 | 111 | :param level_name: The name of the level 112 | :type level_name: str 113 | """ 114 | 115 | if level_name == "all": 116 | return [self.flatten()] 117 | else: 118 | return self._levels[level_name] 119 | 120 | def get_index(self, level, node): 121 | """Return the index of the node in the 122 | level's list of nodes 123 | 124 | :param level: The name of the level 125 | :type level_name: str 126 | 127 | :param node: The group for which the inde 128 | is required 129 | 130 | .. todo:: 131 | 132 | Change name of the arg to group 133 | 134 | :type node: list 135 | """ 136 | 137 | nodes = self.get_level(level) 138 | return nodes.index(node) 139 | 140 | def get_node(self, level, index): 141 | """Get the group at the index in 142 | the level 143 | 144 | :param level: The name of the level 145 | :type level_name: str 146 | 147 | :param index: Index of the group in 148 | the list 149 | :type index: int 150 | """ 151 | 152 | nodes = self.get_level(level) 153 | return nodes[index] 154 | 155 | def __iter__(self): 156 | return self._levels.__iter__() 157 | 158 | def flatten(self): 159 | """Return a flattened list of nodes in the 160 | topology 161 | """ 162 | return list(self._nodes) 163 | 164 | def level_span(self, level): 165 | """Return the number of groups in a level 166 | 167 | :param level: The name of the level 168 | :type level_name: str 169 | """ 170 | if level == "all": 171 | return len(self._nodes) 172 | else: 173 | return len(self._levels[level]) 174 | 175 | def has_level(self, level): 176 | """Returns true if level is present 177 | 178 | :param level: The name of the level 179 | :type level_name: str 180 | """ 181 | 182 | return (level in self._levels) 183 | 184 | -------------------------------------------------------------------------------- /trappy/stats/Trigger.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """Trigger is a representation of the following: 17 | 18 | - Event(s) (:mod:`trappy.base.Base`) 19 | - An associated value 20 | - scalar 21 | - vector 22 | - A set of filters 23 | - value based 24 | - function based 25 | """ 26 | from __future__ import unicode_literals 27 | from __future__ import division 28 | from __future__ import print_function 29 | 30 | from builtins import range 31 | from builtins import object 32 | import types 33 | from trappy.utils import listify 34 | import pandas as pd 35 | 36 | 37 | class Trigger(object): 38 | """Trigger is an event-value relationship which 39 | accepts a trace object to "generate" qualified data 40 | 41 | :param trace: A trappy FTrace object 42 | :type trace: :mod:`trappy.trace.FTrace` 43 | 44 | :param template: A trappy Event to act as a trigger 45 | :type template: trappy.Base 46 | 47 | :param filters: Key value filter pairs 48 | :type filters: dict 49 | 50 | The filter can either have a function: 51 | :: 52 | 53 | def function_based_filter(elem): 54 | if condition: 55 | return True 56 | else: 57 | return False 58 | 59 | or a value/list of values 60 | :: 61 | 62 | f = {} 63 | f["data_column_a"] = function_based_filter 64 | f["data_column_b"] = value 65 | 66 | function_based_filter is anything that behaves like a function, 67 | i.e. a callable. 68 | 69 | :param value: Value can be a string or a numeric 70 | :type value: str, int, float 71 | 72 | :param pivot: This is the column around which the data will be 73 | pivoted 74 | :type pivot: str 75 | """ 76 | 77 | def __init__(self, trace, template, filters, value, pivot): 78 | 79 | self.template = template 80 | self._filters = filters 81 | self._value = value 82 | self._pivot = pivot 83 | self.trace = trace 84 | 85 | def generate(self, pivot_val): 86 | """Generate the trigger data for a given pivot value 87 | and a trace index 88 | 89 | :param pivot_val: The pivot to generate data for 90 | :type pivot_val: hashable 91 | """ 92 | 93 | trappy_event = getattr(self.trace, self.template.name) 94 | data_frame = trappy_event.data_frame 95 | data_frame = data_frame[data_frame[self._pivot] == pivot_val] 96 | 97 | mask = [True for _ in range(len(data_frame))] 98 | 99 | for key, value in self._filters.items(): 100 | if hasattr(value, "__call__"): 101 | mask = mask & (data_frame[key].apply(value)) 102 | else: 103 | mask = apply_filter_kv(key, value, data_frame, mask) 104 | 105 | data_frame = data_frame[mask] 106 | 107 | if isinstance(self._value, str): 108 | return data_frame[value] 109 | else: 110 | return pd.Series(self._value, index=data_frame.index) 111 | 112 | 113 | def apply_filter_kv(key, value, data_frame, mask): 114 | """Internal function to apply a key value 115 | filter to a data_frame and update the initial 116 | condition provided in mask. 117 | 118 | :param value: The value to checked for 119 | 120 | :param data_frame: The data to be filtered 121 | :type data_frame: :mod:`pandas.DataFrame` 122 | 123 | :param mask: Initial Condition Mask 124 | :type mask: :mod:`pandas.Series` 125 | 126 | :return: A **mask** to index the data frame 127 | """ 128 | 129 | value = listify(value) 130 | if key not in data_frame.columns: 131 | return mask 132 | else: 133 | for val in value: 134 | mask = mask & (data_frame[key] == val) 135 | return mask 136 | -------------------------------------------------------------------------------- /trappy/stats/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | # Copyright 2015-2017 ARM Limited 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | 18 | from trappy.stats import Aggregator 19 | from trappy.stats import Correlator 20 | from trappy.stats import Trigger 21 | from trappy.stats import Topology 22 | from trappy.stats import Indexer 23 | from trappy.stats import StatConf 24 | -------------------------------------------------------------------------------- /trappy/systrace.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | from builtins import object 20 | from trappy.ftrace import GenericFTrace 21 | import re 22 | 23 | SYSTRACE_EVENT = re.compile( 24 | r'^(?P[A-Z])(\|(?P\d+)\|(?P[^|]*)(\|(?P.*))?)?') 25 | 26 | class drop_before_trace(object): 27 | """Object that, when called, returns True if the line is not part of 28 | the trace 29 | 30 | We have to first look for the "" and then skip 31 | the headers that start with # 32 | 33 | """ 34 | def __init__(self, tracer): 35 | self.before_begin_trace = True 36 | self.before_actual_trace = True 37 | self.tracer = tracer 38 | 39 | def __call__(self, line): 40 | if self.before_begin_trace: 41 | if line.startswith("") or \ 42 | line.startswith("Android System Trace"): 43 | self.before_begin_trace = False 44 | elif self.before_actual_trace: 45 | if line.startswith(' 85 | 86 | """ 87 | return lambda x: not x.endswith("\n") 88 | 89 | def generate_data_dict(self, data_str): 90 | """ Custom parsing for systrace's userspace events """ 91 | data_dict = None 92 | 93 | match = SYSTRACE_EVENT.match(data_str) 94 | if match: 95 | data_dict = { 96 | 'event': match.group('event'), 97 | 'pid' : int(match.group('pid')) if match.group('pid') else None, 98 | 'func' : match.group('func' ), 99 | 'data' : match.group('data' ) 100 | } 101 | 102 | return data_dict 103 | -------------------------------------------------------------------------------- /trappy/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """Generic functions that can be used in multiple places in trappy 17 | """ 18 | from __future__ import division 19 | from __future__ import unicode_literals 20 | 21 | def listify(to_select): 22 | """Utitlity function to handle both single and 23 | list inputs 24 | """ 25 | 26 | if not isinstance(to_select, list): 27 | to_select = [to_select] 28 | 29 | return to_select 30 | 31 | def handle_duplicate_index(data, 32 | max_delta=0.000001): 33 | """Handle duplicate values in index 34 | 35 | :param data: The timeseries input 36 | :type data: :mod:`pandas.Series` 37 | 38 | :param max_delta: Maximum interval adjustment value that 39 | will be added to duplicate indices 40 | :type max_delta: float 41 | 42 | Consider the following case where a series needs to be reindexed 43 | to a new index (which can be required when different series need to 44 | be combined and compared): 45 | :: 46 | 47 | import pandas 48 | values = [0, 1, 2, 3, 4] 49 | index = [0.0, 1.0, 1.0, 6.0, 7.0] 50 | series = pandas.Series(values, index=index) 51 | new_index = [0.0, 1.0, 2.0, 3.0, 4.0, 6.0, 7.0] 52 | series.reindex(new_index) 53 | 54 | The above code fails with: 55 | :: 56 | 57 | ValueError: cannot reindex from a duplicate axis 58 | 59 | The function :func:`handle_duplicate_axis` changes the duplicate values 60 | to 61 | :: 62 | 63 | >>> import pandas 64 | >>> from trappy.utils import handle_duplicate_index 65 | 66 | >>> values = [0, 1, 2, 3, 4] 67 | index = [0.0, 1.0, 1.0, 6.0, 7.0] 68 | series = pandas.Series(values, index=index) 69 | series = handle_duplicate_index(series) 70 | print series.index.values 71 | >>> [ 0. 1. 1.000001 6. 7. ] 72 | 73 | """ 74 | 75 | # Make sure first that we are sorted by index and then by __line 76 | # so that the order of events is preserved after fixing the duplicated 77 | # timestamp. 78 | if '__line' in data: 79 | data.sort_values(['Time', '__line'], inplace=True) 80 | 81 | index = data.index 82 | new_index = index.values 83 | 84 | dups = index[index.duplicated()].unique() 85 | 86 | for dup in dups: 87 | # Leave one of the values intact 88 | dup_index_left = index.searchsorted(dup, side="left") 89 | dup_index_right = index.searchsorted(dup, side="right") - 1 90 | num_dups = dup_index_right - dup_index_left + 1 91 | 92 | # Calculate delta that needs to be added to each duplicate 93 | # index 94 | try: 95 | delta = (index[dup_index_right + 1] - dup) / num_dups 96 | except IndexError: 97 | # dup_index_right + 1 is outside of the series (i.e. the 98 | # dup is at the end of the series). 99 | delta = max_delta 100 | 101 | # Clamp the maximum delta added to max_delta 102 | if delta > max_delta: 103 | delta = max_delta 104 | 105 | # Add a delta to the others 106 | dup_index_left += 1 107 | while dup_index_left <= dup_index_right: 108 | new_index[dup_index_left] += delta 109 | delta += delta 110 | dup_index_left += 1 111 | 112 | return data.reindex(new_index) 113 | -------------------------------------------------------------------------------- /trappy/wa/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | """A helper module to extract data from WorkloadAutomation 16 | output directories. WorkloadAutomation is a tool for 17 | automating the execution of workloads. For more information 18 | please visit https://github.com/ARM-software/workload-automation 19 | 20 | .. note:: 21 | 22 | TRAPpy does not have a dependency on workload automation 23 | """ 24 | from __future__ import unicode_literals 25 | from __future__ import division 26 | from __future__ import print_function 27 | 28 | from trappy.wa.results import Result, get_results, combine_results 29 | from trappy.wa.sysfs_extractor import SysfsExtractor 30 | -------------------------------------------------------------------------------- /trappy/wa/results.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """Parse the results from a Workload Automation run and show it in a 17 | "pretty" table 18 | 19 | """ 20 | from __future__ import division 21 | from __future__ import unicode_literals 22 | 23 | import os 24 | import collections, csv, re 25 | import pandas as pd 26 | from matplotlib import pyplot as plt 27 | 28 | class Result(pd.DataFrame): 29 | """A DataFrame-like class for storing benchmark results""" 30 | def __init__(self, *args, **kwargs): 31 | super(Result, self).__init__(*args, **kwargs) 32 | self.ax = None 33 | 34 | def init_fig(self): 35 | _, self.ax = plt.subplots() 36 | 37 | def enlarge_axis(self, data): 38 | """Make sure that the axis don't clobber some of the data""" 39 | 40 | (_, _, plot_y_min, plot_y_max) = plt.axis() 41 | 42 | concat_data = pd.concat(data[s] for s in data) 43 | data_min = min(concat_data) 44 | data_max = max(concat_data) 45 | 46 | # A good margin can be 10% of the data range 47 | margin = (data_max - data_min) / 10 48 | if margin < 1: 49 | margin = 1 50 | 51 | update_axis = False 52 | 53 | if data_min <= plot_y_min: 54 | plot_y_min = data_min - margin 55 | update_axis = True 56 | 57 | if data_max >= plot_y_max: 58 | plot_y_max = data_max + margin 59 | update_axis = True 60 | 61 | if update_axis: 62 | self.ax.set_ylim(plot_y_min, plot_y_max) 63 | 64 | def plot_results_benchmark(self, benchmark, title=None): 65 | """Plot the results of the execution of a given benchmark 66 | 67 | A title is added to the plot if title is not supplied 68 | """ 69 | 70 | if title is None: 71 | title = benchmark.replace('_', ' ') 72 | title = title.title() 73 | 74 | self[benchmark].plot(ax=self.ax, kind="bar", title=title) 75 | plt.legend(bbox_to_anchor=(1.05, .5), loc=6) 76 | 77 | def plot_results(self): 78 | for bench in self.columns.levels[0]: 79 | self.plot_results_benchmark(bench) 80 | 81 | def get_run_number(metric): 82 | found = False 83 | run_number = None 84 | 85 | if re.match("Overall_Score|score|FPS", metric): 86 | found = True 87 | 88 | match = re.search(r"(.+)[ _](\d+)", metric) 89 | if match: 90 | run_number = int(match.group(2)) 91 | if match.group(1) == "Overall_Score": 92 | run_number -= 1 93 | else: 94 | run_number = 0 95 | 96 | return (found, run_number) 97 | 98 | def get_results(path=".", name=None): 99 | """Return a pd.DataFrame with the results 100 | 101 | The DataFrame's rows are the scores. The first column is the 102 | benchmark name and the second the id within it. For benchmarks 103 | that have a score result, that's what's used. For benchmarks with 104 | FPS_* result, that's the score. E.g. glbenchmarks "score" is it's 105 | fps. 106 | 107 | An optional name argument can be passed. If supplied, it overrides 108 | the name in the results file. 109 | 110 | """ 111 | 112 | bench_dict = collections.OrderedDict() 113 | 114 | if os.path.isdir(path): 115 | path = os.path.join(path, "results.csv") 116 | 117 | with open(path) as fin: 118 | results = csv.reader(fin) 119 | 120 | for row in results: 121 | (is_result, run_number) = get_run_number(row[3]) 122 | 123 | if is_result: 124 | if name: 125 | run_id = name 126 | else: 127 | run_id = re.sub(r"_\d+", r"", row[0]) 128 | 129 | bench = row[1] 130 | try: 131 | result = int(row[4]) 132 | except ValueError: 133 | result = float(row[4]) 134 | 135 | if bench in bench_dict: 136 | if run_id in bench_dict[bench]: 137 | if run_number not in bench_dict[bench][run_id]: 138 | bench_dict[bench][run_id][run_number] = result 139 | else: 140 | bench_dict[bench][run_id] = {run_number: result} 141 | else: 142 | bench_dict[bench] = {run_id: {run_number: result}} 143 | 144 | bench_dfrs = {} 145 | for bench, run_id_dict in bench_dict.items(): 146 | bench_dfrs[bench] = pd.DataFrame(run_id_dict) 147 | 148 | return Result(pd.concat(bench_dfrs.values(), axis=1, 149 | keys=bench_dfrs.keys())) 150 | 151 | def combine_results(data): 152 | """Combine two DataFrame results into one 153 | 154 | The data should be an array of results like the ones returned by 155 | get_results() or have the same structure. The returned DataFrame 156 | has two column indexes. The first one is the benchmark and the 157 | second one is the key for the result. 158 | 159 | """ 160 | 161 | res_dict = {} 162 | for benchmark in data[0].columns.levels[0]: 163 | concat_objs = [d[benchmark] for d in data] 164 | res_dict[benchmark] = pd.concat(concat_objs, axis=1) 165 | 166 | combined = pd.concat(res_dict.values(), axis=1, keys=res_dict.keys()) 167 | 168 | return Result(combined) 169 | -------------------------------------------------------------------------------- /trappy/wa/sysfs_extractor.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2017 ARM Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from __future__ import unicode_literals 16 | from __future__ import division 17 | from __future__ import print_function 18 | 19 | from builtins import object 20 | import os 21 | import pandas as pd 22 | import re 23 | 24 | class SysfsExtractor(object): 25 | """Operate on the parameters of a dump of Workload Automation's 26 | sysfs extractor instrumentation. 27 | 28 | :param path: The path to the workload in a output directory created by 29 | WA. 30 | :type path: str 31 | """ 32 | 33 | def __init__(self, path): 34 | self.thermal_path = os.path.join(path, "after", "sys", "devices", 35 | "virtual", "thermal", "thermal_zone0") 36 | self.properties = ["integral_cutoff", "k_d", "k_i", "k_po", "k_pu", 37 | "policy", "sustainable_power"] 38 | 39 | try: 40 | sysfs_files = os.listdir(self.thermal_path) 41 | except OSError: 42 | sysfs_files = [] 43 | 44 | for fname in sysfs_files: 45 | if re.search(r"cdev\d+_weight", fname): 46 | self.properties.append(fname) 47 | elif re.search(r"trip_point_\d+_temp", fname): 48 | self.properties.append(fname) 49 | 50 | def get_parameters(self): 51 | """Get the parameters from a sysfs extractor dump 52 | 53 | WorkloadAutomation (WA) can dump sysfs values using its 54 | sysfs_extractor instrumentation. Parse the tree and return the 55 | thermal parameters as a dict of key and values where the keys are 56 | the names of the files and values its corresponding values. 57 | 58 | """ 59 | 60 | ret = {} 61 | 62 | for property_name in self.properties: 63 | property_path = os.path.join(self.thermal_path, property_name) 64 | 65 | if not os.path.isfile(property_path): 66 | continue 67 | 68 | with open(property_path) as fin: 69 | contents = fin.read() 70 | # Trim trailing newline 71 | contents = contents[:-1] 72 | 73 | try: 74 | ret[property_name] = int(contents) 75 | except ValueError: 76 | ret[property_name] = contents 77 | 78 | return ret 79 | 80 | def pretty_print_in_ipython(self): 81 | """Print parameters extracted from sysfs from a WA run in a pretty HTML table. 82 | 83 | This won't work if the code is not running in an ipython notebook.""" 84 | 85 | from IPython.display import display, HTML 86 | 87 | params = self.get_parameters() 88 | 89 | # Don't print anything if we couldn't find any parameters 90 | if len(params) == 0: 91 | return 92 | 93 | params_items = [(key, [value]) for key, value in sorted(params.items())] 94 | dfr = pd.DataFrame.from_items(params_items, orient="index", 95 | columns=["Value"]) 96 | display(HTML(dfr.to_html(header=False))) 97 | --------------------------------------------------------------------------------