├── docs ├── memoro_ismm.pdf ├── set_up.md └── use_case.md ├── .gitignore ├── assets ├── icons │ ├── icon64.ico │ ├── icon64.png │ ├── icon256.png │ ├── icon64.icns │ └── icon_full.png ├── leveldb_graph.png └── memoro_screen.png ├── memoro.sh ├── make-package.sh ├── js ├── renderer.js ├── menu │ └── mainmenu.js └── chunkgraph.js ├── cpp ├── Makefile ├── binding.gyp ├── pattern.h ├── stacktree.h ├── memoro.h ├── stacktree.cc ├── pattern.cc ├── memoro_node.cc └── memoro.cc ├── package.json ├── LICENSE ├── main.js ├── README.md ├── index.js ├── css ├── chunk_graph_dark.css └── chunk_graph_light.css └── index.html /docs/memoro_ismm.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-vlsc/memoro/HEAD/docs/memoro_ismm.pdf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build files 2 | node_modules 3 | cpp/build 4 | 5 | # MAC file 6 | .DS_Store 7 | 8 | -------------------------------------------------------------------------------- /assets/icons/icon64.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-vlsc/memoro/HEAD/assets/icons/icon64.ico -------------------------------------------------------------------------------- /assets/icons/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-vlsc/memoro/HEAD/assets/icons/icon64.png -------------------------------------------------------------------------------- /assets/icons/icon256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-vlsc/memoro/HEAD/assets/icons/icon256.png -------------------------------------------------------------------------------- /assets/icons/icon64.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-vlsc/memoro/HEAD/assets/icons/icon64.icns -------------------------------------------------------------------------------- /assets/leveldb_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-vlsc/memoro/HEAD/assets/leveldb_graph.png -------------------------------------------------------------------------------- /assets/memoro_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-vlsc/memoro/HEAD/assets/memoro_screen.png -------------------------------------------------------------------------------- /assets/icons/icon_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-vlsc/memoro/HEAD/assets/icons/icon_full.png -------------------------------------------------------------------------------- /memoro.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPTPATH=$(dirname "$(readlink -f "$0")") 4 | 5 | (cd "$SCRIPTPATH"/cpp && make) && "$SCRIPTPATH"/node_modules/electron/cli.js "$SCRIPTPATH" 6 | -------------------------------------------------------------------------------- /make-package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | electron-packager . --overwrite --platform=darwin --arch=x64 --electron-version=1.7.9 --icon=assets/icons/icon64.icns --prune=true --out=release-builds 4 | -------------------------------------------------------------------------------- /js/renderer.js: -------------------------------------------------------------------------------- 1 | // This file is required by the index.html file and will 2 | // be executed in the renderer process for that window. 3 | // All of the Node.js APIs are available in this process. 4 | -------------------------------------------------------------------------------- /cpp/Makefile: -------------------------------------------------------------------------------- 1 | 2 | build/Release/memoro.node: $(wildcard *.cc) $(wildcard *.h) Makefile binding.gyp 3 | ../node_modules/node-gyp/bin/node-gyp.js rebuild --release --target=1.8.7 --arch=x64 --dist-url=https://atom.io/download/electron 4 | -------------------------------------------------------------------------------- /cpp/binding.gyp: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "targets": [ 4 | { 5 | "target_name": "memoro", 6 | "sources": [ "memoro.cc" , "memoro_node.cc", "pattern.cc", "stacktree.cc" ], 7 | "cflags": ["-Wall", "-std=c++14"], 8 | 'cflags_cc!': ['-std=gnu++0x'], 9 | "xcode_settings": { 10 | "OTHER_CFLAGS": [ 11 | "-std=c++14" 12 | ] 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "memoro", 3 | "productName": "Memoro", 4 | "version": "1.0.0", 5 | "private": true, 6 | "description": "A visualizer for Memoro traces", 7 | "main": "main.js", 8 | "scripts": { 9 | "start": "electron ." 10 | }, 11 | "keywords": [ 12 | "memoro", 13 | "heap profiling" 14 | ], 15 | "devDependencies": { 16 | "electron": "^1.8.7" 17 | }, 18 | "license": "MIT", 19 | "dependencies": { 20 | "async": "^2.6.0", 21 | "bootstrap": "^3.3.7", 22 | "command-exists": "^1.2.2", 23 | "compute-gmean": "^1.1.0", 24 | "d3": "^4.13.0", 25 | "d3-flame-graph": "^1.0.11", 26 | "d3-flame-graphs": "^0.4.0-alpha", 27 | "d3-selection": "^1.3.0", 28 | "dialog": "^0.3.1", 29 | "electron-settings": "^3.1.4", 30 | "font-awesome": "^4.7.0", 31 | "ipc": "0.0.1", 32 | "jquery": "^3.3.1", 33 | "node-gyp": "^7.1.2", 34 | "npm": "^6.1.0", 35 | "process": "^0.11.10" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 EPFL VLSC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cpp/pattern.h: -------------------------------------------------------------------------------- 1 | 2 | //===-- pattern.h ------------------------------------------------===// 3 | // 4 | // Memoro 5 | // 6 | // This file is distributed under the MIT License. 7 | // See LICENSE for details. 8 | // 9 | //===----------------------------------------------------------------------===// 10 | // 11 | // This file is a part of Memoro. 12 | // Stuart Byma, EPFL. 13 | // 14 | //===----------------------------------------------------------------------===// 15 | 16 | #pragma once 17 | 18 | #include "memoro.h" 19 | #include 20 | 21 | namespace memoro { 22 | 23 | enum Inefficiency : uint64_t { 24 | Unused = 0x1, 25 | WriteOnly = 1 << 1, 26 | ReadOnly = 1 << 2, 27 | ShortLifetime = 1 << 3, 28 | LateFree = 1 << 4, 29 | EarlyAlloc = 1 << 5, 30 | IncreasingReallocs = 1 << 6, 31 | TopPercentileChunks = 1 << 7, 32 | TopPercentileSize = 1 << 8, 33 | MultiThread = 1 << 9, 34 | LowAccessCoverage = 1 << 10 35 | }; 36 | 37 | // TODO bubble these parameters to the JS API and UI 38 | struct PatternParams { 39 | unsigned int short_lifetime = 1000000; // in ns currently 40 | unsigned int alloc_min_run = 4; 41 | float percentile = 0.9f; 42 | float access_coverage = 0.5f; 43 | }; 44 | 45 | bool HasInefficiency(uint64_t bitvec, Inefficiency i); 46 | 47 | float UsageScore(std::vector const& chunks); 48 | // threshold typically 1% of program lifetime 49 | float LifetimeScore(std::vector const& chunks, uint64_t threshold); 50 | float UsefulLifetimeScore(std::vector const& chunks); 51 | 52 | // returns bit vector of inefficiency 53 | uint64_t Detect(std::vector const& chunks, const PatternParams& params); 54 | 55 | // mutates traces vector elements 56 | // requires sorted traces by num chunks 57 | void CalculatePercentilesChunk(std::vector& traces, 58 | const PatternParams& params); 59 | 60 | // requires sorted traces by max agg 61 | void CalculatePercentilesSize(std::vector& traces, 62 | const PatternParams& params); 63 | 64 | } // namespace memoro 65 | -------------------------------------------------------------------------------- /docs/set_up.md: -------------------------------------------------------------------------------- 1 | # Setting Up Memoro 2 | 3 | Memoro consists of two parts - the compiler itself and the visualizer application. This guide will explain the various steps needed to set-up both of these tools. 4 | 5 | ## Compiler 6 | 7 | Memoro was built using a fork of LLVM/Clang and in-order to use Memoro you have to use these forks. They can be found here: 8 | 9 | [LLVM](https://github.com/epfl-vlsc/llvm) 10 | 11 | [Clang](https://github.com/epfl-vlsc/clang) 12 | 13 | [CompilerRT](https://github.com/epfl-vlsc/compiler-rt) 14 | 15 | In order to set this up: 16 | 17 | ```bash 18 | 1. mkdir memoro_compiler 19 | 2. cd memoro_compiler 20 | 3. git clone -b memoro_80 https://github.com/epfl-vlsc/llvm.git 21 | 4. cd llvm/tools 22 | 5. git clone -b memoro_80 https://github.com/epfl-vlsc/clang.git 23 | 6. cd ../projects` 24 | 7. git clone -b memoro_80 https://github.com/epfl-vlsc/compiler-rt.git 25 | 8. cd ../../ 26 | 9. mkdir build 27 | 10. cd build 28 | 11. cmake -G "Ninja" ../llvm 29 | 12. ninja 30 | ``` 31 | 32 | The above instructions are based off the main LLVM [page](https://releases.llvm.org/4.0.1/docs/GettingStarted.html). You need to have cmake installed for this to work. 33 | 34 | There are other ways of doing this but this is a straightforward and tested way. 35 | 36 | The above process will take some time to build. Once it is done, you can test your by running clang or clang++ in `build/bin` folder. 37 | 38 | ## Visualizer 39 | 40 | The visualizer is an electron application that will come in handy to analyse your output from memoro. The visalizer can be set-up as follows: 41 | 42 | ```bash 43 | 1. git clone https://github.com/epfl-vlsc/memoro/ 44 | 2. cd memoro 45 | 3. npm install 46 | 4. cd cpp 47 | 5. make 48 | 6. cd ../ 49 | 7. npm start 50 | ``` 51 | 52 | **Note**: for the above to work you need to have NodeJS/npm installed on your computer. The current tested version is v8.16.1. There is a cpp interface which has to built using `node-gyp` (install this using `npm install -g node-gyp`). The makefile will do the necessary after that. 53 | 54 | ## Using Memoro 55 | 56 | Now that you have both Memoro and the visualizer set-up, you can refer to [here](use_case.md) for how to use Memoro. 57 | -------------------------------------------------------------------------------- /cpp/stacktree.h: -------------------------------------------------------------------------------- 1 | //===-- stacktree.h ------------------------------------------------===// 2 | // 3 | // Memoro 4 | // 5 | // This file is distributed under the MIT License. 6 | // See LICENSE for details. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This file is a part of Memoro. 11 | // Stuart Byma, EPFL. 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | #pragma once 16 | 17 | #include 18 | #include 19 | //#include 20 | #include "memoro.h" 21 | 22 | namespace memoro { 23 | 24 | #define MAX_TRACES 1000ul 25 | 26 | using NameIDs = std::vector>; 27 | 28 | struct isolatedKeys; 29 | 30 | struct TraceAndValue { 31 | Trace* trace; 32 | double value; 33 | }; 34 | 35 | class StackTreeNode { 36 | public: 37 | StackTreeNode(uint64_t id, std::string name, const Trace* trace) 38 | : id_(id), name_(name), trace_(trace) {} 39 | 40 | bool Insert(const TraceAndValue&, NameIDs::const_iterator, const NameIDs&); 41 | void Objectify(v8::Isolate*, v8::Local&, const isolatedKeys&) const; 42 | 43 | protected: 44 | friend class StackTree; 45 | uint64_t id_; 46 | std::string name_; // string_view would be better 47 | 48 | // if trace is not nullptr, there can be no children 49 | const Trace* trace_ = nullptr; 50 | std::vector children_; 51 | double value_ = 0; 52 | }; 53 | 54 | class StackTreeNodeHide : public StackTreeNode { 55 | public: 56 | StackTreeNodeHide() : StackTreeNode(-1, "Hide", nullptr) {}; 57 | 58 | bool Insert(const TraceAndValue&, NameIDs::const_iterator, const NameIDs&); 59 | void Objectify(v8::Isolate*, v8::Local&, const isolatedKeys&) const; 60 | 61 | private: 62 | std::unique_ptr next_ = nullptr; 63 | }; 64 | 65 | class StackTree { 66 | public: 67 | void SetTraces(std::vector&); 68 | void Aggregate(const std::function& f); 69 | 70 | // set args return value to object heirarchy representing tree 71 | // suitable for the calling JS process 72 | void V8Objectify(const v8::FunctionCallbackInfo& args); 73 | 74 | // For other datatype conversions, add an objectify function here 75 | // and a recursive helper in StackTreeNode 76 | 77 | private: 78 | bool InsertTrace(const TraceAndValue& tv); 79 | void BuildTree(); 80 | 81 | // multiple roots are possible because 82 | // not all traces start in ``main'' for example, 83 | // some may start in pthread_create() or equivalent 84 | std::unique_ptr hide_; 85 | std::vector roots_; 86 | std::vector traces_; 87 | double value_ = 0; // the sum total of all root aggregate values 88 | size_t node_count_ = 0; 89 | }; 90 | 91 | } // namespace memoro 92 | -------------------------------------------------------------------------------- /docs/use_case.md: -------------------------------------------------------------------------------- 1 | # Using Memoro - Runtime Library 2 | 3 | This page will explain how to use memoro once it is set-up. If you have not set-up Memoro yet, refer to [here](set_up.md). 4 | 5 | ## Memoro with a simple program 6 | 7 | The general steps with using Memoro on a simple file (e.g. `test.cpp`) are shown below. 8 | 9 | ```bash 10 | 1. /bin/clang++ -fsanitize=memoro -g -fno-omit-frame-pointer test.cpp 11 | 2. ./a.out 12 | ``` 13 | 14 | You need to pass in a flag `-fsanitize=memoro` for Memoro to work. The omitting of frame-pointers and debug mode flags are optional but recommended for Memoro to give you line numbers and better stack traces. 15 | 16 | Once you run this, you should be able to see these items appear: 17 | 1. A folder called `typefiles` - Memoro stores type information here 18 | 2. *.chunks / *.trace files - use this together with the visualizer to analyse your application 19 | 20 | **Note**: If your compiler cannot find the relevant c++ header files, you may need to include their path manually. (e.g on a OSX system, it is: 21 | `-I /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1`) 22 | 23 | ## Memoro on a larger application 24 | 25 | Now that we have tried Memoro with a simple program, it is important to consider how Memoro works with a larger application. After all, the true strength of Memoro is using it to debug large C++ applications and identify areas where memory management can be improved upon. 26 | 27 | For this guide, we will try to analyse [LevelDB](https://github.com/google/leveldb). LevelDB is a open-source fast key-value storage library written at Google. The main steps to do this are as follows: 28 | 29 | 1. Update build system to add in the abovementioned flags. C/C++ applications use build systems (CMake/AutoConf etc.) We have to trace this file and add in the flags needed for Memoro. CMake is the most popular at the moment and LevelDB uses this. We will add these lines to `CMakeLists.txt`: 30 | ```cmake 31 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=memoro") 32 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer") 33 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") 34 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/") 35 | ``` 36 | 37 | 2. Build LevelDB - once the CMake file is updated, we can follow their set-up guide, to build leveldb. e.g. create a dir build, cd build, run cmake and make 38 | 39 | 3. Benchmark LevelDB by running their `db_bench` program 40 | 41 | 4. Pass in *.trace and *.chunks to Memoro Visualizer (just pass in one of them and the visualizer will find the other file) 42 | 43 | The visualizer will give a flame graph of LevelDB's memory allocation as the db_bench program ran along with a detailed view. 44 | 45 | ![alt text](../assets/leveldb_graph.png "LevelDB Flame Graph") 46 | 47 | Through this, we are able to make use of Memoro to analyse heap usage for a large tool. The same technique an be applied to your C++ application or any large program. 48 | 49 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | //===-- main.js ------------------------------------------------===// 2 | // 3 | // Memoro 4 | // 5 | // This file is distributed under the MIT License. 6 | // See LICENSE for details. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This file is a part of Memoro. 11 | // Stuart Byma, EPFL. 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | const electron = require('electron'); 16 | const app = electron.app; 17 | const BrowserWindow = electron.BrowserWindow; 18 | 19 | const path = require('path'); 20 | const url = require('url'); 21 | var process = require('process'); 22 | const settings = require('electron-settings'); 23 | var commandExists = require('command-exists'); 24 | var fs = require('fs'); 25 | 26 | // Keep a global reference of the window object, if you don't, the window will 27 | // be closed automatically when the JavaScript object is garbage collected. 28 | var mainWindow; 29 | 30 | function getDefaultEditorForPlatform() { 31 | if (process.platform === "darwin") { 32 | if (commandExists('xed')) { 33 | console.log('returning xed'); 34 | return { name: "xed", cmd: "xed"} 35 | } else if (fs.existsSync('/Applications/CLion.app')) { 36 | console.log('returning xed'); 37 | return { name: "CLion", cmd: "/Applications/CLion.app/Contents/MacOS/clion" } 38 | } 39 | } else if (process.platform === "win32") { 40 | // TODO define some default crap for windows 41 | } else if (process.platform === "linux") { 42 | // TODO define some default crap for linux 43 | } 44 | return { name: "none", cmd: ""}; 45 | } 46 | 47 | function createWindow () { 48 | // Create the browser window. 49 | var mainScreen = electron.screen.getPrimaryDisplay(); 50 | mainWindow = new BrowserWindow({width: mainScreen.size.width, height: mainScreen.size.height, 51 | titleBarStyle: 'hiddenInset', 52 | icon: path.join(__dirname, 'assets/icons/icon64.png') 53 | }); 54 | 55 | // and load the index.html of the app. 56 | mainWindow.loadURL(url.format({ 57 | pathname: path.join(__dirname, 'index.html'), 58 | protocol: 'file:', 59 | slashes: true 60 | })) 61 | 62 | // Open the DevTools. 63 | // mainWindow.webContents.openDevTools() 64 | 65 | // Emitted when the window is closed. 66 | mainWindow.on('closed', function () { 67 | // Dereference the window object, usually you would store windows 68 | // in an array if your app supports multi windows, this is the time 69 | // when you should delete the corresponding element. 70 | mainWindow = null 71 | }) 72 | 73 | console.log(process.pid); 74 | require('./js/menu/mainmenu'); 75 | //mainWindow.openDevTools() 76 | 77 | if (!settings.has('editor')) { 78 | settings.set('editor', getDefaultEditorForPlatform()) 79 | } 80 | } 81 | 82 | // This method will be called when Electron has finished 83 | // initialization and is ready to create browser windows. 84 | // Some APIs can only be used after this event occurs. 85 | app.on('ready', createWindow); 86 | 87 | // Quit when all windows are closed. 88 | app.on('window-all-closed', function () { 89 | // On OS X it is common for applications and their menu bar 90 | // to stay active until the user quits explicitly with Cmd + Q 91 | if (process.platform !== 'darwin') { 92 | app.quit() 93 | } 94 | }); 95 | 96 | app.on('activate', function () { 97 | // On OS X it's common to re-create a window in the app when the 98 | // dock icon is clicked and there are no other windows open. 99 | if (mainWindow === null) { 100 | createWindow() 101 | } 102 | }) 103 | 104 | // In this file you can include the rest of your app's specific main process 105 | // code. You can also put them in separate files and require them here. 106 | -------------------------------------------------------------------------------- /cpp/memoro.h: -------------------------------------------------------------------------------- 1 | 2 | //===-- memoro.h ------------------------------------------------===// 3 | // 4 | // Memoro 5 | // 6 | // This file is distributed under the MIT License. 7 | // See LICENSE for details. 8 | // 9 | //===----------------------------------------------------------------------===// 10 | // 11 | // This file is a part of Memoro. 12 | // Stuart Byma, EPFL. 13 | // 14 | //===----------------------------------------------------------------------===// 15 | 16 | #pragma once 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | 25 | // honestly this entire API and inteface to JS needs to be 26 | // redesigned. 27 | 28 | namespace memoro { 29 | 30 | enum LoadingState : uint32_t { 31 | LoadData = 0, 32 | Parsing, 33 | Sorting, 34 | Building, 35 | Aggregating, 36 | Done 37 | }; 38 | 39 | struct __attribute__((packed)) Chunk { 40 | uint8_t num_reads = 0; 41 | uint8_t num_writes = 0; 42 | uint8_t allocated = 0; 43 | uint8_t multi_thread = 0; 44 | uint32_t stack_index = 0; // used for file writer 45 | uint64_t size = 0; 46 | uint64_t timestamp_start = 0; 47 | uint64_t timestamp_end = 0; 48 | uint64_t timestamp_first_access = 0; 49 | uint64_t timestamp_last_access = 0; 50 | uint64_t alloc_call_time = 0; 51 | uint32_t access_interval_low = 0; 52 | uint32_t access_interval_high = 0; 53 | }; 54 | 55 | struct TimeValue { 56 | uint64_t time; 57 | int64_t value; 58 | }; 59 | 60 | struct Trace { 61 | std::string trace; 62 | std::string type; 63 | bool filtered = false; 64 | bool type_filtered = false; 65 | uint64_t max_aggregate = 0; 66 | std::vector chunks; 67 | std::vector aggregate; 68 | uint64_t inefficiencies = 0; 69 | uint64_t alloc_time_total = 0; 70 | 71 | float usage_score; 72 | float lifetime_score; 73 | float useful_lifetime_score; 74 | }; 75 | 76 | // API will return a list of these to Node layer 77 | // changing filters will invalidate the indices 78 | struct TraceValue { 79 | std::string* trace; 80 | std::string* type; 81 | int trace_index; 82 | int chunk_index; 83 | int num_chunks; 84 | uint64_t alloc_time_total; 85 | uint64_t max_aggregate; 86 | 87 | float usage_score; 88 | float lifetime_score; 89 | float useful_lifetime_score; 90 | }; 91 | 92 | // set the current dataset file, returns dataset stats (num traces, min/max 93 | // times) 94 | bool SetDataset(const std::string& file_path, const std::string& trace_file, 95 | const std::string& chunk_file, std::string& msg); 96 | 97 | // add a timestamp interval filter 98 | void SetMinMaxTime(uint64_t max, uint64_t min); 99 | void RemoveMinMaxTime(); 100 | 101 | // add a trace keyword filter, filtering traces not containing the keyword 102 | // returns new number of active traces 103 | void SetTraceKeyword(const std::string& keyword); 104 | void RemoveTraceKeyword(const std::string& keyword); 105 | void TraceFilterReset(); 106 | 107 | void SetTypeKeyword(const std::string& keyword); 108 | void TypeFilterReset(); 109 | 110 | // fill times and values with 1000 aggregate data points from whole dataset 111 | // respects filters 112 | void AggregateAll(std::vector& values); 113 | 114 | // aggregate chunks of a single stacktrace 115 | void AggregateTrace(std::vector& values, int trace_index); 116 | 117 | // get the specified number of chunks starting at the specified indexes 118 | // respects filters, returns empty if all filtered 119 | void TraceChunks(std::vector& chunks, int trace_index, int chunk_index, 120 | int num_chunks); 121 | 122 | // build list of traces 123 | void Traces(std::vector& traces); 124 | 125 | void SetFilterMinMax(uint64_t min, uint64_t max); 126 | void FilterMinMaxReset(); 127 | 128 | uint64_t MaxTime(); 129 | uint64_t MinTime(); 130 | uint64_t FilterMaxTime(); 131 | uint64_t FilterMinTime(); 132 | uint64_t GlobalAllocTime(); 133 | 134 | uint64_t Inefficiencies(int trace_index); 135 | 136 | uint64_t MaxAggregate(); 137 | 138 | void StackTreeObject(const v8::FunctionCallbackInfo& args); 139 | void StackTreeAggregate(std::function f); 140 | 141 | } // namespace memoro 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Memoro 2 | 3 | Memoro is a highly detailed heap profiler. 4 | 5 | Memoro not only shows you where and when your program makes heap allocations, but will show you _how_ your program actually used that memory. 6 | 7 | Memoro collects detailed information on accesses to the heap, including reads and writes to memory and when they happen, to give you an idea of how efficiently your program uses heap memory. 8 | 9 | Memoro includes a visualizer application that distills all this information into scores and indicators to help you pinpoint problem areas. 10 | 11 | ![alt text](assets/memoro_screen.png) 12 | 13 | For more detailed information about how Memoro works, see [here](https://github.com/epfl-vlsc/memoro/blob/master/docs/memoro_ismm.pdf) 14 | 15 | # Build & Install 16 | 17 | The below steps outline a quick-start guide to using Memoro. For detailed set-up instructions refer to [here](docs/set_up.md). For a guide on using Memoro, refer [here](docs/use_case.md). 18 | 19 | ## Building the Instrumented compiler 20 | 21 | First, you will need to build a local version of LLVM/Clang so you can compile your code with Memoro instrumentation. 22 | Pre-built releases are not yet available, but if enough people bug me perhaps I will host here. 23 | 24 | Follow the LLVM/Clang build instructions [here](https://releases.llvm.org/4.0.1/docs/GettingStarted.html), but use the specific repositories listed below. 25 | Memoro is not yet in the LLVM/Clang dev branch. 26 | 27 | It's recommended to use the git mirror repositories instead of SVN. 28 | For the main LLVM repo, the Clang repo, and the CompilerRT repo, use the Memoro versions: 29 | 30 | [LLVM](https://github.com/epfl-vlsc/llvm) 31 | 32 | [Clang](https://github.com/epfl-vlsc/clang) 33 | 34 | [CompilerRT](https://github.com/epfl-vlsc/compiler-rt) 35 | 36 | These repos should default to branch memoro_80. 37 | 38 | ## Building the Visusalizer C++ lib 39 | 40 | Clone this repo with `git clone git@github.com:epfl-vlsc/memoro.git` 41 | 42 | Enter the directory. 43 | 44 | `$ cd memoro` 45 | 46 | Run `npm install` to make sure all JS dependencies are present. 47 | You will then need to build the C++ data processing addon. 48 | To do this, you may first need to install `node-gyp` (`npm install -g node-gyp`) 49 | Then, 50 | 51 | ```bash 52 | cd cpp 53 | node-gyp build --release --target=1.7.9 --arch=x64 --dist-url=https://atom.io/download/electron 54 | cd .. 55 | ``` 56 | 57 | The `target` is the electon version that must match that used to run the main app. 58 | Adjust this if you need. 59 | Also adjust `arch` if you need, however I have not yet tested on non-\*nix x64 systems. 60 | There is a Makefile as well that should work for most users. 61 | 62 | Obviously, you will need a C++ compiler installed for node-gyp to use. 63 | 64 | ## Running 65 | 66 | ### Instrument and Run your Software 67 | 68 | First, build the software you want to profile using the LLVM/Clang you have built earlier. 69 | Add the compiler flag `-fsanitize=memoro` to add instrumentation and optionally `-fno-omit-frame-pointer` to get nice stack traces. 70 | If you have a separate linking step, this may also need `-fsanitize=memoro` to ensure the Memoro/Sanitizer runtime is linked. 71 | It is recommended to use the LLVM symbolizer for stack traces, set env variable `MEMORO_SYMBOLIZER_PATH` to point to where `llvm-symbolizer` resides. 72 | Or just have it in your `PATH`. 73 | 74 | Run your program. After completion, the Memoro runtime will generate two files, `*.trace` and `*.chunks`. These can be opened and viewed using the visualizer. 75 | 76 | ### Use the Memoro Visualizer 77 | 78 | After building the C++ addon, the app can be run directly from the repo directory 79 | 80 | ``` 81 | electron . 82 | ``` 83 | 84 | File-\>Open or Cmd/Ctrl-O to open, navigate to and select either the trace or chunk file. 85 | 86 | Happy hunting for heap problems :-) 87 | 88 | 89 | ### Installing the Visualizer 90 | 91 | Use `electron-packager` to gather and export all code and assets into an application package. 92 | 93 | ```bash 94 | electron-packager . --overwrite --platform= --arch=x64 --electron-version=1.7.9 --icon=assets/icons/icon64.icns --prune=true --out=release-builds 95 | ``` 96 | 97 | Again, adjust the platform, arch, and electron version if necessary. 98 | 99 | # Contributing 100 | 101 | Contributions are more than welcome. 102 | Please submit a pull request or get in touch. 103 | 104 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | //===-- index.js ------------------------------------------------===// 3 | // 4 | // Memoro 5 | // 6 | // This file is distributed under the MIT License. 7 | // See LICENSE for details. 8 | // 9 | //===----------------------------------------------------------------------===// 10 | // 11 | // This file is a part of Memoro. 12 | // Stuart Byma, EPFL. 13 | // 14 | //===----------------------------------------------------------------------===// 15 | 16 | var remote = require("electron").remote; 17 | const {ipcRenderer} = require('electron') 18 | const settings = require('electron').remote.require('electron-settings'); 19 | //var Menu = remote.require('menu'); 20 | 21 | var chunk_graph = require("./js/chunkgraph"); 22 | 23 | function initApp() { 24 | addAppEventListeners(); 25 | //chunk_graph.updateData("hplgst.json"); 26 | /* document.getElementById("filter-form") 27 | .addEventListener("keyup", function(event) { 28 | event.preventDefault(); 29 | if (event.keyCode === 13) { 30 | document.querySelector("#filter-button").click(); 31 | } 32 | });*/ 33 | 34 | // add div 35 | var element = document.querySelector("#overlay"); 36 | element.style.visibility = "visible"; 37 | 38 | if (!settings.has('theme')) { 39 | // a default 40 | console.log("did not have a theme in settings"); 41 | settings.set('theme', 'light') 42 | } 43 | if (settings.get('theme') === 'light') { 44 | $('head link#bootstrapSheet').attr('href', 'css/light.css'); 45 | $('head link#colorSheet').attr('href', 'css/chunk_graph_light.css'); 46 | } else { 47 | $('head link#bootstrapSheet').attr('href', 'css/slate.css'); 48 | $('head link#colorSheet').attr('href', 'css/chunk_graph_dark.css'); 49 | } 50 | } 51 | 52 | function resetTimeClick() { 53 | chunk_graph.resetTimeClick(); 54 | } 55 | 56 | function chunkScroll() { 57 | chunk_graph.chunkScroll(); 58 | } 59 | 60 | function traceScroll() { 61 | chunk_graph.traceScroll(); 62 | } 63 | 64 | function stackFilterClick() { 65 | chunk_graph.stackFilterClick(); 66 | } 67 | 68 | function stackFilterResetClick() { 69 | chunk_graph.stackFilterResetClick(); 70 | } 71 | 72 | function typeFilterClick() { 73 | chunk_graph.typeFilterClick(); 74 | } 75 | 76 | function filterExecuteClick() { 77 | chunk_graph.filterExecuteClick(); 78 | } 79 | function typeFilterResetClick() { 80 | chunk_graph.typeFilterResetClick(); 81 | } 82 | 83 | function filterHelpClick() { 84 | chunk_graph.showFilterHelp(); 85 | } 86 | 87 | function fgAllocationsClick() { 88 | console.log("allocations click") 89 | chunk_graph.setFlameGraphNumAllocs(); 90 | } 91 | 92 | function fgBytesTimeClick() { 93 | console.log("bytes time click") 94 | chunk_graph.setFlameGraphBytesTime(); 95 | } 96 | 97 | function fgBytesTotalClick() { 98 | console.log("bytes total click") 99 | chunk_graph.setFlameGraphBytesTotal(); 100 | } 101 | 102 | function fgMostActiveClick() { 103 | console.log("most active click") 104 | } 105 | 106 | function fgHelpClick() { 107 | console.log("fg help click") 108 | chunk_graph.flameGraphHelp(); 109 | } 110 | 111 | function globalInfoHelpClick() { 112 | console.log("fg help click") 113 | chunk_graph.globalInfoHelp(); 114 | } 115 | 116 | function tabSwitchClick() { 117 | 118 | console.log("tab switch click"); 119 | chunk_graph.tabSwitchClick(); 120 | } 121 | 122 | function traceSort(pred) { 123 | chunk_graph.traceSort(pred); 124 | } 125 | 126 | function addAppEventListeners() { 127 | 128 | ipcRenderer.on('open_file', function(emitter, file_path) { 129 | console.log(file_path[0]); 130 | chunk_graph.updateData(file_path[0]); 131 | }); 132 | 133 | ipcRenderer.on('theme_light', function(emitter) { 134 | console.log("changing theme to light!!"); 135 | $('head link#bootstrapSheet').attr('href', 'css/light.css'); 136 | $('head link#colorSheet').attr('href', 'css/chunk_graph_light.css'); 137 | settings.set('theme', 'light') 138 | }); 139 | 140 | ipcRenderer.on('theme_dark', function(emitter) { 141 | console.log("changing theme to dark!!"); 142 | $('head link#bootstrapSheet').attr('href', 'css/slate.css'); 143 | $('head link#colorSheet').attr('href', 'css/chunk_graph_dark.css'); 144 | settings.set('theme', 'dark') 145 | }); 146 | } 147 | -------------------------------------------------------------------------------- /js/menu/mainmenu.js: -------------------------------------------------------------------------------- 1 | const {Menu, BrowserWindow} = require('electron') 2 | const electron = require('electron') 3 | const {dialog} = require('electron') 4 | var ipc = require('ipc') 5 | const app = electron.app 6 | 7 | const template = [ 8 | { 9 | label: 'File', 10 | submenu: [ 11 | { 12 | label: 'Open', 13 | accelerator: 'CmdOrCtrl+O', 14 | click: function() { 15 | var properties = ['openDirectory'], 16 | parentWindow = (process.platform == 'darwin') ? null : BrowserWindow.getFocusedWindow(); 17 | 18 | var window = BrowserWindow.getFocusedWindow(); 19 | dialog.showOpenDialog(properties, function(f) { 20 | if (f) 21 | window.webContents.send('open_file', f); 22 | else 23 | console.log("undefined not sending ipc"); 24 | }); 25 | } 26 | } 27 | ] 28 | }, 29 | { 30 | label: 'Edit', 31 | submenu: [ 32 | { 33 | role: 'undo' 34 | }, 35 | { 36 | role: 'redo' 37 | }, 38 | { 39 | type: 'separator' 40 | }, 41 | { 42 | role: 'cut' 43 | }, 44 | { 45 | role: 'copy' 46 | }, 47 | { 48 | role: 'paste' 49 | }, 50 | { 51 | role: 'pasteandmatchstyle' 52 | }, 53 | { 54 | role: 'delete' 55 | }, 56 | { 57 | role: 'selectall' 58 | } 59 | ] 60 | }, 61 | { 62 | label: 'View', 63 | submenu: [ 64 | { 65 | label: 'Reload', 66 | accelerator: 'CmdOrCtrl+R', 67 | click: function (item, focusedWindow) { 68 | if (focusedWindow) focusedWindow.reload() 69 | } 70 | }, 71 | { 72 | label: 'Toggle Developer Tools', 73 | accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I', 74 | click: function (item, focusedWindow) { 75 | if (focusedWindow) focusedWindow.webContents.toggleDevTools() 76 | } 77 | }, 78 | { 79 | type: 'separator' 80 | }, 81 | { 82 | role: 'resetzoom' 83 | }, 84 | { 85 | role: 'zoomin' 86 | }, 87 | { 88 | role: 'zoomout' 89 | }, 90 | { 91 | type: 'separator' 92 | }, 93 | { 94 | role: 'togglefullscreen' 95 | }, 96 | { 97 | label: 'Theme', 98 | submenu: [ 99 | { 100 | label: 'Light', 101 | click: function() { 102 | var window = BrowserWindow.getFocusedWindow(); 103 | window.webContents.send('theme_light'); 104 | } 105 | }, 106 | { 107 | label: 'Dark', 108 | click: function() { 109 | var window = BrowserWindow.getFocusedWindow(); 110 | window.webContents.send('theme_dark'); 111 | } 112 | } 113 | ] 114 | } 115 | ] 116 | }, 117 | { 118 | role: 'window', 119 | submenu: [ 120 | { 121 | role: 'minimize' 122 | }, 123 | { 124 | role: 'close' 125 | } 126 | ] 127 | }, 128 | { 129 | role: 'help', 130 | submenu: [ 131 | { 132 | label: 'Learn More', 133 | click: function() { require('electron').shell.openExternal('http://electron.atom.io') } 134 | } 135 | ] 136 | } 137 | ] 138 | 139 | if (process.platform === 'darwin') { 140 | const name = app.getName() 141 | console.log("name is " + name) 142 | template.unshift({ 143 | label: name, 144 | submenu: [ 145 | { 146 | role: 'about' 147 | }, 148 | { 149 | type: 'separator' 150 | }, 151 | { 152 | role: 'services', 153 | submenu: [] 154 | }, 155 | { 156 | type: 'separator' 157 | }, 158 | { 159 | role: 'hide' 160 | }, 161 | { 162 | role: 'hideothers' 163 | }, 164 | { 165 | role: 'unhide' 166 | }, 167 | { 168 | type: 'separator' 169 | }, 170 | { 171 | role: 'quit' 172 | } 173 | ] 174 | }) 175 | // Window menu. 176 | template[4].submenu = [ 177 | { 178 | label: 'Close', 179 | accelerator: 'CmdOrCtrl+W', 180 | role: 'close' 181 | }, 182 | { 183 | label: 'Minimize', 184 | accelerator: 'CmdOrCtrl+M', 185 | role: 'minimize' 186 | }, 187 | { 188 | label: 'Zoom', 189 | role: 'zoom' 190 | }, 191 | { 192 | type: 'separator' 193 | }, 194 | { 195 | label: 'Bring All to Front', 196 | role: 'front' 197 | } 198 | ] 199 | } 200 | 201 | const menu = Menu.buildFromTemplate(template) 202 | Menu.setApplicationMenu(menu) 203 | -------------------------------------------------------------------------------- /css/chunk_graph_dark.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Memoro 3 | * Copyright 2018 EPFL VLSC 4 | * Licensed under MIT (see LICENSE) 5 | */ 6 | /*! */ 7 | 8 | #overlay { 9 | position: fixed; 10 | top: 0; 11 | left: 0; 12 | width: 100%; 13 | height: 100%; 14 | background-color: #000; 15 | filter:alpha(opacity=50); 16 | -moz-opacity:0.5; 17 | -khtml-opacity: 0.5; 18 | opacity: 0.5; 19 | z-index: 10000; 20 | visibility: hidden; 21 | text-align: center; 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | -webkit-app-region:drag; 26 | } 27 | 28 | #top-container { 29 | margin-top: 1%; 30 | } 31 | 32 | #fg-top-container { 33 | margin-top: 1%; 34 | } 35 | 36 | #title { 37 | align-items: center; 38 | display: flex; 39 | margin-bottom: 0; 40 | 41 | } 42 | #tooltip { 43 | position: absolute; 44 | text-align: center; 45 | padding: 2px; 46 | width:auto; 47 | font: 12px sans-serif; 48 | color: #dedede; 49 | background: #171717; 50 | border: 0px; 51 | border-radius: 8px; 52 | pointer-events: none; 53 | opacity: 0; 54 | } 55 | #trace { 56 | font: 12px monospace; 57 | height: 60vh; 58 | overflow-y: auto; 59 | overflow-x: hidden; 60 | word-wrap:break-word; 61 | margin-top:0!important; 62 | } 63 | 64 | .indicator-circle { 65 | stroke: white; 66 | } 67 | 68 | .stack-agg-graph { 69 | stroke: #dedede; 70 | } 71 | 72 | .stack-agg-overlay { 73 | stroke: #dedede; 74 | } 75 | 76 | 77 | #global-info { 78 | height: 30vh; 79 | overflow-y: auto; 80 | overflow-x: hidden; 81 | word-wrap:break-word; 82 | font-size: smaller; 83 | margin-top:0!important; 84 | } 85 | 86 | #global-info-fg { 87 | height: 30vh; 88 | overflow-y: auto; 89 | overflow-x: hidden; 90 | word-wrap:break-word; 91 | margin-top:0!important; 92 | } 93 | 94 | #inferences { 95 | height: 40vh; 96 | overflow-y: auto; 97 | overflow-x: hidden; 98 | word-wrap:break-word; 99 | margin-top:0!important; 100 | } 101 | 102 | #trace hr { 103 | border: none; 104 | width: 100%; 105 | margin-top: 5px; 106 | margin-bottom: 5px; 107 | border-top: 1px solid #1c1e22; 108 | } 109 | 110 | body { 111 | overflow-y: hidden; 112 | background: #272b30; 113 | } 114 | 115 | #chunks { 116 | height: 28vh; 117 | overflow-y: scroll; 118 | overflow-x: hidden; 119 | /* padding-left: 100px;*/ 120 | background-color: #353a41; 121 | background-clip:content-box; 122 | } 123 | 124 | #fg-btn { 125 | position: fixed; 126 | width: 30%; 127 | } 128 | #flamegraph-panel { 129 | height: 70vh; 130 | } 131 | 132 | #traces { 133 | height: 28vh; 134 | overflow-y: scroll; 135 | overflow-x: hidden; 136 | } 137 | 138 | #stack-sort-btn { 139 | 140 | } 141 | #chunk-axis { 142 | height: 7vh; 143 | overflow-y: scroll; 144 | overflow-x: hidden; 145 | padding-right: 20px; 146 | } 147 | 148 | #aggregate-graph { 149 | height: 17vh; 150 | } 151 | 152 | #fg-aggregate-graph { 153 | height: 17vh; 154 | } 155 | 156 | .aggregate-background { 157 | fill: #353a41 158 | } 159 | 160 | #chunks-container { 161 | overflow: hidden; 162 | } 163 | 164 | svg.selected { 165 | 166 | } 167 | 168 | .chunk_text { 169 | font-size: 7px; 170 | fill: #eeeeee 171 | } 172 | 173 | .selected-marker { 174 | fill: darkgrey; 175 | } 176 | 177 | .line { 178 | stroke: #dedede; 179 | } 180 | 181 | .firstlastbars { 182 | stroke: #65DC4C; 183 | visibility: visible; 184 | } 185 | 186 | .legend-text { 187 | fill: #dedede; 188 | } 189 | 190 | .focus-line { 191 | stroke: darkgrey; 192 | } 193 | 194 | .focus-text { 195 | fill: #DEDEDE; 196 | stroke: #DEDEDE; 197 | } 198 | 199 | .stack-text { 200 | fill: #DEDEDE; 201 | } 202 | 203 | .background1 { 204 | fill: #353a41; 205 | } 206 | 207 | .background2 { 208 | fill: #4c4c4c; 209 | } 210 | 211 | .svg_spacing { 212 | display:block; 213 | padding:0px; 214 | margin:0px; 215 | border:0px; 216 | } 217 | .svg_spacing_data { 218 | display:block; 219 | padding:0px; 220 | margin:1px; 221 | border:1px; 222 | } 223 | .svg_spacing_trace { 224 | display:block; 225 | padding:0px; 226 | margin:2px; 227 | border:2px; 228 | } 229 | 230 | .chart text { 231 | fill: orange; 232 | font: 10px sans-serif; 233 | text-anchor: end; 234 | } 235 | .axis path, 236 | .axis line { 237 | fill: none; 238 | stroke: #69737C; 239 | shape-rendering: crispEdges; 240 | } 241 | .chart line { 242 | fill: none; 243 | stroke: #DEDEDE; 244 | shape-rendering: crispEdges; 245 | } 246 | 247 | .axis text { 248 | font-family: sans-serif; 249 | font-size: 11px; 250 | /* fill: #eeeeee; 251 | color: #eeeeee;*/ 252 | fill: #DEDEDE; 253 | color: #dedede; 254 | } 255 | 256 | rect.selection { 257 | stroke : gray; 258 | stroke-dasharray: 4px; 259 | stroke-opacity : 0.5; 260 | fill : darkgrey; 261 | opacity: 0.5; 262 | } 263 | 264 | .area { 265 | fill: lightsteelblue; 266 | stroke-width: 0; 267 | opacity:0.3; 268 | } 269 | 270 | svg *::selection { 271 | background : transparent; 272 | } 273 | 274 | svg *::-moz-selection { 275 | background:transparent; 276 | } 277 | 278 | svg *::-webkit-selection { 279 | background:transparent; 280 | } 281 | 282 | .nopadding { 283 | padding: 0 !important; 284 | margin: 0 !important; 285 | } 286 | 287 | ::-webkit-scrollbar { 288 | width: 8px; 289 | } 290 | 291 | /* Track */ 292 | ::-webkit-scrollbar-track-piece { 293 | background-color: transparent; 294 | -webkit-border-radius: 6px; 295 | } 296 | 297 | /* Handle */ 298 | ::-webkit-scrollbar-thumb { 299 | -webkit-border-radius: 10px; 300 | border-radius: 10px; 301 | background: #9a9b9c; 302 | /* background: #0c0d0e;*/ 303 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.5); 304 | } 305 | 306 | .spinny-load, 307 | .spinny-load:before, 308 | .spinny-load:after { 309 | background: #ffffff; 310 | /* visibility: hidden;*/ 311 | -webkit-animation: load1 1s infinite ease-in-out; 312 | animation: load1 1s infinite ease-in-out; 313 | width: 1em; 314 | height: 4em; 315 | } 316 | .spinny-load { 317 | color: #ffffff; 318 | text-indent: -9999em; 319 | margin: 5px auto; 320 | position: relative; 321 | font-size: 5px; 322 | -webkit-transform: translateZ(0); 323 | -ms-transform: translateZ(0); 324 | transform: translateZ(0); 325 | -webkit-animation-delay: -0.16s; 326 | animation-delay: -0.16s; 327 | } 328 | .spinny-load:before, 329 | .spinny-load:after { 330 | position: absolute; 331 | top: 0; 332 | content: ''; 333 | } 334 | .spinny-load:before { 335 | left: -1.5em; 336 | -webkit-animation-delay: -0.32s; 337 | animation-delay: -0.32s; 338 | } 339 | .spinny-load:after { 340 | left: 1.5em; 341 | } 342 | @-webkit-keyframes load1 { 343 | 0%, 344 | 80%, 345 | 100% { 346 | box-shadow: 0 0; 347 | height: 4em; 348 | } 349 | 40% { 350 | box-shadow: 0 -2em; 351 | height: 5em; 352 | } 353 | } 354 | @keyframes load1 { 355 | 0%, 356 | 80%, 357 | 100% { 358 | box-shadow: 0 0; 359 | height: 4em; 360 | } 361 | 40% { 362 | box-shadow: 0 -2em; 363 | height: 5em; 364 | } 365 | } 366 | 367 | .nav-tabs > li, .nav-pills > li { 368 | float:none; 369 | display:inline-block; 370 | *display:inline; /* ie7 fix */ 371 | zoom:1; /* hasLayout ie7 trigger */ 372 | } 373 | 374 | .nav-tabs, .nav-pills { 375 | text-align:center; 376 | } -------------------------------------------------------------------------------- /css/chunk_graph_light.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Memoro 3 | * Copyright 2018 EPFL VLSC 4 | * Licensed under MIT (see LICENSE) 5 | */ 6 | /*! */ 7 | 8 | #overlay { 9 | position: fixed; 10 | top: 0; 11 | left: 0; 12 | width: 100%; 13 | height: 100%; 14 | background-color: #000; 15 | filter:alpha(opacity=50); 16 | -moz-opacity:0.5; 17 | -khtml-opacity: 0.5; 18 | opacity: 0.5; 19 | z-index: 10000; 20 | visibility: hidden; 21 | text-align: center; 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | } 26 | 27 | #top-container { 28 | margin-top: 1%; 29 | } 30 | 31 | #fg-top-container { 32 | margin-top: 1%; 33 | } 34 | 35 | #title { 36 | align-items: center; 37 | display: flex; 38 | margin-bottom: 0; 39 | 40 | } 41 | #tooltip { 42 | position: absolute; 43 | text-align: center; 44 | padding: 2px; 45 | width:auto; 46 | font: 12px sans-serif; 47 | color: #0c0d0e; 48 | background: #cfcfcf; 49 | border: 0px; 50 | border-radius: 8px; 51 | pointer-events: none; 52 | opacity: 0; 53 | } 54 | #trace { 55 | font: 12px monospace; 56 | height: 60vh; 57 | overflow-y: auto; 58 | overflow-x: hidden; 59 | word-wrap:break-word; 60 | margin-top:0!important; 61 | } 62 | 63 | .stack-agg-graph { 64 | stroke: #171717; 65 | } 66 | 67 | .stack-agg-overlay { 68 | stroke: #171717; 69 | } 70 | 71 | 72 | #global-info { 73 | height: 30vh; 74 | overflow-y: auto; 75 | overflow-x: hidden; 76 | word-wrap:break-word; 77 | font-size: smaller; 78 | margin-top:0!important; 79 | } 80 | 81 | #global-info-fg { 82 | height: 30vh; 83 | overflow-y: auto; 84 | overflow-x: hidden; 85 | word-wrap:break-word; 86 | margin-top:0!important; 87 | } 88 | 89 | #inferences { 90 | height: 40vh; 91 | overflow-y: auto; 92 | overflow-x: hidden; 93 | word-wrap:break-word; 94 | margin-top:0!important; 95 | } 96 | 97 | #trace hr { 98 | border: none; 99 | width: 100%; 100 | margin-top: 5px; 101 | margin-bottom: 5px; 102 | border-top: 1px solid #1c1e22; 103 | } 104 | 105 | body { 106 | overflow-y: hidden; 107 | background: #ffffff; 108 | } 109 | 110 | #chunks { 111 | height: 28vh; 112 | overflow-y: scroll; 113 | overflow-x: hidden; 114 | /* padding-left: 100px;*/ 115 | /* background-color: #353a41;*/ 116 | background-color: #ececec; 117 | background-clip:content-box; 118 | } 119 | 120 | #fg-btn { 121 | position: fixed; 122 | width: 30%; 123 | } 124 | #flamegraph-panel { 125 | height: 70vh; 126 | } 127 | 128 | #traces { 129 | height: 28vh; 130 | overflow-y: scroll; 131 | overflow-x: hidden; 132 | } 133 | 134 | #stack-sort-btn { 135 | 136 | } 137 | #chunk-axis { 138 | height: 7vh; 139 | overflow-y: scroll; 140 | overflow-x: hidden; 141 | padding-right: 20px; 142 | } 143 | 144 | #aggregate-graph { 145 | height: 17vh; 146 | } 147 | 148 | #fg-aggregate-graph { 149 | height: 17vh; 150 | } 151 | 152 | .aggregate-background { 153 | fill: #ffffff; 154 | /*fille: #353a41*/ 155 | } 156 | 157 | .indicator-circle { 158 | stroke: black; 159 | } 160 | 161 | #chunks-container { 162 | overflow: hidden; 163 | } 164 | 165 | svg.selected { 166 | 167 | } 168 | 169 | .chunk_text { 170 | font-size: 7px; 171 | fill: #eeeeee 172 | } 173 | 174 | .selected-marker { 175 | fill: darkgrey; 176 | } 177 | 178 | .line { 179 | stroke: black; 180 | } 181 | 182 | .firstlastbars { 183 | /* stroke: #65DC4C;*/ 184 | stroke: #000000; 185 | visibility: visible; 186 | } 187 | 188 | .legend-text { 189 | fill: #000000; 190 | } 191 | 192 | .focus-line { 193 | stroke: darkgrey; 194 | } 195 | 196 | .focus-text { 197 | fill: #000000; 198 | stroke: #000000; 199 | } 200 | 201 | 202 | .background1 { 203 | /* fill: #353a41;*/ 204 | fill: #f3f3f3; 205 | } 206 | 207 | .background2 { 208 | fill: #e6e6e6; 209 | /* fill: #4c4c4c;*/ 210 | } 211 | 212 | .svg_spacing { 213 | display:block; 214 | padding:0px; 215 | margin:0px; 216 | border:0px; 217 | } 218 | .svg_spacing_data { 219 | display:block; 220 | padding:0px; 221 | margin:1px; 222 | border:1px; 223 | } 224 | .svg_spacing_trace { 225 | display:block; 226 | padding:0px; 227 | margin:2px; 228 | border:2px; 229 | } 230 | 231 | .chart text { 232 | fill: orange; 233 | font: 10px sans-serif; 234 | text-anchor: end; 235 | } 236 | .axis path, 237 | .axis line { 238 | fill: none; 239 | stroke: #69737C; 240 | shape-rendering: crispEdges; 241 | } 242 | .chart line { 243 | fill: none; 244 | stroke: black; 245 | shape-rendering: crispEdges; 246 | } 247 | 248 | .axis text { 249 | font-family: sans-serif; 250 | font-size: 11px; 251 | /* fill: #eeeeee; 252 | color: #eeeeee;*/ 253 | fill: #272727; 254 | color: #272727; 255 | } 256 | 257 | rect.selection { 258 | stroke : gray; 259 | stroke-dasharray: 4px; 260 | stroke-opacity : 0.5; 261 | fill : darkgrey; 262 | opacity: 0.5; 263 | } 264 | 265 | .area { 266 | fill: lightsteelblue; 267 | stroke-width: 0; 268 | opacity:0.3; 269 | } 270 | 271 | svg *::selection { 272 | background : transparent; 273 | } 274 | 275 | svg *::-moz-selection { 276 | background:transparent; 277 | } 278 | 279 | svg *::-webkit-selection { 280 | background:transparent; 281 | } 282 | 283 | .nopadding { 284 | padding: 0 !important; 285 | margin: 0 !important; 286 | } 287 | 288 | ::-webkit-scrollbar { 289 | width: 8px; 290 | } 291 | 292 | /* Track */ 293 | ::-webkit-scrollbar-track-piece { 294 | background-color: transparent; 295 | -webkit-border-radius: 6px; 296 | } 297 | 298 | /* Handle */ 299 | ::-webkit-scrollbar-thumb { 300 | -webkit-border-radius: 10px; 301 | border-radius: 10px; 302 | background: #9a9b9c; 303 | /* background: #0c0d0e;*/ 304 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.5); 305 | } 306 | 307 | .spinny-load, 308 | .spinny-load:before, 309 | .spinny-load:after { 310 | background: #ffffff; 311 | /* visibility: hidden;*/ 312 | -webkit-animation: load1 1s infinite ease-in-out; 313 | animation: load1 1s infinite ease-in-out; 314 | width: 1em; 315 | height: 4em; 316 | } 317 | .spinny-load { 318 | color: #ffffff; 319 | text-indent: -9999em; 320 | margin: 5px auto; 321 | position: relative; 322 | font-size: 5px; 323 | -webkit-transform: translateZ(0); 324 | -ms-transform: translateZ(0); 325 | transform: translateZ(0); 326 | -webkit-animation-delay: -0.16s; 327 | animation-delay: -0.16s; 328 | } 329 | .spinny-load:before, 330 | .spinny-load:after { 331 | position: absolute; 332 | top: 0; 333 | content: ''; 334 | } 335 | .spinny-load:before { 336 | left: -1.5em; 337 | -webkit-animation-delay: -0.32s; 338 | animation-delay: -0.32s; 339 | } 340 | .spinny-load:after { 341 | left: 1.5em; 342 | } 343 | @-webkit-keyframes load1 { 344 | 0%, 345 | 80%, 346 | 100% { 347 | box-shadow: 0 0; 348 | height: 4em; 349 | } 350 | 40% { 351 | box-shadow: 0 -2em; 352 | height: 5em; 353 | } 354 | } 355 | @keyframes load1 { 356 | 0%, 357 | 80%, 358 | 100% { 359 | box-shadow: 0 0; 360 | height: 4em; 361 | } 362 | 40% { 363 | box-shadow: 0 -2em; 364 | height: 5em; 365 | } 366 | } 367 | 368 | .nav-tabs > li, .nav-pills > li { 369 | float:none; 370 | display:inline-block; 371 | *display:inline; /* ie7 fix */ 372 | zoom:1; /* hasLayout ie7 trigger */ 373 | } 374 | 375 | .nav-tabs, .nav-pills { 376 | text-align:center; 377 | } -------------------------------------------------------------------------------- /cpp/stacktree.cc: -------------------------------------------------------------------------------- 1 | //===-- stacktree.h ------------------------------------------------===// 2 | // 3 | // Memoro 4 | // 5 | // This file is distributed under the MIT License. 6 | // See LICENSE for details. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This file is a part of Memoro. 11 | // Stuart Byma, EPFL. 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | #include "stacktree.h" 16 | #include 17 | #include 18 | #include 19 | 20 | namespace memoro { 21 | 22 | using namespace std; 23 | using namespace v8; 24 | 25 | struct isolatedKeys { 26 | Local kName, kProcess, kValue; 27 | Local kChildren; 28 | Local kLifetime, kUsage, kUsefulLifetime; 29 | }; 30 | 31 | bool StackTreeNode::Insert(const TraceAndValue& tv, NameIDs::const_iterator pos, 32 | const NameIDs& name_ids) { 33 | // auto next = pos+1; 34 | bool ret = false; 35 | if (pos == name_ids.end()) { 36 | // its the last one and will have no children 37 | // e.g. this is a malloc/new call 38 | trace_ = tv.trace; 39 | value_ = tv.value; 40 | ret = true; 41 | } else { 42 | value_ += tv.value; 43 | 44 | auto it = find_if(children_.begin(), children_.end(), 45 | [pos](const StackTreeNode& a) { 46 | return a.name_ == pos->first && a.id_ == pos->second; 47 | }); 48 | 49 | if (it != children_.end()) { 50 | // exists, advance 51 | ret = it->Insert(tv, pos + 1, name_ids); 52 | } else { 53 | // create new 54 | children_.emplace_back(pos->second, pos->first, nullptr); 55 | ret = children_.back().Insert(tv, pos + 1, name_ids); 56 | } 57 | } 58 | return ret; 59 | } 60 | 61 | void StackTreeNode::Objectify(Isolate* isolate, Local& obj, const isolatedKeys& keys) const { 62 | // put myself in this object 63 | obj->Set(keys.kName, 64 | String::NewFromUtf8(isolate, name_.c_str())); 65 | obj->Set(keys.kValue, 66 | Number::New(isolate, value_)); 67 | 68 | if (trace_ != nullptr) { 69 | obj->Set(keys.kLifetime, 70 | Number::New(isolate, trace_->lifetime_score)); 71 | obj->Set(keys.kUsage, 72 | Number::New(isolate, trace_->usage_score)); 73 | obj->Set(keys.kUsefulLifetime, 74 | Number::New(isolate, trace_->useful_lifetime_score)); 75 | return; 76 | } 77 | 78 | Local children = Array::New(isolate); 79 | 80 | for (size_t i = 0; i < children_.size(); i++) { 81 | auto& child = children_[i]; 82 | Local child_obj = Object::New(isolate); 83 | child.Objectify(isolate, child_obj, keys); 84 | 85 | children->Set(i, child_obj); 86 | } 87 | 88 | obj->Set(keys.kChildren, children); 89 | } 90 | 91 | bool StackTreeNodeHide::Insert(const TraceAndValue& tv, NameIDs::const_iterator pos, const NameIDs& nameIds) { 92 | if (children_.size() < MAX_TRACES) { 93 | return StackTreeNode::Insert(tv, pos, nameIds); 94 | } else { 95 | if (next_ == nullptr) 96 | next_ = std::make_unique(); 97 | 98 | // Already taken care of by StackTreeNode::Insert() for if (true) { … } 99 | value_ += tv.value; 100 | 101 | return next_->Insert(tv, pos, nameIds); 102 | } 103 | } 104 | 105 | void StackTreeNodeHide::Objectify(Isolate* isolate, Local& obj, const isolatedKeys& keys) const { 106 | // put myself in this object 107 | obj->Set(keys.kName, 108 | String::NewFromUtf8(isolate, name_.c_str())); 109 | obj->Set(keys.kValue, 110 | Number::New(isolate, value_)); 111 | 112 | if (next_) { 113 | Local children = Array::New(isolate); 114 | 115 | Local next_obj = Object::New(isolate); 116 | next_->Objectify(isolate, next_obj, keys); 117 | children->Set(0, next_obj); 118 | 119 | obj->Set(keys.kChildren, children); 120 | } 121 | } 122 | 123 | bool StackTree::InsertTrace(const TraceAndValue& tv) { 124 | // assuming stacktrace of form 125 | // #1 0x10be26858 in main test.cpp:57 126 | 127 | const string& trace = tv.trace->trace; 128 | NameIDs name_ids; 129 | stringstream ss; 130 | string name; 131 | uint64_t addr = 0; 132 | 133 | auto position = find(trace.rbegin(), trace.rend(), '#'); 134 | 135 | // this is done once, so we are fine with all this 136 | // C++-y nonsense that allocates stuff :-P 137 | // #27 0x118b1a035 ()| 138 | while (position != trace.rend()) { 139 | ss.clear(); 140 | size_t pos = trace.rend() - position; // backward :-P 141 | size_t p1 = trace.find_first_of(' ', pos); 142 | size_t p2 = trace.find_first_of(' ', p1 + 1); 143 | ss << std::hex << trace.substr(p1 + 1, p2 - p1); 144 | ss >> addr; 145 | if (trace[p2 + 2] == '(') 146 | name = "unknown"; // unknown module 147 | else { 148 | p1 = trace.find_first_of(' ', p2 + 1); 149 | p2 = trace.find_first_of('|', p1 + 1); 150 | name = trace.substr(p1 + 1, p2 - p1 - 1); 151 | } 152 | 153 | name_ids.push_back(make_pair(name, addr)); 154 | 155 | position = find(position + 1, trace.rend(), '#'); 156 | } 157 | 158 | value_ += tv.value; 159 | 160 | // Cap the number of node at MAX_TRACES and hide the rest 161 | if (node_count_++ >= MAX_TRACES) { 162 | if (hide_ == nullptr) 163 | hide_ = std::make_unique(); 164 | 165 | return hide_->Insert(tv, name_ids.begin() + 1, name_ids); 166 | } 167 | 168 | // now we have a list of function name, ID (address) pairs from 169 | // `main' to malloc, so to speak ... insert into the stack tree 170 | auto& first = name_ids[0]; 171 | auto it = 172 | find_if(roots_.begin(), roots_.end(), [first](const StackTreeNode& a) { 173 | return a.name_ == first.first && a.id_ == first.second; 174 | }); 175 | 176 | if (it != roots_.end()) { 177 | // exists, proceed with insert 178 | return (*it).Insert(tv, name_ids.begin() + 1, name_ids); 179 | } else { 180 | // create new root (recall these are entry points, e.g. main, 181 | // pthread_create, etc. ) 182 | roots_.emplace_back(name_ids[0].second, name_ids[0].first, nullptr); 183 | return roots_.back().Insert(tv, name_ids.begin() + 1, name_ids); 184 | } 185 | } 186 | 187 | void StackTree::BuildTree() { 188 | value_ = 0; 189 | node_count_ = 0; 190 | roots_.clear(); 191 | hide_.reset(nullptr); 192 | for (auto it = traces_.cbegin(); it != traces_.cend(); it++) 193 | InsertTrace(*it); 194 | } 195 | 196 | void StackTree::SetTraces(std::vector& traces) { 197 | traces_.clear(); 198 | traces_.reserve(traces.size()); 199 | for (Trace& t: traces) 200 | traces_.push_back({ &t, 0.0 }); 201 | } 202 | 203 | void StackTree::V8Objectify(const v8::FunctionCallbackInfo& args) { 204 | Isolate* isolate = args.GetIsolate(); 205 | const isolatedKeys keys = { 206 | String::NewFromUtf8(isolate, "name"), 207 | String::NewFromUtf8(isolate, "process"), 208 | String::NewFromUtf8(isolate, "value"), 209 | String::NewFromUtf8(isolate, "children"), 210 | 211 | String::NewFromUtf8(isolate, "lifetime_score"), 212 | String::NewFromUtf8(isolate, "usage_score"), 213 | String::NewFromUtf8(isolate, "useful_lifetime_score"), 214 | }; 215 | 216 | Local root = Object::New(isolate); 217 | root->Set(keys.kName, keys.kProcess); 218 | root->Set(keys.kValue, Number::New(isolate, value_)); 219 | 220 | Local children = Array::New(isolate); 221 | 222 | for (size_t i = 0; i < roots_.size(); i++) { 223 | const auto& root = roots_[i]; 224 | Local child_obj = Object::New(isolate); 225 | 226 | root.Objectify(isolate, child_obj, keys); // recursively 227 | 228 | children->Set(i, child_obj); 229 | } 230 | 231 | if (hide_) { 232 | Local hide_obj = Object::New(isolate); 233 | 234 | hide_->Objectify(isolate, hide_obj, keys); // recursively 235 | 236 | children->Set(roots_.size(), hide_obj); 237 | } 238 | 239 | root->Set(keys.kChildren, children); 240 | 241 | args.GetReturnValue().Set(root); 242 | } 243 | 244 | void StackTree::Aggregate(const std::function& f) { 245 | for (auto& tv: traces_) 246 | tv.value = f(tv.trace); 247 | 248 | sort(traces_.begin(), traces_.end(), 249 | [](const TraceAndValue& a, const TraceAndValue& b) { 250 | return a.value > b.value; }); 251 | 252 | BuildTree(); 253 | } 254 | 255 | } // namespace memoro 256 | -------------------------------------------------------------------------------- /cpp/pattern.cc: -------------------------------------------------------------------------------- 1 | 2 | //===-- pattern.cc ------------------------------------------------===// 3 | // 4 | // Memoro 5 | // 6 | // This file is distributed under the MIT License. 7 | // See LICENSE for details. 8 | // 9 | //===----------------------------------------------------------------------===// 10 | // 11 | // This file is a part of Memoro. 12 | // Stuart Byma, EPFL. 13 | // 14 | //===----------------------------------------------------------------------===// 15 | 16 | #include "pattern.h" 17 | #include 18 | #include 19 | #include 20 | 21 | namespace memoro { 22 | 23 | using namespace std; 24 | /*Unused = 0x1, 25 | WriteOnly = 1 << 1, 26 | ReadOnly = 1 << 2, 27 | ShortLifetime = 1 << 3, 28 | LateFree = 1 << 4, 29 | EarlyAlloc = 1 << 5, 30 | IncreasingReallocs = 1 << 6, 31 | TopPercentile = 1 << 7*/ 32 | 33 | bool HasInefficiency(uint64_t bitvec, Inefficiency i) { return bool(bitvec & i); } 34 | 35 | float UsageScore(std::vector const& chunks) { 36 | double sum = 0; 37 | uint64_t total_bytes = 0; 38 | for (auto chunk : chunks) { 39 | if (chunk->num_writes == 0 && chunk->num_reads == 0) { 40 | continue; 41 | } 42 | sum += double(chunk->access_interval_high - chunk->access_interval_low); 43 | total_bytes += chunk->size; 44 | } 45 | if (sum == 0){ 46 | return 0; 47 | } 48 | return float(sum) / float(total_bytes); 49 | } 50 | 51 | // divide chunks into groups (regions) where region boundaries are defined by 52 | // `threshold`. If chunk N and chunk N+1 are separated by more than `threshold`, 53 | // they are in different regions. 54 | float LifetimeScore(std::vector const& chunks, uint64_t threshold) { 55 | // we avoid `memorizing' regions right now, but may add in the future 56 | // if we want to annotate in the gui 57 | double current_lifetime_sum = 0; 58 | uint32_t current_num_chunks = 0; 59 | uint64_t region_start_time = chunks[0]->timestamp_start; 60 | uint64_t region_end_time = chunks[0]->timestamp_end; 61 | double region_score_total = 0; 62 | uint32_t num_regions = 0; 63 | Chunk* prev = nullptr; 64 | for (auto chunk : chunks) { 65 | if (prev != nullptr && chunk->timestamp_start - prev->timestamp_start > threshold) { 66 | // finish this region 67 | if (prev->timestamp_end > region_end_time) { 68 | region_end_time = prev->timestamp_end; 69 | } 70 | uint64_t region_lifetime = region_end_time - region_start_time; 71 | double chunk_avg_lifetime = 72 | current_lifetime_sum / double(current_num_chunks); 73 | region_score_total += chunk_avg_lifetime / double(region_lifetime); 74 | num_regions++; 75 | 76 | // start a new one 77 | current_num_chunks = 0; 78 | current_lifetime_sum = 0; 79 | region_start_time = chunk->timestamp_start; 80 | region_end_time = chunk->timestamp_end; 81 | } 82 | current_lifetime_sum += chunk->timestamp_end - chunk->timestamp_start; 83 | current_num_chunks++; 84 | if (chunk->timestamp_end > region_end_time) { 85 | region_end_time = chunk->timestamp_end; 86 | } 87 | prev = chunk; 88 | } 89 | 90 | // finish up last region 91 | if (prev->timestamp_end > region_end_time) { 92 | region_end_time = prev->timestamp_end; 93 | } 94 | uint64_t region_lifetime = region_end_time - region_start_time; 95 | double chunk_avg_lifetime = current_lifetime_sum / double(current_num_chunks); 96 | region_score_total += chunk_avg_lifetime / double(region_lifetime); 97 | num_regions++; 98 | 99 | return region_score_total / num_regions; 100 | } 101 | 102 | float UsefulLifetimeScore(std::vector const& chunks) { 103 | double score_sum = 0; 104 | for (auto chunk : chunks) { 105 | uint64_t total_life = chunk->timestamp_end - chunk->timestamp_start; 106 | uint64_t active_life = 107 | chunk->timestamp_last_access - chunk->timestamp_first_access; 108 | score_sum += double(active_life) / double(total_life); 109 | } 110 | return score_sum / chunks.size(); 111 | } 112 | 113 | float ReallocScore(std::vector const& chunks) { 114 | uint64_t last_size = 0; 115 | unsigned int current_run = 0, longest_run = 0; 116 | for (auto chunk : chunks) { 117 | if (last_size == 0) { 118 | last_size = chunk->size; 119 | current_run++; 120 | } else { 121 | if (chunk->size >= last_size) { 122 | last_size = chunk->size; 123 | current_run++; 124 | } else { 125 | longest_run = current_run > longest_run ? current_run : longest_run; 126 | current_run = 0; 127 | last_size = chunk->size; 128 | } 129 | } 130 | } 131 | return 0.0f; 132 | } 133 | 134 | uint64_t Detect(std::vector const& chunks, const PatternParams& params) { 135 | uint64_t min_lifetime = UINT64_MAX; 136 | unsigned int total_reads = 0, total_writes = 0; 137 | bool has_early_alloc = false, has_late_free = false; 138 | bool has_multi_thread = false; 139 | bool has_low_access_coverage = false; 140 | uint64_t last_size = 0; 141 | unsigned int current_run = 0, longest_run = 0; 142 | 143 | for (auto chunk : chunks) { 144 | // min lifetime 145 | uint64_t diff = chunk->timestamp_end - chunk->timestamp_start; 146 | if (diff < min_lifetime) { 147 | min_lifetime = diff; 148 | } 149 | 150 | // total reads, writes 151 | total_reads += chunk->num_reads; 152 | total_writes += chunk->num_writes; 153 | 154 | // late free 155 | if (chunk->timestamp_first_access - chunk->timestamp_start > 156 | (chunk->timestamp_end - chunk->timestamp_start) / 2) { 157 | has_early_alloc = true; 158 | } 159 | 160 | // early alloc 161 | if (chunk->timestamp_end - chunk->timestamp_last_access > 162 | (chunk->timestamp_end - chunk->timestamp_start) / 2) { 163 | has_late_free = true; 164 | } 165 | 166 | // increasing alloc sizes 167 | if (last_size == 0) { 168 | last_size = chunk->size; 169 | current_run++; 170 | } else { 171 | if (chunk->size >= last_size) { 172 | last_size = chunk->size; 173 | current_run++; 174 | } else { 175 | longest_run = current_run > longest_run ? current_run : longest_run; 176 | current_run = 0; 177 | last_size = chunk->size; 178 | } 179 | } 180 | // multithread 181 | if (bool(chunk->multi_thread)) { 182 | has_multi_thread = true; 183 | } 184 | 185 | if (float(chunk->access_interval_high - chunk->access_interval_low) / 186 | float(chunk->size) < 187 | params.access_coverage) { 188 | has_low_access_coverage = true; 189 | } 190 | } 191 | 192 | uint64_t i = 0; 193 | if (min_lifetime <= params.short_lifetime) { 194 | i |= Inefficiency::ShortLifetime; 195 | } 196 | if (total_reads == 0 || total_writes == 0) { 197 | if (total_writes > 0) { 198 | i |= Inefficiency::WriteOnly; 199 | } else if (total_reads > 0) { 200 | i |= Inefficiency::ReadOnly; 201 | } else { 202 | i |= Inefficiency::Unused; 203 | } 204 | } 205 | 206 | if (has_early_alloc) { 207 | i |= Inefficiency::EarlyAlloc; 208 | } 209 | if (has_late_free) { 210 | i |= Inefficiency::LateFree; 211 | } 212 | 213 | if (longest_run >= params.alloc_min_run) { 214 | i |= Inefficiency::IncreasingReallocs; 215 | } 216 | if (has_multi_thread) { 217 | i |= Inefficiency::MultiThread; 218 | } 219 | if (has_low_access_coverage) { 220 | i |= Inefficiency::LowAccessCoverage; 221 | } 222 | 223 | return i; 224 | } 225 | 226 | void CalculatePercentilesChunk(std::vector& traces, 227 | const PatternParams& params) { 228 | float percentile = params.percentile; 229 | auto index = int(percentile * traces.size()); 230 | for (unsigned int i = index; i < traces.size(); i++) { 231 | traces[i].inefficiencies |= Inefficiency::TopPercentileChunks; 232 | } 233 | } 234 | 235 | void CalculatePercentilesSize(std::vector& traces, 236 | const PatternParams& params) { 237 | // we need a different sort order but cannot mutate traces like this 238 | // since it will invalidate chunk parent indexes 239 | using TVal = pair ; 240 | vector t; 241 | t.reserve(traces.size()); 242 | for (int i = 0; i < traces.size(); i++) { 243 | t.emplace_back(traces[i].max_aggregate, i); 244 | } 245 | std::sort(t.begin(), t.end(), 246 | [](TVal const& a, TVal const& b) { return a.first < b.first; }); 247 | 248 | float percentile = params.percentile; 249 | auto index = int(percentile * traces.size()); 250 | for (unsigned int i = index; i < t.size(); i++) { 251 | traces[t[i].second].inefficiencies |= Inefficiency::TopPercentileSize; 252 | } 253 | } 254 | 255 | } // namespace memoro 256 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | Memoro Visualizer 17 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 30 | 31 | 32 | 33 |
34 |

