diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..f8bbd24 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,23 @@ +image: registry.gitlab.com/ros_tracing/ros2_tracing/ci-base:latest + +variables: + DOCKER_DRIVER: overlay2 + PACKAGES_LIST: tracetools_analysis + +before_script: + - git clone https://gitlab.com/ros_tracing/ros2_tracing.git + +build: + script: + - colcon build --symlink-install --packages-up-to $PACKAGES_LIST + - colcon test --packages-select $PACKAGES_LIST + - colcon test-result + artifacts: + paths: + - install + - build/*/test_results/*/*.xunit.xml + - build/*/pytest.xml + reports: + junit: + - build/*/test_results/*/*.xunit.xml + - build/*/pytest.xml diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index e69de29..7fcab46 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,20 @@ +# tracetools_analysis + +Analysis tools for [ROS 2 tracing](https://gitlab.com/ros_tracing/ros2_tracing). + +# Setup + +To display results, install: + +* [Jupyter](https://jupyter.org/install) +* [Bokeh](https://bokeh.pydata.org/en/latest/docs/user_guide/quickstart.html#userguide-quickstart-install) + +# Use + +Start Jupyter Notebook: + +``` +$ jupyter notebook +``` + +Then navigate to the [`analysis/`](./tracetools_analysis/analysis/) directory, and select one of the provided notebooks, or create your own! diff --git a/tracetools_analysis/.gitignore b/tracetools_analysis/.gitignore new file mode 100644 index 0000000..eef29c1 --- /dev/null +++ b/tracetools_analysis/.gitignore @@ -0,0 +1,3 @@ +*~ +*.pyc + diff --git a/tracetools_analysis/README.md b/tracetools_analysis/README.md new file mode 100644 index 0000000..e69de29 diff --git a/tracetools_analysis/analysis/.gitignore b/tracetools_analysis/analysis/.gitignore new file mode 100644 index 0000000..4254a9f --- /dev/null +++ b/tracetools_analysis/analysis/.gitignore @@ -0,0 +1,5 @@ +*.svg +*.png +*.pdf +.ipynb_checkpoints + diff --git a/tracetools_analysis/analysis/callback_duration.ipynb b/tracetools_analysis/analysis/callback_duration.ipynb new file mode 100644 index 0000000..e7d033c --- /dev/null +++ b/tracetools_analysis/analysis/callback_duration.ipynb @@ -0,0 +1,618 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "trace_directory = '/tmp/session-pingpong/ust'" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "# Assuming a workspace with:\n", + "# src/tracetools_analysis/\n", + "# src/ros2/ros2_tracing/tracetools_read/\n", + "sys.path.insert(0, '../')\n", + "sys.path.insert(0, '../../../ros2/ros2_tracing/tracetools_read/')\n", + "import datetime as dt\n", + "import os\n", + "\n", + "from bokeh.plotting import figure\n", + "from bokeh.plotting import output_notebook\n", + "from bokeh.io import show\n", + "from bokeh.layouts import row\n", + "from bokeh.models import ColumnDataSource\n", + "from bokeh.models import DatetimeTickFormatter\n", + "from bokeh.models import PrintfTickFormatter\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "from tracetools_analysis.analysis import load\n", + "from tracetools_analysis.analysis import ros2_processor\n", + "from tracetools_analysis.analysis import utils\n", + "from tracetools_analysis.conversion import ctf" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "459 events\n" + ] + } + ], + "source": [ + "# Convert\n", + "pickle_filename = 'pickle'\n", + "pickle_path = os.path.join(trace_directory, pickle_filename)\n", + "count = ctf.convert(trace_directory, pickle_path)\n", + "print(f'{count} events')\n", + "\n", + "# Process\n", + "events = load.load_pickle(pickle_path)\n", + "processor = ros2_processor.ros2_process(events)\n", + "data_model = processor.get_data_model()\n", + "#data_model.print_model()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + " \n", + " Loading BokehJS ...\n", + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "\n", + "(function(root) {\n", + " function now() {\n", + " return new Date();\n", + " }\n", + "\n", + " var force = true;\n", + "\n", + " if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n", + " root._bokeh_onload_callbacks = [];\n", + " root._bokeh_is_loading = undefined;\n", + " }\n", + "\n", + " var JS_MIME_TYPE = 'application/javascript';\n", + " var HTML_MIME_TYPE = 'text/html';\n", + " var EXEC_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json';\n", + " var CLASS_NAME = 'output_bokeh rendered_html';\n", + "\n", + " /**\n", + " * Render data to the DOM node\n", + " */\n", + " function render(props, node) {\n", + " var script = document.createElement(\"script\");\n", + " node.appendChild(script);\n", + " }\n", + "\n", + " /**\n", + " * Handle when an output is cleared or removed\n", + " */\n", + " function handleClearOutput(event, handle) {\n", + " var cell = handle.cell;\n", + "\n", + " var id = cell.output_area._bokeh_element_id;\n", + " var server_id = cell.output_area._bokeh_server_id;\n", + " // Clean up Bokeh references\n", + " if (id != null && id in Bokeh.index) {\n", + " Bokeh.index[id].model.document.clear();\n", + " delete Bokeh.index[id];\n", + " }\n", + "\n", + " if (server_id !== undefined) {\n", + " // Clean up Bokeh references\n", + " var cmd = \"from bokeh.io.state import curstate; print(curstate().uuid_to_server['\" + server_id + \"'].get_sessions()[0].document.roots[0]._id)\";\n", + " cell.notebook.kernel.execute(cmd, {\n", + " iopub: {\n", + " output: function(msg) {\n", + " var id = msg.content.text.trim();\n", + " if (id in Bokeh.index) {\n", + " Bokeh.index[id].model.document.clear();\n", + " delete Bokeh.index[id];\n", + " }\n", + " }\n", + " }\n", + " });\n", + " // Destroy server and session\n", + " var cmd = \"import bokeh.io.notebook as ion; ion.destroy_server('\" + server_id + \"')\";\n", + " cell.notebook.kernel.execute(cmd);\n", + " }\n", + " }\n", + "\n", + " /**\n", + " * Handle when a new output is added\n", + " */\n", + " function handleAddOutput(event, handle) {\n", + " var output_area = handle.output_area;\n", + " var output = handle.output;\n", + "\n", + " // limit handleAddOutput to display_data with EXEC_MIME_TYPE content only\n", + " if ((output.output_type != \"display_data\") || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n", + " return\n", + " }\n", + "\n", + " var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n", + "\n", + " if (output.metadata[EXEC_MIME_TYPE][\"id\"] !== undefined) {\n", + " toinsert[toinsert.length - 1].firstChild.textContent = output.data[JS_MIME_TYPE];\n", + " // store reference to embed id on output_area\n", + " output_area._bokeh_element_id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n", + " }\n", + " if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n", + " var bk_div = document.createElement(\"div\");\n", + " bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n", + " var script_attrs = bk_div.children[0].attributes;\n", + " for (var i = 0; i < script_attrs.length; i++) {\n", + " toinsert[toinsert.length - 1].firstChild.setAttribute(script_attrs[i].name, script_attrs[i].value);\n", + " }\n", + " // store reference to server id on output_area\n", + " output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n", + " }\n", + " }\n", + "\n", + " function register_renderer(events, OutputArea) {\n", + "\n", + " function append_mime(data, metadata, element) {\n", + " // create a DOM node to render to\n", + " var toinsert = this.create_output_subarea(\n", + " metadata,\n", + " CLASS_NAME,\n", + " EXEC_MIME_TYPE\n", + " );\n", + " this.keyboard_manager.register_events(toinsert);\n", + " // Render to node\n", + " var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n", + " render(props, toinsert[toinsert.length - 1]);\n", + " element.append(toinsert);\n", + " return toinsert\n", + " }\n", + "\n", + " /* Handle when an output is cleared or removed */\n", + " events.on('clear_output.CodeCell', handleClearOutput);\n", + " events.on('delete.Cell', handleClearOutput);\n", + "\n", + " /* Handle when a new output is added */\n", + " events.on('output_added.OutputArea', handleAddOutput);\n", + "\n", + " /**\n", + " * Register the mime type and append_mime function with output_area\n", + " */\n", + " OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n", + " /* Is output safe? */\n", + " safe: true,\n", + " /* Index of renderer in `output_area.display_order` */\n", + " index: 0\n", + " });\n", + " }\n", + "\n", + " // register the mime type if in Jupyter Notebook environment and previously unregistered\n", + " if (root.Jupyter !== undefined) {\n", + " var events = require('base/js/events');\n", + " var OutputArea = require('notebook/js/outputarea').OutputArea;\n", + "\n", + " if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n", + " register_renderer(events, OutputArea);\n", + " }\n", + " }\n", + "\n", + " \n", + " if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n", + " root._bokeh_timeout = Date.now() + 5000;\n", + " root._bokeh_failed_load = false;\n", + " }\n", + "\n", + " var NB_LOAD_WARNING = {'data': {'text/html':\n", + " \"
\\n\"+\n", + " \"

\\n\"+\n", + " \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n", + " \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n", + " \"

\\n\"+\n", + " \"\\n\"+\n", + " \"\\n\"+\n", + " \"from bokeh.resources import INLINE\\n\"+\n", + " \"output_notebook(resources=INLINE)\\n\"+\n", + " \"\\n\"+\n", + " \"
\"}};\n", + "\n", + " function display_loaded() {\n", + " var el = document.getElementById(\"1001\");\n", + " if (el != null) {\n", + " el.textContent = \"BokehJS is loading...\";\n", + " }\n", + " if (root.Bokeh !== undefined) {\n", + " if (el != null) {\n", + " el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n", + " }\n", + " } else if (Date.now() < root._bokeh_timeout) {\n", + " setTimeout(display_loaded, 100)\n", + " }\n", + " }\n", + "\n", + "\n", + " function run_callbacks() {\n", + " try {\n", + " root._bokeh_onload_callbacks.forEach(function(callback) {\n", + " if (callback != null)\n", + " callback();\n", + " });\n", + " } finally {\n", + " delete root._bokeh_onload_callbacks\n", + " }\n", + " console.debug(\"Bokeh: all callbacks have finished\");\n", + " }\n", + "\n", + " function load_libs(css_urls, js_urls, callback) {\n", + " if (css_urls == null) css_urls = [];\n", + " if (js_urls == null) js_urls = [];\n", + "\n", + " root._bokeh_onload_callbacks.push(callback);\n", + " if (root._bokeh_is_loading > 0) {\n", + " console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n", + " return null;\n", + " }\n", + " if (js_urls == null || js_urls.length === 0) {\n", + " run_callbacks();\n", + " return null;\n", + " }\n", + " console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n", + " root._bokeh_is_loading = css_urls.length + js_urls.length;\n", + "\n", + " function on_load() {\n", + " root._bokeh_is_loading--;\n", + " if (root._bokeh_is_loading === 0) {\n", + " console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n", + " run_callbacks()\n", + " }\n", + " }\n", + "\n", + " function on_error() {\n", + " console.error(\"failed to load \" + url);\n", + " }\n", + "\n", + " for (var i = 0; i < css_urls.length; i++) {\n", + " var url = css_urls[i];\n", + " const element = document.createElement(\"link\");\n", + " element.onload = on_load;\n", + " element.onerror = on_error;\n", + " element.rel = \"stylesheet\";\n", + " element.type = \"text/css\";\n", + " element.href = url;\n", + " console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n", + " document.body.appendChild(element);\n", + " }\n", + "\n", + " for (var i = 0; i < js_urls.length; i++) {\n", + " var url = js_urls[i];\n", + " var element = document.createElement('script');\n", + " element.onload = on_load;\n", + " element.onerror = on_error;\n", + " element.async = false;\n", + " element.src = url;\n", + " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n", + " document.head.appendChild(element);\n", + " }\n", + " };var element = document.getElementById(\"1001\");\n", + " if (element == null) {\n", + " console.error(\"Bokeh: ERROR: autoload.js configured with elementid '1001' but no matching script tag was found. \")\n", + " return false;\n", + " }\n", + "\n", + " function inject_raw_css(css) {\n", + " const element = document.createElement(\"style\");\n", + " element.appendChild(document.createTextNode(css));\n", + " document.body.appendChild(element);\n", + " }\n", + "\n", + " var js_urls = [\"https://cdn.pydata.org/bokeh/release/bokeh-1.2.0.min.js\", \"https://cdn.pydata.org/bokeh/release/bokeh-widgets-1.2.0.min.js\", \"https://cdn.pydata.org/bokeh/release/bokeh-tables-1.2.0.min.js\", \"https://cdn.pydata.org/bokeh/release/bokeh-gl-1.2.0.min.js\"];\n", + " var css_urls = [\"https://cdn.pydata.org/bokeh/release/bokeh-1.2.0.min.css\", \"https://cdn.pydata.org/bokeh/release/bokeh-widgets-1.2.0.min.css\", \"https://cdn.pydata.org/bokeh/release/bokeh-tables-1.2.0.min.css\"];\n", + "\n", + " var inline_js = [\n", + " function(Bokeh) {\n", + " Bokeh.set_log_level(\"info\");\n", + " },\n", + " \n", + " function(Bokeh) {\n", + " \n", + " },\n", + " function(Bokeh) {} // ensure no trailing comma for IE\n", + " ];\n", + "\n", + " function run_inline_js() {\n", + " \n", + " if ((root.Bokeh !== undefined) || (force === true)) {\n", + " for (var i = 0; i < inline_js.length; i++) {\n", + " inline_js[i].call(root, root.Bokeh);\n", + " }if (force === true) {\n", + " display_loaded();\n", + " }} else if (Date.now() < root._bokeh_timeout) {\n", + " setTimeout(run_inline_js, 100);\n", + " } else if (!root._bokeh_failed_load) {\n", + " console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n", + " root._bokeh_failed_load = true;\n", + " } else if (force !== true) {\n", + " var cell = $(document.getElementById(\"1001\")).parents('.cell').data().cell;\n", + " cell.output_area.append_execute_result(NB_LOAD_WARNING)\n", + " }\n", + "\n", + " }\n", + "\n", + " if (root._bokeh_is_loading === 0) {\n", + " console.debug(\"Bokeh: BokehJS loaded, going straight to plotting\");\n", + " run_inline_js();\n", + " } else {\n", + " load_libs(css_urls, js_urls, function() {\n", + " console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n", + " run_inline_js();\n", + " });\n", + " }\n", + "}(window));" + ], + "application/vnd.bokehjs_load.v0+json": "\n(function(root) {\n function now() {\n return new Date();\n }\n\n var force = true;\n\n if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n root._bokeh_onload_callbacks = [];\n root._bokeh_is_loading = undefined;\n }\n\n \n\n \n if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n var NB_LOAD_WARNING = {'data': {'text/html':\n \"
\\n\"+\n \"

