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",
+ " \"- re-rerun `output_notebook()` to attempt to load from CDN again, or
\\n\"+\n",
+ " \"- use INLINE resources instead, as so:
\\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 \"- re-rerun `output_notebook()` to attempt to load from CDN again, or
\\n\"+\n \"- use INLINE resources instead, as so:
\\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()