File Open...

35 |
36 |
37 |
38 |
39 | 40 | 44 |
45 |
46 |
47 |
48 |
49 |
50 |

Heap Allocation Points

51 |
52 |
53 |
54 | 58 | 65 |
66 |
67 |
68 |
69 |
70 |
71 | 72 |
73 |
74 |
75 | 76 |
77 | 78 |

Aggregate Memory Usage

79 |
80 |
81 | Reset Time 82 |
83 | 84 |
85 |
86 |
Allocation Point Stacktrace
87 |
88 |
89 | 90 |
91 |
92 | 93 | 97 |
98 | 99 | 100 | 101 | 102 |
103 | Reset Trace 104 | Reset Type 105 | 106 | 107 |
108 |
109 |
110 |
Inferences
111 |
112 |
113 |
114 |
115 |
Global 116 | 117 |
118 |
119 |
120 |
121 |
122 |
123 | 124 | 125 | 127 | 128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 | Allocations 138 | Bytes in Time 139 | Bytes Total 140 | Help 141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 | 150 |
151 |
Global 152 | 153 |
154 |
155 |
156 |
157 | 158 |
159 |
160 | 161 | 165 |
166 | 167 | 168 | 169 | 170 |
171 | Reset Trace 172 | Reset Type 173 | 174 |
175 |
176 |
177 |
178 | 195 |
196 | 197 |
198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /cpp/memoro_node.cc: -------------------------------------------------------------------------------- 1 | 2 | //===-- memoro_node.cc ------------------------------------------------===// 3 | // 4 | // Memoro 5 | // 6 | // This file is distributed under the MIT License. 7 | // See LICENSE for details. 8 | // 9 | //===----------------------------------------------------------------------===// 10 | // 11 | // This file is a part of Memoro. 12 | // Stuart Byma, EPFL. 13 | // 14 | //===----------------------------------------------------------------------===// 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include "memoro.h" 23 | #include "pattern.h" 24 | 25 | using namespace v8; 26 | using namespace memoro; 27 | 28 | // primary interface to node/JS here 29 | // technique / org lifted from https://github.com/freezer333/nodecpp-demo 30 | // more or less the interface just converts to/from in memory data structures 31 | // (memoro.h/cc) to V8 JS objects that are passed back to the JS gui layer 32 | 33 | struct LoadDatasetWork { 34 | uv_work_t request; 35 | Persistent callback; 36 | 37 | std::string dir_path; 38 | std::string trace_path; 39 | std::string chunk_path; 40 | std::string msg; 41 | bool result; 42 | }; 43 | 44 | static void LoadDatasetAsync(uv_work_t* req) { 45 | LoadDatasetWork* work = static_cast(req->data); 46 | 47 | work->result = 48 | SetDataset(work->dir_path, work->trace_path, work->chunk_path, work->msg); 49 | } 50 | 51 | static void LoadDatasetAsyncComplete(uv_work_t* req, int status) { 52 | Isolate* isolate = Isolate::GetCurrent(); 53 | 54 | // Fix for Node 4.x - thanks to 55 | // https://github.com/nwjs/blink/commit/ecda32d117aca108c44f38c8eb2cb2d0810dfdeb 56 | v8::HandleScope handleScope(isolate); 57 | 58 | LoadDatasetWork* work = static_cast(req->data); 59 | 60 | Local result = Object::New(isolate); 61 | result->Set(String::NewFromUtf8(isolate, "message"), 62 | String::NewFromUtf8(isolate, work->msg.c_str())); 63 | result->Set(String::NewFromUtf8(isolate, "result"), 64 | Boolean::New(isolate, work->result)); 65 | 66 | // set up return arguments 67 | Local argv[1] = {result}; 68 | 69 | // execute the callback 70 | Local::New(isolate, work->callback) 71 | ->Call(isolate->GetCurrentContext()->Global(), 1, argv); 72 | 73 | // Free up the persistent function callback 74 | work->callback.Reset(); 75 | delete work; 76 | } 77 | 78 | void Memoro_SetDataset(const v8::FunctionCallbackInfo& args) { 79 | Isolate* isolate = args.GetIsolate(); 80 | 81 | v8::String::Utf8Value s(args[0]); 82 | std::string dir_path(*s); 83 | 84 | v8::String::Utf8Value trace_file(args[1]); 85 | std::string trace_path(*trace_file); 86 | 87 | v8::String::Utf8Value chunk_file(args[2]); 88 | std::string chunk_path(*chunk_file); 89 | 90 | // launch in a separate thread with callback to keep the gui responsive 91 | // since this can take some time 92 | LoadDatasetWork* work = new LoadDatasetWork(); 93 | work->request.data = work; 94 | work->dir_path = dir_path; 95 | work->trace_path = trace_path; 96 | work->chunk_path = chunk_path; 97 | 98 | Local callback = Local::Cast(args[3]); 99 | work->callback.Reset(isolate, callback); 100 | 101 | uv_queue_work(uv_default_loop(), &work->request, LoadDatasetAsync, 102 | LoadDatasetAsyncComplete); 103 | 104 | args.GetReturnValue().Set(Undefined(isolate)); 105 | } 106 | 107 | void Memoro_AggregateAll(const v8::FunctionCallbackInfo& args) { 108 | Isolate* isolate = args.GetIsolate(); 109 | std::vector values; 110 | AggregateAll(values); 111 | 112 | auto kTs = String::NewFromUtf8(isolate, "ts"); 113 | auto kValue = String::NewFromUtf8(isolate, "value"); 114 | 115 | Local result_list = Array::New(isolate); 116 | for (unsigned int i = 0; i < values.size(); i++) { 117 | Local result = Object::New(isolate); 118 | result->Set(kTs, Number::New(isolate, values[i].time)); 119 | result->Set(kValue, Number::New(isolate, values[i].value)); 120 | result_list->Set(i, result); 121 | } 122 | 123 | args.GetReturnValue().Set(result_list); 124 | } 125 | 126 | void Memoro_AggregateTrace(const v8::FunctionCallbackInfo& args) { 127 | Isolate* isolate = args.GetIsolate(); 128 | static std::vector values; 129 | values.clear(); 130 | 131 | int trace_index = args[0]->NumberValue(); 132 | 133 | AggregateTrace(values, trace_index); 134 | 135 | auto kTs = String::NewFromUtf8(isolate, "ts"); 136 | auto kValue = String::NewFromUtf8(isolate, "value"); 137 | 138 | Local result_list = Array::New(isolate); 139 | for (unsigned int i = 0; i < values.size(); i++) { 140 | Local result = Object::New(isolate); 141 | result->Set(kTs, Number::New(isolate, values[i].time)); 142 | result->Set(kValue, Number::New(isolate, values[i].value)); 143 | result_list->Set(i, result); 144 | } 145 | 146 | args.GetReturnValue().Set(result_list); 147 | } 148 | 149 | void Memoro_TraceChunks(const v8::FunctionCallbackInfo& args) { 150 | Isolate* isolate = args.GetIsolate(); 151 | static std::vector chunks; 152 | chunks.clear(); 153 | 154 | int trace_index = args[0]->NumberValue(); 155 | int chunk_index = args[1]->NumberValue(); 156 | int num_chunks = args[2]->NumberValue(); 157 | 158 | TraceChunks(chunks, trace_index, chunk_index, num_chunks); 159 | 160 | auto kNumReads = String::NewFromUtf8(isolate, "num_reads"); 161 | auto kNumWrites = String::NewFromUtf8(isolate, "num_writes"); 162 | auto kSize = String::NewFromUtf8(isolate, "size"); 163 | auto kTsStart = String::NewFromUtf8(isolate, "ts_start"); 164 | auto kTsEnd = String::NewFromUtf8(isolate, "ts_end"); 165 | auto kTsFirst = String::NewFromUtf8(isolate, "ts_first"); 166 | auto kTsLast = String::NewFromUtf8(isolate, "ts_last"); 167 | auto kAllocCallTime = String::NewFromUtf8(isolate, "alloc_call_time"); 168 | auto kMultiThread = String::NewFromUtf8(isolate, "multiThread"); 169 | auto kAccessLow = String::NewFromUtf8(isolate, "access_interval_low"); 170 | auto kAccessHigh = String::NewFromUtf8(isolate, "access_interval_high"); 171 | 172 | Local result_list = Array::New(isolate); 173 | for (unsigned int i = 0; i < chunks.size(); i++) { 174 | Local result = Object::New(isolate); 175 | result->Set(kNumReads, Number::New(isolate, chunks[i]->num_reads)); 176 | result->Set(kNumWrites, Number::New(isolate, chunks[i]->num_writes)); 177 | result->Set(kSize, Number::New(isolate, chunks[i]->size)); 178 | result->Set(kTsStart, Number::New(isolate, chunks[i]->timestamp_start)); 179 | result->Set(kTsEnd, Number::New(isolate, chunks[i]->timestamp_end)); 180 | result->Set(kTsFirst, Number::New(isolate, chunks[i]->timestamp_first_access)); 181 | result->Set(kTsLast, Number::New(isolate, chunks[i]->timestamp_last_access)); 182 | result->Set(kAllocCallTime, Number::New(isolate, chunks[i]->alloc_call_time)); 183 | result->Set(kMultiThread, Boolean::New(isolate, chunks[i]->multi_thread)); 184 | result->Set(kAccessLow, Number::New(isolate, chunks[i]->access_interval_low)); 185 | result->Set(kAccessHigh, Number::New(isolate, chunks[i]->access_interval_high)); 186 | result_list->Set(i, result); 187 | } 188 | 189 | args.GetReturnValue().Set(result_list); 190 | } 191 | 192 | static std::vector traces; // just reuse this 193 | void Memoro_SortTraces(const v8::FunctionCallbackInfo& args) { 194 | traces.clear(); 195 | Traces(traces); 196 | 197 | v8::String::Utf8Value sortByV8(args[0]); 198 | std::string sortBy(*sortByV8, sortByV8.length()); 199 | 200 | if (sortBy == "bytes") { 201 | sort(traces.begin(), traces.end(), [](const TraceValue& a, const TraceValue&b) { 202 | return b.max_aggregate < a.max_aggregate; }); 203 | } else if (sortBy == "allocations") { 204 | sort(traces.begin(), traces.end(), [](const TraceValue& a, const TraceValue&b) { 205 | return b.num_chunks < a.num_chunks; }); 206 | } else if (sortBy == "usage") { 207 | sort(traces.begin(), traces.end(), [](const TraceValue& a, const TraceValue&b) { 208 | return b.usage_score < a.usage_score; }); 209 | } else if (sortBy == "lifetime") { 210 | sort(traces.begin(), traces.end(), [](const TraceValue& a, const TraceValue&b) { 211 | return b.lifetime_score < a.lifetime_score; }); 212 | } else if (sortBy == "useful_lifetime") { 213 | sort(traces.begin(), traces.end(), [](const TraceValue& a, const TraceValue&b) { 214 | return b.useful_lifetime_score < a.useful_lifetime_score; }); 215 | } 216 | } 217 | 218 | void Memoro_Traces(const v8::FunctionCallbackInfo& args) { 219 | Isolate* isolate = args.GetIsolate(); 220 | 221 | size_t offset = args[0]->IntegerValue(); 222 | size_t count = std::min((size_t)args[1]->IntegerValue(), traces.size() - offset); 223 | 224 | auto kTrace = String::NewFromUtf8(isolate, "trace"); 225 | auto kType = String::NewFromUtf8(isolate, "type"); 226 | auto kTraceIndex = String::NewFromUtf8(isolate, "trace_index"); 227 | auto kNumChunks = String::NewFromUtf8(isolate, "num_chunks"); 228 | auto kChunkIndex = String::NewFromUtf8(isolate, "chunk_index"); 229 | auto kMaxAggregate = String::NewFromUtf8(isolate, "max_aggregate"); 230 | auto kAllocTimeTotal = String::NewFromUtf8(isolate, "alloc_time_total"); 231 | auto kUsage = String::NewFromUtf8(isolate, "usage_score"); 232 | auto kLifetime = String::NewFromUtf8(isolate, "lifetime_score"); 233 | auto kUsefulLifetime = String::NewFromUtf8(isolate, "useful_lifetime_score"); 234 | 235 | Local result_list = Array::New(isolate); 236 | for (size_t i = 0; i < count; i++) { 237 | const TraceValue& t = traces[i + offset]; 238 | 239 | Local result = Object::New(isolate); 240 | result->Set(kTrace, String::NewFromUtf8(isolate, t.trace->c_str())); 241 | result->Set(kType, String::NewFromUtf8(isolate, t.type->c_str())); 242 | result->Set(kTraceIndex, Number::New(isolate, t.trace_index)); 243 | result->Set(kNumChunks, Number::New(isolate, t.num_chunks)); 244 | result->Set(kChunkIndex, Number::New(isolate, t.chunk_index)); 245 | result->Set(kMaxAggregate, Number::New(isolate, t.max_aggregate)); 246 | result->Set(kAllocTimeTotal, Number::New(isolate, t.alloc_time_total)); 247 | result->Set(kUsage, Number::New(isolate, t.usage_score)); 248 | result->Set(kLifetime, Number::New(isolate, t.lifetime_score)); 249 | result->Set(kUsefulLifetime, Number::New(isolate, t.useful_lifetime_score)); 250 | 251 | result_list->Set(i, result); 252 | } 253 | 254 | args.GetReturnValue().Set(result_list); 255 | } 256 | 257 | void Memoro_MaxTime(const v8::FunctionCallbackInfo& args) { 258 | Isolate* isolate = args.GetIsolate(); 259 | 260 | Local retval = v8::Number::New(isolate, MaxTime()); 261 | 262 | args.GetReturnValue().Set(retval); 263 | } 264 | 265 | void Memoro_MinTime(const v8::FunctionCallbackInfo& args) { 266 | Isolate* isolate = args.GetIsolate(); 267 | 268 | Local retval = v8::Number::New(isolate, MinTime()); 269 | 270 | args.GetReturnValue().Set(retval); 271 | } 272 | 273 | void Memoro_FilterMaxTime(const v8::FunctionCallbackInfo& args) { 274 | Isolate* isolate = args.GetIsolate(); 275 | 276 | Local retval = v8::Number::New(isolate, FilterMaxTime()); 277 | 278 | args.GetReturnValue().Set(retval); 279 | } 280 | 281 | void Memoro_FilterMinTime(const v8::FunctionCallbackInfo& args) { 282 | Isolate* isolate = args.GetIsolate(); 283 | 284 | Local retval = v8::Number::New(isolate, FilterMinTime()); 285 | 286 | args.GetReturnValue().Set(retval); 287 | } 288 | 289 | void Memoro_MaxAggregate(const v8::FunctionCallbackInfo& args) { 290 | Isolate* isolate = args.GetIsolate(); 291 | 292 | Local retval = v8::Number::New(isolate, MaxAggregate()); 293 | 294 | args.GetReturnValue().Set(retval); 295 | } 296 | 297 | void Memoro_GlobalAllocTime(const v8::FunctionCallbackInfo& args) { 298 | Isolate* isolate = args.GetIsolate(); 299 | 300 | Local retval = v8::Number::New(isolate, GlobalAllocTime()); 301 | 302 | args.GetReturnValue().Set(retval); 303 | } 304 | 305 | void Memoro_SetTraceKeyword(const v8::FunctionCallbackInfo& args) { 306 | v8::String::Utf8Value s(args[0]); 307 | std::string keyword(*s); 308 | 309 | SetTraceKeyword(keyword); 310 | } 311 | 312 | void Memoro_SetTypeKeyword(const v8::FunctionCallbackInfo& args) { 313 | v8::String::Utf8Value s(args[0]); 314 | std::string keyword(*s); 315 | 316 | SetTypeKeyword(keyword); 317 | } 318 | 319 | void Memoro_SetFilterMinMax(const v8::FunctionCallbackInfo& args) { 320 | uint64_t t1 = args[0]->NumberValue(); 321 | uint64_t t2 = args[1]->NumberValue(); 322 | SetFilterMinMax(t1, t2); 323 | } 324 | 325 | void Memoro_TraceFilterReset(const v8::FunctionCallbackInfo& args) { 326 | TraceFilterReset(); 327 | } 328 | 329 | void Memoro_TypeFilterReset(const v8::FunctionCallbackInfo& args) { 330 | TypeFilterReset(); 331 | } 332 | 333 | void Memoro_FilterMinMaxReset(const v8::FunctionCallbackInfo& args) { 334 | FilterMinMaxReset(); 335 | } 336 | 337 | void Memoro_Inefficiencies(const v8::FunctionCallbackInfo& args) { 338 | Isolate* isolate = args.GetIsolate(); 339 | 340 | int trace_index = args[0]->NumberValue(); 341 | uint64_t i = Inefficiencies(trace_index); 342 | 343 | // there will be more here eventually, probably 344 | Local result = Object::New(isolate); 345 | result->Set(String::NewFromUtf8(isolate, "unused"), 346 | Boolean::New(isolate, HasInefficiency(i, Inefficiency::Unused))); 347 | result->Set( 348 | String::NewFromUtf8(isolate, "write_only"), 349 | Boolean::New(isolate, HasInefficiency(i, Inefficiency::WriteOnly))); 350 | result->Set( 351 | String::NewFromUtf8(isolate, "read_only"), 352 | Boolean::New(isolate, HasInefficiency(i, Inefficiency::ReadOnly))); 353 | result->Set( 354 | String::NewFromUtf8(isolate, "short_lifetime"), 355 | Boolean::New(isolate, HasInefficiency(i, Inefficiency::ShortLifetime))); 356 | result->Set( 357 | String::NewFromUtf8(isolate, "late_free"), 358 | Boolean::New(isolate, HasInefficiency(i, Inefficiency::LateFree))); 359 | result->Set( 360 | String::NewFromUtf8(isolate, "early_alloc"), 361 | Boolean::New(isolate, HasInefficiency(i, Inefficiency::EarlyAlloc))); 362 | result->Set(String::NewFromUtf8(isolate, "increasing_allocs"), 363 | Boolean::New(isolate, HasInefficiency( 364 | i, Inefficiency::IncreasingReallocs))); 365 | result->Set(String::NewFromUtf8(isolate, "top_percentile_size"), 366 | Boolean::New(isolate, HasInefficiency( 367 | i, Inefficiency::TopPercentileSize))); 368 | result->Set(String::NewFromUtf8(isolate, "top_percentile_chunks"), 369 | Boolean::New(isolate, HasInefficiency( 370 | i, Inefficiency::TopPercentileChunks))); 371 | result->Set( 372 | String::NewFromUtf8(isolate, "multi_thread"), 373 | Boolean::New(isolate, HasInefficiency(i, Inefficiency::MultiThread))); 374 | result->Set(String::NewFromUtf8(isolate, "low_access_coverage"), 375 | Boolean::New(isolate, HasInefficiency( 376 | i, Inefficiency::LowAccessCoverage))); 377 | 378 | args.GetReturnValue().Set(result); 379 | } 380 | 381 | void Memoro_StackTree(const v8::FunctionCallbackInfo& args) { 382 | // was hoping to keep all the V8 stuff in this file, oh well .. 383 | StackTreeObject(args); 384 | } 385 | 386 | void Memoro_StackTreeByBytes(const v8::FunctionCallbackInfo& args) { 387 | uint64_t time = args[0]->NumberValue(); 388 | 389 | StackTreeAggregate([time](const Trace* t) -> double { 390 | auto it = find_if(t->aggregate.begin(), t->aggregate.end(), 391 | [time](const TimeValue& a) { return a.time >= time; }); 392 | if (it == t->aggregate.end()) { 393 | // std::cout << "found no time"; 394 | return (double)0; 395 | } 396 | // take the prev value when our point was actually greater than the selected 397 | // time 398 | if (it != t->aggregate.begin() && it->time > time) it--; 399 | // std::cout << "value is " << it->value << " at time " << it->time << " for 400 | // trace " << t->trace << std::endl; 401 | return (double)it->value; 402 | }); 403 | } 404 | 405 | void Memoro_StackTreeByBytesTotal(const v8::FunctionCallbackInfo& args) { 406 | StackTreeAggregate( 407 | [](const Trace* t) -> double { 408 | return accumulate(t->chunks.begin(), t->chunks.end(), 0.0, 409 | [](double sum, const Chunk* c) { return sum + c->size; }); 410 | }); 411 | } 412 | 413 | void Memoro_StackTreeByNumAllocs( 414 | const v8::FunctionCallbackInfo& args) { 415 | StackTreeAggregate( 416 | [](const Trace* t) -> double { return (double)t->chunks.size(); }); 417 | } 418 | 419 | void init(Handle exports, Handle module) { 420 | NODE_SET_METHOD(exports, "set_dataset", Memoro_SetDataset); 421 | NODE_SET_METHOD(exports, "aggregate_all", Memoro_AggregateAll); 422 | NODE_SET_METHOD(exports, "max_time", Memoro_MaxTime); 423 | NODE_SET_METHOD(exports, "min_time", Memoro_MinTime); 424 | NODE_SET_METHOD(exports, "filter_max_time", Memoro_FilterMaxTime); 425 | NODE_SET_METHOD(exports, "filter_min_time", Memoro_FilterMinTime); 426 | NODE_SET_METHOD(exports, "max_aggregate", Memoro_MaxAggregate); 427 | NODE_SET_METHOD(exports, "set_trace_keyword", Memoro_SetTraceKeyword); 428 | NODE_SET_METHOD(exports, "set_type_keyword", Memoro_SetTypeKeyword); 429 | NODE_SET_METHOD(exports, "sort_traces", Memoro_SortTraces); 430 | NODE_SET_METHOD(exports, "traces", Memoro_Traces); 431 | NODE_SET_METHOD(exports, "aggregate_trace", Memoro_AggregateTrace); 432 | NODE_SET_METHOD(exports, "trace_chunks", Memoro_TraceChunks); 433 | NODE_SET_METHOD(exports, "set_filter_minmax", Memoro_SetFilterMinMax); 434 | NODE_SET_METHOD(exports, "trace_filter_reset", Memoro_TraceFilterReset); 435 | NODE_SET_METHOD(exports, "type_filter_reset", Memoro_TypeFilterReset); 436 | NODE_SET_METHOD(exports, "filter_minmax_reset", Memoro_FilterMinMaxReset); 437 | NODE_SET_METHOD(exports, "inefficiencies", Memoro_Inefficiencies); 438 | NODE_SET_METHOD(exports, "global_alloc_time", Memoro_GlobalAllocTime); 439 | NODE_SET_METHOD(exports, "stacktree", Memoro_StackTree); 440 | NODE_SET_METHOD(exports, "stacktree_by_bytes", Memoro_StackTreeByBytes); 441 | NODE_SET_METHOD(exports, "stacktree_by_bytes_total", Memoro_StackTreeByBytesTotal); 442 | NODE_SET_METHOD(exports, "stacktree_by_numallocs", 443 | Memoro_StackTreeByNumAllocs); 444 | } 445 | 446 | NODE_MODULE(memoro, init) 447 | -------------------------------------------------------------------------------- /cpp/memoro.cc: -------------------------------------------------------------------------------- 1 | 2 | //===-- memoro.cc ------------------------------------------------===// 3 | // 4 | // Memoro 5 | // 6 | // This file is distributed under the MIT License. 7 | // See LICENSE for details. 8 | // 9 | //===----------------------------------------------------------------------===// 10 | // 11 | // This file is a part of Memoro. 12 | // Stuart Byma, EPFL. 13 | // 14 | //===----------------------------------------------------------------------===// 15 | 16 | #include 17 | #include "memoro.h" 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "pattern.h" 29 | #include "stacktree.h" 30 | #include 31 | #include 32 | 33 | namespace memoro { 34 | 35 | using namespace std; 36 | 37 | #define MAX_POINTS 700 38 | #define VERSION_MAJOR 0 39 | #define VERSION_MINOR 1 40 | 41 | struct __attribute__((packed)) Header { 42 | uint8_t version_major = 0; 43 | uint8_t version_minor = 1; 44 | uint8_t compression_type = 0; 45 | uint16_t segment_start = 0; 46 | uint32_t index_size = 0; 47 | }; 48 | 49 | bool operator<(const TimeValue& a, const TimeValue& b) { 50 | return a.time > b.time; 51 | } 52 | 53 | class Dataset { 54 | public: 55 | Dataset() = default; 56 | bool Reset(const string& dir_path, const string& trace_file, const string& chunk_file, 57 | string& msg) { 58 | if (chunk_ptr_ != nullptr) delete[] chunk_ptr_; 59 | traces_.clear(); 60 | min_time_ = UINT64_MAX; 61 | aggregates_.clear(); 62 | trace_filters_.clear(); 63 | global_alloc_time_ = 0; 64 | 65 | if (!InitTypeData(dir_path, msg)) { 66 | return false; 67 | } 68 | 69 | cout << "opening " << trace_file << endl; 70 | // fopen trace file, build traces array 71 | FILE* trace_fd = fopen(trace_file.c_str(), "r"); 72 | if (trace_fd == NULL) { 73 | msg = "failed to open file " + trace_file; 74 | return false; 75 | } 76 | Header header; 77 | fread(&header, sizeof(Header), 1, trace_fd); 78 | 79 | if (header.version_major != VERSION_MAJOR || 80 | header.version_minor != VERSION_MINOR) { 81 | msg = "Header version mismatch in " + trace_file + 82 | ". \ 83 | Is this a valid trace/chunk file?"; 84 | return false; 85 | } 86 | 87 | vector index; 88 | index.resize(header.index_size); 89 | fread(&index[0], 2, header.index_size, trace_fd); 90 | 91 | cout << "reading " << header.index_size << " traces" << endl; 92 | 93 | traces_.reserve(header.index_size); 94 | Trace t; 95 | vector trace_buf; // used as a resizable buffer 96 | for (unsigned int i = 0; i < header.index_size; i++) { 97 | if (index[i] > trace_buf.size()) trace_buf.resize(index[i]); 98 | 99 | fread(&trace_buf[0], index[i], 1, trace_fd); 100 | t.trace = string(&trace_buf[0], index[i]); 101 | 102 | // now i admit, that this is indeed hacky, and entirely 103 | // dependent on stack traces being produced by llvm-symbolizer 104 | // or at least ending in dir/filename.cpp:: 105 | // if/when we switch to symbolizing here, we will have more options 106 | // and more robust code 107 | size_t pos = t.trace.find_first_of("|"); 108 | size_t pos2 = t.trace.find_first_of("|", pos + 1); 109 | if (pos != string::npos && pos2 != string::npos) { 110 | pos = pos2; 111 | while (t.trace[pos] != ' ') pos--; 112 | string value = t.trace.substr(pos + 1, pos2 - pos - 1); 113 | /* cout << "got value:" << value << endl; */ 114 | /* cout << "with mapped types: " << endl; */ 115 | auto range = type_map_.equal_range(value); 116 | int position = 1000000000; // should be max_int? 117 | t.type = ""; 118 | for (auto ty = range.first; ty != range.second; ty++) { 119 | cout << "(" << ty->second.first << ", " << ty->second.second << ")\n"; 120 | if (int p = t.trace.find(ty->second.first) != string::npos) 121 | if (p < position) { 122 | position = p; 123 | t.type = ty->second.second; 124 | } 125 | } 126 | } 127 | 128 | /*auto ty = type_map_.find(value); 129 | t.type = ty == type_map_.end() ? string("") : ty->second.second;*/ 130 | traces_.push_back(t); 131 | } 132 | fclose(trace_fd); 133 | 134 | // for some reason I can't mmap the file so we open and copy ... 135 | FILE* chunk_fd = fopen(chunk_file.c_str(), "r"); 136 | // file size produced by sanitizer is buggy and adds a bunch 0 data to 137 | // end of file. not sure why yet ... 138 | // int file_size = 0; 139 | if (chunk_fd == NULL) { 140 | msg = "failed to open file " + chunk_file; 141 | return false; 142 | } /*else { 143 | fseek(chunk_fd, 0L, SEEK_END); 144 | file_size = ftell(chunk_fd); 145 | rewind(chunk_fd); 146 | }*/ 147 | 148 | fread(&header, sizeof(Header), 1, chunk_fd); 149 | if (header.version_major != VERSION_MAJOR || 150 | header.version_minor != VERSION_MINOR) { 151 | msg = "Header version mismatch in " + trace_file + 152 | ". \ 153 | Is this a valid trace/chunk file?"; 154 | return false; 155 | } 156 | 157 | cout << "reading " << header.index_size << " chunks" << endl; 158 | 159 | index.resize(header.index_size); 160 | fread(&index[0], 2, header.index_size, chunk_fd); 161 | 162 | num_chunks_ = header.index_size; 163 | // file_size = file_size - sizeof(Header) - header.index_size*2; 164 | // cout << " file size now " << file_size << endl; 165 | chunk_ptr_ = new char[num_chunks_ * sizeof(Chunk)]; 166 | fread(chunk_ptr_, num_chunks_ * sizeof(Chunk), 1, chunk_fd); 167 | 168 | // we can do this because all the fields are the same size (structs) 169 | chunks_ = (Chunk*)(chunk_ptr_); 170 | fclose(chunk_fd); 171 | cout << "done reading\n"; 172 | 173 | // sort the chunks makes bin/aggregate easier 174 | cout << "sorting chunks..." << endl; 175 | sort(chunks_, chunks_ + num_chunks_, [](Chunk& a, Chunk& b) { 176 | return a.timestamp_start < b.timestamp_start; 177 | }); 178 | 179 | // set trace structure pointers to their chunks 180 | cout << "building structures..." << endl; 181 | min_time_ = 0; 182 | for (unsigned int i = 0; i < num_chunks_; i++) { 183 | Trace& t = traces_[chunks_[i].stack_index]; 184 | t.chunks.push_back(&chunks_[i]); 185 | if (chunks_[i].timestamp_end > max_time_) 186 | max_time_ = chunks_[i].timestamp_end; 187 | } 188 | filter_min_time_ = 0; 189 | filter_max_time_ = max_time_; 190 | 191 | // populate chunk aggregate vectors 192 | cout << "aggregating traces ..." << endl; 193 | uint64_t total_alloc_time = 0; 194 | for (auto& t : traces_) { 195 | // aggregate data 196 | Aggregate(t.aggregate, t.max_aggregate, t.chunks); 197 | t.inefficiencies = Detect(t.chunks, pattern_params_); 198 | t.usage_score = UsageScore(t.chunks); 199 | t.lifetime_score = LifetimeScore( 200 | t.chunks, 201 | filter_max_time_ * 0.01f); // 1 percent lifetime for region threshold 202 | t.useful_lifetime_score = UsefulLifetimeScore(t.chunks); 203 | for (auto c : t.chunks) { 204 | total_alloc_time += c->alloc_call_time; 205 | } 206 | t.alloc_time_total = total_alloc_time; 207 | global_alloc_time_ += total_alloc_time; 208 | total_alloc_time = 0; 209 | // build the stack tree 210 | } 211 | 212 | stack_tree_.SetTraces(traces_); 213 | 214 | CalculatePercentilesChunk(traces_, pattern_params_); 215 | CalculatePercentilesSize(traces_, pattern_params_); 216 | // leave this sort order until the user changes 217 | aggregates_.reserve(num_chunks_ * 2); 218 | 219 | return true; 220 | } 221 | 222 | bool InitTypeData(const string& dir_path, string& msg) { 223 | string dir(dir_path + "typefiles/"); 224 | vector files; 225 | if (GetFiles(dir, files) != 0) { 226 | msg = "Directory " + dir + 227 | " did not contain valid type files for program\n"; 228 | cout << msg << endl; 229 | return true; 230 | } 231 | string line; 232 | ifstream infile; 233 | type_map_.clear(); 234 | for (auto& f : files) { 235 | if (f == "." || f == "..") continue; 236 | 237 | infile.open(dir + f); 238 | 239 | //.Users.byma.projects.bamtools.src.third_party.jsoncpp.json_value.cpp.types 240 | auto final_dot = f.find_last_of('.'); 241 | auto first_dot = f.find_last_of('.', final_dot - 1); 242 | first_dot = f.find_last_of('.', first_dot - 1); 243 | if (first_dot == string::npos) first_dot = 0; 244 | string module = f.substr(first_dot, final_dot); 245 | cout << "module is " << module << endl; 246 | while (getline(infile, line)) { 247 | size_t pos = line.find_first_of("|"); 248 | if (pos == string::npos) { 249 | msg = "detected incorrect formatting in line " + line + "\n"; 250 | return false; 251 | } 252 | auto location = 253 | line.substr(0, pos); // equiv to last line of stack trace 254 | // module is needed to differentiate locations in headers that may be 255 | // included in multiple files 256 | auto type_value = make_pair( 257 | module, 258 | line.substr(pos + 1, line.size() - 1)); // (module, type) pair 259 | type_map_.insert(make_pair(location, type_value)); 260 | // type_map_[line.substr(0, pos)] = std::make_pair(module, 261 | // line.substr(pos+1, line.size() - 1)); 262 | } 263 | infile.close(); 264 | } 265 | cout << "the types are: \n"; 266 | for (auto& k : type_map_) { 267 | cout << k.first << " -> " 268 | << "(" << k.second.first << ", " << k.second.second << ")" << endl; 269 | } 270 | return true; 271 | } 272 | 273 | void AggregateAll(vector& values) { 274 | // build aggregate structure 275 | // bin via sampling into times and values arrays 276 | cout << "aggregating all ..." << endl; 277 | if (aggregates_.empty()) 278 | Aggregate(aggregates_, max_aggregate_, chunks_, num_chunks_); 279 | // cout << "done, sampling ..." << endl; 280 | SampleValues(aggregates_, values); 281 | // cout << "done" << endl; 282 | } 283 | 284 | void AggregateTrace(vector& values, int trace_index) { 285 | // build aggregate structure 286 | // bin via sampling into times and values arrays 287 | Trace& t = traces_[trace_index]; 288 | // cout << "sampling trace ..." << endl; 289 | SampleValues(t.aggregate, values); 290 | // cout << "done, with " << values.size() << " values" << endl; 291 | } 292 | 293 | uint64_t MaxAggregate() { return max_aggregate_; } 294 | uint64_t MaxTime() { return max_time_; } 295 | uint64_t MinTime() { return min_time_; } 296 | uint64_t FilterMaxTime() { return filter_max_time_; } 297 | uint64_t FilterMinTime() { return filter_min_time_; } 298 | uint64_t GlobalAllocTime() { return global_alloc_time_; } 299 | 300 | void SetTraceFilter(const string & str) { 301 | for (auto& s : trace_filters_) { 302 | if (s == str) { 303 | return; 304 | } 305 | } 306 | trace_filters_.push_back(str); 307 | bool filtered = false; 308 | for (auto& trace : traces_) { 309 | if (trace.trace.find(str) == string::npos) { 310 | trace.filtered = true; 311 | filtered = true; 312 | } 313 | } 314 | if (filtered) aggregates_.clear(); 315 | } 316 | 317 | void SetTypeFilter(string const& str) { 318 | cout << "adding type filter:" << str << "|\n"; 319 | for (auto& s : type_filters_) 320 | if (s == str) return; 321 | type_filters_.push_back(str); 322 | 323 | for (auto& trace : traces_) { 324 | trace.type_filtered = true; 325 | for (auto& s : type_filters_) { 326 | if (s == trace.type) { 327 | // cout << "not filtering trace type " << trace.type << " since it 328 | // matched " << s << endl; 329 | trace.type_filtered = false; 330 | break; 331 | } 332 | } 333 | } 334 | // we assume *something* changed 335 | aggregates_.clear(); 336 | } 337 | 338 | void TraceFilterReset() { 339 | if (!trace_filters_.empty()) aggregates_.clear(); 340 | trace_filters_.clear(); 341 | for (auto& trace : traces_) trace.filtered = false; 342 | } 343 | 344 | void TypeFilterReset() { 345 | if (!type_filters_.empty()) aggregates_.clear(); 346 | type_filters_.clear(); 347 | for (auto& trace : traces_) trace.type_filtered = false; 348 | } 349 | 350 | void Traces(vector& traces) { 351 | // TODO TraceValue not really needed, could just pass pointers to Trace 352 | // and convert directly to V8 objects 353 | TraceValue tmp; 354 | traces.reserve(traces_.size()); 355 | for (int i = 0; i < traces_.size(); i++) { 356 | if (IsTraceFiltered(traces_[i])) continue; 357 | 358 | tmp.trace = &traces_[i].trace; 359 | tmp.trace_index = i; 360 | tmp.chunk_index = 0; 361 | bool overlaps = false; 362 | for (auto& chunk : traces_[i].chunks) { 363 | if (chunk->timestamp_start < filter_max_time_ && 364 | chunk->timestamp_end > filter_min_time_) { 365 | overlaps = true; 366 | break; 367 | } 368 | } 369 | if (!overlaps) continue; 370 | 371 | tmp.num_chunks = traces_[i].chunks.size(); 372 | tmp.type = &traces_[i].type; 373 | tmp.alloc_time_total = traces_[i].alloc_time_total; 374 | tmp.max_aggregate = traces_[i].max_aggregate; 375 | tmp.usage_score = traces_[i].usage_score; 376 | tmp.lifetime_score = traces_[i].lifetime_score; 377 | tmp.useful_lifetime_score = traces_[i].useful_lifetime_score; 378 | traces.push_back(tmp); 379 | } 380 | } 381 | 382 | void TraceChunks(std::vector& chunks, int trace_index, 383 | int chunk_index, int num_chunks) { 384 | // TODO adjust indexing to account for time filtering 385 | chunks.reserve(num_chunks); 386 | if (trace_index >= traces_.size()) { 387 | cout << "TRACE INDEX OVER SIZE\n"; 388 | return; 389 | } 390 | Trace& t = traces_[trace_index]; 391 | if (chunk_index >= t.chunks.size()) { 392 | cout << "CHUNK INDEX OVER SIZE\n"; 393 | return; 394 | } 395 | int bound = chunk_index + num_chunks; 396 | if (bound > t.chunks.size()) bound = t.chunks.size(); 397 | for (int i = chunk_index; i < bound; i++) { 398 | cout << "interval low:" << t.chunks[i]->access_interval_low << "\n"; 399 | chunks.push_back(t.chunks[i]); 400 | } 401 | } 402 | 403 | void SetFilterMinMax(uint64_t min, uint64_t max) { 404 | if (min >= max) return; 405 | if (max > max_time_) return; 406 | 407 | filter_min_time_ = min; 408 | filter_max_time_ = max; 409 | } 410 | 411 | void FilterMinMaxReset() { 412 | filter_min_time_ = min_time_; 413 | filter_max_time_ = max_time_; 414 | } 415 | 416 | uint64_t Inefficiences(int trace_index) { 417 | return traces_[trace_index].inefficiencies; 418 | } 419 | 420 | void StackTreeObject(const v8::FunctionCallbackInfo& args) { 421 | stack_tree_.V8Objectify(args); 422 | } 423 | 424 | void StackTreeAggregate(std::function f) { 425 | stack_tree_.Aggregate(f); 426 | } 427 | 428 | private: 429 | Chunk* chunks_; 430 | vector aggregates_; 431 | uint32_t num_chunks_; 432 | vector traces_; 433 | char* chunk_ptr_ = nullptr; 434 | uint64_t min_time_ = UINT64_MAX; 435 | uint64_t max_time_ = 0; 436 | uint64_t max_aggregate_ = 0; 437 | uint64_t filter_max_time_; 438 | uint64_t filter_min_time_; 439 | uint64_t global_alloc_time_ = 0; 440 | PatternParams pattern_params_; 441 | unordered_multimap> type_map_; 442 | 443 | StackTree stack_tree_; 444 | 445 | vector trace_filters_; 446 | vector type_filters_; 447 | priority_queue queue_; 448 | 449 | inline bool IsTraceFiltered(Trace const& t) { 450 | return t.filtered || t.type_filtered; 451 | } 452 | 453 | int GetFiles(string dir, vector& files) { 454 | DIR* dp; 455 | struct dirent* dirp; 456 | if ((dp = opendir(dir.c_str())) == NULL) { 457 | cout << "Error(" << errno << ") opening " << dir << endl; 458 | return errno; 459 | } 460 | 461 | while ((dirp = readdir(dp)) != NULL) { 462 | files.push_back(string(dirp->d_name)); 463 | } 464 | closedir(dp); 465 | return 0; 466 | } 467 | 468 | // TODO sampling will miss max values that can be pretty stark sometimes 469 | // probably need to make sure that particular MAX or MIN values appear in the 470 | // sample 471 | void SampleValues(const vector& points, 472 | vector& values) { 473 | values.clear(); 474 | values.reserve(MAX_POINTS + 2); 475 | // first, find the filtered interval 476 | // cout << "points size is " << points.size() << endl; 477 | unsigned int j = 0; 478 | for (; j < points.size() && points[j].time < filter_min_time_; j++) 479 | ; 480 | int min = j; 481 | j++; 482 | // cout << "min " << min << " j now " << j << endl; 483 | if (j == points.size()) { 484 | // basically, there were no points inside the interval, 485 | // so we set a straight line of the appropriate value during the filter 486 | // interval 487 | values.push_back({filter_min_time_, 0}); 488 | values.push_back({filter_min_time_ + 1, points[min - 1].value}); 489 | values.push_back({filter_max_time_ - 1, points[min - 1].value}); 490 | values.push_back({filter_max_time_, 0}); 491 | return; 492 | } 493 | for (; j < points.size() && points[j].time <= filter_max_time_; j++) 494 | ; 495 | int max = j; 496 | // cout << "max " << max << endl; 497 | int num_points = max - min + 1; 498 | if (num_points == 0 || num_points < 0) { 499 | cout << "num points is buggy: num points " << num_points << endl; 500 | return; 501 | } 502 | 503 | if (num_points > MAX_POINTS) { 504 | // float bin_size = float(num_points) / float(MAX_POINTS); 505 | values.push_back( 506 | {filter_min_time_, points[min == 0 ? min : min - 1].value}); 507 | /*auto sz = points.size(); 508 | for (uint32_t i = 0; i < num_points; i++) { 509 | // find max value in interval [min + i, min + i + bin_size] 510 | const TimeValue* maxval = &points[min+i]; 511 | for (auto x = min + i; x < min + i + bin_size && x < sz; x++) 512 | if (points[x].value > maxval->value) maxval = &points[x]; 513 | values.push_back(*maxval); 514 | i += bin_size - 1; 515 | }*/ 516 | // sample MAX POINTS points 517 | float interval = (float)num_points / (float)MAX_POINTS; 518 | int i = 0; 519 | float index = 0.0f; 520 | while (i < MAX_POINTS) { 521 | int idx = (int)index + min; 522 | if (idx >= points.size()) { 523 | cout << "POSSIBLE BUG points size is " << points.size() << " idx is " 524 | << idx << " interval is " << interval << endl; 525 | break; 526 | } 527 | values.push_back(points[idx]); 528 | index += interval; 529 | i++; 530 | } 531 | } else { 532 | values.resize(max - min + 1); 533 | values[0] = {filter_min_time_, points[min == 0 ? min : min - 1].value}; 534 | memcpy(&values[1], &points[min], sizeof(TimeValue) * (max - min)); 535 | } 536 | // cout << "first value is " << values[0].value << " at time " << 537 | // values[0].time << endl; 538 | if (values[values.size() - 1].time > filter_max_time_) 539 | values[values.size() - 1].time = filter_max_time_; 540 | else 541 | values.push_back({filter_max_time_ > values[values.size() - 1].time 542 | ? filter_max_time_ 543 | : points[points.size() - 1].time, 544 | values[values.size() - 1].value}); 545 | 546 | // cout << "values size is " << values.size() << endl; 547 | } 548 | 549 | void Aggregate(vector& points, uint64_t& max_aggregate, 550 | Chunk* chunks, int num_chunks) { 551 | if (!queue_.empty()) { 552 | cout << "THE QUEUE ISNT EMPTY MAJOR ERROR"; 553 | return; 554 | } 555 | TimeValue tmp; 556 | int64_t running = 0; 557 | points.clear(); 558 | points.push_back({0, 0}); 559 | 560 | int i = 0; 561 | while (i < num_chunks) { 562 | if (IsTraceFiltered(traces_[chunks[i].stack_index])) { 563 | i++; 564 | continue; 565 | } 566 | if (!queue_.empty() && queue_.top().time < chunks[i].timestamp_start) { 567 | tmp.time = queue_.top().time; 568 | running += queue_.top().value; 569 | tmp.value = running; 570 | queue_.pop(); 571 | points.push_back(tmp); 572 | } else { 573 | running += chunks[i].size; 574 | if (running > max_aggregate) max_aggregate = running; 575 | tmp.time = chunks[i].timestamp_start; 576 | tmp.value = running; 577 | points.push_back(tmp); 578 | tmp.time = chunks[i].timestamp_end; 579 | tmp.value = -chunks[i].size; 580 | queue_.push(tmp); 581 | i++; 582 | } 583 | } 584 | // drain the queue 585 | while (!queue_.empty()) { 586 | tmp.time = queue_.top().time; 587 | running += queue_.top().value; 588 | tmp.value = running; 589 | queue_.pop(); 590 | points.push_back(tmp); 591 | } 592 | // points.push_back({max_time_,0}); 593 | } 594 | 595 | // TODO deduplicate this code 596 | void Aggregate(vector& points, uint64_t& max_aggregate, 597 | vector& chunks) { 598 | int num_chunks = chunks.size(); 599 | if (!queue_.empty()) { 600 | cout << "THE QUEUE ISNT EMPTY MAJOR ERROR"; 601 | return; 602 | } 603 | TimeValue tmp; 604 | int64_t running = 0; 605 | points.clear(); 606 | points.push_back({0, 0}); 607 | 608 | int i = 0; 609 | while (i < num_chunks) { 610 | if (IsTraceFiltered(traces_[chunks[i]->stack_index])) { 611 | i++; 612 | continue; 613 | } 614 | if (!queue_.empty() && queue_.top().time < chunks[i]->timestamp_start) { 615 | tmp.time = queue_.top().time; 616 | running += queue_.top().value; 617 | tmp.value = running; 618 | queue_.pop(); 619 | points.push_back(tmp); 620 | } else { 621 | running += chunks[i]->size; 622 | if (running > max_aggregate) max_aggregate = running; 623 | tmp.time = chunks[i]->timestamp_start; 624 | tmp.value = running; 625 | points.push_back(tmp); 626 | tmp.time = chunks[i]->timestamp_end; 627 | tmp.value = -chunks[i]->size; 628 | queue_.push(tmp); 629 | i++; 630 | } 631 | } 632 | // drain the queue 633 | while (!queue_.empty()) { 634 | tmp.time = queue_.top().time; 635 | running += queue_.top().value; 636 | tmp.value = running; 637 | queue_.pop(); 638 | points.push_back(tmp); 639 | } 640 | } 641 | }; 642 | 643 | // its just easier this way ... 644 | static Dataset theDataset; 645 | 646 | bool SetDataset(const std::string& dir_path, const string& trace_file, const string& chunk_file, 647 | string& msg) { 648 | return theDataset.Reset(dir_path, trace_file, chunk_file, msg); 649 | } 650 | 651 | void AggregateAll(std::vector& values) { 652 | theDataset.AggregateAll(values); 653 | } 654 | 655 | uint64_t MaxAggregate() { return theDataset.MaxAggregate(); } 656 | 657 | // TODO maybe these should just default to the filtered numbers? 658 | uint64_t MaxTime() { return theDataset.MaxTime(); } 659 | 660 | uint64_t MinTime() { return theDataset.MinTime(); } 661 | 662 | uint64_t FilterMaxTime() { return theDataset.FilterMaxTime(); } 663 | 664 | uint64_t FilterMinTime() { return theDataset.FilterMinTime(); } 665 | 666 | void SetTraceKeyword(const std::string& keyword) { 667 | // will only include traces that contain this keyword 668 | theDataset.SetTraceFilter(keyword); 669 | } 670 | 671 | void SetTypeKeyword(const std::string& keyword) { 672 | // will only include traces that contain this keyword 673 | theDataset.SetTypeFilter(keyword); 674 | } 675 | 676 | void Traces(std::vector& traces) { theDataset.Traces(traces); } 677 | 678 | void AggregateTrace(std::vector& values, int trace_index) { 679 | theDataset.AggregateTrace(values, trace_index); 680 | } 681 | 682 | void TraceChunks(std::vector& chunks, int trace_index, int chunk_index, 683 | int num_chunks) { 684 | theDataset.TraceChunks(chunks, trace_index, chunk_index, num_chunks); 685 | } 686 | 687 | void SetFilterMinMax(uint64_t min, uint64_t max) { 688 | theDataset.SetFilterMinMax(min, max); 689 | } 690 | 691 | void TraceFilterReset() { theDataset.TraceFilterReset(); } 692 | 693 | void TypeFilterReset() { theDataset.TypeFilterReset(); } 694 | 695 | void FilterMinMaxReset() { theDataset.FilterMinMaxReset(); } 696 | 697 | uint64_t Inefficiencies(int trace_index) { 698 | return theDataset.Inefficiences(trace_index); 699 | } 700 | 701 | uint64_t GlobalAllocTime() { return theDataset.GlobalAllocTime(); } 702 | 703 | void StackTreeObject(const v8::FunctionCallbackInfo& args) { 704 | theDataset.StackTreeObject(args); 705 | } 706 | 707 | void StackTreeAggregate(std::function f) { 708 | theDataset.StackTreeAggregate(f); 709 | } 710 | 711 | } // namespace memoro 712 | -------------------------------------------------------------------------------- /js/chunkgraph.js: -------------------------------------------------------------------------------- 1 | //===-- chunkgraph.js ------------------------------------------------===// 2 | // 3 | // Memoro 4 | // 5 | // This file is distributed under the MIT License. 6 | // See LICENSE for details. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This file is a part of Memoro. 11 | // Stuart Byma, EPFL. 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | 16 | 17 | 18 | 19 | // bear in mind that I don't _really_ do javascript 20 | 21 | 22 | 23 | 24 | 25 | global.d3 = require("d3"); 26 | const exec = require('child_process').exec; 27 | require("../node_modules/d3-flame-graph/dist/d3.flameGraph"); 28 | require("../node_modules/d3-tip/index"); 29 | var memoro = require('../cpp/build/Release/memoro.node'); 30 | var path = require('path'); 31 | var fs = require("fs"); 32 | var gmean = require('compute-gmean'); 33 | 34 | const settings = require('electron').remote.require('electron-settings'); 35 | 36 | function bytesToString(bytes,decimals) { 37 | if(bytes == 0) return '0 B'; 38 | var k = 1024, 39 | dm = decimals || 2, 40 | sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], 41 | i = Math.floor(Math.log(bytes) / Math.log(k)); 42 | return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; 43 | } 44 | 45 | var bytesToStringNoDecimal = function (bytes) { 46 | 47 | var fmt = d3.format('.0f'); 48 | if (bytes < 1024) { 49 | return fmt(bytes) + 'B'; 50 | } else if (bytes < 1024 * 1024) { 51 | return fmt(bytes / 1024) + 'kB'; 52 | } else if (bytes < 1024 * 1024 * 1024) { 53 | return fmt(bytes / 1024 / 1024) + 'MB'; 54 | } else { 55 | return fmt(bytes / 1024 / 1024 / 1024) + 'GB'; 56 | } 57 | } 58 | 59 | // convenience globals 60 | var barHeight; 61 | var total_chunks; 62 | 63 | var chunk_graph_width; 64 | var chunk_graph_height; 65 | var aggregate_graph_height; 66 | var chunk_y_axis_space; 67 | var x; // the x range 68 | var global_x; // the global tab x range 69 | var y; // aggregate y range 70 | var max_x; 71 | var num_traces; 72 | var current_fg_type = "num_allocs"; 73 | var current_fg_time = 0; 74 | 75 | var avg_lifetime; 76 | var avg_usage; 77 | var avg_useful_life; 78 | var lifetime_var; 79 | var usage_var; 80 | var useful_life_var; 81 | 82 | var aggregate_max = 0; 83 | var filter_words = []; 84 | 85 | var colors = [ 86 | "#b0c4de", 87 | "#b0c4de", 88 | "#a1b8d4", 89 | "#93acca", 90 | "#84a0c0", 91 | "#7594b6", 92 | "#6788ac", 93 | "#587ba2", 94 | "#496f98", 95 | "#3b638e", 96 | "#2c5784", 97 | "#1d4b7a", 98 | "#0f3f70", 99 | "#003366"]; 100 | 101 | var badness_colors = [ 102 | "#0085ff", 103 | "#00cfff", 104 | "#00ffe5", 105 | "#00ff9b", 106 | "#00ff51", 107 | "#00ff07", 108 | "#43ff00", 109 | "#8cff00", 110 | "#d6ff00", 111 | "#ffde00", 112 | "#ff9400", 113 | "#ff4a00", 114 | "#ff0000"]; 115 | 116 | // chunk color gradient scale 117 | var colorScale = d3.scaleQuantile() 118 | .range(colors); 119 | 120 | function showLoader() { 121 | var div = document.createElement("div"); 122 | div.id = "loadspinner"; 123 | div.className = "spinny-load"; 124 | div.html = "Loading ..."; 125 | document.getElementById("middle-column").appendChild(div); 126 | } 127 | 128 | function hideLoader() { 129 | // completely remove, otherwise it takes CPU cycles while 130 | // animating, even if hidden? 131 | var element = document.getElementById("loadspinner"); 132 | element.parentNode.removeChild(element); 133 | } 134 | 135 | function showModal(title, body, icon = "") { 136 | $("#modal-title-text").text(" " + title); 137 | var ic = $("#modal-icon"); 138 | ic.removeClass(); 139 | if (icon !== '') 140 | ic.addClass("fa " + icon); 141 | $(".modal-body").html(body); 142 | $("#main-modal").modal("show") 143 | } 144 | // file open callback function 145 | function updateData(datafile) { 146 | 147 | showLoader(); 148 | 149 | var folder = path.dirname(datafile); 150 | var filename = datafile.replace(/^.*[\\\/]/, ''); 151 | var idx = filename.lastIndexOf('.'); 152 | var name = filename.substring(0, idx); 153 | var trace_path = folder + "/" + name + ".trace"; 154 | var chunk_path = folder + "/" + name + ".chunks"; 155 | console.log("trace " + trace_path + " chunk path " + chunk_path); 156 | // set dataset is async, because it can take some time with large trace files 157 | memoro.set_dataset(folder+'/', trace_path, chunk_path, function(result) { 158 | hideLoader(); 159 | //console.log(result); 160 | if (!result.result) { 161 | showModal("Error", "File parsing failed with error: " + result.message, "fa-exclamation-triangle"); 162 | } else { 163 | 164 | // add default "main" filter? 165 | drawEverything(); 166 | } 167 | var element = document.querySelector("#overlay"); 168 | element.style.visibility = "hidden"; 169 | }); 170 | } 171 | 172 | function badnessTooltip(idx) { 173 | 174 | return function() { 175 | var div = d3.select("#tooltip"); 176 | div.transition() 177 | .duration(200) 178 | .style("opacity", .9); 179 | div.html("") 180 | .style("left", (d3.event.pageX) + "px") 181 | .style("top", (d3.event.pageY - 28) + "px"); 182 | var svg = div.append("svg") 183 | .attr("width", 10*badness_colors.length) 184 | .attr("height", 15); 185 | 186 | badness_colors.forEach(function(c, i) { 187 | var circ = svg.append("circle") 188 | .attr("transform", "translate(" + (5+i*10) + ", 8)") 189 | .attr("r", 5) 190 | .style("fill", c); 191 | if (idx === i) 192 | circ.classed("indicator-circle", true) 193 | }) 194 | 195 | //div.attr("width", width); 196 | } 197 | } 198 | function constructInferences(inef) { 199 | var ret = ""; 200 | if (inef.unused) 201 | ret += " unused chunks
" 202 | if (!inef.unused) { 203 | if (inef.read_only) 204 | ret += " read-only chunks
" 205 | if (inef.write_only) 206 | ret += " write-only chunks
" 207 | if (inef.short_lifetime) 208 | ret += " short lifetime chunks
" 209 | if (!inef.short_lifetime) { 210 | if (inef.late_free) 211 | ret += " chunks freed late
" 212 | if (inef.early_alloc) 213 | ret += " chunks allocated early
" 214 | } 215 | if (inef.increasing_allocs) 216 | ret += " increasing allocations
" 217 | if (inef.top_percentile_chunks) 218 | ret += " in top-% chunks alloc'd
" 219 | if (inef.top_percentile_size) 220 | ret += " in top-% bytes alloc'd
" 221 | if (inef.multi_thread) 222 | ret += " multithread accesses
" 223 | if (inef.low_access_coverage) 224 | ret += " low access coverage
" 225 | } 226 | 227 | return ret; 228 | } 229 | 230 | var current_sort_order = 'bytes'; 231 | function sortTraces() { 232 | memoro.sort_traces(current_sort_order); 233 | } 234 | 235 | function generateOpenSourceCmd(file, line) { 236 | var editor = settings.get('editor'); 237 | if (editor.name === "none") { 238 | showModal("Error", "No editor defined, and no defaults found on system.", "fa-exclamation-triangle"); 239 | return ""; 240 | } 241 | if (editor.name === "CLion") { 242 | return editor.cmd + " " + file + " --line " + line + " " + file; 243 | } else if (editor.name === "xed") { 244 | return editor.cmd + " --line " + line + " " + file; 245 | } else { 246 | return ""; 247 | } 248 | // TODO add other supported editors here 249 | } 250 | 251 | function generateTraceHtml(raw) { 252 | // parse each trace for source file and line numbers, 253 | // add ondblclick events to open in desired editor 254 | var traces = raw.split('|'); 255 | 256 | var trace = document.getElementById("trace"); 257 | // remove existing 258 | while (trace.firstChild) { 259 | trace.removeChild(trace.firstChild); 260 | } 261 | 262 | traces.forEach(function (t) { 263 | if (t === "") return; 264 | var p = document.createElement("p"); 265 | p.innerHTML = t + " "; 266 | // yet more stuff depending on exact trace formatting 267 | // eventually we need to move symbolizer execution to the visualizer 268 | // i.e. *here* and have the compiler RT store only binary addrs 269 | var cmd = ""; 270 | var line = ""; 271 | var file = ""; 272 | var file_line = t.substr(t.indexOf('/')); 273 | var last_colon = file_line.lastIndexOf(':'); 274 | var second_last_colon = file_line.lastIndexOf(':', last_colon-1); 275 | // TODO clean up this parsing to something more readable 276 | if (second_last_colon !== -1) { 277 | line = file_line.substring(second_last_colon+1, last_colon); 278 | if (isNaN(line)){ 279 | line = file_line.substr(last_colon+1); 280 | if (!isNaN(line)) { 281 | file = file_line.substring(0, last_colon); 282 | cmd = generateOpenSourceCmd(file, line); 283 | } 284 | } else { 285 | file = file_line.substring(0, second_last_colon); 286 | //cmd = '/Applications/CLion.app/Contents/MacOS/clion ' + file + ' --line ' + line + " " + file; 287 | cmd = generateOpenSourceCmd(file, line); 288 | } 289 | } else { 290 | line = file_line.substr(last_colon+1); 291 | if (!isNaN(line)) { 292 | file = file_line.substring(0, last_colon); 293 | //cmd = '/Applications/CLion.app/Contents/MacOS/clion ' + file + ' --line ' + line + " " + file; 294 | cmd = generateOpenSourceCmd(file, line); 295 | } 296 | } 297 | 298 | /* p.ondblclick = function () { 299 | /!* console.log("file is " + file + ", line is " + line); 300 | console.log("cmd is: " + cmd);*!/ 301 | if (!fs.existsSync(file)) { 302 | showModal("Error", "Cannot locate file: " + file); 303 | } 304 | if (cmd !== "") { 305 | exec(cmd, function(err, stdout, stderr) { 306 | if (err) { 307 | // node couldn't execute the command 308 | console.log("could not open text editor " + stdout + "\n" + stderr); 309 | showModal("Error", "Open editor failed with stderr: " + stderr); 310 | } 311 | }); 312 | } 313 | };*/ 314 | var btn = document.createElement("i"); 315 | btn.className += "fa fa-folder-open"; 316 | btn.style.visibility = "hidden"; 317 | btn.onclick = function () { 318 | /* console.log("file is " + file + ", line is " + line); 319 | console.log("cmd is: " + cmd);*/ 320 | if (!fs.existsSync(file)) { 321 | showModal("Error", "Cannot locate file: " + file, "fa-exclamation-triangle"); 322 | } 323 | if (cmd !== "") { 324 | exec(cmd, function(err, stdout, stderr) { 325 | if (err) { 326 | // node couldn't execute the command 327 | console.log("could not open text editor " + stdout + "\n" + stderr); 328 | showModal("Error", "Open editor failed with stderr: " + stderr, "fa-exclamation-triangle"); 329 | } 330 | }); 331 | } 332 | }; 333 | btn.onmouseover = function() { 334 | btn.className = "fa fa-folder-open-o"; 335 | }; 336 | btn.onmouseout = function() { 337 | btn.className = "fa fa-folder-open"; 338 | }; 339 | p.appendChild(btn); 340 | p.onmouseover = function () { 341 | p.style.fontWeight = 'bold'; 342 | p.getElementsByClassName("fa")[0].style.visibility = "visible"; 343 | }; 344 | p.onmouseout = function () { 345 | p.style.fontWeight = 'normal'; 346 | p.getElementsByClassName("fa")[0].style.visibility = "hidden"; 347 | }; 348 | var br = document.createElement("hr"); 349 | trace.appendChild(p); 350 | trace.appendChild(br); 351 | }) 352 | } 353 | 354 | var load_threshold = 90; 355 | let stacktraces_count = 20; 356 | var current_stacktrace_index = stacktraces_count; 357 | var current_stacktrace_index_low = 0; 358 | 359 | function removeStackTracesTop(num) { 360 | var svgs = d3.select('#traces').selectAll('svg')._groups[0]; 361 | for (i = 0; i < num; i++) 362 | svgs[i].remove(); 363 | } 364 | 365 | function removeStackTracesBottom(num) { 366 | var svgs = d3.select('#traces').selectAll('svg')._groups[0]; 367 | var end = svgs.length; 368 | 369 | for (i = end - num; i < end; i++) 370 | svgs[i].remove(); 371 | } 372 | 373 | function traceScroll() { 374 | var element = document.querySelector("#traces"); 375 | var percent = 100 * element.scrollTop / (element.scrollHeight - element.clientHeight); 376 | if (percent > load_threshold) { 377 | var traces = memoro.traces(current_stacktrace_index, stacktraces_count); 378 | if (traces.length > 0) { 379 | // there are still some to display 380 | var to_append = traces.length; 381 | var to_remove = to_append; 382 | // remove from top 383 | for (var i = 0; i < to_append; i++) { 384 | var sampled = memoro.aggregate_trace(traces[i].trace_index); 385 | renderStackTraceSvg(traces[i], i, sampled, true); 386 | } 387 | current_stacktrace_index += to_append; 388 | current_stacktrace_index_low += to_append; 389 | 390 | removeStackTracesTop(to_remove); 391 | } 392 | } else if (percent < (100 - load_threshold)) { 393 | if (current_stacktrace_index_low > 0) { 394 | var traces = memoro.traces( 395 | Math.max(current_stacktrace_index_low - stacktraces_count, 0), // Don't go <0 396 | Math.min(stacktraces_count, current_stacktrace_index_low)); // Don't ask <0 397 | if (traces.length === 0) 398 | console.log("oh fuck traces is 0"); 399 | var to_prepend = traces.length; 400 | var to_remove = to_prepend; 401 | 402 | for (i = traces.length-1; i >= 0; i--) { 403 | var sampled = memoro.aggregate_trace(traces[i].trace_index); 404 | renderStackTraceSvg(traces[i], i, sampled, false); 405 | } 406 | current_stacktrace_index -= to_prepend; 407 | current_stacktrace_index_low -= to_prepend; 408 | 409 | removeStackTracesBottom(to_remove); 410 | } 411 | } 412 | } 413 | 414 | function renderStackTraceSvg(d, i, sampled, bottom) { 415 | var trace = d3.select("#trace"); 416 | var traces_div = d3.select("#traces"); 417 | var peak = d.max_aggregate 418 | var rectHeight = 55; 419 | 420 | var new_svg_g; 421 | if (bottom) 422 | new_svg_g = traces_div.append("svg"); 423 | else 424 | new_svg_g = traces_div.insert("svg", ":first-child"); 425 | 426 | new_svg_g 427 | .attr("width", chunk_graph_width) 428 | .attr("height", rectHeight) 429 | .classed("svg_spacing_trace", true) 430 | .on("click", function(s) { 431 | clearChunks(); 432 | if (d3.select(this).classed("selected")) { 433 | d3.selectAll(".select-rect").style("display", "none"); 434 | d3.select(this).classed("selected", false); 435 | trace.html("Select an allocation point under \"Heap Allocation\""); 436 | var info = d3.select("#inferences"); 437 | info.html(""); 438 | d3.select("#stack-agg-path").remove(); 439 | } else { 440 | colorScale.domain([1, peak]); 441 | 442 | generateTraceHtml(d.trace); 443 | 444 | d3.selectAll(".select-rect").style("display", "none"); 445 | d3.select(this).selectAll(".select-rect").style("display", "inline"); 446 | d3.select(this).classed("selected", true); 447 | drawChunks(d); 448 | var info = d3.select("#inferences"); 449 | var inef = memoro.inefficiencies(d.trace_index); 450 | var html = constructInferences(inef); 451 | html += "
Usage: " + d.usage_score.toFixed(2) + "
Lifetime: " + d.lifetime_score.toFixed(2) 452 | + "
Useful Lifetime: " + d.useful_lifetime_score.toFixed(2); 453 | info.html(html); 454 | 455 | var fresh_sampled = memoro.aggregate_trace(d.trace_index); 456 | var agg_line = d3.line() 457 | .x(function(v) { 458 | return x(v["ts"]); 459 | }) 460 | .y(function(v) { return y(v["value"]); }) 461 | .curve(d3.curveStepAfter); 462 | 463 | d3.select("#stack-agg-path").remove(); 464 | var aggregate_graph_g = d3.select("#aggregate-group"); 465 | aggregate_graph_g.append("path") 466 | .datum(fresh_sampled) 467 | .attr("id", "stack-agg-path") 468 | .attr("fill", "none") 469 | .classed("stack-agg-graph", true) 470 | .attr("stroke-width", 1.0) 471 | .attr("transform", "translate(0, 5)") 472 | .attr("d", agg_line); 473 | } 474 | }) 475 | .append("g"); 476 | 477 | new_svg_g.append("rect") 478 | .attr("transform", "translate(0, 0)") 479 | .attr("width", chunk_graph_width - chunk_y_axis_space) 480 | .attr("height", rectHeight) 481 | .attr("class", function(x) { 482 | if (i % 2 == 0) { 483 | return "background1"; 484 | } else { 485 | return "background2"; 486 | } 487 | }); 488 | 489 | var mean = gmean([d.lifetime_score < 0.01 ? 0.01 : d.lifetime_score, d.usage_score < 0.01 ? 0.01 : d.usage_score, 490 | d.useful_lifetime_score < 0.01 ? 0.01 : d.useful_lifetime_score]); 491 | var badness_col = Math.round((1.0 - mean) * (badness_colors.length - 1)); 492 | new_svg_g.append('circle') 493 | .attr("transform", "translate(" + (chunk_graph_width - chunk_y_axis_space - 15) + ", 4)") 494 | .attr("cx", 5) 495 | .attr("cy", 5) 496 | .attr("r", 5) 497 | .style("fill", badness_colors[badness_col]) 498 | .on("mouseover", badnessTooltip(badness_col)) 499 | .on("mouseout", function(d) { 500 | var div = d3.select("#tooltip"); 501 | div.transition() 502 | .duration(500) 503 | .style("opacity", 0); 504 | }); 505 | 506 | new_svg_g.append("rect") 507 | .attr("transform", "translate(0, 0)") 508 | .attr("width", chunk_graph_width - chunk_y_axis_space) 509 | .attr("height", rectHeight) 510 | .style("fill", "none") 511 | .style("stroke-width", "2px") 512 | .style("stroke", "steelblue") 513 | .style("display", "none") 514 | .classed("select-rect", true); 515 | 516 | //sampled = memoro.aggregate_trace(d.trace_index); 517 | 518 | var t = new_svg_g.append("text"); 519 | t.style("font-size", "small") 520 | .classed("stack-text", true) 521 | .attr("x", 5) 522 | .attr("y", "15") 523 | .text("Chunks: " + (d.num_chunks) + ", Peak Bytes: " + bytesToString(peak) + " Type: " + d.type); 524 | 525 | 526 | var stack_y = d3.scaleLinear() 527 | .range([rectHeight-25, 0]); 528 | 529 | stack_y.domain(d3.extent(sampled, function(v) { return v["value"]; })); 530 | 531 | var yAxisRight = d3.axisRight(stack_y) 532 | .tickFormat(bytesToStringNoDecimal) 533 | .ticks(2); 534 | 535 | var line = d3.line() 536 | .x(function(v) { 537 | return x(v["ts"]); 538 | }) 539 | .y(function(v) { return stack_y(v["value"]); }) 540 | .curve(d3.curveStepAfter); 541 | 542 | new_svg_g.append("path") 543 | .datum(sampled) 544 | .attr("fill", "none") 545 | .classed("stack-agg-overlay", true) 546 | .attr("stroke-width", 1.0) 547 | .attr("transform", "translate(0, 20)") 548 | .attr("d", line); 549 | new_svg_g.append("g") 550 | .attr("class", "y axis") 551 | .attr("transform", "translate(" + (chunk_graph_width - chunk_y_axis_space + 1) + ",20)") 552 | .style("fill", "white") 553 | .call(yAxisRight); 554 | } 555 | 556 | function drawStackTraces() { 557 | 558 | var trace = d3.select("#trace"); 559 | var traces_div = d3.select("#traces"); 560 | traces_div.selectAll("svg").remove(); 561 | 562 | traces_div.property('scrollTop', 0); 563 | 564 | d3.select("#inferences").html(""); 565 | 566 | //var max_x = xMax(); 567 | var max_x = memoro.filter_max_time(); 568 | var min_x = memoro.filter_min_time(); 569 | 570 | x.domain([min_x, max_x]); 571 | 572 | sortTraces(); 573 | current_stacktrace_index_low = 0; 574 | current_stacktrace_index = stacktraces_count * 3; 575 | var traces = memoro.traces(current_stacktrace_index_low, current_stacktrace_index); 576 | num_traces = traces.length; 577 | total_chunks = 0; 578 | 579 | var total_usage = 0; 580 | var total_lifetime = 0; 581 | var total_useful_lifetime = 0; 582 | 583 | var cur_background_class = 0; 584 | traces.forEach(function (d, i) { 585 | total_usage += d.usage_score; 586 | total_lifetime += d.lifetime_score; 587 | total_useful_lifetime += d.useful_lifetime_score; 588 | 589 | var sampled = memoro.aggregate_trace(d.trace_index); 590 | total_chunks += d.num_chunks; 591 | 592 | renderStackTraceSvg(d, i, sampled, true); 593 | }); 594 | 595 | avg_lifetime = total_lifetime / traces.length; 596 | avg_usage = total_usage / traces.length; 597 | avg_useful_life = total_useful_lifetime / traces.length; 598 | 599 | var lifetime_var_total = 0; 600 | var usage_var_total = 0; 601 | var useful_lifetime_var_total = 0; 602 | traces.forEach(function(t) { 603 | lifetime_var_total += Math.pow(t.lifetime_score - avg_lifetime, 2); 604 | usage_var_total += Math.pow(t.usage_score - avg_usage, 2); 605 | useful_lifetime_var_total += Math.pow(t.useful_lifetime_score - avg_useful_life, 2); 606 | }); 607 | 608 | lifetime_var = lifetime_var_total / traces.length; 609 | usage_var = usage_var_total / traces.length; 610 | useful_life_var = useful_lifetime_var_total / traces.length; 611 | } 612 | 613 | function tooltip(chunk) { 614 | return function() { 615 | var div = d3.select("#tooltip") 616 | div.transition() 617 | .duration(200) 618 | .style("opacity", .9); 619 | div .html(bytesToString(chunk["size"]) + "
Reads: " + chunk["num_reads"] 620 | + "
Writes: " + chunk["num_writes"] + "
Access Ratio: " + 621 | ((chunk.access_interval_high - chunk.access_interval_low) / chunk.size).toFixed(1) 622 | + "
Access Interval: [" + chunk.access_interval_low + "," + chunk.access_interval_high 623 | + "]
MultiThread: " + chunk.multi_thread + "
") 624 | .style("left", (d3.event.pageX) + "px") 625 | .style("top", (d3.event.pageY - 100) + "px"); 626 | div.append("svg") 627 | .attr("width", 10) 628 | .attr("height", 10) 629 | .append("rect") 630 | .attr("width", 10) 631 | .attr("height", 10) 632 | .style("fill", colorScale(chunk.size)) 633 | //div.attr("width", width); 634 | } 635 | } 636 | 637 | function renderChunkSvg(chunk, text, bottom) { 638 | var min_x = memoro.filter_min_time() 639 | 640 | 641 | var chunk_div = d3.select("#chunks"); 642 | var div = d3.select("#tooltip"); 643 | var new_svg_g; 644 | if (bottom) { 645 | new_svg_g = chunk_div.append("div").append("svg"); 646 | } 647 | else { 648 | new_svg_g = chunk_div.insert("div", ":first-child").append("svg") 649 | } 650 | 651 | new_svg_g.attr("width", chunk_graph_width) 652 | .attr("height", barHeight) 653 | .classed("svg_spacing_data", true); 654 | new_svg_g.append("rect") 655 | .attr("transform", "translate("+ x(chunk["ts_start"]) +",0)") 656 | .attr("width", Math.max(x(min_x + chunk["ts_end"] - chunk["ts_start"]), 3)) 657 | .attr("height", barHeight) 658 | .style("fill", colorScale(chunk.size)) 659 | .on("mouseover", tooltip(chunk)) 660 | .on("mouseout", function(d) { 661 | div.transition() 662 | .duration(500) 663 | .style("opacity", 0); 664 | }); 665 | /*new_svg_g.append("text") 666 | .attr("x", (chunk_graph_width - 130)) 667 | .attr("y", 6) 668 | .text(text.toString()) 669 | .classed("chunk_text", true);*/ 670 | 671 | new_svg_g.append("line") 672 | .attr("y1", 0) 673 | .attr("y2", barHeight) 674 | .attr("x1", x(chunk["ts_first"])) 675 | .attr("x2", x(chunk["ts_first"])) 676 | .style("stroke-width", 2) 677 | .attr("display", chunk["ts_first"] === 0 ? "none" : null) 678 | .classed("firstlastbars", true); 679 | 680 | var next = 0; 681 | if (x(chunk["ts_last"]) - x(chunk["ts_first"]) < 1) 682 | { 683 | next = x(chunk["ts_first"]) + 3; 684 | } else { 685 | next = x(chunk["ts_last"]); 686 | } 687 | new_svg_g.append("line") 688 | .attr("y1", 0) 689 | .attr("y2", barHeight) 690 | .attr("x1", next) 691 | .attr("x2", next) 692 | .attr("display", chunk["ts_first"] === 0 ? "none" : null) 693 | .style("stroke-width", 2) 694 | .classed("firstlastbars", true); 695 | } 696 | 697 | function removeChunksTop(num) { 698 | var svgs = d3.select("#chunks").selectAll("div")._groups[0]; 699 | 700 | for (i = 0; i < num; i++) 701 | { 702 | svgs[i].remove(); 703 | } 704 | } 705 | 706 | function removeChunksBottom(num) { 707 | 708 | var svgs = d3.select("#chunks").selectAll("div")._groups[0]; 709 | var end = svgs.length; 710 | 711 | //console.log(svgs) 712 | for (i = end - num; i < end; i++) 713 | { 714 | svgs[i].remove(); 715 | } 716 | } 717 | 718 | var current_trace_index = null; 719 | var current_chunk_index = 0; 720 | var current_chunk_index_low = 0; 721 | 722 | function chunkScroll() { 723 | var element = document.querySelector("#chunks"); 724 | var percent = 100 * element.scrollTop / (element.scrollHeight - element.clientHeight); 725 | if (percent > load_threshold) { 726 | var chunks = memoro.trace_chunks(current_trace_index, current_chunk_index, 25); 727 | if (chunks.length > 0) { 728 | // there are still some to display 729 | var to_append = chunks.length; 730 | var to_remove = to_append; 731 | // remove from top 732 | for (var i = 0; i < to_append; i++) { 733 | renderChunkSvg(chunks[i], current_chunk_index + i, true); 734 | } 735 | current_chunk_index += to_append; 736 | current_chunk_index_low += to_append; 737 | 738 | removeChunksTop(to_remove); 739 | } 740 | } else if (percent < (100 - load_threshold)) { 741 | if (current_chunk_index_low > 0) { 742 | var chunks = memoro.trace_chunks(current_trace_index, Math.max(current_chunk_index_low-25, 0), Math.min(25, current_chunk_index_low)); 743 | if (chunks.length === 0) 744 | console.log("oh fuck chunks is 0"); 745 | var to_prepend = chunks.length; 746 | var to_remove = to_prepend; 747 | 748 | for (i = chunks.length-1; i >= 0; i--) { 749 | renderChunkSvg(chunks[i], current_chunk_index_low - (to_prepend - i), false); 750 | } 751 | current_chunk_index -= to_prepend; 752 | current_chunk_index_low -= to_prepend; 753 | 754 | removeChunksBottom(to_remove); 755 | } 756 | 757 | } 758 | } 759 | 760 | function clearChunks() { 761 | var chunk_div = d3.select("#chunks"); 762 | 763 | chunk_div.selectAll("div").remove(); 764 | current_trace_index = null; 765 | 766 | chunk_div.property('scrollTop', 0); 767 | } 768 | 769 | // draw the first X chunks from this trace 770 | function drawChunks(trace) { 771 | 772 | // test if this is already present in the div 773 | if (current_trace_index === trace.trace_index) 774 | return; 775 | 776 | current_trace_index = trace.trace_index; 777 | 778 | var chunk_div = d3.select("#chunks"); 779 | 780 | chunk_div.selectAll("div").remove(); 781 | 782 | var max_x = memoro.filter_max_time(); 783 | var min_x = memoro.filter_min_time(); 784 | //console.log("min x " + min_x + " max x " + max_x) 785 | 786 | x.domain([min_x, max_x]); 787 | 788 | current_chunk_index_low = trace.chunk_index; 789 | current_chunk_index = Math.min(trace.num_chunks, 200); 790 | chunks = memoro.trace_chunks(trace.trace_index, current_chunk_index_low, current_chunk_index); 791 | 792 | for (var i = 0; i < chunks.length; i++) { 793 | renderChunkSvg(chunks[i], current_chunk_index_low + i, true); 794 | } 795 | 796 | // add the gradient heatmap scale 797 | d3.select("#chunks-yaxis").selectAll("svg").remove(); 798 | 799 | var legend_g = d3.select("#chunks-yaxis").append("svg") 800 | .attr("height", chunk_graph_height) 801 | .selectAll() 802 | .data(colorScale.quantiles()) 803 | .enter() 804 | .append("g"); 805 | 806 | var elem_height = chunk_graph_height / colors.length; 807 | legend_g.append("rect") 808 | .attr("y", function(d, i) { return elem_height*i; }) 809 | .attr("width", 10) 810 | .attr("height", elem_height) 811 | .style("fill", function(d, i) { return colors[i]}) 812 | 813 | // now the text 814 | legend_g.append("text") 815 | .attr("x", 15) 816 | .attr("y", function(d, i) { return elem_height*i + elem_height/1.5; }) 817 | .classed("legend-text", true) 818 | .text(function(d, i) { return bytesToStringNoDecimal(d)}) 819 | .style("font-size", "10px"); 820 | } 821 | 822 | function xMax() { 823 | // find the max TS value 824 | return memoro.filter_max_time(); 825 | } 826 | 827 | function drawChunkXAxis() { 828 | 829 | var xAxis = d3.axisBottom(x) 830 | .tickFormat(function(xval) { 831 | if (max_x < 1000000000) 832 | return (xval / 1000).toFixed(1) + "Kc"; 833 | else if (max_x < 10000000000) return (xval / 1000000).toFixed(1) + "Mc"; 834 | else return (xval / 1000000000).toFixed(1) + "Gc"; 835 | }) 836 | .ticks(7) 837 | //.orient("bottom"); 838 | 839 | var max_x = memoro.filter_max_time(); 840 | var min_x = memoro.filter_min_time(); 841 | 842 | x.domain([min_x, max_x]); 843 | 844 | d3.select("#chunk-x-axis").remove(); 845 | 846 | d3.select("#chunk-axis") 847 | .append("svg") 848 | .attr("id", "chunk-x-axis") 849 | .attr("width", chunk_graph_width-chunk_y_axis_space) 850 | .attr("height", 30) 851 | .append("g") 852 | .attr("class", "axis") 853 | .call(xAxis); 854 | } 855 | 856 | function drawAggregateAxis() { 857 | 858 | var xAxis = d3.axisBottom(x) 859 | .tickFormat(function(xval) { 860 | if (max_x < 1000000000) 861 | return (xval / 1000).toFixed(1) + "Kc"; 862 | else if (max_x < 10000000000) return (xval / 1000000).toFixed(1) + "Mc"; 863 | else return (xval / 1000000000).toFixed(1) + "Gc"; 864 | }) 865 | //.orient("bottom") 866 | .ticks(7) 867 | .tickSizeInner(-120); 868 | 869 | var max_x = memoro.filter_max_time(); 870 | var min_x = memoro.filter_min_time(); 871 | 872 | x.domain([min_x, max_x]); 873 | 874 | d3.select("#aggregate-x-axis").remove(); 875 | 876 | var xaxis_height = aggregate_graph_height*0.8 - 5; 877 | d3.select("#aggregate-group") 878 | .append("g") 879 | .attr("id", "aggregate-x-axis") 880 | //.attr("width", chunk_graph_width-chunk_y_axis_space) 881 | .attr("transform", "translate(0," + xaxis_height + ")") 882 | .attr("class", "axis") 883 | .call(xAxis); 884 | } 885 | 886 | function drawGlobalAggregateAxis() { 887 | 888 | var xAxis = d3.axisBottom(global_x) 889 | .tickFormat(function(xval) { 890 | if (max_x < 1000000000) 891 | return (xval / 1000).toFixed(1) + "Kc"; 892 | else if (max_x < 10000000000) return (xval / 1000000).toFixed(1) + "Mc"; 893 | else return (xval / 1000000000).toFixed(1) + "Gc"; 894 | }) 895 | .ticks(7) 896 | .tickSizeInner(-120); 897 | 898 | d3.select("#fg-aggregate-x-axis").remove(); 899 | 900 | var xaxis_height = aggregate_graph_height*0.8 - 5; 901 | d3.select("#fg-aggregate-group") 902 | .append("g") 903 | .attr("id", "fg-aggregate-x-axis") 904 | .attr("transform", "translate(0," + xaxis_height + ")") 905 | .attr("class", "axis") 906 | .call(xAxis); 907 | } 908 | 909 | function drawGlobalAggregatePath() { 910 | 911 | y = d3.scaleLinear() 912 | .range([aggregate_graph_height*.8 - 10, 0]); 913 | 914 | var fg_width = window.innerWidth *0.75; 915 | global_x = d3.scaleLinear() 916 | .range([0, fg_width - chunk_y_axis_space]); 917 | 918 | // find the max TS value 919 | max_x = xMax(); 920 | global_x.domain([0, max_x]); 921 | 922 | var aggregate_data = memoro.aggregate_all(); 923 | 924 | aggregate_max = memoro.max_aggregate(); 925 | var binned_ag = aggregate_data; 926 | 927 | y.domain(d3.extent(binned_ag, function(v) { return v["value"]; })); 928 | 929 | var yAxisRight = d3.axisRight(y) 930 | .ticks(5) 931 | .tickFormat(bytesToStringNoDecimal); 932 | 933 | var line = d3.line() 934 | .x(function(v) { return global_x(v["ts"]); }) 935 | .y(function(v) { return y(v["value"]); }) 936 | .curve(d3.curveStepAfter); 937 | 938 | var area = d3.area() 939 | .x(function(v) { return global_x(v["ts"]); }) 940 | .y0(aggregate_graph_height*0.8 - 10) 941 | .y1(function(v) { return y(v["value"]); }) 942 | .curve(d3.curveStepAfter); 943 | 944 | d3.select("#fg-aggregate-path").remove(); 945 | d3.select("#fg-aggregate-y-axis").remove(); 946 | 947 | var fg_aggregate_graph_g = d3.select("#fg-aggregate-group"); 948 | fg_aggregate_graph_g.selectAll("rect").remove(); 949 | fg_aggregate_graph_g.selectAll("g").remove(); 950 | 951 | fg_aggregate_graph_g.append("rect") 952 | .attr("width", fg_width-chunk_y_axis_space + 2) 953 | .attr("height", aggregate_graph_height) 954 | .classed("aggregate-background", true); 955 | 956 | 957 | var yaxis_height = aggregate_graph_height; 958 | console.log("y axis height is " + aggregate_graph_height) 959 | 960 | fg_aggregate_graph_g.append("path") 961 | .datum(binned_ag) 962 | .attr("fill", "none") 963 | .attr("stroke", "steelblue") 964 | .attr("stroke-width", 1.5) 965 | .attr("transform", "translate(0, 5)") 966 | .attr("height", yaxis_height) 967 | .attr("id", "fg-aggregate-path") 968 | .attr("d", line); 969 | d3.select("#fg-aggregate-group").append("g") 970 | .attr("class", "y axis") 971 | .attr("transform", "translate(" + (fg_width - chunk_y_axis_space + 3) + ", 5)") 972 | .attr("height", yaxis_height) 973 | .attr("id", "fg-aggregate-y-axis") 974 | .style("fill", "white") 975 | .call(yAxisRight); 976 | 977 | fg_aggregate_graph_g.append("path") 978 | .datum(binned_ag) 979 | .classed("area", true) 980 | .attr("transform", "translate(0, 5)") 981 | .attr("id", "fg-aggregate-area") 982 | .attr("d", area); 983 | 984 | // draw the focus line on the graph 985 | var focus_g = fg_aggregate_graph_g.append("g") 986 | .classed("focus-line", true); 987 | 988 | fg_aggregate_graph_g.append("rect") 989 | .attr("width", fg_width-chunk_y_axis_space + 2) 990 | .attr("height", yaxis_height) 991 | .style("fill", "transparent") 992 | .on("mouseover", function() { focus_g.style("display", null); }) 993 | .on("mouseout", function() { focus_g.style("display", "none"); }) 994 | .on("mousemove", mousemove); 995 | 996 | focus_g.append("line") 997 | .attr("y0", 0).attr("y1", yaxis_height-5) 998 | .attr("x0", 0).attr("x1", 0); 999 | var text = focus_g.append("text") 1000 | .attr("x", 20) 1001 | .attr("y", "30"); 1002 | text.append("tspan") 1003 | .classed("focus-text", true) 1004 | .attr("id", "time"); 1005 | text.append("tspan") 1006 | .classed("focus-text", true) 1007 | .attr("id", "value"); 1008 | 1009 | // display on mouseover 1010 | function mousemove() { 1011 | var x0 = global_x.invert(d3.mouse(this)[0]); 1012 | var y_pos = d3.bisector(function(d) { 1013 | return d["ts"]; 1014 | }).left(binned_ag, x0, 1); 1015 | var y = binned_ag[y_pos-1]["value"]; 1016 | focus_g.attr("transform", "translate(" + global_x(x0) + ",0)"); 1017 | focus_g.select("#time").text(Math.round(x0) + "c " + bytesToString(y)); 1018 | if (d3.mouse(this)[0] > fg_width / 2) 1019 | focus_g.select("text").attr("x", -170); 1020 | else 1021 | focus_g.select("text").attr("x", 20); 1022 | } 1023 | 1024 | } 1025 | 1026 | function drawAggregatePath() { 1027 | 1028 | y = d3.scaleLinear() 1029 | .range([aggregate_graph_height*.8 - 10, 0]); 1030 | 1031 | max_x = xMax(); 1032 | 1033 | var aggregate_data = memoro.aggregate_all(); 1034 | 1035 | aggregate_max = memoro.max_aggregate(); 1036 | var binned_ag = aggregate_data; 1037 | 1038 | y.domain(d3.extent(binned_ag, function(v) { return v["value"]; })); 1039 | 1040 | var yAxisRight = d3.axisRight(y) 1041 | .ticks(5) 1042 | .tickFormat(bytesToStringNoDecimal); 1043 | 1044 | var line = d3.line() 1045 | .x(function(v) { return x(v["ts"]); }) 1046 | .y(function(v) { return y(v["value"]); }) 1047 | .curve(d3.curveStepAfter); 1048 | 1049 | var area = d3.area() 1050 | .x(function(v) { return x(v["ts"]); }) 1051 | .y0(aggregate_graph_height*0.8 - 10) 1052 | .y1(function(v) { return y(v["value"]); }) 1053 | .curve(d3.curveStepAfter); 1054 | 1055 | d3.select("#aggregate-path").remove(); 1056 | d3.select("#aggregate-y-axis").remove(); 1057 | 1058 | var aggregate_graph_g = d3.select("#aggregate-group"); 1059 | aggregate_graph_g.selectAll("rect").remove(); 1060 | aggregate_graph_g.selectAll("g").remove(); 1061 | 1062 | aggregate_graph_g.append("rect") 1063 | .attr("width", chunk_graph_width-chunk_y_axis_space + 2) 1064 | .attr("height", aggregate_graph_height) 1065 | //.style("fill", "#353a41") 1066 | .classed("aggregate-background", true); 1067 | 1068 | var yaxis_height = .8 * aggregate_graph_height; 1069 | aggregate_graph_g.append("path") 1070 | .datum(binned_ag) 1071 | .attr("fill", "none") 1072 | .attr("stroke", "steelblue") 1073 | .attr("stroke-width", 1.5) 1074 | .attr("transform", "translate(0, 5)") 1075 | .attr("id", "aggregate-path") 1076 | .attr("d", line); 1077 | d3.select("#aggregate-group").append("g") 1078 | .attr("class", "y axis") 1079 | .attr("transform", "translate(" + (chunk_graph_width - chunk_y_axis_space + 3) + ", 5)") 1080 | //.attr("height", yaxis_height) 1081 | .attr("id", "aggregate-y-axis") 1082 | .style("fill", "white") 1083 | .call(yAxisRight); 1084 | 1085 | aggregate_graph_g.append("path") 1086 | .datum(binned_ag) 1087 | .classed("area", true) 1088 | .attr("transform", "translate(0, 5)") 1089 | .attr("id", "aggregate-area") 1090 | .attr("d", area); 1091 | 1092 | // draw the focus line on the graph 1093 | var focus_g = aggregate_graph_g.append("g") 1094 | .classed("focus-line", true); 1095 | 1096 | aggregate_graph_g.append("rect") 1097 | .attr("width", chunk_graph_width-chunk_y_axis_space + 2) 1098 | .attr("height", yaxis_height) 1099 | .style("fill", "transparent") 1100 | .on("mouseover", function() { focus_g.style("display", null); }) 1101 | .on("mouseout", function() { focus_g.style("display", "none"); }) 1102 | .on("mousemove", mousemove); 1103 | 1104 | focus_g.append("line") 1105 | .attr("y0", 0).attr("y1", yaxis_height-5) 1106 | .attr("x0", 0).attr("x1", 0); 1107 | var text = focus_g.append("text") 1108 | .classed("focus-text", true) 1109 | .attr("x", 20) 1110 | .attr("y", "30"); 1111 | text.append("tspan") 1112 | .attr("id", "time"); 1113 | text.append("tspan") 1114 | .attr("id", "value"); 1115 | 1116 | // display on mouseover 1117 | function mousemove() { 1118 | var x0 = x.invert(d3.mouse(this)[0]); 1119 | var y_pos = d3.bisector(function(d) { 1120 | return d["ts"]; 1121 | }).left(binned_ag, x0, 1); 1122 | var y = binned_ag[y_pos-1]["value"]; 1123 | focus_g.attr("transform", "translate(" + x(x0) + ",0)"); 1124 | focus_g.select("#time").text(Math.round(x0) + "c " + bytesToString(y)); 1125 | if (d3.mouse(this)[0] > chunk_graph_width / 2) 1126 | focus_g.select("text").attr("x", -170); 1127 | else 1128 | focus_g.select("text").attr("x", 20); 1129 | } 1130 | } 1131 | 1132 | // copied this color hashing generation stuff from the d3 flamegraph code 1133 | // API is not clearly documented wrt the flameGraph::color function 1134 | function generateHash(name) { 1135 | // Return a vector (0.0->1.0) that is a hash of the input string. 1136 | // The hash is computed to favor early characters over later ones, so 1137 | // that strings with similar starts have similar vectors. Only the first 1138 | // 6 characters are considered. 1139 | var hash = 0, weight = 1, max_hash = 0, mod = 10, max_char = 6; 1140 | if (name) { 1141 | for (var i = 0; i < name.length; i++) { 1142 | if (i > max_char) { break; } 1143 | hash += weight * (name.charCodeAt(i) % mod); 1144 | max_hash += weight * (mod - 1); 1145 | weight *= 0.70; 1146 | } 1147 | if (max_hash > 0) { hash = hash / max_hash; } 1148 | } 1149 | return hash; 1150 | } 1151 | 1152 | function colorHash(name) { 1153 | // Return an rgb() color string that is a hash of the provided name, 1154 | // and with a warm palette. 1155 | var vector = 0; 1156 | if (name) { 1157 | var nameArr = name.split('`'); 1158 | if (nameArr.length > 1) { 1159 | name = nameArr[nameArr.length -1]; // drop module name if present 1160 | } 1161 | name = name.split('(')[0]; // drop extra info 1162 | vector = generateHash(name); 1163 | } 1164 | var r = 0 + Math.round(200 * vector); 1165 | var g = 150 + Math.round(50 * (1 - vector)); 1166 | var b = 220 + Math.round(25 * (1 - vector)); 1167 | return "rgb(" + r + "," + g + "," + b + ")"; 1168 | } 1169 | 1170 | function name(d) { 1171 | return d.data.n || d.data.name; 1172 | } 1173 | 1174 | var colorMapper = function(d) { 1175 | if ('lifetime_score' in d.data) { 1176 | var mean = gmean([d.data.lifetime_score < 0.01 ? 0.01 : d.data.lifetime_score, d.data.usage_score < 0.01 ? 0.01 : d.data.usage_score, 1177 | d.data.useful_lifetime_score < 0.01 ? 0.01 : d.data.useful_lifetime_score]); 1178 | // console.log(mean); 1179 | var badness_col = Math.round((1.0 - mean) * (badness_colors.length - 1)); 1180 | // console.log(badness_col); 1181 | return badness_colors[badness_col]; 1182 | } 1183 | return d.highlight ? "#E600E6" : colorHash(name(d)); 1184 | }; 1185 | 1186 | function filterTree(tree) { 1187 | 1188 | if (filter_words.length === 0) 1189 | return false; 1190 | 1191 | for (var w in filter_words) { 1192 | if (tree.name.indexOf(filter_words[w]) !== -1) { 1193 | // found, node has keyword so keep all descendants 1194 | console.log("found " + filter_words[w] + " in " + tree.name); 1195 | return false; 1196 | } 1197 | } 1198 | 1199 | if ('children' in tree) { 1200 | for (var i = tree.children.length - 1; i >= 0; i--) { 1201 | var value = tree.children[i].value; 1202 | var remove = filterTree(tree.children[i]); 1203 | if (remove) { 1204 | console.log("deleting child"); 1205 | tree.value -= value; 1206 | tree.children.splice(i, 1); 1207 | } 1208 | } 1209 | } 1210 | 1211 | if ('children' in tree && tree.children.length === 0) { 1212 | delete tree.children; 1213 | return true; 1214 | } else if (!('children' in tree)) 1215 | return true; 1216 | else return false; 1217 | 1218 | 1219 | } 1220 | 1221 | function drawFlameGraph() { 1222 | switch (current_fg_type) { 1223 | case "bytes_time": 1224 | memoro.stacktree_by_bytes(current_fg_time); 1225 | break; 1226 | case "bytes_total": 1227 | memoro.stacktree_by_bytes_total(current_fg_time); 1228 | break; 1229 | case "num_allocs": 1230 | default: 1231 | memoro.stacktree_by_numallocs(); 1232 | } 1233 | 1234 | var tree = memoro.stacktree(); 1235 | filterTree(tree); // it just seems easier to filter this here ... 1236 | console.log(tree); 1237 | d3.select("#flame-graph-div").html(""); 1238 | 1239 | var fg_width = window.innerWidth *0.70; // getboundingclientrect isnt working i dont understand this crap 1240 | var fgg = d3.flameGraph() 1241 | .width(fg_width) 1242 | .height(window.innerHeight*0.65) 1243 | .cellHeight(17) 1244 | .transitionDuration(400) 1245 | .transitionEase(d3.easeCubic) 1246 | .minFrameSize(0) 1247 | .sort(true) 1248 | //Example to sort in reverse order 1249 | //.sort(function(a,b){ return d3.descending(a.name, b.name);}) 1250 | .title(""); 1251 | 1252 | /* fgg.onClick(function (d) { 1253 | console.info("You clicked on frame "+ d.data.name); 1254 | });*/ 1255 | 1256 | var tip = d3.tip() 1257 | .direction("s") 1258 | .offset([8, 0]) 1259 | .attr('class', 'd3-flame-graph-tip') 1260 | .html(function(d) { 1261 | var ret = ""; 1262 | if (current_fg_type === "num_allocs") 1263 | ret = d.data.name + ", NumAllocs: " + d.data.value; 1264 | else 1265 | ret = d.data.name + ", bytes: " + bytesToString(d.data.value, 2); 1266 | 1267 | if ('lifetime_score' in d.data) { 1268 | ret += "
Lifetime: " + d.data.lifetime_score.toFixed(2) + " Usage: " + d.data.usage_score.toFixed(2) 1269 | + " Useful Life: " + d.data.useful_lifetime_score.toFixed(2); 1270 | } 1271 | return ret; 1272 | 1273 | }); 1274 | 1275 | fgg.tooltip(tip); 1276 | fgg.color(colorMapper); 1277 | 1278 | var details = document.getElementById("flame-graph-details"); 1279 | 1280 | fgg.details(details); 1281 | 1282 | d3.select("#flame-graph-div") 1283 | .datum(tree) 1284 | .call(fgg); 1285 | } 1286 | 1287 | function tabSwitchClick() { 1288 | 1289 | // doing this because when you start on global tab, 1290 | // stuff is hidden and i dont know how to get these values 1291 | // when the detailed tab is hidden 1292 | // fucking javascript 1293 | if (chunk_graph_width === 0) { 1294 | chunk_graph_width = d3.select("#chunks-container").node().getBoundingClientRect().width; 1295 | chunk_graph_height = d3.select("#chunks").node().getBoundingClientRect().height; 1296 | console.log("width is now " + chunk_graph_width); 1297 | 1298 | clearChunks(); 1299 | drawStackTraces(); 1300 | drawChunkXAxis(); 1301 | 1302 | drawAggregatePath(); 1303 | drawAggregateAxis(); 1304 | } 1305 | } 1306 | 1307 | function drawEverything() { 1308 | 1309 | d3.selectAll("g > *").remove(); 1310 | d3.selectAll("svg").remove(); 1311 | 1312 | barHeight = 12; 1313 | chunk_graph_width = window.innerWidth / 2; 1314 | chunk_graph_height = d3.select("#chunks").node().getBoundingClientRect().height; 1315 | console.log("chunk graph height " + chunk_graph_height + " width " + chunk_graph_width); 1316 | chunk_y_axis_space = chunk_graph_width*0.13; // ten percent should be enough 1317 | 1318 | x = d3.scaleLinear() 1319 | .range([0, chunk_graph_width - chunk_y_axis_space]); 1320 | 1321 | // find the max TS value 1322 | max_x = xMax(); 1323 | x.domain([0, max_x]); 1324 | 1325 | drawChunkXAxis(); 1326 | 1327 | aggregate_graph_height = d3.select("#aggregate-graph").node().getBoundingClientRect().height; 1328 | if (aggregate_graph_height === 0) { 1329 | aggregate_graph_height = d3.select("#fg-aggregate-graph").node().getBoundingClientRect().height; 1330 | } 1331 | console.log("agg graph height is " + aggregate_graph_height); 1332 | 1333 | var aggregate_graph = d3.select("#aggregate-graph") 1334 | .append("svg") 1335 | .attr("width", chunk_graph_width) 1336 | .attr("height", aggregate_graph_height-10); 1337 | 1338 | aggregate_graph.on("mousedown", function() { 1339 | 1340 | var p = d3.mouse(this); 1341 | 1342 | aggregate_graph.append( "rect") 1343 | .attr("rx", 6) 1344 | .attr("ry", 6) 1345 | .attr("x", p[0]) 1346 | .attr("y", 0) 1347 | .attr("height", aggregate_graph_height*.8 - 5) 1348 | .attr("width", 0) 1349 | .classed("selection", true) 1350 | .attr("id", "select-rect") 1351 | }) 1352 | .on( "mousemove", function() { 1353 | var s = aggregate_graph.select("#select-rect"); 1354 | 1355 | var y = d3.select(this).attr("height"); 1356 | if( !s.empty()) { 1357 | var p = d3.mouse( this), 1358 | d = { 1359 | x : parseInt( s.attr( "x"), 0), 1360 | y : parseInt( s.attr( "y"), 0), 1361 | width : parseInt( s.attr( "width"), 10), 1362 | height : y 1363 | }, 1364 | move = { 1365 | x : p[0] - d.x, 1366 | y : p[1] - d.y 1367 | }; 1368 | 1369 | if( move.x < 1 || (move.x*2Total Allocations: " + total_chunks + 1495 | "
Max Heap: " + bytesToString(aggregate_max) + 1496 | "
Global alloc time: " + alloc_time + 1497 | "
which is " + percent_alloc_time.toFixed(2) + "% of program time." + 1498 | "
Avg Lifetime: " + avg_lifetime.toFixed(2) + " \u03C3 " + lifetime_var.toFixed(2) + 1499 | "
Avg Usage: " + avg_usage.toFixed(2) + " \u03C3 " + usage_var.toFixed(2) + 1500 | "
Avg Useful Life: " + avg_useful_life.toFixed(2) + " \u03C3 " + useful_life_var.toFixed(2); 1501 | info.html(html); 1502 | var fg_info = d3.select("#global-info-fg"); 1503 | fg_info.html(html); 1504 | 1505 | } 1506 | var current_filter = "trace"; 1507 | function typeFilterClick() { 1508 | current_filter = "type"; 1509 | document.getElementById("filter-select").innerHTML = "Type "; 1510 | document.getElementById("filter-select-fg").innerHTML = "Type "; 1511 | } 1512 | 1513 | function stackFilterClick() { 1514 | console.log("stack filter click"); 1515 | current_filter = "trace"; 1516 | document.getElementById("filter-select").innerHTML = "Trace "; 1517 | document.getElementById("filter-select-fg").innerHTML = "Type "; 1518 | } 1519 | 1520 | function stackFilter() { 1521 | showLoader(); 1522 | setTimeout(function() { 1523 | var element = document.querySelector("#filter-form"); 1524 | if (element.value === "") 1525 | element = document.querySelector("#filter-form-fg"); 1526 | var filterText = element.value; 1527 | element.value = ""; 1528 | var filterWords = filterText.split(" "); 1529 | 1530 | for (var w in filterWords) { 1531 | filter_words.push(filterWords[w]); 1532 | console.log("setting filter " + filterWords[w]); 1533 | memoro.set_trace_keyword(filterWords[w]); 1534 | } 1535 | clearChunks(); 1536 | drawStackTraces(); 1537 | drawChunkXAxis(); 1538 | 1539 | drawAggregatePath(); 1540 | drawAggregateAxis(); 1541 | drawGlobalAggregatePath(); 1542 | drawGlobalAggregateAxis(); 1543 | drawFlameGraph(); 1544 | hideLoader(); 1545 | setGlobalInfo(); 1546 | }, 100); 1547 | } 1548 | 1549 | function stackFilterResetClick() { 1550 | //drawChunks(); 1551 | showLoader(); 1552 | filter_words = []; 1553 | setTimeout(function() { 1554 | memoro.trace_filter_reset(); 1555 | clearChunks(); 1556 | drawStackTraces(); 1557 | drawChunkXAxis(); 1558 | 1559 | drawAggregatePath(); 1560 | drawAggregateAxis(); 1561 | drawGlobalAggregatePath(); 1562 | drawGlobalAggregateAxis(); 1563 | drawFlameGraph(); 1564 | hideLoader(); 1565 | setGlobalInfo(); 1566 | }, 100); 1567 | } 1568 | 1569 | function typeFilter() { 1570 | showLoader(); 1571 | setTimeout(function() { 1572 | var element = document.querySelector("#filter-form"); 1573 | var filterText = element.value; 1574 | element.value = ""; 1575 | var filterWords = filterText.split(" "); 1576 | 1577 | for (var w in filterWords) { 1578 | console.log("setting filter " + filterWords[w]); 1579 | memoro.set_type_keyword(filterWords[w]); 1580 | } 1581 | clearChunks(); 1582 | drawStackTraces(); 1583 | drawChunkXAxis(); 1584 | 1585 | drawAggregatePath(); 1586 | drawAggregateAxis(); 1587 | drawGlobalAggregatePath(); 1588 | drawGlobalAggregateAxis(); 1589 | drawFlameGraph(); 1590 | hideLoader(); 1591 | setGlobalInfo(); 1592 | }, 100); 1593 | } 1594 | 1595 | function typeFilterResetClick() { 1596 | //drawChunks(); 1597 | showLoader(); 1598 | setTimeout(function() { 1599 | memoro.type_filter_reset(); 1600 | clearChunks(); 1601 | drawStackTraces(); 1602 | drawChunkXAxis(); 1603 | 1604 | drawAggregatePath(); 1605 | drawAggregateAxis(); 1606 | drawGlobalAggregatePath(); 1607 | drawGlobalAggregateAxis(); 1608 | drawFlameGraph(); 1609 | hideLoader(); 1610 | setGlobalInfo(); 1611 | }, 100); 1612 | } 1613 | 1614 | function filterExecuteClick() { 1615 | if (current_filter === "trace") 1616 | stackFilter(); 1617 | else 1618 | typeFilter(); 1619 | } 1620 | 1621 | function resetTimeClick() { 1622 | showLoader(); 1623 | setTimeout(function() { 1624 | memoro.filter_minmax_reset(); 1625 | clearChunks(); 1626 | drawStackTraces(); 1627 | drawChunkXAxis(); 1628 | 1629 | drawAggregatePath(); 1630 | drawAggregateAxis(); 1631 | hideLoader(); 1632 | setGlobalInfo(); 1633 | }, 100); 1634 | } 1635 | 1636 | function showFilterHelp() { 1637 | showModal("Filter Trace/Type", "Dropdown to the left of the input box specifies whether you want to filter stack \F\ 1638 | traces or types by keyword. \ 1639 | Enter space separated keywords. Any trace not containing those keywords (or not matching the specified types) will be filtered out.
", 1640 | "fa-info-circle"); 1641 | } 1642 | 1643 | function flameGraphHelp() { 1644 | showModal("Flame Graph", "Several aggregate data can be displayed by the global flame graph. \ 1645 | First, the total number of allocations (#Allocations) of each allocation point in the code across total program lifetime. \ 1646 | Second, the flame graph can show bytes allocated by each allocation point at a specific point in time. Choose the \ 1647 | specific time point by clicking on the aggregate graph below.", "fa-info-circle") 1648 | } 1649 | 1650 | function globalInfoHelp() { 1651 | showModal("Global Info", "Globally applicable data.
\ 1652 | Total alloc points: Total number of allocation points in the profiled run.
\ 1653 | Total Allocations: Total number of allocations made over the program lifetime.
\ 1654 | Max Heap: Bytes allocated on the heap at it's maximum point in time.
\ 1655 | Global Alloc time: Approximate time spent in allocation functions. This tends to be overestimated due to instrumentation.
\ 1656 |
The following scores provide quantitative intuition into how good or bad allocations from particular points were in several categories. \ 1657 | 1.0 is best, 0.0 is worst. See the documentation for details on how these are calculated.
\ 1658 | Avg Lifetime: The average lifetime score and variance of all allocation points.
\ 1659 | Avg Usage: The average usage score and variance of all allocation points.
\ 1660 | Avg Useful Life: The average useful life score and variance of all allocation points.
", "fa-info-circle") 1661 | } 1662 | 1663 | function setFlameGraphNumAllocs() { 1664 | if (current_fg_type != "num_allocs") { 1665 | current_fg_type = "num_allocs"; 1666 | drawFlameGraph(); 1667 | } 1668 | } 1669 | 1670 | function setFlameGraphBytesTime() { 1671 | if (current_fg_type != "bytes_time") { 1672 | current_fg_type = "bytes_time"; 1673 | drawFlameGraph(); 1674 | } 1675 | } 1676 | 1677 | function setFlameGraphBytesTotal() { 1678 | if (current_fg_type != "bytes_total") { 1679 | current_fg_type = "bytes_total"; 1680 | drawFlameGraph(); 1681 | } 1682 | } 1683 | 1684 | function traceSort(pred) { 1685 | current_sort_order = pred; 1686 | sortTraces(); 1687 | drawStackTraces(); 1688 | } 1689 | 1690 | // TODO separate out functionality, modularize 1691 | module.exports = { 1692 | updateData: updateData, 1693 | stackFilterClick: stackFilterClick, 1694 | stackFilterResetClick: stackFilterResetClick, 1695 | typeFilterClick: typeFilterClick, 1696 | typeFilterResetClick: typeFilterResetClick, 1697 | filterExecuteClick: filterExecuteClick, 1698 | filterFgExecuteClick: filterExecuteClick, 1699 | chunkScroll: chunkScroll, 1700 | traceScroll: traceScroll, 1701 | resetTimeClick: resetTimeClick, 1702 | showFilterHelp: showFilterHelp, 1703 | setFlameGraphBytesTime: setFlameGraphBytesTime, 1704 | setFlameGraphBytesTotal: setFlameGraphBytesTotal, 1705 | setFlameGraphNumAllocs: setFlameGraphNumAllocs, 1706 | flameGraphHelp: flameGraphHelp, 1707 | traceSort: traceSort, 1708 | globalInfoHelp: globalInfoHelp, 1709 | tabSwitchClick: tabSwitchClick 1710 | }; 1711 | --------------------------------------------------------------------------------