\\n\"+\n \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n \"

\\n\"+\n \"\\n\"+\n \"\\n\"+\n \"from bokeh.resources import INLINE\\n\"+\n \"output_notebook(resources=INLINE)\\n\"+\n \"\\n\"+\n \"
\"}};\n\n function display_loaded() {\n var el = document.getElementById(\"1001\");\n if (el != null) {\n el.textContent = \"BokehJS is loading...\";\n }\n if (root.Bokeh !== undefined) {\n if (el != null) {\n el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(display_loaded, 100)\n }\n }\n\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n\n root._bokeh_onload_callbacks.push(callback);\n if (root._bokeh_is_loading > 0) {\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls == null || js_urls.length === 0) {\n run_callbacks();\n return null;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n root._bokeh_is_loading = css_urls.length + js_urls.length;\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n\n function on_error() {\n console.error(\"failed to load \" + url);\n }\n\n for (var i = 0; i < css_urls.length; i++) {\n var url = css_urls[i];\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n }\n\n for (var i = 0; i < js_urls.length; i++) {\n var url = js_urls[i];\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n };var element = document.getElementById(\"1001\");\n if (element == null) {\n console.error(\"Bokeh: ERROR: autoload.js configured with elementid '1001' but no matching script tag was found. \")\n return false;\n }\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n var js_urls = [\"https://cdn.pydata.org/bokeh/release/bokeh-1.2.0.min.js\", \"https://cdn.pydata.org/bokeh/release/bokeh-widgets-1.2.0.min.js\", \"https://cdn.pydata.org/bokeh/release/bokeh-tables-1.2.0.min.js\", \"https://cdn.pydata.org/bokeh/release/bokeh-gl-1.2.0.min.js\"];\n var css_urls = [\"https://cdn.pydata.org/bokeh/release/bokeh-1.2.0.min.css\", \"https://cdn.pydata.org/bokeh/release/bokeh-widgets-1.2.0.min.css\", \"https://cdn.pydata.org/bokeh/release/bokeh-tables-1.2.0.min.css\"];\n\n var inline_js = [\n function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\n \n function(Bokeh) {\n \n },\n function(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n \n if ((root.Bokeh !== undefined) || (force === true)) {\n for (var i = 0; i < inline_js.length; i++) {\n inline_js[i].call(root, root.Bokeh);\n }if (force === true) {\n display_loaded();\n }} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n } else if (force !== true) {\n var cell = $(document.getElementById(\"1001\")).parents('.cell').data().cell;\n cell.output_area.append_execute_result(NB_LOAD_WARNING)\n }\n\n }\n\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: BokehJS loaded, going straight to plotting\");\n run_inline_js();\n } else {\n load_libs(css_urls, js_urls, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n}(window));" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "(function(root) {\n", + " function embed_document(root) {\n", + " \n", + " var docs_json = {\"25f18ded-2f35-4a38-abec-8df60f34ad3e\":{\"roots\":{\"references\":[{\"attributes\":{\"children\":[{\"id\":\"1003\",\"subtype\":\"Figure\",\"type\":\"Plot\"},{\"id\":\"1052\",\"subtype\":\"Figure\",\"type\":\"Plot\"}]},\"id\":\"1091\",\"type\":\"Row\"},{\"attributes\":{\"active_drag\":\"auto\",\"active_inspect\":\"auto\",\"active_multi\":null,\"active_scroll\":\"auto\",\"active_tap\":\"auto\",\"tools\":[{\"id\":\"1073\",\"type\":\"PanTool\"},{\"id\":\"1074\",\"type\":\"WheelZoomTool\"},{\"id\":\"1075\",\"type\":\"BoxZoomTool\"},{\"id\":\"1076\",\"type\":\"SaveTool\"},{\"id\":\"1077\",\"type\":\"ResetTool\"},{\"id\":\"1078\",\"type\":\"HelpTool\"}]},\"id\":\"1079\",\"type\":\"Toolbar\"},{\"attributes\":{},\"id\":\"1050\",\"type\":\"DatetimeTickFormatter\"},{\"attributes\":{},\"id\":\"1015\",\"type\":\"BasicTicker\"},{\"attributes\":{},\"id\":\"1073\",\"type\":\"PanTool\"},{\"attributes\":{\"callback\":null},\"id\":\"1008\",\"type\":\"DataRange1d\"},{\"attributes\":{\"source\":{\"id\":\"1086\",\"type\":\"ColumnDataSource\"}},\"id\":\"1090\",\"type\":\"CDSView\"},{\"attributes\":{},\"id\":\"1074\",\"type\":\"WheelZoomTool\"},{\"attributes\":{\"dimension\":1,\"ticker\":{\"id\":\"1020\",\"type\":\"BasicTicker\"}},\"id\":\"1023\",\"type\":\"Grid\"},{\"attributes\":{\"ticker\":{\"id\":\"1015\",\"type\":\"BasicTicker\"}},\"id\":\"1018\",\"type\":\"Grid\"},{\"attributes\":{\"data_source\":{\"id\":\"1086\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"1087\",\"type\":\"Quad\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"1088\",\"type\":\"Quad\"},\"selection_glyph\":null,\"view\":{\"id\":\"1090\",\"type\":\"CDSView\"}},\"id\":\"1089\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"overlay\":{\"id\":\"1104\",\"type\":\"BoxAnnotation\"}},\"id\":\"1075\",\"type\":\"BoxZoomTool\"},{\"attributes\":{\"below\":[{\"id\":\"1063\",\"type\":\"LinearAxis\"}],\"center\":[{\"id\":\"1067\",\"type\":\"Grid\"},{\"id\":\"1072\",\"type\":\"Grid\"}],\"left\":[{\"id\":\"1068\",\"type\":\"LinearAxis\"}],\"plot_height\":450,\"plot_width\":450,\"renderers\":[{\"id\":\"1089\",\"type\":\"GlyphRenderer\"}],\"title\":{\"id\":\"1053\",\"type\":\"Title\"},\"toolbar\":{\"id\":\"1079\",\"type\":\"Toolbar\"},\"x_range\":{\"id\":\"1055\",\"type\":\"DataRange1d\"},\"x_scale\":{\"id\":\"1059\",\"type\":\"LinearScale\"},\"y_range\":{\"id\":\"1057\",\"type\":\"DataRange1d\"},\"y_scale\":{\"id\":\"1061\",\"type\":\"LinearScale\"}},\"id\":\"1052\",\"subtype\":\"Figure\",\"type\":\"Plot\"},{\"attributes\":{},\"id\":\"1076\",\"type\":\"SaveTool\"},{\"attributes\":{\"axis_label\":\"duration (ms)\",\"formatter\":{\"id\":\"1044\",\"type\":\"BasicTickFormatter\"},\"ticker\":{\"id\":\"1020\",\"type\":\"BasicTicker\"}},\"id\":\"1019\",\"type\":\"LinearAxis\"},{\"attributes\":{\"active_drag\":\"auto\",\"active_inspect\":\"auto\",\"active_multi\":null,\"active_scroll\":\"auto\",\"active_tap\":\"auto\",\"tools\":[{\"id\":\"1024\",\"type\":\"PanTool\"},{\"id\":\"1025\",\"type\":\"WheelZoomTool\"},{\"id\":\"1026\",\"type\":\"BoxZoomTool\"},{\"id\":\"1027\",\"type\":\"SaveTool\"},{\"id\":\"1028\",\"type\":\"ResetTool\"},{\"id\":\"1029\",\"type\":\"HelpTool\"}]},\"id\":\"1030\",\"type\":\"Toolbar\"},{\"attributes\":{},\"id\":\"1077\",\"type\":\"ResetTool\"},{\"attributes\":{\"align\":\"center\",\"text\":\"Duration histogram\"},\"id\":\"1053\",\"type\":\"Title\"},{\"attributes\":{\"label\":{\"value\":\"timer\"},\"renderers\":[{\"id\":\"1040\",\"type\":\"GlyphRenderer\"}]},\"id\":\"1049\",\"type\":\"LegendItem\"},{\"attributes\":{\"line_color\":\"#1f77b4\",\"line_width\":2,\"x\":{\"field\":\"timestamp\"},\"y\":{\"field\":\"duration\"}},\"id\":\"1038\",\"type\":\"Line\"},{\"attributes\":{\"line_alpha\":0.1,\"line_color\":\"#1f77b4\",\"line_width\":2,\"x\":{\"field\":\"timestamp\"},\"y\":{\"field\":\"duration\"}},\"id\":\"1039\",\"type\":\"Line\"},{\"attributes\":{\"callback\":null},\"id\":\"1055\",\"type\":\"DataRange1d\"},{\"attributes\":{},\"id\":\"1078\",\"type\":\"HelpTool\"},{\"attributes\":{},\"id\":\"1010\",\"type\":\"LinearScale\"},{\"attributes\":{},\"id\":\"1024\",\"type\":\"PanTool\"},{\"attributes\":{\"callback\":null},\"id\":\"1057\",\"type\":\"DataRange1d\"},{\"attributes\":{\"bottom\":{\"value\":0},\"fill_color\":{\"value\":\"#1f77b4\"},\"left\":{\"field\":\"left\"},\"line_color\":{\"value\":\"#1f77b4\"},\"right\":{\"field\":\"right\"},\"top\":{\"field\":\"top\"}},\"id\":\"1087\",\"type\":\"Quad\"},{\"attributes\":{\"overlay\":{\"id\":\"1047\",\"type\":\"BoxAnnotation\"}},\"id\":\"1026\",\"type\":\"BoxZoomTool\"},{\"attributes\":{},\"id\":\"1059\",\"type\":\"LinearScale\"},{\"attributes\":{\"bottom\":{\"value\":0},\"fill_alpha\":{\"value\":0.1},\"fill_color\":{\"value\":\"#1f77b4\"},\"left\":{\"field\":\"left\"},\"line_alpha\":{\"value\":0.1},\"line_color\":{\"value\":\"#1f77b4\"},\"right\":{\"field\":\"right\"},\"top\":{\"field\":\"top\"}},\"id\":\"1088\",\"type\":\"Quad\"},{\"attributes\":{},\"id\":\"1027\",\"type\":\"SaveTool\"},{\"attributes\":{},\"id\":\"1061\",\"type\":\"LinearScale\"},{\"attributes\":{},\"id\":\"1028\",\"type\":\"ResetTool\"},{\"attributes\":{\"axis_label\":\"duration (ms)\",\"formatter\":{\"id\":\"1100\",\"type\":\"BasicTickFormatter\"},\"ticker\":{\"id\":\"1064\",\"type\":\"BasicTicker\"}},\"id\":\"1063\",\"type\":\"LinearAxis\"},{\"attributes\":{\"callback\":null,\"data\":{\"duration\":{\"__ndarray__\":\"cXUAxF29xD8UzJiCNc6yPxoziXrBp7U/v2N47GextD+L4lXWNsWzP6Cp1y0CY7k/7KNTVz7Lsz/YDdsWZTa0P8goz7wcdrM/XqEPlrGhsz/tgVZgyOqyP4pamlshrLI/cQSpFDsasz8HXFfMCG+zPx2vQPSkTLI/HJjcKLLWtD+xwFd06zW1P3SZmgRvSLM/Ga2jqgmisj8NVMa/z7igPxMro5HPK7I/4zREFf4Msz8JwhVQqKezP9gQHJdxU7M/RYDTu3g/tj9uwOeHEcKzP+lEgqlm1rI/c0urIXGPtT/Z6Qd1kUK1P1TJAFDFjbM/cJo+O+C6sj8r+G2I8ZqzP7pnXaPlQLM/si0DzlKytD+3Q8Ni1LWyP5/L1CR4Q7Y/dEUpIVhVsz+TUWUYd4O0P3pU/N8RFbY/e4LEdvcAtT/4UnjQ7LqzP4C21awzvrM/2lVI+Um1sz/rjsU2qWi0PzLJyFnY07I/btv3qL9etT8Iy9jQzf60P0sEqn8QybQ/CvZf56bNtD9UxyqlZ3q1PzPFHAQdrbI/6fNRRlwAtj82qz5XW7GzP/YoXI/C9aA/Y5l+iXjrtD/59xkXDoS0PzG2EOSghLE//8wgPrDjsz8XLquwGeCyP/SLEvQXerQ//Yf029eBsz8vppnudVKzP8VU+glnt7I/\",\"dtype\":\"float64\",\"shape\":[63]},\"index\":[0,3,6,9,12,15,18,21,24,27,30,33,36,39,42,45,48,51,54,57,60,63,66,69,72,75,78,81,84,87,90,93,96,99,102,105,108,111,114,117,120,123,126,129,132,135,138,141,144,147,150,153,156,159,162,165,168,171,174,177,180,183,186],\"timestamp\":{\"__ndarray__\":\"c0yDOMu6dkKPgqI4y7p2QhTCwTjLunZCSAPhOMu6dkKYQgA5y7p2QriCHznLunZCBsM+Ocu6dkI7A145y7p2QhdDfTnLunZC9oKcOcu6dkLpwrs5y7p2QhsD2znLunZC1UL6Ocu6dkLughk6y7p2QhvDODrLunZCDgNYOsu6dkICQ3c6y7p2QjuDljrLunZCM8O1Osu6dkLDAdU6y7p2QlBD9DrLunZCeYMTO8u6dkJYwzI7y7p2Qm0DUjvLunZCbUNxO8u6dkKWg5A7y7p2QljDrzvLunZCugPPO8u6dkKWQ+47y7p2QtODDTzLunZCw8MsPMu6dkIMBEw8y7p2QsNDazzLunZC+IOKPMu6dkL8w6k8y7p2QvQDyTzLunZC8EPoPMu6dkIphAc9y7p2Qj3EJj3LunZCZgRGPcu6dkJqRGU9y7p2QoOEhD3LunZCj8SjPcu6dkKYBMM9y7p2QotE4j3LunZCsIQBPsu6dkK8xCA+y7p2QgIFQD7LunZC0URfPsu6dkL+hH4+y7p2QvbEnT7LunZCtgW9Psu6dkIXRdw+y7p2QraD+z7LunZCAsUaP8u6dkJYBTo/y7p2QuVEWT/LunZCeYV4P8u6dkJtxZc/y7p2QisFtz/LunZCP0XWP8u6dkLnhfU/y7p2Qt/FFEDLunZC\",\"dtype\":\"float64\",\"shape\":[63]}},\"selected\":{\"id\":\"1102\",\"type\":\"Selection\"},\"selection_policy\":{\"id\":\"1103\",\"type\":\"UnionRenderers\"}},\"id\":\"1002\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"callback\":null},\"id\":\"1006\",\"type\":\"DataRange1d\"},{\"attributes\":{},\"id\":\"1029\",\"type\":\"HelpTool\"},{\"attributes\":{},\"id\":\"1098\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{},\"id\":\"1064\",\"type\":\"BasicTicker\"},{\"attributes\":{},\"id\":\"1106\",\"type\":\"UnionRenderers\"},{\"attributes\":{},\"id\":\"1100\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{},\"id\":\"1012\",\"type\":\"LinearScale\"},{\"attributes\":{\"ticker\":{\"id\":\"1064\",\"type\":\"BasicTicker\"}},\"id\":\"1067\",\"type\":\"Grid\"},{\"attributes\":{},\"id\":\"1020\",\"type\":\"BasicTicker\"},{\"attributes\":{\"axis_label\":\"start (2019-07-01 08:42)\",\"formatter\":{\"id\":\"1050\",\"type\":\"DatetimeTickFormatter\"},\"ticker\":{\"id\":\"1015\",\"type\":\"BasicTicker\"}},\"id\":\"1014\",\"type\":\"LinearAxis\"},{\"attributes\":{\"below\":[{\"id\":\"1014\",\"type\":\"LinearAxis\"}],\"center\":[{\"id\":\"1018\",\"type\":\"Grid\"},{\"id\":\"1023\",\"type\":\"Grid\"},{\"id\":\"1048\",\"type\":\"Legend\"}],\"left\":[{\"id\":\"1019\",\"type\":\"LinearAxis\"}],\"plot_height\":450,\"plot_width\":450,\"renderers\":[{\"id\":\"1040\",\"type\":\"GlyphRenderer\"}],\"title\":{\"id\":\"1004\",\"type\":\"Title\"},\"toolbar\":{\"id\":\"1030\",\"type\":\"Toolbar\"},\"x_range\":{\"id\":\"1006\",\"type\":\"DataRange1d\"},\"x_scale\":{\"id\":\"1010\",\"type\":\"LinearScale\"},\"y_range\":{\"id\":\"1008\",\"type\":\"DataRange1d\"},\"y_scale\":{\"id\":\"1012\",\"type\":\"LinearScale\"}},\"id\":\"1003\",\"subtype\":\"Figure\",\"type\":\"Plot\"},{\"attributes\":{\"axis_label\":\"frequency\",\"formatter\":{\"id\":\"1098\",\"type\":\"BasicTickFormatter\"},\"ticker\":{\"id\":\"1069\",\"type\":\"BasicTicker\"}},\"id\":\"1068\",\"type\":\"LinearAxis\"},{\"attributes\":{\"data_source\":{\"id\":\"1002\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"1038\",\"type\":\"Line\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"1039\",\"type\":\"Line\"},\"selection_glyph\":null,\"view\":{\"id\":\"1041\",\"type\":\"CDSView\"}},\"id\":\"1040\",\"type\":\"GlyphRenderer\"},{\"attributes\":{},\"id\":\"1102\",\"type\":\"Selection\"},{\"attributes\":{\"source\":{\"id\":\"1002\",\"type\":\"ColumnDataSource\"}},\"id\":\"1041\",\"type\":\"CDSView\"},{\"attributes\":{},\"id\":\"1025\",\"type\":\"WheelZoomTool\"},{\"attributes\":{},\"id\":\"1069\",\"type\":\"BasicTicker\"},{\"attributes\":{},\"id\":\"1103\",\"type\":\"UnionRenderers\"},{\"attributes\":{},\"id\":\"1044\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{\"dimension\":1,\"ticker\":{\"id\":\"1069\",\"type\":\"BasicTicker\"}},\"id\":\"1072\",\"type\":\"Grid\"},{\"attributes\":{\"bottom_units\":\"screen\",\"fill_alpha\":{\"value\":0.5},\"fill_color\":{\"value\":\"lightgrey\"},\"left_units\":\"screen\",\"level\":\"overlay\",\"line_alpha\":{\"value\":1.0},\"line_color\":{\"value\":\"black\"},\"line_dash\":[4,4],\"line_width\":{\"value\":2},\"render_mode\":\"css\",\"right_units\":\"screen\",\"top_units\":\"screen\"},\"id\":\"1104\",\"type\":\"BoxAnnotation\"},{\"attributes\":{\"bottom_units\":\"screen\",\"fill_alpha\":{\"value\":0.5},\"fill_color\":{\"value\":\"lightgrey\"},\"left_units\":\"screen\",\"level\":\"overlay\",\"line_alpha\":{\"value\":1.0},\"line_color\":{\"value\":\"black\"},\"line_dash\":[4,4],\"line_width\":{\"value\":2},\"render_mode\":\"css\",\"right_units\":\"screen\",\"top_units\":\"screen\"},\"id\":\"1047\",\"type\":\"BoxAnnotation\"},{\"attributes\":{\"callback\":null,\"data\":{\"left\":{\"__ndarray__\":\"DVTGv8+4oD85FJkUelinP2XUa2kk+K0/SEofX+dLsj9eqoiJvJu1P3QK8rOR67g/impb3mY7vD+gysQIPIu/P1sVl5mIbcE/ZsXLLnMVwz8=\",\"dtype\":\"float64\",\"shape\":[10]},\"right\":{\"__ndarray__\":\"ORSZFHpYpz9l1GtpJPitP0hKH1/nS7I/XqqIibybtT90CvKzkeu4P4pqW95mO7w/oMrECDyLvz9bFZeZiG3BP2bFyy5zFcM/cXUAxF29xD8=\",\"dtype\":\"float64\",\"shape\":[10]},\"top\":[2,0,2,52,5,1,0,0,0,1]},\"selected\":{\"id\":\"1105\",\"type\":\"Selection\"},\"selection_policy\":{\"id\":\"1106\",\"type\":\"UnionRenderers\"}},\"id\":\"1086\",\"type\":\"ColumnDataSource\"},{\"attributes\":{},\"id\":\"1105\",\"type\":\"Selection\"},{\"attributes\":{\"items\":[{\"id\":\"1049\",\"type\":\"LegendItem\"}]},\"id\":\"1048\",\"type\":\"Legend\"},{\"attributes\":{\"align\":\"center\",\"text\":\"Timer -- tid: 9876, period: 500 ms\"},\"id\":\"1004\",\"type\":\"Title\"}],\"root_ids\":[\"1091\"]},\"title\":\"Bokeh Application\",\"version\":\"1.2.0\"}};\n", + " var render_items = [{\"docid\":\"25f18ded-2f35-4a38-abec-8df60f34ad3e\",\"roots\":{\"1091\":\"55770a40-08ff-495a-8fa8-6023595f7557\"}}];\n", + " root.Bokeh.embed.embed_items_notebook(docs_json, render_items);\n", + "\n", + " }\n", + " if (root.Bokeh !== undefined) {\n", + " embed_document(root);\n", + " } else {\n", + " var attempts = 0;\n", + " var timer = setInterval(function(root) {\n", + " if (root.Bokeh !== undefined) {\n", + " embed_document(root);\n", + " clearInterval(timer);\n", + " }\n", + " attempts++;\n", + " if (attempts > 100) {\n", + " console.log(\"Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing\");\n", + " clearInterval(timer);\n", + " }\n", + " }, 10, root)\n", + " }\n", + "})(window);" + ], + "application/vnd.bokehjs_exec.v0+json": "" + }, + "metadata": { + "application/vnd.bokehjs_exec.v0+json": { + "id": "1091" + } + }, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "(function(root) {\n", + " function embed_document(root) {\n", + " \n", + " var docs_json = {\"5e908bd4-c7eb-4bae-b6d0-89beed606b4c\":{\"roots\":{\"references\":[{\"attributes\":{\"children\":[{\"id\":\"1198\",\"subtype\":\"Figure\",\"type\":\"Plot\"},{\"id\":\"1247\",\"subtype\":\"Figure\",\"type\":\"Plot\"}]},\"id\":\"1286\",\"type\":\"Row\"},{\"attributes\":{},\"id\":\"1312\",\"type\":\"Selection\"},{\"attributes\":{\"dimension\":1,\"ticker\":{\"id\":\"1215\",\"type\":\"BasicTicker\"}},\"id\":\"1218\",\"type\":\"Grid\"},{\"attributes\":{\"dimension\":1,\"ticker\":{\"id\":\"1264\",\"type\":\"BasicTicker\"}},\"id\":\"1267\",\"type\":\"Grid\"},{\"attributes\":{},\"id\":\"1313\",\"type\":\"UnionRenderers\"},{\"attributes\":{\"callback\":null},\"id\":\"1250\",\"type\":\"DataRange1d\"},{\"attributes\":{\"below\":[{\"id\":\"1258\",\"type\":\"LinearAxis\"}],\"center\":[{\"id\":\"1262\",\"type\":\"Grid\"},{\"id\":\"1267\",\"type\":\"Grid\"}],\"left\":[{\"id\":\"1263\",\"type\":\"LinearAxis\"}],\"plot_height\":450,\"plot_width\":450,\"renderers\":[{\"id\":\"1284\",\"type\":\"GlyphRenderer\"}],\"title\":{\"id\":\"1248\",\"type\":\"Title\"},\"toolbar\":{\"id\":\"1274\",\"type\":\"Toolbar\"},\"x_range\":{\"id\":\"1250\",\"type\":\"DataRange1d\"},\"x_scale\":{\"id\":\"1254\",\"type\":\"LinearScale\"},\"y_range\":{\"id\":\"1252\",\"type\":\"DataRange1d\"},\"y_scale\":{\"id\":\"1256\",\"type\":\"LinearScale\"}},\"id\":\"1247\",\"subtype\":\"Figure\",\"type\":\"Plot\"},{\"attributes\":{\"active_drag\":\"auto\",\"active_inspect\":\"auto\",\"active_multi\":null,\"active_scroll\":\"auto\",\"active_tap\":\"auto\",\"tools\":[{\"id\":\"1219\",\"type\":\"PanTool\"},{\"id\":\"1220\",\"type\":\"WheelZoomTool\"},{\"id\":\"1221\",\"type\":\"BoxZoomTool\"},{\"id\":\"1222\",\"type\":\"SaveTool\"},{\"id\":\"1223\",\"type\":\"ResetTool\"},{\"id\":\"1224\",\"type\":\"HelpTool\"}]},\"id\":\"1225\",\"type\":\"Toolbar\"},{\"attributes\":{\"callback\":null,\"data\":{\"left\":{\"__ndarray__\":\"YCAIkKFjlz/i7ZzBOumkP5TLNbukIK4/o1RnWgessz98wzNXvEe4P1UyAFRx47w/l1BmKJO/wD8EiMymbQ3DP3C/MiVIW8U/3PaYoyKpxz8=\",\"dtype\":\"float64\",\"shape\":[10]},\"right\":{\"__ndarray__\":\"4u2cwTrppD+UyzW7pCCuP6NUZ1oHrLM/fMMzV7xHuD9VMgBUceO8P5dQZiiTv8A/BIjMpm0Nwz9wvzIlSFvFP9z2mKMiqcc/SS7/If32yT8=\",\"dtype\":\"float64\",\"shape\":[10]},\"top\":[2,0,45,13,0,1,0,0,0,2]},\"selected\":{\"id\":\"1315\",\"type\":\"Selection\"},\"selection_policy\":{\"id\":\"1316\",\"type\":\"UnionRenderers\"}},\"id\":\"1281\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"bottom_units\":\"screen\",\"fill_alpha\":{\"value\":0.5},\"fill_color\":{\"value\":\"lightgrey\"},\"left_units\":\"screen\",\"level\":\"overlay\",\"line_alpha\":{\"value\":1.0},\"line_color\":{\"value\":\"black\"},\"line_dash\":[4,4],\"line_width\":{\"value\":2},\"render_mode\":\"css\",\"right_units\":\"screen\",\"top_units\":\"screen\"},\"id\":\"1314\",\"type\":\"BoxAnnotation\"},{\"attributes\":{},\"id\":\"1222\",\"type\":\"SaveTool\"},{\"attributes\":{},\"id\":\"1315\",\"type\":\"Selection\"},{\"attributes\":{},\"id\":\"1219\",\"type\":\"PanTool\"},{\"attributes\":{\"active_drag\":\"auto\",\"active_inspect\":\"auto\",\"active_multi\":null,\"active_scroll\":\"auto\",\"active_tap\":\"auto\",\"tools\":[{\"id\":\"1268\",\"type\":\"PanTool\"},{\"id\":\"1269\",\"type\":\"WheelZoomTool\"},{\"id\":\"1270\",\"type\":\"BoxZoomTool\"},{\"id\":\"1271\",\"type\":\"SaveTool\"},{\"id\":\"1272\",\"type\":\"ResetTool\"},{\"id\":\"1273\",\"type\":\"HelpTool\"}]},\"id\":\"1274\",\"type\":\"Toolbar\"},{\"attributes\":{\"callback\":null},\"id\":\"1203\",\"type\":\"DataRange1d\"},{\"attributes\":{},\"id\":\"1316\",\"type\":\"UnionRenderers\"},{\"attributes\":{\"callback\":null},\"id\":\"1201\",\"type\":\"DataRange1d\"},{\"attributes\":{},\"id\":\"1268\",\"type\":\"PanTool\"},{\"attributes\":{\"overlay\":{\"id\":\"1242\",\"type\":\"BoxAnnotation\"}},\"id\":\"1221\",\"type\":\"BoxZoomTool\"},{\"attributes\":{},\"id\":\"1269\",\"type\":\"WheelZoomTool\"},{\"attributes\":{},\"id\":\"1223\",\"type\":\"ResetTool\"},{\"attributes\":{\"axis_label\":\"start (2019-07-01 08:42)\",\"formatter\":{\"id\":\"1245\",\"type\":\"DatetimeTickFormatter\"},\"ticker\":{\"id\":\"1210\",\"type\":\"BasicTicker\"}},\"id\":\"1209\",\"type\":\"LinearAxis\"},{\"attributes\":{\"overlay\":{\"id\":\"1314\",\"type\":\"BoxAnnotation\"}},\"id\":\"1270\",\"type\":\"BoxZoomTool\"},{\"attributes\":{\"axis_label\":\"frequency\",\"formatter\":{\"id\":\"1308\",\"type\":\"BasicTickFormatter\"},\"ticker\":{\"id\":\"1264\",\"type\":\"BasicTicker\"}},\"id\":\"1263\",\"type\":\"LinearAxis\"},{\"attributes\":{},\"id\":\"1224\",\"type\":\"HelpTool\"},{\"attributes\":{},\"id\":\"1254\",\"type\":\"LinearScale\"},{\"attributes\":{},\"id\":\"1271\",\"type\":\"SaveTool\"},{\"attributes\":{},\"id\":\"1210\",\"type\":\"BasicTicker\"},{\"attributes\":{},\"id\":\"1272\",\"type\":\"ResetTool\"},{\"attributes\":{},\"id\":\"1264\",\"type\":\"BasicTicker\"},{\"attributes\":{\"data_source\":{\"id\":\"1197\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"1233\",\"type\":\"Line\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"1234\",\"type\":\"Line\"},\"selection_glyph\":null,\"view\":{\"id\":\"1236\",\"type\":\"CDSView\"}},\"id\":\"1235\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"ticker\":{\"id\":\"1259\",\"type\":\"BasicTicker\"}},\"id\":\"1262\",\"type\":\"Grid\"},{\"attributes\":{},\"id\":\"1207\",\"type\":\"LinearScale\"},{\"attributes\":{},\"id\":\"1273\",\"type\":\"HelpTool\"},{\"attributes\":{\"align\":\"center\",\"text\":\"Duration histogram\"},\"id\":\"1248\",\"type\":\"Title\"},{\"attributes\":{\"callback\":null,\"data\":{\"duration\":{\"__ndarray__\":\"GZC93v3xyD+EhChf0EKyP90LzApFurM/dES+S6lLsj8oY3yYvWyzPwCrI0c6A7M/oyB4fHvXtD8pIO1/gLWyPxuADYgQV7I/HHxhMlUwsj+hn6nXLQKzP+RNfotOlrI/ajNOQ1Thsz8d5ssLsI+yP9FBl3DoLbI/7l9ZaVIKsj9Tlba4xmeyP9DU6xaBsbI//HCQEOULsj9tHRzsTQyZP61RD9HoDrI/C+wxkdJssj8QecvVj02yP5GA0eXN4bI/f73CgvsBsz/cSUT4F0GzP4gNFk7S/LE/SS7/If32yT/zOAzmr5CxPy8zbJT1m7E/TQ8KStHKsT9RacTMPo+xP3HIBtLFprE/yenr+Zrlsj9xHeOKi6OyPxrEB3b8F7Q/ipRm8zgMsj/+uP3yyYqxP84z9iUbD7I/9Gvrp/+svT8bf6KyYU21P8v49xkXDrQ/ODC5UWSttT81e6AVGLK2P8L2kzE+zLI/M6MfDafMsT8ng6Pk1TmyP3Yzox8Np7Q/i6azk8FRsj9/vFetTPi1P91ELc2tELI/XynLEMe6tD9XXByVm6ixP2AgCJChY5c/2UP7WMFvsz+94T5ya9KxP57RViWRfbA/Suza3m5Jsj9F2PD0SlmyP2q8dJMYBLI/n6ut2F92tz+21hcJbTmzP8mwijcyj7Q/\",\"dtype\":\"float64\",\"shape\":[63]},\"index\":[2,5,8,11,14,17,20,23,26,29,32,35,38,41,44,47,50,53,56,59,62,65,68,71,74,77,80,83,86,89,92,95,98,101,104,107,110,113,116,119,122,125,128,131,134,137,140,143,146,149,152,155,158,161,164,167,170,173,176,179,182,185,188],\"timestamp\":{\"__ndarray__\":\"H2eDOMu6dkJEkaI4y7p2QhfTwTjLunZC5xPhOMu6dkLNUgA5y7p2Qr6THznLunZCAtU+Ocu6dkJOFF45y7p2QgJTfTnLunZCUJOcOcu6dkLh0rs5y7p2QnET2znLunZCwVL6Ocu6dkL+khk6y7p2QjvTODrLunZC5RJYOsu6dkIGU3c6y7p2QgKTljrLunZCgdO1Osu6dkJtB9U6y7p2Qh9T9DrLunZCaJMTO8u6dkIE1jI7y7p2QjMVUjvLunZCmFRxO8u6dkJxk5A7y7p2QqrTrzvLunZCfRXPO8u6dkJOVO47y7p2QgyUDTzLunZC9NMsPMu6dkLlFEw8y7p2QstTazzLunZC5ZSKPMu6dkI11Kk8y7p2QrAUyTzLunZCQlToPMu6dkIjlQc9y7p2QmTVJj3LunZCzxVGPcu6dkICVWU9y7p2QgKXhD3LunZCZNWjPcu6dkJUFcM9y7p2QpFX4j3LunZCG5cBPsu6dkLX1SA+y7p2QggYQD7LunZCZFVfPsu6dkI5mH4+y7p2QifVnT7LunZC6Ri9Psu6dkKyVdw+y7p2QpqJ+z7LunZCXNcaP8u6dkI/FTo/y7p2QnFVWT/LunZC9JV4P8u6dkJq1pc/y7p2QgwWtz/LunZCM1XWP8u6dkJamPU/y7p2Qo3VFEDLunZC\",\"dtype\":\"float64\",\"shape\":[63]}},\"selected\":{\"id\":\"1312\",\"type\":\"Selection\"},\"selection_policy\":{\"id\":\"1313\",\"type\":\"UnionRenderers\"}},\"id\":\"1197\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"source\":{\"id\":\"1197\",\"type\":\"ColumnDataSource\"}},\"id\":\"1236\",\"type\":\"CDSView\"},{\"attributes\":{\"bottom\":{\"value\":0},\"fill_color\":{\"value\":\"#1f77b4\"},\"left\":{\"field\":\"left\"},\"line_color\":{\"value\":\"#1f77b4\"},\"right\":{\"field\":\"right\"},\"top\":{\"field\":\"top\"}},\"id\":\"1282\",\"type\":\"Quad\"},{\"attributes\":{\"line_alpha\":0.1,\"line_color\":\"#1f77b4\",\"line_width\":2,\"x\":{\"field\":\"timestamp\"},\"y\":{\"field\":\"duration\"}},\"id\":\"1234\",\"type\":\"Line\"},{\"attributes\":{\"bottom\":{\"value\":0},\"fill_alpha\":{\"value\":0.1},\"fill_color\":{\"value\":\"#1f77b4\"},\"left\":{\"field\":\"left\"},\"line_alpha\":{\"value\":0.1},\"line_color\":{\"value\":\"#1f77b4\"},\"right\":{\"field\":\"right\"},\"top\":{\"field\":\"top\"}},\"id\":\"1283\",\"type\":\"Quad\"},{\"attributes\":{\"items\":[{\"id\":\"1244\",\"type\":\"LegendItem\"}]},\"id\":\"1243\",\"type\":\"Legend\"},{\"attributes\":{},\"id\":\"1239\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{\"align\":\"center\",\"text\":\"Subscription -- node: test_ping, tid: 9876, topic: /pong\"},\"id\":\"1199\",\"type\":\"Title\"},{\"attributes\":{},\"id\":\"1259\",\"type\":\"BasicTicker\"},{\"attributes\":{\"data_source\":{\"id\":\"1281\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"1282\",\"type\":\"Quad\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"1283\",\"type\":\"Quad\"},\"selection_glyph\":null,\"view\":{\"id\":\"1285\",\"type\":\"CDSView\"}},\"id\":\"1284\",\"type\":\"GlyphRenderer\"},{\"attributes\":{},\"id\":\"1220\",\"type\":\"WheelZoomTool\"},{\"attributes\":{\"axis_label\":\"duration (ms)\",\"formatter\":{\"id\":\"1310\",\"type\":\"BasicTickFormatter\"},\"ticker\":{\"id\":\"1259\",\"type\":\"BasicTicker\"}},\"id\":\"1258\",\"type\":\"LinearAxis\"},{\"attributes\":{\"bottom_units\":\"screen\",\"fill_alpha\":{\"value\":0.5},\"fill_color\":{\"value\":\"lightgrey\"},\"left_units\":\"screen\",\"level\":\"overlay\",\"line_alpha\":{\"value\":1.0},\"line_color\":{\"value\":\"black\"},\"line_dash\":[4,4],\"line_width\":{\"value\":2},\"render_mode\":\"css\",\"right_units\":\"screen\",\"top_units\":\"screen\"},\"id\":\"1242\",\"type\":\"BoxAnnotation\"},{\"attributes\":{},\"id\":\"1205\",\"type\":\"LinearScale\"},{\"attributes\":{\"source\":{\"id\":\"1281\",\"type\":\"ColumnDataSource\"}},\"id\":\"1285\",\"type\":\"CDSView\"},{\"attributes\":{\"callback\":null},\"id\":\"1252\",\"type\":\"DataRange1d\"},{\"attributes\":{\"ticker\":{\"id\":\"1210\",\"type\":\"BasicTicker\"}},\"id\":\"1213\",\"type\":\"Grid\"},{\"attributes\":{},\"id\":\"1256\",\"type\":\"LinearScale\"},{\"attributes\":{},\"id\":\"1310\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{},\"id\":\"1308\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{\"below\":[{\"id\":\"1209\",\"type\":\"LinearAxis\"}],\"center\":[{\"id\":\"1213\",\"type\":\"Grid\"},{\"id\":\"1218\",\"type\":\"Grid\"},{\"id\":\"1243\",\"type\":\"Legend\"}],\"left\":[{\"id\":\"1214\",\"type\":\"LinearAxis\"}],\"plot_height\":450,\"plot_width\":450,\"renderers\":[{\"id\":\"1235\",\"type\":\"GlyphRenderer\"}],\"title\":{\"id\":\"1199\",\"type\":\"Title\"},\"toolbar\":{\"id\":\"1225\",\"type\":\"Toolbar\"},\"x_range\":{\"id\":\"1201\",\"type\":\"DataRange1d\"},\"x_scale\":{\"id\":\"1205\",\"type\":\"LinearScale\"},\"y_range\":{\"id\":\"1203\",\"type\":\"DataRange1d\"},\"y_scale\":{\"id\":\"1207\",\"type\":\"LinearScale\"}},\"id\":\"1198\",\"subtype\":\"Figure\",\"type\":\"Plot\"},{\"attributes\":{\"label\":{\"value\":\"[lambda]\"},\"renderers\":[{\"id\":\"1235\",\"type\":\"GlyphRenderer\"}]},\"id\":\"1244\",\"type\":\"LegendItem\"},{\"attributes\":{\"axis_label\":\"duration (ms)\",\"formatter\":{\"id\":\"1239\",\"type\":\"BasicTickFormatter\"},\"ticker\":{\"id\":\"1215\",\"type\":\"BasicTicker\"}},\"id\":\"1214\",\"type\":\"LinearAxis\"},{\"attributes\":{\"line_color\":\"#1f77b4\",\"line_width\":2,\"x\":{\"field\":\"timestamp\"},\"y\":{\"field\":\"duration\"}},\"id\":\"1233\",\"type\":\"Line\"},{\"attributes\":{},\"id\":\"1245\",\"type\":\"DatetimeTickFormatter\"},{\"attributes\":{},\"id\":\"1215\",\"type\":\"BasicTicker\"}],\"root_ids\":[\"1286\"]},\"title\":\"Bokeh Application\",\"version\":\"1.2.0\"}};\n", + " var render_items = [{\"docid\":\"5e908bd4-c7eb-4bae-b6d0-89beed606b4c\",\"roots\":{\"1286\":\"8339ff2c-a1ca-4310-a781-682c80a2e61b\"}}];\n", + " root.Bokeh.embed.embed_items_notebook(docs_json, render_items);\n", + "\n", + " }\n", + " if (root.Bokeh !== undefined) {\n", + " embed_document(root);\n", + " } else {\n", + " var attempts = 0;\n", + " var timer = setInterval(function(root) {\n", + " if (root.Bokeh !== undefined) {\n", + " embed_document(root);\n", + " clearInterval(timer);\n", + " }\n", + " attempts++;\n", + " if (attempts > 100) {\n", + " console.log(\"Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing\");\n", + " clearInterval(timer);\n", + " }\n", + " }, 10, root)\n", + " }\n", + "})(window);" + ], + "application/vnd.bokehjs_exec.v0+json": "" + }, + "metadata": { + "application/vnd.bokehjs_exec.v0+json": { + "id": "1286" + } + }, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "(function(root) {\n", + " function embed_document(root) {\n", + " \n", + " var docs_json = {\"b61d949d-b1bd-490c-9a29-a17619314661\":{\"roots\":{\"references\":[{\"attributes\":{\"children\":[{\"id\":\"1408\",\"subtype\":\"Figure\",\"type\":\"Plot\"},{\"id\":\"1457\",\"subtype\":\"Figure\",\"type\":\"Plot\"}]},\"id\":\"1496\",\"type\":\"Row\"},{\"attributes\":{},\"id\":\"1432\",\"type\":\"SaveTool\"},{\"attributes\":{},\"id\":\"1540\",\"type\":\"Selection\"},{\"attributes\":{},\"id\":\"1464\",\"type\":\"LinearScale\"},{\"attributes\":{\"callback\":null},\"id\":\"1411\",\"type\":\"DataRange1d\"},{\"attributes\":{},\"id\":\"1433\",\"type\":\"ResetTool\"},{\"attributes\":{},\"id\":\"1541\",\"type\":\"UnionRenderers\"},{\"attributes\":{},\"id\":\"1466\",\"type\":\"LinearScale\"},{\"attributes\":{\"line_alpha\":0.1,\"line_color\":\"#1f77b4\",\"line_width\":2,\"x\":{\"field\":\"timestamp\"},\"y\":{\"field\":\"duration\"}},\"id\":\"1444\",\"type\":\"Line\"},{\"attributes\":{},\"id\":\"1434\",\"type\":\"HelpTool\"},{\"attributes\":{\"align\":\"center\",\"text\":\"Subscription -- node: test_pong, tid: 9877, topic: /ping\"},\"id\":\"1409\",\"type\":\"Title\"},{\"attributes\":{\"axis_label\":\"duration (ms)\",\"formatter\":{\"id\":\"1535\",\"type\":\"BasicTickFormatter\"},\"ticker\":{\"id\":\"1469\",\"type\":\"BasicTicker\"}},\"id\":\"1468\",\"type\":\"LinearAxis\"},{\"attributes\":{\"bottom_units\":\"screen\",\"fill_alpha\":{\"value\":0.5},\"fill_color\":{\"value\":\"lightgrey\"},\"left_units\":\"screen\",\"level\":\"overlay\",\"line_alpha\":{\"value\":1.0},\"line_color\":{\"value\":\"black\"},\"line_dash\":[4,4],\"line_width\":{\"value\":2},\"render_mode\":\"css\",\"right_units\":\"screen\",\"top_units\":\"screen\"},\"id\":\"1539\",\"type\":\"BoxAnnotation\"},{\"attributes\":{},\"id\":\"1469\",\"type\":\"BasicTicker\"},{\"attributes\":{\"callback\":null},\"id\":\"1413\",\"type\":\"DataRange1d\"},{\"attributes\":{\"ticker\":{\"id\":\"1469\",\"type\":\"BasicTicker\"}},\"id\":\"1472\",\"type\":\"Grid\"},{\"attributes\":{\"data_source\":{\"id\":\"1407\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"1443\",\"type\":\"Line\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"1444\",\"type\":\"Line\"},\"selection_glyph\":null,\"view\":{\"id\":\"1446\",\"type\":\"CDSView\"}},\"id\":\"1445\",\"type\":\"GlyphRenderer\"},{\"attributes\":{},\"id\":\"1415\",\"type\":\"LinearScale\"},{\"attributes\":{\"source\":{\"id\":\"1407\",\"type\":\"ColumnDataSource\"}},\"id\":\"1446\",\"type\":\"CDSView\"},{\"attributes\":{\"axis_label\":\"frequency\",\"formatter\":{\"id\":\"1533\",\"type\":\"BasicTickFormatter\"},\"ticker\":{\"id\":\"1474\",\"type\":\"BasicTicker\"}},\"id\":\"1473\",\"type\":\"LinearAxis\"},{\"attributes\":{},\"id\":\"1417\",\"type\":\"LinearScale\"},{\"attributes\":{},\"id\":\"1449\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{\"callback\":null,\"data\":{\"duration\":{\"__ndarray__\":\"OKJ71jVa0T+bV3VWC+y9P5Hwvb9Be70/GAYsuYrFvz8CS65i8Zu+PwpJZvUOt78/9E2aBkXzvD8otRfRdky9PzPABdmyfL0/Z7lsdM5PvT92NA71u7C9Pz7NyYtMwL8/Gy5yT1d3vD8WaHdIMUC+P2A8g4b+CcA/lstG5/wUvz+k/+VatAC9PyS3Jt2WyL0/ij20jxX8vj9fzmxX6IOlP4du9gfKbb8/pwUv+grSvD+vsOB+wAPDP4I2OXzSicA/bJVgcTjzwT8RUrezrzzAP3IZNzXQfL4/2jnNAu0OvT+OdAZGXtbAP2fXvRWJCb4/ic4yi1BsvT/Cobd4eM/BP4TXLm04LL0/wF/MlqyKwj91j2yumufCP/SpY5XSM8M/N1FLcyuEvT88aHbdW5HCP0UsYthhTMI/n8n+eRowxD+l3H2OjxbBP79hokEKnsQ/R450BkZewj8ydOygEtfBPyZxVkRN9MU/SnmthO6SxD/uBPuvc9PCP59ZEqCmlsM/fuAqTyDswj8Z4lgXt9HAP8Bd9utOd8I/uagWEcXkvT9QOSaL+4/CP0HvjSEAOKY/NlZinpW0xD/CL/XzpiLDP3BCIQIOobY/GCe+2lGcwz8PuRluwOfBPxgjEoWWdcE/2zaMguDxwT8AA0GADB3HP0eum1JeK8E/\",\"dtype\":\"float64\",\"shape\":[63]},\"index\":[1,4,7,10,13,16,19,22,25,28,31,34,37,40,43,46,49,52,55,58,61,64,67,70,73,76,79,82,85,88,91,94,97,100,103,106,109,112,115,118,121,124,127,130,133,136,139,142,145,148,151,154,157,160,163,166,169,172,175,178,181,184,187],\"timestamp\":{\"__ndarray__\":\"01mDOMu6dkLDiaI4y7p2QmbKwTjLunZCxwvhOMu6dkLZSgA5y7p2QlSLHznLunZCnss+Ocu6dkLPC145y7p2QitLfTnLunZCP4ucOcu6dkLlyrs5y7p2QhsL2znLunZCvEr6Ocu6dkIOixk6y7p2QifLODrLunZCNwtYOsu6dkIvS3c6y7p2QhuLljrLunZCCsu1Osu6dkLuBNU6y7p2QkxL9DrLunZCYIsTO8u6dkLJzDI7y7p2Qq4LUjvLunZCZkxxO8u6dkJYi5A7y7p2QnHLrzvLunZCIQzPO8u6dkJiTO47y7p2QvyLDTzLunZC7MssPMu6dkK4DEw8y7p2QvhLazzLunZCj4yKPMu6dkJCzKk8y7p2QjEMyTzLunZCHUzoPMu6dkIKjQc9y7p2QmjNJj3LunZC/gxGPcu6dkJiTGU9y7p2Qg6NhD3LunZC4cyjPcu6dkIrDcM9y7p2QiNN4j3LunZC540BPsu6dkJxzSA+y7p2Qj8PQD7LunZCUE1fPsu6dkLyjn4+y7p2QlDNnT7LunZCrA69Psu6dkJUTdw+y7p2QvKG+z7LunZCO80aP8u6dkJgDTo/y7p2QoVLWT/LunZC0414P8u6dkKRzZc/y7p2QtcNtz/LunZCfU3WP8u6dkLjjfU/y7p2QtvNFEDLunZC\",\"dtype\":\"float64\",\"shape\":[63]}},\"selected\":{\"id\":\"1537\",\"type\":\"Selection\"},\"selection_policy\":{\"id\":\"1538\",\"type\":\"UnionRenderers\"}},\"id\":\"1407\",\"type\":\"ColumnDataSource\"},{\"attributes\":{},\"id\":\"1474\",\"type\":\"BasicTicker\"},{\"attributes\":{\"overlay\":{\"id\":\"1539\",\"type\":\"BoxAnnotation\"}},\"id\":\"1480\",\"type\":\"BoxZoomTool\"},{\"attributes\":{\"axis_label\":\"start (2019-07-01 08:42)\",\"formatter\":{\"id\":\"1455\",\"type\":\"DatetimeTickFormatter\"},\"ticker\":{\"id\":\"1420\",\"type\":\"BasicTicker\"}},\"id\":\"1419\",\"type\":\"LinearAxis\"},{\"attributes\":{\"dimension\":1,\"ticker\":{\"id\":\"1474\",\"type\":\"BasicTicker\"}},\"id\":\"1477\",\"type\":\"Grid\"},{\"attributes\":{},\"id\":\"1483\",\"type\":\"HelpTool\"},{\"attributes\":{\"active_drag\":\"auto\",\"active_inspect\":\"auto\",\"active_multi\":null,\"active_scroll\":\"auto\",\"active_tap\":\"auto\",\"tools\":[{\"id\":\"1478\",\"type\":\"PanTool\"},{\"id\":\"1479\",\"type\":\"WheelZoomTool\"},{\"id\":\"1480\",\"type\":\"BoxZoomTool\"},{\"id\":\"1481\",\"type\":\"SaveTool\"},{\"id\":\"1482\",\"type\":\"ResetTool\"},{\"id\":\"1483\",\"type\":\"HelpTool\"}]},\"id\":\"1484\",\"type\":\"Toolbar\"},{\"attributes\":{},\"id\":\"1420\",\"type\":\"BasicTicker\"},{\"attributes\":{\"below\":[{\"id\":\"1419\",\"type\":\"LinearAxis\"}],\"center\":[{\"id\":\"1423\",\"type\":\"Grid\"},{\"id\":\"1428\",\"type\":\"Grid\"},{\"id\":\"1453\",\"type\":\"Legend\"}],\"left\":[{\"id\":\"1424\",\"type\":\"LinearAxis\"}],\"plot_height\":450,\"plot_width\":450,\"renderers\":[{\"id\":\"1445\",\"type\":\"GlyphRenderer\"}],\"title\":{\"id\":\"1409\",\"type\":\"Title\"},\"toolbar\":{\"id\":\"1435\",\"type\":\"Toolbar\"},\"x_range\":{\"id\":\"1411\",\"type\":\"DataRange1d\"},\"x_scale\":{\"id\":\"1415\",\"type\":\"LinearScale\"},\"y_range\":{\"id\":\"1413\",\"type\":\"DataRange1d\"},\"y_scale\":{\"id\":\"1417\",\"type\":\"LinearScale\"}},\"id\":\"1408\",\"subtype\":\"Figure\",\"type\":\"Plot\"},{\"attributes\":{\"bottom_units\":\"screen\",\"fill_alpha\":{\"value\":0.5},\"fill_color\":{\"value\":\"lightgrey\"},\"left_units\":\"screen\",\"level\":\"overlay\",\"line_alpha\":{\"value\":1.0},\"line_color\":{\"value\":\"black\"},\"line_dash\":[4,4],\"line_width\":{\"value\":2},\"render_mode\":\"css\",\"right_units\":\"screen\",\"top_units\":\"screen\"},\"id\":\"1452\",\"type\":\"BoxAnnotation\"},{\"attributes\":{\"bottom\":{\"value\":0},\"fill_alpha\":{\"value\":0.1},\"fill_color\":{\"value\":\"#1f77b4\"},\"left\":{\"field\":\"left\"},\"line_alpha\":{\"value\":0.1},\"line_color\":{\"value\":\"#1f77b4\"},\"right\":{\"field\":\"right\"},\"top\":{\"field\":\"top\"}},\"id\":\"1493\",\"type\":\"Quad\"},{\"attributes\":{},\"id\":\"1481\",\"type\":\"SaveTool\"},{\"attributes\":{\"ticker\":{\"id\":\"1420\",\"type\":\"BasicTicker\"}},\"id\":\"1423\",\"type\":\"Grid\"},{\"attributes\":{\"items\":[{\"id\":\"1454\",\"type\":\"LegendItem\"}]},\"id\":\"1453\",\"type\":\"Legend\"},{\"attributes\":{\"axis_label\":\"duration (ms)\",\"formatter\":{\"id\":\"1449\",\"type\":\"BasicTickFormatter\"},\"ticker\":{\"id\":\"1425\",\"type\":\"BasicTicker\"}},\"id\":\"1424\",\"type\":\"LinearAxis\"},{\"attributes\":{\"data_source\":{\"id\":\"1491\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"1492\",\"type\":\"Quad\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"1493\",\"type\":\"Quad\"},\"selection_glyph\":null,\"view\":{\"id\":\"1495\",\"type\":\"CDSView\"}},\"id\":\"1494\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"label\":{\"value\":\"[lambda]\"},\"renderers\":[{\"id\":\"1445\",\"type\":\"GlyphRenderer\"}]},\"id\":\"1454\",\"type\":\"LegendItem\"},{\"attributes\":{},\"id\":\"1425\",\"type\":\"BasicTicker\"},{\"attributes\":{\"source\":{\"id\":\"1491\",\"type\":\"ColumnDataSource\"}},\"id\":\"1495\",\"type\":\"CDSView\"},{\"attributes\":{},\"id\":\"1455\",\"type\":\"DatetimeTickFormatter\"},{\"attributes\":{},\"id\":\"1479\",\"type\":\"WheelZoomTool\"},{\"attributes\":{\"bottom\":{\"value\":0},\"fill_color\":{\"value\":\"#1f77b4\"},\"left\":{\"field\":\"left\"},\"line_color\":{\"value\":\"#1f77b4\"},\"right\":{\"field\":\"right\"},\"top\":{\"field\":\"top\"}},\"id\":\"1492\",\"type\":\"Quad\"},{\"attributes\":{\"dimension\":1,\"ticker\":{\"id\":\"1425\",\"type\":\"BasicTicker\"}},\"id\":\"1428\",\"type\":\"Grid\"},{\"attributes\":{\"line_color\":\"#1f77b4\",\"line_width\":2,\"x\":{\"field\":\"timestamp\"},\"y\":{\"field\":\"duration\"}},\"id\":\"1443\",\"type\":\"Line\"},{\"attributes\":{},\"id\":\"1533\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{},\"id\":\"1535\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{\"callback\":null,\"data\":{\"left\":{\"__ndarray__\":\"X85sV+iDpT9bN+9JcZ+wP4YHKGjufLY/stdghmtavD/u00xS9BvBPwQ8aeGyCsQ/GqSFcHH5xj8vDKL/L+jJP0V0vo7u1sw/W9zaHa3Fzz8=\",\"dtype\":\"float64\",\"shape\":[10]},\"right\":{\"__ndarray__\":\"WzfvSXGfsD+GByho7ny2P7LXYIZrWrw/7tNMUvQbwT8EPGnhsgrEPxqkhXBx+cY/Lwyi/y/oyT9FdL6O7tbMP1vc2h2txc8/OKJ71jVa0T8=\",\"dtype\":\"float64\",\"shape\":[10]},\"top\":[2,0,1,32,21,5,1,0,0,1]},\"selected\":{\"id\":\"1540\",\"type\":\"Selection\"},\"selection_policy\":{\"id\":\"1541\",\"type\":\"UnionRenderers\"}},\"id\":\"1491\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"active_drag\":\"auto\",\"active_inspect\":\"auto\",\"active_multi\":null,\"active_scroll\":\"auto\",\"active_tap\":\"auto\",\"tools\":[{\"id\":\"1429\",\"type\":\"PanTool\"},{\"id\":\"1430\",\"type\":\"WheelZoomTool\"},{\"id\":\"1431\",\"type\":\"BoxZoomTool\"},{\"id\":\"1432\",\"type\":\"SaveTool\"},{\"id\":\"1433\",\"type\":\"ResetTool\"},{\"id\":\"1434\",\"type\":\"HelpTool\"}]},\"id\":\"1435\",\"type\":\"Toolbar\"},{\"attributes\":{\"align\":\"center\",\"text\":\"Duration histogram\"},\"id\":\"1458\",\"type\":\"Title\"},{\"attributes\":{},\"id\":\"1482\",\"type\":\"ResetTool\"},{\"attributes\":{},\"id\":\"1429\",\"type\":\"PanTool\"},{\"attributes\":{\"below\":[{\"id\":\"1468\",\"type\":\"LinearAxis\"}],\"center\":[{\"id\":\"1472\",\"type\":\"Grid\"},{\"id\":\"1477\",\"type\":\"Grid\"}],\"left\":[{\"id\":\"1473\",\"type\":\"LinearAxis\"}],\"plot_height\":450,\"plot_width\":450,\"renderers\":[{\"id\":\"1494\",\"type\":\"GlyphRenderer\"}],\"title\":{\"id\":\"1458\",\"type\":\"Title\"},\"toolbar\":{\"id\":\"1484\",\"type\":\"Toolbar\"},\"x_range\":{\"id\":\"1460\",\"type\":\"DataRange1d\"},\"x_scale\":{\"id\":\"1464\",\"type\":\"LinearScale\"},\"y_range\":{\"id\":\"1462\",\"type\":\"DataRange1d\"},\"y_scale\":{\"id\":\"1466\",\"type\":\"LinearScale\"}},\"id\":\"1457\",\"subtype\":\"Figure\",\"type\":\"Plot\"},{\"attributes\":{},\"id\":\"1537\",\"type\":\"Selection\"},{\"attributes\":{},\"id\":\"1538\",\"type\":\"UnionRenderers\"},{\"attributes\":{\"callback\":null},\"id\":\"1460\",\"type\":\"DataRange1d\"},{\"attributes\":{},\"id\":\"1478\",\"type\":\"PanTool\"},{\"attributes\":{},\"id\":\"1430\",\"type\":\"WheelZoomTool\"},{\"attributes\":{\"callback\":null},\"id\":\"1462\",\"type\":\"DataRange1d\"},{\"attributes\":{\"overlay\":{\"id\":\"1452\",\"type\":\"BoxAnnotation\"}},\"id\":\"1431\",\"type\":\"BoxZoomTool\"}],\"root_ids\":[\"1496\"]},\"title\":\"Bokeh Application\",\"version\":\"1.2.0\"}};\n", + " var render_items = [{\"docid\":\"b61d949d-b1bd-490c-9a29-a17619314661\",\"roots\":{\"1496\":\"d093cb06-af36-418e-95f4-e6532fb522a3\"}}];\n", + " root.Bokeh.embed.embed_items_notebook(docs_json, render_items);\n", + "\n", + " }\n", + " if (root.Bokeh !== undefined) {\n", + " embed_document(root);\n", + " } else {\n", + " var attempts = 0;\n", + " var timer = setInterval(function(root) {\n", + " if (root.Bokeh !== undefined) {\n", + " embed_document(root);\n", + " clearInterval(timer);\n", + " }\n", + " attempts++;\n", + " if (attempts > 100) {\n", + " console.log(\"Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing\");\n", + " clearInterval(timer);\n", + " }\n", + " }, 10, root)\n", + " }\n", + "})(window);" + ], + "application/vnd.bokehjs_exec.v0+json": "" + }, + "metadata": { + "application/vnd.bokehjs_exec.v0+json": { + "id": "1496" + } + }, + "output_type": "display_data" + } + ], + "source": [ + "data_util = utils.DataModelUtil(data_model)\n", + "\n", + "callback_symbols = data_util.get_callback_symbols()\n", + "\n", + "output_notebook()\n", + "psize = 450\n", + "\n", + "# Plot durations\n", + "for obj, symbol in callback_symbols.items():\n", + " owner_info = data_util.get_callback_owner_info(obj)\n", + " if owner_info is None:\n", + " owner_info = '[unknown]'\n", + "\n", + " # Duration\n", + " duration_df = data_util.get_callback_durations(obj)\n", + " starttime = duration_df.loc[:, 'timestamp'].iloc[0].strftime('%Y-%m-%d %H:%M')\n", + " source = ColumnDataSource(duration_df)\n", + " duration = figure(title=owner_info,\n", + " x_axis_label=f'start ({starttime})',\n", + " y_axis_label='duration (ms)',\n", + " plot_width=psize, plot_height=psize)\n", + " duration.title.align = 'center'\n", + " duration.line(x='timestamp', y='duration', legend=str(symbol), line_width=2, source=source)\n", + " duration.xaxis[0].formatter = DatetimeTickFormatter(seconds=['%Ss'])\n", + "\n", + " # Histogram\n", + " dur_hist, edges = np.histogram(duration_df['duration'])\n", + " duration_hist = pd.DataFrame({'duration': dur_hist, \n", + " 'left': edges[:-1], \n", + " 'right': edges[1:]})\n", + " hist = figure(title='Duration histogram',\n", + " x_axis_label='duration (ms)',\n", + " y_axis_label='frequency',\n", + " plot_width=psize, plot_height=psize)\n", + " hist.title.align = 'center'\n", + " hist.quad(bottom=0, top=duration_hist['duration'], \n", + " left=duration_hist['left'], right=duration_hist['right'])\n", + "\n", + " show(row(duration, hist))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tracetools_analysis/package.xml b/tracetools_analysis/package.xml new file mode 100644 index 0000000..c07368c --- /dev/null +++ b/tracetools_analysis/package.xml @@ -0,0 +1,23 @@ + + + + tracetools_analysis + 0.0.1 + Tools for analysing trace data + Christophe Bedard + Ingo Lütkebohle + Apache 2.0 + Ingo Luetkebohle + Christophe Bedard + + tracetools_read + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/tracetools_analysis/setup.cfg b/tracetools_analysis/setup.cfg new file mode 100644 index 0000000..721ff12 --- /dev/null +++ b/tracetools_analysis/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script-dir=$base/lib/tracetools_analysis +[install] +install-scripts=$base/lib/tracetools_analysis diff --git a/tracetools_analysis/setup.py b/tracetools_analysis/setup.py new file mode 100644 index 0000000..b694047 --- /dev/null +++ b/tracetools_analysis/setup.py @@ -0,0 +1,41 @@ +from setuptools import find_packages +from setuptools import setup + +package_name = 'tracetools_analysis' + +setup( + name=package_name, + version='0.0.1', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + maintainer=( + 'Christophe Bedard, ' + 'Ingo Lütkebohle' + ), + maintainer_email=( + 'fixed-term.christophe.bourquebedard@de.bosch.com, ' + 'ingo.luetkebohle@de.bosch.com' + ), + author=( + 'Christophe Bedard, ' + 'Ingo Lütkebohle' + ), + author_email=( + 'fixed-term.christophe.bourquebedard@de.bosch.com, ' + 'ingo.luetkebohle@de.bosch.com' + ), + # url='', + keywords=['ROS'], + description='Tools for analysing trace data', + entry_points={ + 'console_scripts': [ + f'convert = {package_name}.convert:main', + f'process = {package_name}.process:main', + ], + }, + license='Apache 2.0', + tests_require=['pytest'], +) diff --git a/tracetools_analysis/test/test_copyright.py b/tracetools_analysis/test/test_copyright.py new file mode 100644 index 0000000..cf0fae3 --- /dev/null +++ b/tracetools_analysis/test/test_copyright.py @@ -0,0 +1,23 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/tracetools_analysis/test/test_flake8.py b/tracetools_analysis/test/test_flake8.py new file mode 100644 index 0000000..eff8299 --- /dev/null +++ b/tracetools_analysis/test/test_flake8.py @@ -0,0 +1,23 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc = main(argv=[]) + assert rc == 0, 'Found errors' diff --git a/tracetools_analysis/test/test_pep257.py b/tracetools_analysis/test/test_pep257.py new file mode 100644 index 0000000..3aeb4d3 --- /dev/null +++ b/tracetools_analysis/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=[]) + assert rc == 0, 'Found code style errors / warnings' diff --git a/tracetools_analysis/tracetools_analysis/__init__.py b/tracetools_analysis/tracetools_analysis/__init__.py new file mode 100644 index 0000000..e4deba3 --- /dev/null +++ b/tracetools_analysis/tracetools_analysis/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2019 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Reading and interpreting of LTTng trace data.""" +__author__ = 'Luetkebohle Ingo (CR/AEX3)' diff --git a/tracetools_analysis/tracetools_analysis/analysis/__init__.py b/tracetools_analysis/tracetools_analysis/analysis/__init__.py new file mode 100644 index 0000000..4b18865 --- /dev/null +++ b/tracetools_analysis/tracetools_analysis/analysis/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2019 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/tracetools_analysis/tracetools_analysis/analysis/data_model.py b/tracetools_analysis/tracetools_analysis/analysis/data_model.py new file mode 100644 index 0000000..d9814f1 --- /dev/null +++ b/tracetools_analysis/tracetools_analysis/analysis/data_model.py @@ -0,0 +1,168 @@ +# Copyright 2019 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module for data model.""" + +import pandas as pd + + +class DataModel(): + """ + Container to model pre-processed data for analysis. + + Contains data for an analysis to use. This is a middleground between trace events data and the + output data of an analysis. This aims to represent the data in a ROS-aware way. + It uses pandas DataFrames directly. + """ + + def __init__(self) -> None: + # Objects (one-time events, usually when something is created) + self.contexts = pd.DataFrame(columns=['context_handle', + 'timestamp', + 'pid', + 'version']) + self.contexts.set_index(['context_handle'], inplace=True, drop=True) + self.nodes = pd.DataFrame(columns=['node_handle', + 'timestamp', + 'tid', + 'rmw_handle', + 'name', + 'namespace']) + self.nodes.set_index(['node_handle'], inplace=True, drop=True) + self.publishers = pd.DataFrame(columns=['publisher_handle', + 'timestamp', + 'node_handle', + 'rmw_handle', + 'topic_name', + 'depth']) + self.publishers.set_index(['publisher_handle'], inplace=True, drop=True) + self.subscriptions = pd.DataFrame(columns=['subscription_handle', + 'timestamp', + 'node_handle', + 'rmw_handle', + 'topic_name', + 'depth']) + self.subscriptions.set_index(['subscription_handle'], inplace=True, drop=True) + self.services = pd.DataFrame(columns=['service_handle', + 'timestamp', + 'node_handle', + 'rmw_handle', + 'service_name']) + self.services.set_index(['service_handle'], inplace=True, drop=True) + self.clients = pd.DataFrame(columns=['client_handle', + 'timestamp', + 'node_handle', + 'rmw_handle', + 'service_name']) + self.clients.set_index(['client_handle'], inplace=True, drop=True) + self.timers = pd.DataFrame(columns=['timer_handle', + 'timestamp', + 'period', + 'tid']) + self.timers.set_index(['timer_handle'], inplace=True, drop=True) + + self.callback_objects = pd.DataFrame(columns=['handle', + 'timestamp', + 'callback_object']) + self.callback_objects.set_index(['handle'], inplace=True, drop=True) + self.callback_symbols = pd.DataFrame(columns=['callback_object', + 'timestamp', + 'symbol']) + self.callback_symbols.set_index(['callback_object'], inplace=True, drop=True) + + # Events (multiple instances, may not have a meaningful index) + self.callback_instances = pd.DataFrame(columns=['callback_object', + 'timestamp', + 'duration', + 'intra_process']) + + def add_context( + self, context_handle, timestamp, pid, version + ) -> None: + self.contexts.loc[context_handle] = [timestamp, pid, version] + + def add_node( + self, node_handle, timestamp, tid, rmw_handle, name, namespace + ) -> None: + self.nodes.loc[node_handle] = [timestamp, tid, rmw_handle, name, namespace] + + def add_publisher( + self, handle, timestamp, node_handle, rmw_handle, topic_name, depth + ) -> None: + self.publishers.loc[handle] = [timestamp, node_handle, rmw_handle, topic_name, depth] + + def add_subscription( + self, handle, timestamp, node_handle, rmw_handle, topic_name, depth + ) -> None: + self.subscriptions.loc[handle] = [timestamp, node_handle, rmw_handle, topic_name, depth] + + def add_service( + self, handle, timestamp, node_handle, rmw_handle, service_name + ) -> None: + self.services.loc[handle] = [timestamp, node_handle, rmw_handle, service_name] + + def add_client( + self, handle, timestamp, node_handle, rmw_handle, service_name + ) -> None: + self.clients.loc[handle] = [timestamp, node_handle, rmw_handle, service_name] + + def add_timer( + self, handle, timestamp, period, tid + ) -> None: + self.timers.loc[handle] = [timestamp, period, tid] + + def add_callback_object( + self, handle, timestamp, callback_object + ) -> None: + self.callback_objects.loc[handle] = [timestamp, callback_object] + + def add_callback_symbol( + self, callback_object, timestamp, symbol + ) -> None: + self.callback_symbols.loc[callback_object] = [timestamp, symbol] + + def add_callback_instance( + self, callback_object, timestamp, duration, intra_process + ) -> None: + data = { + 'callback_object': callback_object, + 'timestamp': timestamp, + 'duration': duration, + 'intra_process': intra_process, + } + self.callback_instances = self.callback_instances.append(data, ignore_index=True) + + def print_model(self) -> None: + """Debug method to print every contained df.""" + print('====================DATA MODEL====================') + print(f'Contexts:\n{self.contexts.to_string()}') + print() + print(f'Nodes:\n{self.nodes.to_string()}') + print() + print(f'Publishers:\n{self.publishers.to_string()}') + print() + print(f'Subscriptions:\n{self.subscriptions.to_string()}') + print() + print(f'Services:\n{self.services.to_string()}') + print() + print(f'Clients:\n{self.clients.to_string()}') + print() + print(f'Timers:\n{self.timers.to_string()}') + print() + print(f'Callback objects:\n{self.callback_objects.to_string()}') + print() + print(f'Callback symbols:\n{self.callback_symbols.to_string()}') + print() + print(f'Callback instances:\n{self.callback_instances.to_string()}') + print('==================================================') diff --git a/tracetools_analysis/tracetools_analysis/analysis/handler.py b/tracetools_analysis/tracetools_analysis/analysis/handler.py new file mode 100644 index 0000000..71de848 --- /dev/null +++ b/tracetools_analysis/tracetools_analysis/analysis/handler.py @@ -0,0 +1,71 @@ +# Copyright 2019 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module for event handler.""" + +import sys +from typing import Callable +from typing import Dict +from typing import List + +from tracetools_read.utils import get_event_name +from tracetools_read.utils import get_field + +from .lttng_models import EventMetadata + + +class EventHandler(): + """Base event handling class.""" + + def __init__(self, handler_map: Dict[str, Callable[[Dict, EventMetadata], None]]) -> None: + """ + Constructor. + + :param handler_map: the mapping from event name to handling method + """ + self._handler_map = handler_map + + def handle_events(self, events: List[Dict[str, str]]) -> None: + """ + Handle events by calling their handlers. + + :param events: the events to process + """ + for event in events: + self._handle(event) + + def _handle(self, event: Dict[str, str]) -> None: + event_name = get_event_name(event) + handler_function = self._handler_map.get(event_name, None) + if handler_function is not None: + pid = get_field( + event, + 'vpid', + default=get_field( + event, + 'pid', + raise_if_not_found=False)) + tid = get_field( + event, + 'vtid', + default=get_field( + event, + 'tid', + raise_if_not_found=False)) + timestamp = get_field(event, '_timestamp') + procname = get_field(event, 'procname') + metadata = EventMetadata(event_name, pid, tid, timestamp, procname) + handler_function(event, metadata) + else: + print(f'unhandled event name: {event_name}', file=sys.stderr) diff --git a/tracetools_analysis/tracetools_analysis/analysis/load.py b/tracetools_analysis/tracetools_analysis/analysis/load.py new file mode 100644 index 0000000..e79dfa4 --- /dev/null +++ b/tracetools_analysis/tracetools_analysis/analysis/load.py @@ -0,0 +1,38 @@ +# Copyright 2019 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module for pickle loading.""" + +import pickle +from typing import Dict +from typing import List + + +def load_pickle(pickle_file_path: str) -> List[Dict]: + """ + Load pickle file containing converted trace events. + + :param pickle_file_path: the path to the pickle file to load + :return: the list of events read from the file + """ + events = [] + with open(pickle_file_path, 'rb') as f: + p = pickle.Unpickler(f) + while True: + try: + events.append(p.load()) + except EOFError: + break # we're done + + return events diff --git a/tracetools_analysis/tracetools_analysis/analysis/lttng_models.py b/tracetools_analysis/tracetools_analysis/analysis/lttng_models.py new file mode 100644 index 0000000..7de325c --- /dev/null +++ b/tracetools_analysis/tracetools_analysis/analysis/lttng_models.py @@ -0,0 +1,46 @@ +# Copyright 2019 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module LTTng traces/events models.""" + + +class EventMetadata(): + """Container for event metadata.""" + + def __init__(self, event_name, pid, tid, timestamp, procname) -> None: + self._event_name = event_name + self._pid = pid + self._tid = tid + self._timestamp = timestamp + self._procname = procname + + @property + def event_name(self): + return self._event_name + + @property + def pid(self): + return self._pid + + @property + def tid(self): + return self._tid + + @property + def timestamp(self): + return self._timestamp + + @property + def procname(self): + return self._procname diff --git a/tracetools_analysis/tracetools_analysis/analysis/ros2_processor.py b/tracetools_analysis/tracetools_analysis/analysis/ros2_processor.py new file mode 100644 index 0000000..47b6dea --- /dev/null +++ b/tracetools_analysis/tracetools_analysis/analysis/ros2_processor.py @@ -0,0 +1,212 @@ +# Copyright 2019 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module for trace events processor and ROS model creation.""" + +from typing import Dict +from typing import List + +from tracetools_read.utils import get_field + +from .data_model import DataModel +from .handler import EventHandler +from .lttng_models import EventMetadata + + +class Ros2Processor(EventHandler): + """ + ROS 2-aware event processing/handling class implementation. + + Handles a trace's events and builds a model with the data. + """ + + def __init__(self) -> None: + # Link a ROS trace event to its corresponding handling method + handler_map = { + 'ros2:rcl_init': + self._handle_rcl_init, + 'ros2:rcl_node_init': + self._handle_rcl_node_init, + 'ros2:rcl_publisher_init': + self._handle_rcl_publisher_init, + 'ros2:rcl_subscription_init': + self._handle_subscription_init, + 'ros2:rclcpp_subscription_callback_added': + self._handle_rclcpp_subscription_callback_added, + 'ros2:rcl_service_init': + self._handle_rcl_service_init, + 'ros2:rclcpp_service_callback_added': + self._handle_rclcpp_service_callback_added, + 'ros2:rcl_client_init': + self._handle_rcl_client_init, + 'ros2:rcl_timer_init': + self._handle_rcl_timer_init, + 'ros2:rclcpp_timer_callback_added': + self._handle_rclcpp_timer_callback_added, + 'ros2:rclcpp_callback_register': + self._handle_rclcpp_callback_register, + 'ros2:callback_start': + self._handle_callback_start, + 'ros2:callback_end': + self._handle_callback_end, + } + super().__init__(handler_map) + + self._data = DataModel() + + # Temporary buffers + self._callback_instances = {} + + def get_data_model(self) -> DataModel: + return self._data + + def _handle_rcl_init( + self, event: Dict, metadata: EventMetadata + ) -> None: + context_handle = get_field(event, 'context_handle') + timestamp = metadata.timestamp + pid = metadata.pid + version = get_field(event, 'version') + self._data.add_context(context_handle, timestamp, pid, version) + + def _handle_rcl_node_init( + self, event: Dict, metadata: EventMetadata + ) -> None: + handle = get_field(event, 'node_handle') + timestamp = metadata.timestamp + tid = metadata.tid + rmw_handle = get_field(event, 'rmw_handle') + name = get_field(event, 'node_name') + namespace = get_field(event, 'namespace') + self._data.add_node(handle, timestamp, tid, rmw_handle, name, namespace) + + def _handle_rcl_publisher_init( + self, event: Dict, metadata: EventMetadata + ) -> None: + handle = get_field(event, 'publisher_handle') + timestamp = metadata.timestamp + node_handle = get_field(event, 'node_handle') + rmw_handle = get_field(event, 'rmw_publisher_handle') + topic_name = get_field(event, 'topic_name') + depth = get_field(event, 'queue_depth') + self._data.add_publisher(handle, timestamp, node_handle, rmw_handle, topic_name, depth) + + def _handle_subscription_init( + self, event: Dict, metadata: EventMetadata + ) -> None: + handle = get_field(event, 'subscription_handle') + timestamp = metadata.timestamp + node_handle = get_field(event, 'node_handle') + rmw_handle = get_field(event, 'rmw_subscription_handle') + topic_name = get_field(event, 'topic_name') + depth = get_field(event, 'queue_depth') + self._data.add_subscription(handle, timestamp, node_handle, rmw_handle, topic_name, depth) + + def _handle_rclcpp_subscription_callback_added( + self, event: Dict, metadata: EventMetadata + ) -> None: + handle = get_field(event, 'subscription_handle') + timestamp = metadata.timestamp + callback_object = get_field(event, 'callback') + self._data.add_callback_object(handle, timestamp, callback_object) + + def _handle_rcl_service_init( + self, event: Dict, metadata: EventMetadata + ) -> None: + handle = get_field(event, 'service_handle') + timestamp = metadata.timestamp + node_handle = get_field(event, 'node_handle') + rmw_handle = get_field(event, 'rmw_service_handle') + service_name = get_field(event, 'service_name') + self._data.add_service(handle, timestamp, node_handle, rmw_handle, service_name) + + def _handle_rclcpp_service_callback_added( + self, event: Dict, metadata: EventMetadata + ) -> None: + handle = get_field(event, 'service_handle') + timestamp = metadata.timestamp + callback_object = get_field(event, 'callback') + self._data.add_callback_object(handle, timestamp, callback_object) + + def _handle_rcl_client_init( + self, event: Dict, metadata: EventMetadata + ) -> None: + handle = get_field(event, 'client_handle') + timestamp = metadata.timestamp + node_handle = get_field(event, 'node_handle') + rmw_handle = get_field(event, 'rmw_client_handle') + service_name = get_field(event, 'service_name') + self._data.add_client(handle, timestamp, node_handle, rmw_handle, service_name) + + def _handle_rcl_timer_init( + self, event: Dict, metadata: EventMetadata + ) -> None: + handle = get_field(event, 'timer_handle') + timestamp = metadata.timestamp + period = get_field(event, 'period') + tid = metadata.tid + self._data.add_timer(handle, timestamp, period, tid) + + def _handle_rclcpp_timer_callback_added( + self, event: Dict, metadata: EventMetadata + ) -> None: + handle = get_field(event, 'timer_handle') + timestamp = metadata.timestamp + callback_object = get_field(event, 'callback') + self._data.add_callback_object(handle, timestamp, callback_object) + + def _handle_rclcpp_callback_register( + self, event: Dict, metadata: EventMetadata + ) -> None: + callback_object = get_field(event, 'callback') + timestamp = metadata.timestamp + symbol = get_field(event, 'symbol') + self._data.add_callback_symbol(callback_object, timestamp, symbol) + + def _handle_callback_start( + self, event: Dict, metadata: EventMetadata + ) -> None: + # Add to dict + callback_addr = get_field(event, 'callback') + self._callback_instances[callback_addr] = (event, metadata) + + def _handle_callback_end( + self, event: Dict, metadata: EventMetadata + ) -> None: + # Fetch from dict + callback_object = get_field(event, 'callback') + (event_start, metadata_start) = self._callback_instances.get(callback_object) + if event_start is not None and metadata_start is not None: + del self._callback_instances[callback_object] + duration = metadata.timestamp - metadata_start.timestamp + is_intra_process = get_field(event_start, 'is_intra_process', raise_if_not_found=False) + self._data.add_callback_instance( + callback_object, + metadata_start.timestamp, + duration, + bool(is_intra_process)) + else: + print(f'No matching callback start for callback object "{callback_object}"') + + +def ros2_process(events: List[Dict[str, str]]) -> Ros2Processor: + """ + Process unpickled events and create ROS 2 model. + + :param events: the list of events + :return: the processor object + """ + processor = Ros2Processor() + processor.handle_events(events) + return processor diff --git a/tracetools_analysis/tracetools_analysis/analysis/utils.py b/tracetools_analysis/tracetools_analysis/analysis/utils.py new file mode 100644 index 0000000..2140730 --- /dev/null +++ b/tracetools_analysis/tracetools_analysis/analysis/utils.py @@ -0,0 +1,231 @@ +# Copyright 2019 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module for data model utility class.""" + +from datetime import datetime as dt +from typing import Any +from typing import Mapping +from typing import Union + +from pandas import DataFrame + +from .data_model import DataModel + + +class DataModelUtil(): + """ + Data model utility class. + + Provides functions to get info on a data model. + """ + + def __init__(self, data_model: DataModel) -> None: + """ + Constructor. + + :param data_model: the data model object to use + """ + self._data = data_model + + def get_callback_symbols(self) -> Mapping[int, str]: + """ + Get mappings between a callback object and its resolved symbol. + + :return: the map + """ + callback_instances = self._data.callback_instances + callback_symbols = self._data.callback_symbols + + # Get a list of callback objects + callback_objects = set(callback_instances['callback_object']) + # Get their symbol + return {obj: callback_symbols.loc[obj, 'symbol'] for obj in callback_objects} + + def get_callback_durations( + self, callback_obj: int + ) -> DataFrame: + """ + Get durations of callback instances for a given callback object. + + :param callback_obj: the callback object value + :return: a dataframe containing the start timestamp (datetime) + and duration (ms) of all callback instances for that object + """ + data = self._data.callback_instances.loc[ + self._data.callback_instances.loc[:, 'callback_object'] == callback_obj, + ['timestamp', 'duration'] + ] + # Transform both columns to ms + data[['timestamp', 'duration']] = data[ + ['timestamp', 'duration'] + ].apply(lambda d: d / 1000000.0) + # Transform start timestamp column to datetime objects + data['timestamp'] = data['timestamp'].apply(lambda t: dt.fromtimestamp(t / 1000.0)) + return data + + def get_callback_owner_info( + self, callback_obj: int + ) -> Union[str, None]: + """ + Get information about the owner of a callback. + + Depending on the type of callback, it will give different kinds of info: + * subscription: node name, topic name + * timer: tid, period of timer + * service/client: node name, service name + + :param callback_obj: the callback object value + :return: information about the owner of the callback, or `None` if it fails + """ + # Get handle corresponding to callback object + handle = self._data.callback_objects.loc[ + self._data.callback_objects['callback_object'] == callback_obj + ].index.values.astype(int)[0] + + type_name = None + info = None + # Check if it's a timer first (since it's slightly different than the others) + if handle in self._data.timers.index: + type_name = 'Timer' + info = self.get_timer_handle_info(handle) + elif handle in self._data.publishers.index: + type_name = 'Publisher' + info = self.get_publisher_handle_info(handle) + elif handle in self._data.subscriptions.index: + type_name = 'Subscription' + info = self.get_subscription_handle_info(handle) + elif handle in self._data.services.index: + type_name = 'Service' + info = self.get_subscription_handle_info(handle) + elif handle in self._data.clients.index: + type_name = 'Client' + info = self.get_client_handle_info(handle) + + if info is not None: + info = f'{type_name} -- {self.format_info_dict(info)}' + return info + + def get_timer_handle_info( + self, timer_handle: int + ) -> Union[Mapping[str, Any], None]: + """ + Get information about the owner of a timer. + + :param timer_handle: the timer handle value + :return: a dictionary with name:value info, or `None` if it fails + """ + # TODO find a way to link a timer to a specific node + if timer_handle not in self._data.timers.index: + return None + + tid = self._data.timers.loc[timer_handle, 'tid'] + period_ns = self._data.timers.loc[timer_handle, 'period'] + period_ms = period_ns / 1000000.0 + return {'tid': tid, 'period': f'{period_ms:.0f} ms'} + + def get_publisher_handle_info( + self, publisher_handle: int + ) -> Union[Mapping[str, Any], None]: + """ + Get information about a publisher handle. + + :param publisher_handle: the publisher handle value + :return: a dictionary with name:value info, or `None` if it fails + """ + if publisher_handle not in self._data.publishers.index: + return None + + node_handle = self._data.publishers.loc[publisher_handle, 'node_handle'] + node_handle_info = self.get_node_handle_info(node_handle) + topic_name = self._data.publishers.loc[publisher_handle, 'topic_name'] + publisher_info = {'topic': topic_name} + return {**node_handle_info, **publisher_info} + + def get_subscription_handle_info( + self, subscription_handle: int + ) -> Union[Mapping[str, Any], None]: + """ + Get information about a subscription handle. + + :param subscription_handle: the subscription handle value + :return: a dictionary with name:value info, or `None` if it fails + """ + subscriptions_info = self._data.subscriptions.merge( + self._data.nodes, + left_on='node_handle', + right_index=True) + if subscription_handle not in self._data.subscriptions.index: + return None + + node_handle = subscriptions_info.loc[subscription_handle, 'node_handle'] + node_handle_info = self.get_node_handle_info(node_handle) + topic_name = subscriptions_info.loc[subscription_handle, 'topic_name'] + subscription_info = {'topic': topic_name} + return {**node_handle_info, **subscription_info} + + def get_service_handle_info( + self, service_handle: int + ) -> Union[Mapping[str, Any], None]: + """ + Get information about a service handle. + + :param service_handle: the service handle value + :return: a dictionary with name:value info, or `None` if it fails + """ + if service_handle not in self._data.services: + return None + + node_handle = self._data.services.loc[service_handle, 'node_handle'] + node_handle_info = self.get_node_handle_info(node_handle) + service_name = self._data.services.loc[service_handle, 'service_name'] + service_info = {'service': service_name} + return {**node_handle_info, **service_info} + + def get_client_handle_info( + self, client_handle: int + ) -> Union[Mapping[str, Any], None]: + """ + Get information about a client handle. + + :param client_handle: the client handle value + :return: a dictionary with name:value info, or `None` if it fails + """ + if client_handle not in self._data.clients: + return None + + node_handle = self._data.clients.loc[client_handle, 'node_handle'] + node_handle_info = self.get_node_handle_info(node_handle) + service_name = self._data.clients.loc[client_handle, 'service_name'] + service_info = {'service': service_name} + return {**node_handle_info, **service_info} + + def get_node_handle_info( + self, node_handle: int + ) -> Union[Mapping[str, Any], None]: + """ + Get information about a node handle. + + :param node_handle: the node handle value + :return: a dictionary with name:value info, or `None` if it fails + """ + if node_handle not in self._data.nodes.index: + return None + + node_name = self._data.nodes.loc[node_handle, 'name'] + tid = self._data.nodes.loc[node_handle, 'tid'] + return {'node': node_name, 'tid': tid} + + def format_info_dict(self, info_dict: Mapping[str, Any]) -> str: + return ', '.join([f'{key}: {val}' for key, val in info_dict.items()]) diff --git a/tracetools_analysis/tracetools_analysis/conversion/__init__.py b/tracetools_analysis/tracetools_analysis/conversion/__init__.py new file mode 100644 index 0000000..4b18865 --- /dev/null +++ b/tracetools_analysis/tracetools_analysis/conversion/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2019 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/tracetools_analysis/tracetools_analysis/conversion/ctf.py b/tracetools_analysis/tracetools_analysis/conversion/ctf.py new file mode 100644 index 0000000..a81c523 --- /dev/null +++ b/tracetools_analysis/tracetools_analysis/conversion/ctf.py @@ -0,0 +1,57 @@ +# Copyright 2019 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module with CTF to pickle conversion functions.""" + +from pickle import Pickler + +from tracetools_read import utils + + +def ctf_to_pickle(trace_directory: str, target: Pickler) -> int: + """ + Load CTF trace, convert events, and dump to a pickle file. + + :param trace_directory: the trace directory + :param target: the target pickle file to write to + :return: the number of events written + """ + ctf_events = utils._get_trace_ctf_events(trace_directory) + + count = 0 + count_written = 0 + + for event in ctf_events: + count += 1 + + pod = utils.event_to_dict(event) + target.dump(pod) + count_written += 1 + + return count_written + + +def convert(trace_directory: str, pickle_target_path: str) -> int: + """ + Convert CTF trace to pickle file. + + :param trace_directory: the trace directory + :param pickle_target_path: the path to the pickle file that will be created + :return: the number of events written to the pickle file + """ + with open(pickle_target_path, 'wb') as f: + p = Pickler(f, protocol=4) + count = ctf_to_pickle(trace_directory, p) + + return count diff --git a/tracetools_analysis/tracetools_analysis/convert.py b/tracetools_analysis/tracetools_analysis/convert.py new file mode 100644 index 0000000..b1ffa42 --- /dev/null +++ b/tracetools_analysis/tracetools_analysis/convert.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# Copyright 2019 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Entrypoint/script to convert CTF trace data to a pickle file.""" + +import argparse +import os +import time + +from tracetools_analysis.conversion import ctf + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Convert CTF trace data to a pickle file.') + parser.add_argument( + 'trace_directory', help='the path to the main CTF trace directory') + parser.add_argument( + '--pickle-path', '-p', + help='the path to the target pickle file to generate (default: $trace_directory/pickle)') + args = parser.parse_args() + if args.pickle_path is None: + args.pickle_path = os.path.join(args.trace_directory, 'pickle') + return args + + +def main(): + args = parse_args() + + trace_directory = args.trace_directory + pickle_target_path = args.pickle_path + + print(f'importing trace directory: {trace_directory}') + start_time = time.time() + count = ctf.convert(trace_directory, pickle_target_path) + time_diff = time.time() - start_time + print(f'converted {count} events in {time_diff * 1000:.2f} ms') + print(f'pickle written to: {pickle_target_path}') diff --git a/tracetools_analysis/tracetools_analysis/process.py b/tracetools_analysis/tracetools_analysis/process.py new file mode 100644 index 0000000..e02470e --- /dev/null +++ b/tracetools_analysis/tracetools_analysis/process.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# Copyright 2019 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Entrypoint/script to process events from a pickle file to build a ROS model.""" + +import argparse +import time + +from tracetools_analysis.analysis import load +from tracetools_analysis.analysis import ros2_processor + + +def parse_args(): + parser = argparse.ArgumentParser(description='Process a pickle file generated ' + 'from tracing and analyze the data.') + parser.add_argument('pickle_file', help='the pickle file to import') + return parser.parse_args() + + +def main(): + args = parse_args() + + pickle_filename = args.pickle_file + + start_time = time.time() + events = load.load_pickle(pickle_filename) + processor = ros2_processor.ros2_process(events) + time_diff = time.time() - start_time + print(f'processed {len(events)} events in {time_diff * 1000:.2f} ms') + + processor.get_data_model().print_model()