GitOrigin-RevId: f0eab26398
tags/v1.5.0
| @@ -1,3 +1,11 @@ | |||||
| #! /usr/bin/env python3 | |||||
| # MegEngine is Licensed under the Apache License, Version 2.0 (the "License") | |||||
| # | |||||
| # Copyright (c) 2014-2021 Megvii Inc. All rights reserved. | |||||
| # | |||||
| # Unless required by applicable law or agreed to in writing, | |||||
| # software distributed under the License is distributed on an | |||||
| # "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| import argparse | import argparse | ||||
| import getopt | import getopt | ||||
| import os | import os | ||||
| @@ -18,6 +18,8 @@ | |||||
| var svg = undefined; | var svg = undefined; | ||||
| var svgWidth = undefined; | var svgWidth = undefined; | ||||
| var svgHeight = undefined; | var svgHeight = undefined; | ||||
| var resetBtn = document.getElementById('resetBtn'); | |||||
| var relocBtn = document.getElementById('relocBtn'); | |||||
| var loadDesc = (svgElem) => { | var loadDesc = (svgElem) => { | ||||
| var mgeType = svgElem.attributes['tag:type']; | var mgeType = svgElem.attributes['tag:type']; | ||||
| @@ -52,7 +54,7 @@ | |||||
| lastColor = undefined; | lastColor = undefined; | ||||
| }; | }; | ||||
| function recLoadSVG(svgElem) { | |||||
| var recLoadSVG = svgElem => { | |||||
| if (svgElem.children === undefined) { | if (svgElem.children === undefined) { | ||||
| return; | return; | ||||
| } | } | ||||
| @@ -71,8 +73,37 @@ | |||||
| recLoadSVG(child); | recLoadSVG(child); | ||||
| } | } | ||||
| } | } | ||||
| var scaleBoard = (x, y) => { | |||||
| var transform = 'scale(' + x + ',' + y + ')'; | |||||
| svg.setAttribute('transform', transform); | |||||
| board.style['width'] = svgWidth * x; | |||||
| board.style['height'] = svgHeight * y; | |||||
| } | |||||
| var autoScaleBoard = () => { | |||||
| var hRangeValue = Math.sqrt(Number(hRange.value) / 10); | |||||
| var vRangeValue = Math.sqrt(Number(vRange.value) / 10); | |||||
| scaleBoard(Number(hRangeValue), Number(vRangeValue)); | |||||
| } | |||||
| var zoomBoard = dScale => { | |||||
| scale *= dScale; | |||||
| scaleBoard(scale, scale); | |||||
| }; | |||||
| function loadSVG() { | |||||
| var reset = () => { | |||||
| if (!svgHeight || !svgWidth) { | |||||
| return; | |||||
| } | |||||
| var vScale = window.screen.availHeight / svgHeight; | |||||
| var hScale = window.screen.availWidth / svgWidth; | |||||
| var minScale = Math.min(hScale, vScale); | |||||
| zoomBoard(minScale / scale); | |||||
| window.scrollTo({ | |||||
| top: 0, | |||||
| left: 0, | |||||
| }); | |||||
| }; | |||||
| var loadSVG = () => { | |||||
| var file = fileInput.files[0]; | var file = fileInput.files[0]; | ||||
| var reader = new FileReader(); | var reader = new FileReader(); | ||||
| reader.readAsText(file, "UTF-8"); | reader.readAsText(file, "UTF-8"); | ||||
| @@ -98,26 +129,10 @@ | |||||
| info.innerHTML = elemList.join(''); | info.innerHTML = elemList.join(''); | ||||
| } | } | ||||
| } | } | ||||
| setTimeout(reset, 1); | |||||
| }; | }; | ||||
| } | } | ||||
| function scaleBoard(x, y) { | |||||
| var transform = 'scale(' + x + ',' + y + ')'; | |||||
| svg.setAttribute('transform', transform); | |||||
| board.style['width'] = svgWidth * x; | |||||
| board.style['height'] = svgHeight * y; | |||||
| } | |||||
| function autoScaleBoard() { | |||||
| var hRangeValue = Math.sqrt(Number(hRange.value) / 10); | |||||
| var vRangeValue = Math.sqrt(Number(vRange.value) / 10); | |||||
| scaleBoard(Number(hRangeValue), Number(vRangeValue)); | |||||
| } | |||||
| fileInput.onchange = loadSVG; | |||||
| var zoomBoard = dScale => { | |||||
| scale *= dScale; | |||||
| scaleBoard(scale, scale); | |||||
| }; | |||||
| window.addEventListener('wheel', e => { | window.addEventListener('wheel', e => { | ||||
| console.log(e); | |||||
| if (e.ctrlKey) { | if (e.ctrlKey) { | ||||
| e.preventDefault(); | e.preventDefault(); | ||||
| e.stopPropagation(); | e.stopPropagation(); | ||||
| @@ -136,19 +151,39 @@ | |||||
| top: y, | top: y, | ||||
| left: x, | left: x, | ||||
| }); | }); | ||||
| console.log('scroll', [x, y]); | |||||
| } else if (e.altKey) { | |||||
| } | } | ||||
| }, { 'passive': false }); | }, { 'passive': false }); | ||||
| fileInput.onchange = loadSVG; | |||||
| resetBtn.onclick = reset; | |||||
| relocBtn.onclick = () => { | |||||
| if (!lastElem) { | |||||
| return; | |||||
| } | |||||
| y = scale * lastElem.attributes['y'].value; | |||||
| x = scale * lastElem.attributes['x'].value; | |||||
| window.scrollTo({ | |||||
| top: y - window.screen.availHeight/2, | |||||
| left: x - window.screen.availWidth/2, | |||||
| behavior: 'smooth', | |||||
| }); | |||||
| }; | |||||
| }; | }; | ||||
| </script> | </script> | ||||
| <body> | <body> | ||||
| <p id="desc" style="position: fixed;bottom: 0; background-color: white;">desc</p> | |||||
| <p id="info" style="position: fixed;top: 0; right: 0; background-color: white;">info</p> | |||||
| <p id="board" | <p id="board" | ||||
| style="white-space: nowrap; display: flex; justify-content: center; align-content: center; align-items: center; margin: 0;opacity: 0.7;"> | |||||
| style="white-space: nowrap; display: flex; justify-content: center; align-content: center; align-items: center; margin: 0; left: 0;"> | |||||
| </p> | </p> | ||||
| <input type='file' id='fileInput' style="position: fixed; top: 0; background-color: white;"></input> | |||||
| <div style="display: flex; position: fixed; top: 0; left: 0; right: 0; background-color: white; flex-grow: 2;"> | |||||
| <input type='file' id='fileInput'></input> | |||||
| <div style="flex-grow: 1;"></div> | |||||
| <button id='resetBtn'>reset</button> | |||||
| <button id='relocBtn'>reloc</button> | |||||
| </div> | |||||
| <p id="desc" style="position: fixed; bottom: 0; background-color: white;">desc</p> | |||||
| <p id="info" style="position: fixed;top: 0; right: 0; background-color: white;">info</p> | |||||
| </body> | </body> | ||||
| </html> | </html> | ||||
| @@ -0,0 +1,61 @@ | |||||
| #! /usr/bin/env python3 | |||||
| # MegEngine is Licensed under the Apache License, Version 2.0 (the "License") | |||||
| # | |||||
| # Copyright (c) 2014-2021 Megvii Inc. All rights reserved. | |||||
| # | |||||
| # Unless required by applicable law or agreed to in writing, | |||||
| # software distributed under the License is distributed on an | |||||
| # "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| import argparse | |||||
| import contextlib | |||||
| import getopt | |||||
| import http.server | |||||
| import os | |||||
| import runpy | |||||
| import sys | |||||
| import tempfile | |||||
| from megengine.logger import get_logger | |||||
| def main(): | |||||
| parser = argparse.ArgumentParser( | |||||
| prog="megengine.tools.svg_viewer", | |||||
| description="View SVG Graph produced bt megengine profiler", | |||||
| ) | |||||
| parser.add_argument("-p", "--port", type=int, default=8000, help="server port") | |||||
| parser.add_argument( | |||||
| "-a", "--address", type=str, default="localhost", help="server address" | |||||
| ) | |||||
| args = parser.parse_args() | |||||
| address = args.address | |||||
| port = args.port | |||||
| src_filename = "svg_viewer.html" | |||||
| dst_filename = "index.html" | |||||
| src_path = os.path.join(os.path.dirname(__file__), src_filename) | |||||
| url = "http://{}:{}/{}".format("localhost", port, dst_filename) | |||||
| ssh_fwd_cmd = "ssh -L {}:localhost:{} <remote ip>".format(port, port) | |||||
| with tempfile.TemporaryDirectory() as serve_dir: | |||||
| dst_path = os.path.join(serve_dir, dst_filename) | |||||
| os.symlink(src_path, dst_path) | |||||
| os.chdir(serve_dir) | |||||
| get_logger().info("cd to serve directory: {}, starting".format(serve_dir)) | |||||
| server = http.server.HTTPServer( | |||||
| (address, port), http.server.SimpleHTTPRequestHandler | |||||
| ) | |||||
| get_logger().info( | |||||
| "server started, please visit '{}' to watch profiling result".format(url) | |||||
| ) | |||||
| get_logger().info( | |||||
| "if you are in remote environment, use '{}' to forward port to local".format( | |||||
| ssh_fwd_cmd | |||||
| ) | |||||
| ) | |||||
| try: | |||||
| server.serve_forever() | |||||
| except KeyboardInterrupt: | |||||
| get_logger().info("server exiting") | |||||
| if __name__ == "__main__": | |||||
| main() | |||||
| @@ -81,6 +81,7 @@ class Profiler(ContextDecorator): | |||||
| for opt, optval in Profiler.valid_options.items(): | for opt, optval in Profiler.valid_options.items(): | ||||
| self._options[opt] = int(kwargs.pop(opt, optval)) | self._options[opt] = int(kwargs.pop(opt, optval)) | ||||
| self._pid = "<PID>" | self._pid = "<PID>" | ||||
| self._dump_callback = None | |||||
| @property | @property | ||||
| def path(self): | def path(self): | ||||
| @@ -48,7 +48,11 @@ namespace mgb { | |||||
| using namespace profiler; | using namespace profiler; | ||||
| } | } | ||||
| #ifdef __GNUG__ | |||||
| #if defined(_WIN32) || defined(_WIN64) | |||||
| #define SYMBOL_EXPORT __declspec(dllexport) | |||||
| #else | |||||
| #define SYMBOL_EXPORT __attribute__((visibility("default"))) | |||||
| #endif | |||||
| namespace mgb { | namespace mgb { | ||||
| @@ -62,17 +66,17 @@ namespace mgb { | |||||
| * mgb::imperative_log_profile("MY MESSAGE"); | * mgb::imperative_log_profile("MY MESSAGE"); | ||||
| * | * | ||||
| **/ | **/ | ||||
| __attribute__((visibility("default"))) | |||||
| SYMBOL_EXPORT | |||||
| void imperative_log_profile_begin(const char* message) { | void imperative_log_profile_begin(const char* message) { | ||||
| RECORD_EVENT(CustomEvent, std::string{message}); | RECORD_EVENT(CustomEvent, std::string{message}); | ||||
| } | } | ||||
| __attribute__((visibility("default"))) | |||||
| SYMBOL_EXPORT | |||||
| void imperative_log_profile_end(const char* message) { | void imperative_log_profile_end(const char* message) { | ||||
| RECORD_EVENT(CustomFinishEvent, std::string{message}); | RECORD_EVENT(CustomFinishEvent, std::string{message}); | ||||
| } | } | ||||
| __attribute__((visibility("default"))) | |||||
| SYMBOL_EXPORT | |||||
| void imperative_log_profile(const char* message){ | void imperative_log_profile(const char* message){ | ||||
| imperative_log_profile_begin(message); | imperative_log_profile_begin(message); | ||||
| imperative_log_profile_end(message); | imperative_log_profile_end(message); | ||||
| @@ -80,8 +84,6 @@ void imperative_log_profile(const char* message){ | |||||
| } | } | ||||
| #endif | |||||
| std::thread::id ChannelImpl::get_worker_tid() { | std::thread::id ChannelImpl::get_worker_tid() { | ||||
| return m_worker_state.tid; | return m_worker_state.tid; | ||||
| } | } | ||||
| @@ -73,6 +73,8 @@ void Profiler::dump_profile(std::string basename, std::string format, results_t | |||||
| auto thread_dict = get_thread_dict(); | auto thread_dict = get_thread_dict(); | ||||
| if (format == "chrome_timeline.json") { | if (format == "chrome_timeline.json") { | ||||
| profiler::dump_chrome_timeline(basename, options, thread_dict, results); | profiler::dump_chrome_timeline(basename, options, thread_dict, results); | ||||
| } else if (format == "memory_flow.svg") { | |||||
| profiler::dump_memory_flow(basename, options, thread_dict, results); | |||||
| } else { | } else { | ||||
| mgb_log_error("unsupported profiling format %s", format.c_str()); | mgb_log_error("unsupported profiling format %s", format.c_str()); | ||||
| } | } | ||||
| @@ -161,12 +161,10 @@ public: | |||||
| event_list->add(event.to_json()); | event_list->add(event.to_json()); | ||||
| } | } | ||||
| (*result)["traceEvents"] = event_list; | (*result)["traceEvents"] = event_list; | ||||
| //(*result)["localTime"] = json::String::make(std::to_string((double)m_local_time/1e3)); | |||||
| return result; | return result; | ||||
| } | } | ||||
| private: | private: | ||||
| std::vector<ChromeTraceEvent> m_content; | std::vector<ChromeTraceEvent> m_content; | ||||
| uint64_t m_local_time; | |||||
| }; | }; | ||||
| @@ -408,9 +406,7 @@ void dump_chrome_timeline(std::string filename, Profiler::options_t options, Pro | |||||
| }); | }); | ||||
| HANDLE_EVENT(TensorWaitPropEvent, { | HANDLE_EVENT(TensorWaitPropEvent, { | ||||
| auto& tensor_state = state.tensors[event.tensor_id]; | |||||
| NEW_HOST("TensorWaitProp", 'B'); | NEW_HOST("TensorWaitProp", 'B'); | ||||
| //.args(TENSOR_PROPS); | |||||
| if (event.prop == TensorProp::HostValue) { | if (event.prop == TensorProp::HostValue) { | ||||
| INC_COUNTER(wait_value_count, 1); | INC_COUNTER(wait_value_count, 1); | ||||
| } else if (event.prop == TensorProp::Shape) { | } else if (event.prop == TensorProp::Shape) { | ||||
| @@ -433,7 +429,6 @@ void dump_chrome_timeline(std::string filename, Profiler::options_t options, Pro | |||||
| }); | }); | ||||
| HANDLE_EVENT(TensorNotifyPropEvent, { | HANDLE_EVENT(TensorNotifyPropEvent, { | ||||
| auto& tensor_state = state.tensors[event.tensor_id]; | |||||
| NEW_HOST(ssprintf("%d", pid), 's') | NEW_HOST(ssprintf("%d", pid), 's') | ||||
| .id(event.tensor_id) | .id(event.tensor_id) | ||||
| .cat("TensorProp") | .cat("TensorProp") | ||||
| @@ -471,9 +466,7 @@ void dump_chrome_timeline(std::string filename, Profiler::options_t options, Pro | |||||
| }); | }); | ||||
| HANDLE_EVENT(TensorCommandEvent, { | HANDLE_EVENT(TensorCommandEvent, { | ||||
| auto& tensor_state = state.tensors[event.tensor_id]; | |||||
| NEW_HOST(ssprintf("%s %zu", TENSOR_COMMAND_KIND, event.tensor_id), 'B'); | NEW_HOST(ssprintf("%s %zu", TENSOR_COMMAND_KIND, event.tensor_id), 'B'); | ||||
| //.args(TENSOR_PROPS); | |||||
| }); | }); | ||||
| HANDLE_EVENT(TensorCommandFinishEvent, { | HANDLE_EVENT(TensorCommandFinishEvent, { | ||||
| @@ -19,4 +19,6 @@ namespace mgb::imperative::profiler { | |||||
| void dump_chrome_timeline(std::string filename, Profiler::options_t options, Profiler::thread_dict_t thread_dict, Profiler::results_t results); | void dump_chrome_timeline(std::string filename, Profiler::options_t options, Profiler::thread_dict_t thread_dict, Profiler::results_t results); | ||||
| void dump_memory_flow(std::string filename, Profiler::options_t options, Profiler::thread_dict_t thread_dict, Profiler::results_t results); | |||||
| } | } | ||||
| @@ -0,0 +1,306 @@ | |||||
| #include <map> | |||||
| #include <vector> | |||||
| #include <array> | |||||
| #include "megbrain/imperative/utils/to_string.h" | |||||
| #include "megbrain/utils/debug.h" | |||||
| #include "./formats.h" | |||||
| #include "./states.h" | |||||
| #include "./events.h" | |||||
| namespace mgb::imperative::profiler { | |||||
| class XMLWriter { | |||||
| private: | |||||
| std::vector<std::vector<std::string>> elements; | |||||
| public: | |||||
| struct ElementGuard { | |||||
| XMLWriter* writer; | |||||
| std::string name; | |||||
| std::vector<std::pair<std::string, std::string>> attrs; | |||||
| template <typename T> | |||||
| ElementGuard& attr(std::string key, T&& value) { | |||||
| attrs.push_back({key, mgb::imperative::to_string(value)}); | |||||
| return *this; | |||||
| } | |||||
| std::string to_string_start() const { | |||||
| std::string builder; | |||||
| builder.append(ssprintf("<%s", | |||||
| name.c_str())); | |||||
| for (auto&& [k, v]: attrs) { | |||||
| builder.append(ssprintf(" %s=\"%s\"", k.c_str(), v.c_str())); | |||||
| } | |||||
| builder.append(">\n"); | |||||
| return builder; | |||||
| } | |||||
| std::string to_string_end() const { | |||||
| return ssprintf("</%s>\n", name.c_str()); | |||||
| } | |||||
| ElementGuard(XMLWriter* writer, std::string name): writer{writer}, name{name} { | |||||
| writer->elements.emplace_back(); | |||||
| } | |||||
| ~ElementGuard() { | |||||
| auto children = std::move(writer->elements.back()); | |||||
| writer->elements.pop_back(); | |||||
| std::string builder; | |||||
| builder.append(to_string_start()); | |||||
| for (auto&& child: children) { | |||||
| builder.append(child); | |||||
| } | |||||
| builder.append(to_string_end()); | |||||
| writer->elements.back().push_back(builder); | |||||
| } | |||||
| }; | |||||
| XMLWriter() { | |||||
| elements.emplace_back().push_back("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); | |||||
| } | |||||
| ElementGuard element(std::string tag) { | |||||
| return ElementGuard{this, tag}; | |||||
| } | |||||
| void text(std::string text) { | |||||
| elements.back().push_back(text); | |||||
| } | |||||
| void doctype(std::string element, std::string dtd, std::vector<std::string> args) { | |||||
| std::string builder = ssprintf("<!DOCTYPE %s %s", element.c_str(), dtd.c_str()); | |||||
| for (auto&& arg: args) { | |||||
| builder.append(ssprintf(" %s", arg.c_str())); | |||||
| } | |||||
| builder.append(">\n"); | |||||
| elements.back().push_back(builder); | |||||
| } | |||||
| std::string to_string() const { | |||||
| mgb_assert(elements.size() == 1 && elements[0].size() >= 1); | |||||
| std::string builder; | |||||
| for (auto&& element: elements[0]) { | |||||
| builder.append(element); | |||||
| } | |||||
| return builder; | |||||
| } | |||||
| }; | |||||
| struct MemoryChunk { | |||||
| std::array<uintptr_t, 2> address; | |||||
| std::string name; | |||||
| TensorLayout layout; | |||||
| std::array<uint64_t, 2> time; | |||||
| bool empty() const { | |||||
| return address[1] - address[0] == 0; | |||||
| } | |||||
| }; | |||||
| struct MemoryFlow { | |||||
| std::unordered_map<uint64_t, MemoryChunk> chunks; | |||||
| std::pair<uintptr_t, uintptr_t> address_range() const { | |||||
| auto addr_begin = std::numeric_limits<uintptr_t>::max(); | |||||
| auto addr_end = std::numeric_limits<uintptr_t>::min(); | |||||
| for(auto&& [id, chunk]: chunks) { | |||||
| if (chunk.empty()) continue; | |||||
| addr_begin = std::min(addr_begin, chunk.address[0]); | |||||
| addr_end = std::max(addr_end, chunk.address[1]); | |||||
| } | |||||
| return {addr_begin, addr_end}; | |||||
| } | |||||
| std::pair<uint64_t, uint64_t> time_range() const { | |||||
| auto time_begin = std::numeric_limits<uint64_t>::max(); | |||||
| auto time_end = std::numeric_limits<uint64_t>::min(); | |||||
| for(auto&& [id, chunk]: chunks) { | |||||
| if (chunk.empty()) continue; | |||||
| time_begin = std::min(time_begin, chunk.time[0]); | |||||
| time_end = std::max(time_end, chunk.time[1]); | |||||
| } | |||||
| return {time_begin, time_end}; | |||||
| } | |||||
| std::shared_ptr<json::Array> to_json() const { | |||||
| auto results = json::Array::make(); | |||||
| for(auto&& [id, chunk]: chunks) { | |||||
| if (chunk.empty()) continue; | |||||
| auto address = json::Array::make(); | |||||
| auto time = json::Array::make(); | |||||
| address->add(json::String::make(std::to_string(chunk.address[0]))); | |||||
| address->add(json::String::make(std::to_string(chunk.address[1]))); | |||||
| time->add(json::String::make(std::to_string(chunk.time[0]))); | |||||
| time->add(json::String::make(std::to_string(chunk.time[1]))); | |||||
| results->add(json::Object::make({ | |||||
| {"address", address}, | |||||
| {"name", json::String::make(chunk.name)}, | |||||
| {"layout", json::String::make(chunk.layout.to_string())}, | |||||
| {"time", time} | |||||
| })); | |||||
| } | |||||
| return results; | |||||
| } | |||||
| XMLWriter to_svg() const { | |||||
| XMLWriter writer; | |||||
| auto&& [addr_begin, addr_end] = address_range(); | |||||
| auto&& [time_begin, time_end] = time_range(); | |||||
| writer.doctype("svg", "PUBLIC", { | |||||
| "\"-//W3C//DTD SVG 1.1//EN\"", | |||||
| "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"" | |||||
| }); | |||||
| auto svg = writer.element("svg"); | |||||
| svg.attr("xmlns", std::string{"http://www.w3.org/2000/svg"}); | |||||
| svg.attr("xmlns:tag", std::string{"https://megengine.org.cn"}); | |||||
| double time_scale = 1e5; | |||||
| double addr_scale = 1e6; | |||||
| svg.attr("width", (time_end-time_begin)/time_scale); | |||||
| svg.attr("height", (addr_end-addr_begin)/addr_scale); | |||||
| { | |||||
| auto rect = writer.element("rect"); | |||||
| rect.attr("x", 0); | |||||
| rect.attr("y", 0); | |||||
| rect.attr("width", (time_end-time_begin)/time_scale); | |||||
| rect.attr("height", (addr_end-addr_begin)/addr_scale); | |||||
| rect.attr("fill", std::string{"blue"}); | |||||
| } | |||||
| double us = 1e3, ms = 1e6; | |||||
| std::map<double, std::string> time2color = { | |||||
| {0 * us, "#DDDDDD"}, | |||||
| {100 * us, "#CCCCCC"}, | |||||
| {1 * ms, "#BBBBBB"}, | |||||
| {10 * ms, "#AAAAAA"}, | |||||
| {100 * ms, "#999999"}, | |||||
| {1000 * ms, "#888888"}, | |||||
| {std::numeric_limits<double>::infinity(), "#555555"}, | |||||
| }; | |||||
| auto time2str = [](uint64_t ns){ | |||||
| using pair_t = std::pair<uint64_t, const char*>; | |||||
| static pair_t units[] = { | |||||
| {1, "ns "}, | |||||
| {1e3, "us "}, | |||||
| {1e6, "ms "}, | |||||
| {1e9, "s "}, | |||||
| }; | |||||
| std::string builder; | |||||
| auto comparator = [](const pair_t& lhs, const pair_t& rhs) { | |||||
| return lhs.first < rhs.first; | |||||
| }; | |||||
| while (ns > 0) { | |||||
| auto iter = std::upper_bound(std::begin(units), std::end(units), std::make_pair(ns, ""), comparator) - 1; | |||||
| builder += std::to_string(ns / iter->first) + iter->second; | |||||
| ns = ns % iter->first; | |||||
| } | |||||
| return builder; | |||||
| }; | |||||
| auto size2str = [](size_t sz){ | |||||
| using pair_t = std::pair<size_t, const char*>; | |||||
| static pair_t units[] = { | |||||
| {1, "B "}, | |||||
| {1024, "KB "}, | |||||
| {1024*1024, "MB "}, | |||||
| {1024*1024*1024, "GB "}, | |||||
| }; | |||||
| std::string builder; | |||||
| auto comparator = [](const pair_t& lhs, const pair_t& rhs) { | |||||
| return lhs.first < rhs.first; | |||||
| }; | |||||
| while (sz > 0) { | |||||
| auto iter = std::upper_bound(std::begin(units), std::end(units), std::make_pair(sz, ""), comparator) - 1; | |||||
| builder += std::to_string(sz / iter->first) + iter->second; | |||||
| sz = sz % iter->first; | |||||
| } | |||||
| return builder; | |||||
| }; | |||||
| for (auto&& [id, chunk]: chunks) { | |||||
| if (chunk.empty()) continue; | |||||
| double left = (chunk.time[0]-time_begin)/time_scale; | |||||
| double right = (chunk.time[1]-time_begin)/time_scale; | |||||
| double top = (chunk.address[0]-addr_begin)/addr_scale; | |||||
| double bottom = (chunk.address[1]-addr_begin)/addr_scale; | |||||
| double duration = chunk.time[1] - chunk.time[0]; | |||||
| { | |||||
| auto rect = writer.element("rect"); | |||||
| rect.attr("x", left); | |||||
| rect.attr("y", top); | |||||
| rect.attr("height", bottom - top); | |||||
| rect.attr("width", right - left); | |||||
| rect.attr("fill", time2color.lower_bound(duration)->second); | |||||
| auto mge_attr = [&](const char* name, auto&& value) { | |||||
| rect.attr(ssprintf("tag:%s", name), value); | |||||
| }; | |||||
| mge_attr("type", std::string("tensor")); | |||||
| mge_attr("name", chunk.name); | |||||
| mge_attr("address", ssprintf("%p", reinterpret_cast<void*>(chunk.address[0]))); | |||||
| mge_attr("size", size2str(chunk.address[1] - chunk.address[0])); | |||||
| mge_attr("layout", chunk.layout.to_string()); | |||||
| mge_attr("produced", time2str(chunk.time[0])); | |||||
| mge_attr("erased", time2str(chunk.time[1])); | |||||
| mge_attr("duration", time2str(chunk.time[1] - chunk.time[0])); | |||||
| } | |||||
| } | |||||
| return writer; | |||||
| } | |||||
| }; | |||||
| void dump_memory_flow(std::string filename, Profiler::options_t options, Profiler::thread_dict_t thread_dict, Profiler::results_t results) { | |||||
| MemoryFlow flow; | |||||
| ProfileDataCollector collector; | |||||
| ProfileState state; | |||||
| #define HANDLE_EVENT(type, ...) \ | |||||
| collector.handle<type>([&](uint64_t id, std::thread::id tid, uint64_t time, type event) __VA_ARGS__ ); | |||||
| HANDLE_EVENT(TensorDeclareEvent, { | |||||
| auto& tensor_state = state.tensors[event.tensor_id] = {}; | |||||
| tensor_state.id = event.tensor_id; | |||||
| tensor_state.name = event.name; | |||||
| }); | |||||
| HANDLE_EVENT(TensorProduceEvent, { | |||||
| auto& tensor_state = state.tensors[event.tensor_id]; | |||||
| tensor_state.device = event.device; | |||||
| tensor_state.layout = event.layout; | |||||
| tensor_state.produced = time; | |||||
| state.tensors_by_size.insert({tensor_state.id, tensor_state.size_in_bytes()}); | |||||
| state.tensors_by_produced.insert({tensor_state.id, tensor_state.produced}); | |||||
| auto& chunk = flow.chunks[event.tensor_id]; | |||||
| uintptr_t address = reinterpret_cast<uintptr_t>(event.ptr); | |||||
| auto span = event.layout.span(); | |||||
| auto dtype = event.layout.dtype; | |||||
| // assume dtype is not lowbit | |||||
| if (!address) { | |||||
| chunk.address = {0, 0}; | |||||
| } else { | |||||
| chunk.address = {address+span.low_elem*dtype.size(), address+span.high_elem*dtype.size()}; | |||||
| } | |||||
| chunk.layout = tensor_state.layout; | |||||
| chunk.time[0] = time; | |||||
| chunk.name = tensor_state.name; | |||||
| }); | |||||
| HANDLE_EVENT(TensorReleaseEvent, { | |||||
| auto& tensor_state = state.tensors[event.tensor_id]; | |||||
| state.tensors_by_size.erase({tensor_state.id, tensor_state.size_in_bytes()}); | |||||
| state.tensors_by_produced.erase({tensor_state.id, tensor_state.produced}); | |||||
| auto& chunk = flow.chunks[event.tensor_id]; | |||||
| chunk.time[1] = time; | |||||
| }); | |||||
| HANDLE_EVENT(ScopeEvent, { | |||||
| state.threads[tid].scope_stack.push_back(event.name); | |||||
| }); | |||||
| HANDLE_EVENT(ScopeFinishEvent, { | |||||
| mgb_assert(state.threads[tid].scope_stack.back() == event.name); | |||||
| state.threads[tid].scope_stack.pop_back(); | |||||
| }); | |||||
| for (auto&& result: results) { | |||||
| collector(result.second.id, result.first, result.second.time, result.second.data); | |||||
| } | |||||
| debug::write_to_file(filename.c_str(), flow.to_svg().to_string()); | |||||
| } | |||||
| } | |||||