1317 lines
No EOL
53 KiB
Text
1317 lines
No EOL
53 KiB
Text
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%%\n"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"import os\n",
|
|
"import sys\n",
|
|
"\n",
|
|
"import numpy as np\n",
|
|
"import pandas as pd\n",
|
|
"from matplotlib import pyplot as plt\n",
|
|
"\n",
|
|
"from clang_interop.process_clang_output import process_clang_output\n",
|
|
"from clang_interop.types import ClContext\n",
|
|
"\n",
|
|
"sys.path.append(\"../autoware/build/tracetools_read/\")\n",
|
|
"sys.path.append(\"../autoware/build/tracetools_analysis/\")\n",
|
|
"from tracetools_read.trace import *\n",
|
|
"from tracetools_analysis.loading import load_file\n",
|
|
"from tracetools_analysis.processor.ros2 import Ros2Handler\n",
|
|
"from tracetools_analysis.utils.ros2 import Ros2DataModelUtil\n",
|
|
"\n",
|
|
"from dataclasses import dataclass\n",
|
|
"from typing import List, Dict, Set, Tuple\n",
|
|
"\n",
|
|
"from tracing_interop.types import TrTimer, TrTopic, TrPublisher, TrPublishInstance, TrCallbackInstance, \\\n",
|
|
" TrCallbackSymbol, TrCallbackObject, TrSubscriptionObject, TrContext, TrNode\n",
|
|
"from misc.utils import ProgressPrinter, cached"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%%\n"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"TR_PATH = os.path.expanduser(\"~/Downloads/autoware-trace/ust\")\n",
|
|
"CL_PATH = os.path.expanduser(\"~/Projects/llvm-project/clang-tools-extra/ros2-internal-dependency-checker/output\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%% md\n"
|
|
}
|
|
},
|
|
"source": [
|
|
"# Organize Trace Data"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%%\n",
|
|
"is_executing": true
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"[CACHE] Creating cache entry for tr_objects (in cache/tr_objects_c1e0d50b8d.pkl).\n",
|
|
"found converted file: /home/max/Downloads/autoware-trace/ust/converted\n",
|
|
" [100%] [Ros2Handler]\r\n",
|
|
"[TrContext] Processing ROS 2 objects from traces...\n",
|
|
" ├─ Processed 216 nodes\n",
|
|
" ├─ Processed 668 publishers\n",
|
|
" ├─ Processed 748 subscriptions\n",
|
|
" ├─ Processed 154 timers\n",
|
|
" ├─ Processed 69 timer-node links\n",
|
|
" ├─ Processed 824 subscription objects\n",
|
|
" ├─ Processed 1842 callback objects\n",
|
|
" ├─ Processed 1842 callback symbols\n",
|
|
" ├─ Processed 70808 publish instances\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"def _load_traces():\n",
|
|
" file = load_file(TR_PATH)\n",
|
|
" handler = Ros2Handler.process(file)\n",
|
|
" util = Ros2DataModelUtil(handler)\n",
|
|
" return TrContext(util, handler)\n",
|
|
"\n",
|
|
"_tracing_context = cached(\"tr_objects\", _load_traces, [TR_PATH])\n",
|
|
"_tr_globals = [\"nodes\", \"publishers\", \"subscriptions\", \"timers\", \"timer_node_links\", \"subscription_objects\",\n",
|
|
" \"callback_objects\", \"callback_symbols\", \"publish_instances\", \"callback_instances\", \"topics\"]\n",
|
|
"\n",
|
|
"# Help the IDE recognize those identifiers\n",
|
|
"nodes = publishers = subscriptions = timers = timer_node_links = subscription_objects = callback_objects = callback_symbols = publish_instances = callback_instances = topics = None\n",
|
|
"\n",
|
|
"for name in _tr_globals:\n",
|
|
" globals()[name] = getattr(_tracing_context, name)\n",
|
|
"\n",
|
|
"print(\"Done.\")\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%% md\n"
|
|
}
|
|
},
|
|
"source": [
|
|
"# Callback-Sub & Callback-Timer Links"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%%\n"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"import re\n",
|
|
"\n",
|
|
"sym_table = []\n",
|
|
"\n",
|
|
"for sym in callback_symbols.values():\n",
|
|
" try:\n",
|
|
" cbo = list(filter(lambda val: val.callback_object == sym.id, callback_objects.values()))\n",
|
|
" assert len(cbo) == 1\n",
|
|
" cbo = cbo[0]\n",
|
|
" except:\n",
|
|
" print(len(cbo))\n",
|
|
" continue\n",
|
|
" owner_info = cbo.owner_info\n",
|
|
"\n",
|
|
" if None in owner_info: continue\n",
|
|
" type, info = owner_info\n",
|
|
" sym_table.append((sym, type, info))\n",
|
|
"\n",
|
|
"sym_table.sort(key=lambda tup: tup[1])\n",
|
|
"\n",
|
|
"\n",
|
|
"def trim(string, length):\n",
|
|
" if len(string) > length:\n",
|
|
" return f\"{string[:length - 3]}...\"\n",
|
|
" return string\n",
|
|
"\n",
|
|
"\n",
|
|
"for sym, type, info in sym_table:\n",
|
|
" sym: TrCallbackSymbol\n",
|
|
" pretty_sym = Ros2DataModelUtil._prettify(None, sym.symbol)\n",
|
|
" pretty_sym = re.sub(r\"std::shared_ptr<(.*?) *(const)?>\", r\"\\1*\", pretty_sym)\n",
|
|
" try:\n",
|
|
" i = len(sym.callback_obj.callback_instances)\n",
|
|
" except KeyError:\n",
|
|
" i = -1\n",
|
|
" print(f\"{trim(pretty_sym, 100):100s}: i={i:>4d} {type:12s} n={info['node']:40s}\", end=' ')\n",
|
|
" if type == 'Timer':\n",
|
|
" print(f\"p={info['period']:7s}\")\n",
|
|
" elif type == 'Subscription':\n",
|
|
" print(f\"t={info['topic']:30s}\")\n",
|
|
" else:\n",
|
|
" print()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%% md\n"
|
|
}
|
|
},
|
|
"source": [
|
|
"# Topic-Node Mapping"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%%\n"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Aggregate topics that have the same pubs and subs\n",
|
|
"topic_cohorts = {}\n",
|
|
"for topic in topics.values():\n",
|
|
" key = (frozenset({*(pub.node_handle for pub in topic.publishers)}),\n",
|
|
" frozenset({*(sub.node_handle for sub in topic.subscriptions)}))\n",
|
|
" if key not in topic_cohorts:\n",
|
|
" topic_cohorts[key] = []\n",
|
|
" topic_cohorts[key].append(topic)\n",
|
|
"\n",
|
|
"print(f\"{len(topics)} topics were aggregated into {len(topic_cohorts)} cohorts\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%% md\n"
|
|
}
|
|
},
|
|
"source": [
|
|
"# Timer-Node Mapping"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%%\n"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"unknowns = {}\n",
|
|
"\n",
|
|
"print_node_timer = lambda node_path, period: print(f\"{node_path:<90s}: {1 / (period * 1e-9):8.2f}Hz\")\n",
|
|
"\n",
|
|
"for timer in timers.values():\n",
|
|
" timer_nodes = timer.nodes\n",
|
|
" if not timer_nodes:\n",
|
|
" if timer.period not in unknowns:\n",
|
|
" unknowns[timer.period] = 0\n",
|
|
" unknowns[timer.period] += 1\n",
|
|
"\n",
|
|
" for node in timer_nodes: print_node_timer(node.path, timer.period)\n",
|
|
"\n",
|
|
"for period, count in unknowns.items():\n",
|
|
" print_node_timer(f\"UNKNOWN (x{count})\", period)\n",
|
|
"\n",
|
|
"n_unknown = sum(unknowns.values()) # Values are counts per period\n",
|
|
"print(f\"Found {len(timers) - n_unknown} timers with a recorded node, {n_unknown} without.\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%% md\n"
|
|
}
|
|
},
|
|
"source": [
|
|
"# Measure Frequency Deviations"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%%\n"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Get Publisher frequencies\n",
|
|
"df_publications = handler.data.rcl_publish_instances\n",
|
|
"pub_stats = {}\n",
|
|
"unknown = 0\n",
|
|
"for pi in publish_instances:\n",
|
|
" try:\n",
|
|
" pub = pi.publisher\n",
|
|
" except KeyError:\n",
|
|
" unknown += 1\n",
|
|
" continue\n",
|
|
" if pub.id not in pub_stats:\n",
|
|
" pub_stats[pub.id] = {'times': []}\n",
|
|
" pub_stats[pub.id]['times'].append(pi.timestamp * 1e-9) # Nanoseconds to seconds float\n",
|
|
"\n",
|
|
"print(f\"{unknown} unknown publisher handles ({len(pub_stats)} known ones)\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%% md\n"
|
|
}
|
|
},
|
|
"source": [
|
|
"# Plot Frequency Deviations"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": true,
|
|
"pycharm": {
|
|
"name": "#%%\n"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"fig_dirname = \"fig_frequency\"\n",
|
|
"os.makedirs(fig_dirname, exist_ok=True)\n",
|
|
"for i, (k, v) in enumerate(sorted(pub_stats.items(), key=lambda kv: len(kv[1]['times']), reverse=True)):\n",
|
|
" pub_time_diff = np.diff(np.array(v['times']))\n",
|
|
" v['period'] = pub_time_diff.mean()\n",
|
|
" v['period_std'] = pub_time_diff.std()\n",
|
|
" v['frequency'] = 1 / v['period']\n",
|
|
" v['frequency_std'] = (1 / pub_time_diff).std()\n",
|
|
"\n",
|
|
" try:\n",
|
|
" publisher = publishers[k]\n",
|
|
" publisher_node = publisher.node\n",
|
|
" topic_name = publisher.topic_name\n",
|
|
" node_path = publisher_node.path\n",
|
|
" except Exception:\n",
|
|
" topic_name = \"UNKNOWN\"\n",
|
|
" node_path = \"UNKNOWN\"\n",
|
|
"\n",
|
|
" fig = plt.figure(figsize=(15, 5))\n",
|
|
" ax = fig.add_subplot()\n",
|
|
" ax.hist(1 / pub_time_diff)\n",
|
|
" ax.set_xlabel(\"Publication Frequency [Hz]\")\n",
|
|
" ax.set_ylabel(\"#Publications\")\n",
|
|
" ax.set_title(f\"{node_path} =({v['frequency']:.2f}Hz)=> {topic_name}\")\n",
|
|
" plt.savefig('/'.join((fig_dirname, f\"{i:06}{node_path}__{topic_name}\".replace('/', '-'))))\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%% md\n"
|
|
}
|
|
},
|
|
"source": [
|
|
"# Data Flow Graph"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": false,
|
|
"pycharm": {
|
|
"name": "#%%\n"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"node_filters = [\"transform_listener_impl\", \"_monitor\"]\n",
|
|
"topic_filters = [\"/rosout\", \"/parameter_events\", \"/diagnostics\"]\n",
|
|
"\n",
|
|
"from pyvis.network import Network\n",
|
|
"\n",
|
|
"net = Network(notebook=True, height='750px', width='100%', bgcolor='#ffffff', font_color='#000000')\n",
|
|
"\n",
|
|
"net.add_node(\"INPUT\", label=\"Input\", size=100, color=\"green\", physics=False, x=0, y=0)\n",
|
|
"net.add_node(\"OUTPUT\", label=\"Output\", size=100, color=\"red\", physics=False, x=6000, y=0)\n",
|
|
"\n",
|
|
"for node in nodes.values():\n",
|
|
" if any(f in node.path for f in node_filters):\n",
|
|
" continue\n",
|
|
" net.add_node(node.id, label=node.name, title=node.path, size=20, color=\"#333\")\n",
|
|
"\n",
|
|
"for cohort_key, cohort_topics in topic_cohorts.items():\n",
|
|
" cohort_topic_names = [topic.name for topic in cohort_topics if not any(f in topic.name for f in topic_filters)]\n",
|
|
" if not cohort_topic_names:\n",
|
|
" continue\n",
|
|
" cohort_id = \"\\n\".join(cohort_topic_names)\n",
|
|
" cohort_weight = len(cohort_topic_names)\n",
|
|
" net.add_node(cohort_id, label=\" \", title=cohort_id, size=5, color=\"#333\")\n",
|
|
"\n",
|
|
" pubs = cohort_key[0]\n",
|
|
" subs = cohort_key[1]\n",
|
|
" n_pubs = len(pubs)\n",
|
|
" n_subs = len(subs)\n",
|
|
"\n",
|
|
" try:\n",
|
|
" if not n_pubs:\n",
|
|
" net.add_edge(\"INPUT\", cohort_id, arrows=\"to\", color=\"green\", weight=cohort_weight)\n",
|
|
" if not n_subs:\n",
|
|
" net.add_edge(cohort_id, \"OUTPUT\", arrows=\"to\", color=\"red\", weight=cohort_weight)\n",
|
|
"\n",
|
|
" for pub in pubs:\n",
|
|
" net.add_edge(pub, cohort_id, arrows=\"to\", color=\"green\", weight=cohort_weight)\n",
|
|
" for sub in subs:\n",
|
|
" net.add_edge(cohort_id, sub, arrows=\"to\", color=\"red\", weight=cohort_weight)\n",
|
|
" except:\n",
|
|
" continue\n",
|
|
"\n",
|
|
"net.toggle_physics(True)\n",
|
|
"net.show_buttons()\n",
|
|
"net.show(\"graph.html\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%% md\n"
|
|
}
|
|
},
|
|
"source": [
|
|
"# Pub-Use Latencies\n",
|
|
"Compute for each node and its data dependencies the list of pub-use delays (per-topic-per-node list of pub-use delays)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%%\n"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"def filter_none(ls):\n",
|
|
" return filter(lambda x: x is not None, ls)\n",
|
|
"\n",
|
|
"\n",
|
|
"def safe_map(func, ls):\n",
|
|
" def safe_func(arg):\n",
|
|
" try:\n",
|
|
" return func(arg)\n",
|
|
" except:\n",
|
|
" return None\n",
|
|
"\n",
|
|
" return map(safe_func, ls)\n",
|
|
"\n",
|
|
"\n",
|
|
"pub_use_delays = {node.id: {\n",
|
|
" 'pubs': {},\n",
|
|
" 'invocations': {},\n",
|
|
" 'n_unknown_invocations': 0,\n",
|
|
" 'n_pub_timestamps': 0\n",
|
|
"} for node in nodes.values()}\n",
|
|
"\n",
|
|
"for node in nodes.values():\n",
|
|
" node_pub_use_dict = pub_use_delays[node.id]\n",
|
|
" timestamp_min = np.inf;\n",
|
|
" timestamp_max = 0\n",
|
|
"\n",
|
|
" n_pub_timestamps = 0\n",
|
|
" for sub in node.subscriptions:\n",
|
|
" node_pub_use_dict['pubs'][sub.topic_name] = {}\n",
|
|
" for pub in sub.publishers:\n",
|
|
" pub_timestamps = [inst.timestamp for inst in pub.instances]\n",
|
|
"\n",
|
|
" try:\n",
|
|
" pub_t_min = min(pub_timestamps);\n",
|
|
" pub_t_max = max(pub_timestamps)\n",
|
|
" except ValueError:\n",
|
|
" pub_t_min = np.inf;\n",
|
|
" pub_t_max = 0\n",
|
|
"\n",
|
|
" if pub_t_min < timestamp_min: timestamp_min = pub_t_min\n",
|
|
" if pub_t_max > timestamp_max: timestamp_max = pub_t_max\n",
|
|
"\n",
|
|
" node_pub_use_dict['pubs'][sub.topic_name][pub.node.path] = pub_timestamps\n",
|
|
" node_pub_use_dict['n_pub_timestamps'] += len(pub_timestamps)\n",
|
|
"\n",
|
|
" timer_cb_objs = list(filter_none(safe_map(lambda timer: timer.callback_object, node.timers)))\n",
|
|
" subsc_cb_objs = list(\n",
|
|
" filter_none(safe_map(lambda subsc: subsc.subscription_object.callback_object, node.subscriptions)))\n",
|
|
"\n",
|
|
" print(\n",
|
|
" f\"{node.path:95s} has {len(timer_cb_objs):1d} timer callbacks, {len(subsc_cb_objs):2d} subscription callbacks, {len(node_pub_use_dict['pubs']):2d} subscribed topics.\")\n",
|
|
"\n",
|
|
" node_invocations = node_pub_use_dict['invocations']\n",
|
|
"\n",
|
|
" for cb_obj in timer_cb_objs + subsc_cb_objs:\n",
|
|
" cb_invocations = []\n",
|
|
" for inst in cb_obj.callback_instances:\n",
|
|
" cb_invocations.append((inst.timestamp, inst.duration))\n",
|
|
"\n",
|
|
" node_invocations[cb_obj.id] = cb_invocations"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%%\n"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"from matplotlib import cm\n",
|
|
"\n",
|
|
"fig_dirname = \"fig_pub_use\"\n",
|
|
"os.makedirs(fig_dirname, exist_ok=True)\n",
|
|
"plt.close('all')\n",
|
|
"\n",
|
|
"node_filters = [] #\"transform_listener_impl\",]\n",
|
|
"\n",
|
|
"nodes_filtered = [node for node in nodes.values() if not any(f in node.path for f in node_filters)]\n",
|
|
"print(f\"Ignoring {len(nodes.values()) - len(nodes_filtered)} nodes due to filters.\")\n",
|
|
"\n",
|
|
"common_offset = min(map(lambda cb_inst: cb_inst.timestamp.timestamp(), callback_instances))\n",
|
|
"\n",
|
|
"zero_color = cm.get_cmap('viridis')(0.0)\n",
|
|
"\n",
|
|
"for node_i, (node, node_path, node_pub_use_dict) in enumerate(\n",
|
|
" map(lambda node: (node, node.path, pub_use_delays[node.id]), nodes_filtered)):\n",
|
|
"\n",
|
|
" if not node_pub_use_dict['invocations']:\n",
|
|
" print(f\"{node_path:95s} has no invocations, skipping.\")\n",
|
|
" continue\n",
|
|
"\n",
|
|
" if len(node_pub_use_dict['pubs']) == 0:\n",
|
|
" print(f\"Skipping {node_path}, no publications\")\n",
|
|
" continue\n",
|
|
"\n",
|
|
" fig = plt.figure(figsize=(15, 5))\n",
|
|
" ax: plt.Axes = fig.add_subplot()\n",
|
|
"\n",
|
|
" max_pubs_per_topic = max(len(pubs) for pubs in node_pub_use_dict['pubs'].values())\n",
|
|
" topic_names, topic_pubs = (zip(*node_pub_use_dict['pubs'].items()))\n",
|
|
"\n",
|
|
" vmin = 0;\n",
|
|
" vmax = max_pubs_per_topic\n",
|
|
"\n",
|
|
" y_labels = []\n",
|
|
" current_y = 0\n",
|
|
"\n",
|
|
" for invoc_i, (cb_obj_id, cb_invocations) in enumerate(node_pub_use_dict['invocations'].items()):\n",
|
|
" try:\n",
|
|
" cb_obj = callback_objects[cb_obj_id]\n",
|
|
" sym = callback_symbols[cb_obj.callback_object].symbol\n",
|
|
" sym = Ros2DataModelUtil._prettify(None, sym)\n",
|
|
" sym = re.sub(r\"std::shared_ptr<(.*?)>\", r\"\\1*\", sym)\n",
|
|
"\n",
|
|
" cb_owner = cb_obj.owner\n",
|
|
" if isinstance(cb_owner, TrTimer):\n",
|
|
" cb_type = \"T\"\n",
|
|
" elif isinstance(cb_owner, TrSubscriptionObject):\n",
|
|
" cb_type = \"S\"\n",
|
|
" except KeyError or AttributeError:\n",
|
|
" sym = \"UNKNOWN\"\n",
|
|
" cb_type = \"U\"\n",
|
|
"\n",
|
|
" y_labels.append(f\"{sym} {cb_type}\")\n",
|
|
" n_markers = len(cb_invocations)\n",
|
|
"\n",
|
|
" points_x = [];\n",
|
|
" points_y = []\n",
|
|
" for time, dur in cb_invocations:\n",
|
|
" time = time.timestamp() - common_offset;\n",
|
|
" dur = dur.total_seconds()\n",
|
|
" points_x += [time, time + dur, None]\n",
|
|
" points_y += [current_y, current_y, 0.0]\n",
|
|
"\n",
|
|
" ax.plot(points_x, points_y, marker='.', c=zero_color)\n",
|
|
" current_y += 1\n",
|
|
"\n",
|
|
" n_cbs = current_y\n",
|
|
"\n",
|
|
" for topic_i, (topic_name, pubs) in enumerate(zip(topic_names, topic_pubs)):\n",
|
|
" for pub_i, (pub_name, timestamps) in enumerate(pubs.items()):\n",
|
|
" n_markers = len(timestamps)\n",
|
|
" ax.scatter(np.array(timestamps) * 1e-9 - common_offset, (current_y,) * n_markers, marker='.',\n",
|
|
" c=(pub_i,) * n_markers, vmin=vmin, vmax=vmax)\n",
|
|
"\n",
|
|
" y_labels.append(topic_name)\n",
|
|
" current_y += 1\n",
|
|
"\n",
|
|
" trigger_strs = []\n",
|
|
" t = node.timers\n",
|
|
" if t:\n",
|
|
" n_timers = len(t)\n",
|
|
" freqs = map(lambda timer: 1 / (timer.period * 1e-9), t)\n",
|
|
" trigger_strs.append(\n",
|
|
" f\"{n_timers} timer{'s' if n_timers != 1 else ''}, {'Hz, '.join((f'{freq:.0f}' for freq in freqs))}Hz\")\n",
|
|
" if node.subscriptions:\n",
|
|
" n_subs = len(node.subscriptions)\n",
|
|
" trigger_strs.append(f\"{n_subs} subscription{'s' if n_subs != 1 else ''}\")\n",
|
|
"\n",
|
|
" ax.set_xlabel(\"Publication / Invocation Timestamp [s]\")\n",
|
|
" ax.set_ylabel(\"Topic\")\n",
|
|
" ax.set_yticks(range(current_y))\n",
|
|
" ax.set_yticklabels(y_labels)\n",
|
|
" ax.set_ylim(0 - .1, current_y - 1 + .1)\n",
|
|
" ax.set_title(f\"{node_path} ({'; '.join(trigger_strs)})\")\n",
|
|
" ax.set_xlim(50, 50.25)\n",
|
|
"\n",
|
|
" ax.hlines(n_cbs - 0.5, *ax.get_xlim(), linestyles='dashed')\n",
|
|
" plt.savefig(os.path.join(fig_dirname, f\"{node_i:06}{node_path}\".replace('/', '-')))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"# ROS2 Tracing & Clang Matching"
|
|
],
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"pycharm": {
|
|
"name": "#%% md\n"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"outputs": [],
|
|
"source": [
|
|
"def _load_cl_objects():\n",
|
|
" return process_clang_output(CL_PATH)\n",
|
|
"\n",
|
|
"_cl_context: ClContext = cached(\"cl_objects\", _load_cl_objects, [CL_PATH])"
|
|
],
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"pycharm": {
|
|
"name": "#%%\n"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"outputs": [],
|
|
"source": [
|
|
"#################################################\n",
|
|
"# Match nodes\n",
|
|
"#################################################\n",
|
|
"\n",
|
|
"node_map = {}\n",
|
|
"for node in nodes:\n",
|
|
" node: TrNode\n",
|
|
" for tu_path, tu in _cl_context.translation_units.items():\n",
|
|
" for cl_node in tu.nodes.values():\n",
|
|
" if node.name == cl_node.ros_name:\n",
|
|
" if node in node_map:\n",
|
|
" print(f\"[WARN ] Node already mapped: {node} -> {node_map[node]}\")\n",
|
|
" node_map[node] = cl_node\n",
|
|
"\n",
|
|
"#################################################\n",
|
|
"# Match subscriptions\n",
|
|
"#################################################\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"#################################################\n",
|
|
"# Match publishers\n",
|
|
"#################################################\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"#################################################\n",
|
|
"# Match timers\n",
|
|
"#################################################"
|
|
],
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"pycharm": {
|
|
"name": "#%%\n"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%% md\n"
|
|
}
|
|
},
|
|
"source": [
|
|
"# E2E Latency Calculation"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%%\n"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"(1842/1842) Processing done. \r\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"#################################################\n",
|
|
"# Data structures & helpers\n",
|
|
"#################################################\n",
|
|
"\n",
|
|
"LatencyStats = pd.Series\n",
|
|
"\n",
|
|
"\n",
|
|
"@dataclass\n",
|
|
"class LatencyGraph:\n",
|
|
" verts: Set[TrCallbackObject]\n",
|
|
" edges: Dict[Tuple[TrCallbackObject, TrCallbackObject], Tuple[TrTopic, LatencyStats]]\n",
|
|
" starts: Dict[TrCallbackObject, TrTopic]\n",
|
|
" ends: Dict[TrCallbackObject, TrTopic]\n",
|
|
"\n",
|
|
"\n",
|
|
"def pub_use_latencies(cb_instances: List[TrCallbackInstance], pub_instances: List[TrPublishInstance]):\n",
|
|
" cb_times = sorted([inst.timestamp.timestamp() for inst in cb_instances])\n",
|
|
"\n",
|
|
" if not pub_instances:\n",
|
|
" return pd.Series(np.full(len(cb_instances), np.nan), index=cb_times)\n",
|
|
"\n",
|
|
" pub_times = np.array(sorted([pub.timestamp * 1e-9 for pub in pub_instances]))\n",
|
|
"\n",
|
|
" pub_use_lats = np.array([cb_time - np.max(pub_times[pub_times < cb_time], initial=-np.inf) for cb_time in cb_times])\n",
|
|
" pub_use_lats[np.isposinf(pub_use_lats)] = np.nan\n",
|
|
" ret_series = pd.Series(pub_use_lats, index=cb_times)\n",
|
|
" return ret_series\n",
|
|
"\n",
|
|
"\n",
|
|
"def inst_runtime_interval(cb_inst):\n",
|
|
" inst_t_min = cb_inst.timestamp.timestamp()\n",
|
|
" inst_t_max = inst_t_min + cb_inst.duration.total_seconds()\n",
|
|
" return (inst_t_min, inst_t_max)\n",
|
|
"\n",
|
|
"\n",
|
|
"def count_pub_insts_in_intervals(cb_intervals: List[Tuple[float, float]], pub_insts: List[TrPublishInstance]):\n",
|
|
" \"\"\"\n",
|
|
" Counts number of publication instancess that lie within one of the cb_intervals.\n",
|
|
" \"\"\"\n",
|
|
" pub_timestamps = [inst.timestamp * 1e-9 for inst in pub_insts]\n",
|
|
"\n",
|
|
" # Algorithm: Two-pointer method\n",
|
|
" # With both the pub_timestamps and cb_intervals sorted ascending,\n",
|
|
" # we can cut down the O(m*n) comparisons to O(m+n).\n",
|
|
" pub_timestamps.sort()\n",
|
|
" cb_intervals.sort(key=lambda tup: tup[0])\n",
|
|
"\n",
|
|
" n_overlaps = 0\n",
|
|
" cb_iter = iter(cb_intervals)\n",
|
|
" pub_iter = iter(pub_timestamps)\n",
|
|
" (t_min, t_max) = next(cb_iter, (None, None))\n",
|
|
" t_pub = next(pub_iter, None)\n",
|
|
"\n",
|
|
" while t_pub is not None and t_min is not None:\n",
|
|
" if t_min <= t_pub <= t_max: # If publication in interval, increase counter, go to next pub (multiple pubs can be within one interval)\n",
|
|
" n_overlaps += 1\n",
|
|
" t_pub = next(pub_iter, None)\n",
|
|
" elif t_pub < t_min: # If publication before interval, increase pub\n",
|
|
" t_pub = next(pub_iter, None)\n",
|
|
" else: # If interval before publication, increase interval\n",
|
|
" (t_min, t_max) = next(cb_iter, (None, None))\n",
|
|
"\n",
|
|
" return n_overlaps\n",
|
|
"\n",
|
|
"\n",
|
|
"#################################################\n",
|
|
"# Identify input and output topics\n",
|
|
"#################################################\n",
|
|
"\n",
|
|
"in_topics = [t for t in topics.values() if not t.publishers]\n",
|
|
"out_topics = [t for t in topics.values() if not t.subscriptions]\n",
|
|
"\n",
|
|
"#################################################\n",
|
|
"# For each node, work out dependencies and\n",
|
|
"# publications of each callback\n",
|
|
"#################################################\n",
|
|
"\n",
|
|
"cb_to_scored_pub: Dict[TrCallbackObject, Set[Tuple[TrPublisher, float]]] = {}\n",
|
|
"topic_to_dep_cb: Dict[TrTopic, Set[TrCallbackObject]] = {}\n",
|
|
"pub_cb_to_lat_stats: Dict[Tuple[TrPublisher, TrCallbackObject], LatencyStats] = {}\n",
|
|
"\n",
|
|
"with ProgressPrinter(\"Processing\", len(callback_objects)) as p:\n",
|
|
" for cb in callback_objects.values():\n",
|
|
" cb_sym = callback_symbols[cb.callback_object].symbol if cb.callback_object in callback_symbols else None\n",
|
|
" if cb_sym:\n",
|
|
" p.step(Ros2DataModelUtil._prettify(None, cb_sym))\n",
|
|
" else:\n",
|
|
" p.step(cb.id)\n",
|
|
"\n",
|
|
" if \"ParameterEvent\" in cb_sym:\n",
|
|
" continue\n",
|
|
"\n",
|
|
" # Find topics the callback EXPLICITLY depends on\n",
|
|
" # - Timer callbacks: no EXPLICIT dependencies\n",
|
|
" # - Subscription callbacks: callback depends on the subscribed topic. Possibly also has other IMPLICIT dependencies\n",
|
|
"\n",
|
|
" if type(cb.owner).__name__ == TrSubscriptionObject.__name__:\n",
|
|
" owner_node = cb.owner.subscription.node\n",
|
|
" dep_topics = [cb.owner.subscription.topic, ]\n",
|
|
" elif type(cb.owner).__name__ == TrTimer.__name__:\n",
|
|
" owner_nodes = cb.owner.nodes\n",
|
|
" if len(owner_nodes) != 1:\n",
|
|
" raise (ValueError(\"Timer has more than one owner!\"))\n",
|
|
" dep_topics = []\n",
|
|
" elif cb.owner is None:\n",
|
|
" dep_topics = []\n",
|
|
" continue\n",
|
|
" else:\n",
|
|
" raise RuntimeError(\n",
|
|
" f\"Callback owners other than timers/subscriptions cannot be handled: {cb.owner} {cb.owner_info}\")\n",
|
|
"\n",
|
|
" for topic in dep_topics:\n",
|
|
" if topic not in topic_to_dep_cb:\n",
|
|
" topic_to_dep_cb[topic] = set()\n",
|
|
" topic_to_dep_cb[topic].add(cb)\n",
|
|
"\n",
|
|
" for pub in topic.publishers:\n",
|
|
" pub_cb_to_lat_stats[(pub, cb)] = pub_use_latencies(cb.callback_instances, pub.instances)\n",
|
|
"\n",
|
|
" # Find topics the callback publishes to (HEURISTICALLY!)\n",
|
|
" # For topics published to during the runtime of the callback's instances, \n",
|
|
" # assume that they are published by the callback\n",
|
|
"\n",
|
|
" cb_runtime_intervals = [inst_runtime_interval(inst) for inst in cb.callback_instances]\n",
|
|
" cb_pub_overlap_counts = [count_pub_insts_in_intervals(cb_runtime_intervals, pub.instances) for pub in\n",
|
|
" owner_node.publishers]\n",
|
|
"\n",
|
|
" for pub, olap_count in zip(owner_node.publishers, cb_pub_overlap_counts):\n",
|
|
" if olap_count == 0 or not pub.instances:\n",
|
|
" continue\n",
|
|
" score = olap_count / len(pub.instances)\n",
|
|
"\n",
|
|
" if cb not in cb_to_scored_pub:\n",
|
|
" cb_to_scored_pub[cb] = set()\n",
|
|
" cb_to_scored_pub[cb].add((pub, score))\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 19,
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%%\n"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"/diagnostics:\n",
|
|
" 93.922652% void (diagnostic_updater::Updater::?)(),\n",
|
|
" 6.629834% void (ProcessMonitor::?)()\n",
|
|
"/diagnostics:\n",
|
|
" 0.202429% void (rclcpp::TimeSource::?)(rosgraph_msgs::msg::Clock*),\n",
|
|
" 60.526316% void (planning_diagnostics::PlanningErrorMonitorNode::?)()\n",
|
|
"/awapi/vehicle/get/status:\n",
|
|
" 0.689655% void (rclcpp::TimeSource::?)(rosgraph_msgs::msg::Clock*),\n",
|
|
" 100.000000% void (autoware_api::AutowareIvAdapter::?)()\n",
|
|
"/diagnostics:\n",
|
|
" 0.005231% void (rclcpp::TimeSource::?)(rosgraph_msgs::msg::Clock*),\n",
|
|
" 0.005231% void (NDTScanMatcher::?)(geometry_msgs::msg::PoseWithCovarianceStamped*),\n",
|
|
" 0.428960% void (NDTScanMatcher::?)(sensor_msgs::msg::PointCloud2*),\n",
|
|
" 4.723792% void (NDTScanMatcher::?)(sensor_msgs::msg::PointCloud2*),\n",
|
|
" 0.136012% void (AutowareStateMonitorNode::?)()\n",
|
|
"/localization/pose_estimator/pose_with_covariance:\n",
|
|
" 0.384615% void (rclcpp::TimeSource::?)(rosgraph_msgs::msg::Clock*),\n",
|
|
" 100.000000% void (NDTScanMatcher::?)(sensor_msgs::msg::PointCloud2*)\n",
|
|
"/localization/pose_estimator/initial_to_result_distance:\n",
|
|
" 100.000000% void (NDTScanMatcher::?)(sensor_msgs::msg::PointCloud2*),\n",
|
|
" 0.373134% void (AutowareStateMonitorNode::?)()\n",
|
|
"/localization/pose_estimator/initial_to_result_distance_new:\n",
|
|
" 100.000000% void (NDTScanMatcher::?)(sensor_msgs::msg::PointCloud2*),\n",
|
|
" 0.373134% void (AutowareStateMonitorNode::?)()\n",
|
|
"/localization/pose_estimator/transform_probability:\n",
|
|
" 100.000000% void (NDTScanMatcher::?)(sensor_msgs::msg::PointCloud2*),\n",
|
|
" 0.373134% void (AutowareStateMonitorNode::?)()\n",
|
|
"/localization/pose_estimator/exe_time_ms:\n",
|
|
" 100.000000% void (NDTScanMatcher::?)(sensor_msgs::msg::PointCloud2*),\n",
|
|
" 0.373134% void (AutowareStateMonitorNode::?)()\n",
|
|
"/localization/pose_estimator/nearest_voxel_transformation_likelihood:\n",
|
|
" 100.000000% void (NDTScanMatcher::?)(sensor_msgs::msg::PointCloud2*),\n",
|
|
" 0.373134% void (AutowareStateMonitorNode::?)()\n",
|
|
"/localization/pose_estimator/initial_to_result_distance_old:\n",
|
|
" 100.000000% void (NDTScanMatcher::?)(sensor_msgs::msg::PointCloud2*),\n",
|
|
" 0.373134% void (AutowareStateMonitorNode::?)()\n",
|
|
"/localization/pose_estimator/iteration_num:\n",
|
|
" 100.000000% void (NDTScanMatcher::?)(sensor_msgs::msg::PointCloud2*),\n",
|
|
" 0.373134% void (AutowareStateMonitorNode::?)()\n",
|
|
"/diagnostics:\n",
|
|
" 34.070796% void (diagnostic_updater::Updater::?)(),\n",
|
|
" 65.486726% void (vehicle_cmd_gate::VehicleCmdGate::?)()\n",
|
|
"/control/current_gate_mode:\n",
|
|
" 50.000000% void (vehicle_cmd_gate::VehicleCmdGate::?)(),\n",
|
|
" 50.000000% void (vehicle_cmd_gate::VehicleCmdGate::?)()\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"pub_to_scored_cb = {}\n",
|
|
"\n",
|
|
"verts = set(callback_objects.values())\n",
|
|
"edges = {}\n",
|
|
"for send_cb, scored_pubs in cb_to_scored_pub.items():\n",
|
|
" for pub, score in scored_pubs:\n",
|
|
" if score == 0.0:\n",
|
|
" continue\n",
|
|
" if pub not in pub_to_scored_cb:\n",
|
|
" pub_to_scored_cb[pub] = []\n",
|
|
" pub_to_scored_cb[pub].append((send_cb, score))\n",
|
|
" receiver_cbs = [sub_obj.callback_object for sub in pub.subscriptions for sub_obj in sub.subscription_objects]\n",
|
|
" for recv_cb in receiver_cbs:\n",
|
|
" edges[(send_cb, recv_cb)] = (pub.topic, pub_cb_to_lat_stats[(pub, recv_cb)])\n",
|
|
"\n",
|
|
"for pub, scored_cbs in pub_to_scored_cb.items():\n",
|
|
" if len(scored_cbs) > 1:\n",
|
|
" def _mapfun(tup):\n",
|
|
" cb, score = tup\n",
|
|
" pretty_sym = Ros2DataModelUtil._prettify(None, callback_symbols[cb.callback_object].symbol)\n",
|
|
" pretty_sym = re.sub(r\"std::shared_ptr<(.*?) *(const)?>\", r\"\\1*\", pretty_sym)\n",
|
|
" return f'{score * 100:>10.6f}% {pretty_sym}'\n",
|
|
"\n",
|
|
"\n",
|
|
" cbstr = ',\\n '.join(map(_mapfun, scored_cbs))\n",
|
|
" print(f\"{pub.topic_name}:\\n {cbstr}\")\n",
|
|
"\n",
|
|
"inputs = {}\n",
|
|
"outputs = {}\n",
|
|
"for topic in out_topics:\n",
|
|
" outputs.update(\n",
|
|
" {cb: topic for pub in topic.publishers if pub in pub_to_scored_cb for cb, score in pub_to_scored_cb[pub]})\n",
|
|
"for topic in in_topics:\n",
|
|
" inputs.update(\n",
|
|
" {sub_obj.callback_object: topic for sub in topic.subscriptions for sub_obj in sub.subscription_objects})\n",
|
|
"\n",
|
|
"#################################################\n",
|
|
"# Filter callback objects and topics\n",
|
|
"#################################################\n",
|
|
"\n",
|
|
"callback_symbol_filters = [\n",
|
|
" \"rcl_interfaces::msg::ParameterEvent\", \"diagnostic_updater::Updater\",\n",
|
|
" \"rclcpp::ParameterService::ParameterService\", \"tf2_ros::TransformListener\",\n",
|
|
" \"rclcpp_components::ComponentManager\", \"diagnostic_aggregator::Aggregator\"\n",
|
|
"]\n",
|
|
"\n",
|
|
"verts = set(\n",
|
|
" filter(\n",
|
|
" lambda vert: not any(\n",
|
|
" f in callback_symbols[vert.callback_object].symbol for f in callback_symbol_filters),\n",
|
|
" verts))\n",
|
|
"edges = {(cb1, cb2): val for (cb1, cb2), val in edges.items() if cb1 in verts and cb2 in verts}\n",
|
|
"outputs = {cb: topic for cb, topic in outputs.items() if cb in verts}\n",
|
|
"inputs = {cb: topic for cb, topic in inputs.items() if cb in verts}\n",
|
|
"\n",
|
|
"latency_graph = LatencyGraph(verts, edges, inputs, outputs)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 20,
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%%\n"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"ename": "KeyError",
|
|
"evalue": "281471221778768",
|
|
"output_type": "error",
|
|
"traceback": [
|
|
"\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
|
|
"\u001B[0;31mKeyError\u001B[0m Traceback (most recent call last)",
|
|
"\u001B[0;32m/tmp/ipykernel_6800/33364117.py\u001B[0m in \u001B[0;36m<module>\u001B[0;34m\u001B[0m\n\u001B[1;32m 12\u001B[0m \u001B[0mnode_internal_deps\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0myaml\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mload\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mf\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 13\u001B[0m \u001B[0;31m# Convert node path to node instance\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 14\u001B[0;31m node_internal_deps = {_find_node(path): {\n\u001B[0m\u001B[1;32m 15\u001B[0m \u001B[0mcallback_objects\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mcb_id\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m:\u001B[0m \u001B[0;34m[\u001B[0m\u001B[0mcallback_objects\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mdep_id\u001B[0m\u001B[0;34m]\u001B[0m \u001B[0;32mfor\u001B[0m \u001B[0mdep_id\u001B[0m \u001B[0;32min\u001B[0m \u001B[0mdep_ids\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 16\u001B[0m \u001B[0;32mfor\u001B[0m \u001B[0mcb_id\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mdep_ids\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n",
|
|
"\u001B[0;32m/tmp/ipykernel_6800/33364117.py\u001B[0m in \u001B[0;36m<dictcomp>\u001B[0;34m(.0)\u001B[0m\n\u001B[1;32m 12\u001B[0m \u001B[0mnode_internal_deps\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0myaml\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mload\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mf\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 13\u001B[0m \u001B[0;31m# Convert node path to node instance\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 14\u001B[0;31m node_internal_deps = {_find_node(path): {\n\u001B[0m\u001B[1;32m 15\u001B[0m \u001B[0mcallback_objects\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mcb_id\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m:\u001B[0m \u001B[0;34m[\u001B[0m\u001B[0mcallback_objects\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mdep_id\u001B[0m\u001B[0;34m]\u001B[0m \u001B[0;32mfor\u001B[0m \u001B[0mdep_id\u001B[0m \u001B[0;32min\u001B[0m \u001B[0mdep_ids\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 16\u001B[0m \u001B[0;32mfor\u001B[0m \u001B[0mcb_id\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mdep_ids\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n",
|
|
"\u001B[0;32m/tmp/ipykernel_6800/33364117.py\u001B[0m in \u001B[0;36m<dictcomp>\u001B[0;34m(.0)\u001B[0m\n\u001B[1;32m 13\u001B[0m \u001B[0;31m# Convert node path to node instance\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 14\u001B[0m node_internal_deps = {_find_node(path): {\n\u001B[0;32m---> 15\u001B[0;31m \u001B[0mcallback_objects\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mcb_id\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m:\u001B[0m \u001B[0;34m[\u001B[0m\u001B[0mcallback_objects\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mdep_id\u001B[0m\u001B[0;34m]\u001B[0m \u001B[0;32mfor\u001B[0m \u001B[0mdep_id\u001B[0m \u001B[0;32min\u001B[0m \u001B[0mdep_ids\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 16\u001B[0m \u001B[0;32mfor\u001B[0m \u001B[0mcb_id\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mdep_ids\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 17\u001B[0m \u001B[0;32min\u001B[0m \u001B[0mdeps\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mitems\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n",
|
|
"\u001B[0;31mKeyError\u001B[0m: 281471221778768"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"#################################################\n",
|
|
"# Get intra-node dependencies from settings\n",
|
|
"#################################################\n",
|
|
"\n",
|
|
"def _find_node(path):\n",
|
|
" return next(filter(lambda n: n.path == path, nodes.values()))\n",
|
|
"\n",
|
|
"\n",
|
|
"from ruamel.yaml import YAML\n",
|
|
"\n",
|
|
"yaml = YAML()\n",
|
|
"with open(\"settings/intra-node-data-deps.yaml\", \"r\") as f:\n",
|
|
" node_internal_deps = yaml.load(f)\n",
|
|
" # Convert node path to node instance\n",
|
|
" node_internal_deps = {_find_node(path): {\n",
|
|
" callback_objects[cb_id]: [callback_objects[dep_id] for dep_id in dep_ids]\n",
|
|
" for cb_id, dep_ids\n",
|
|
" in deps.items()\n",
|
|
" } for path, deps in node_internal_deps.items()}\n",
|
|
"\n",
|
|
"for node, cb_mappings in node_internal_deps.items():\n",
|
|
" print(node)\n",
|
|
" for cb, deps in cb_mappings.items():\n",
|
|
" print(\" \", type(cb).__name__, \"->\", ' '.join(map(lambda x: type(x).__name__, deps)))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%%\n"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"\n",
|
|
"#################################################\n",
|
|
"# Plot DFG\n",
|
|
"#################################################\n",
|
|
"\n",
|
|
"import graphviz as gv\n",
|
|
"\n",
|
|
"g = gv.Digraph('G', filename=\"latency_graph.gv\",\n",
|
|
" node_attr={'shape': 'record', 'margin': '0.00001', 'width': '0.00001', 'height': '0.001'},\n",
|
|
" graph_attr={'pack': '1'})\n",
|
|
"g.graph_attr['rankdir'] = 'LR'\n",
|
|
"\n",
|
|
"g.node(\"INPUT\", gv.nohtml(\"{INPUT |<out>}\"))\n",
|
|
"g.node(\"OUTPUT\", gv.nohtml(\"{<in> |OUTPUT}\"))\n",
|
|
"\n",
|
|
"nodes_to_cbs = {}\n",
|
|
"export_dict = {}\n",
|
|
"\n",
|
|
"for vert in latency_graph.verts:\n",
|
|
" vert: TrCallbackObject\n",
|
|
"\n",
|
|
" if isinstance(vert.owner, TrTimer):\n",
|
|
" owner_nodes = vert.owner.nodes\n",
|
|
" elif isinstance(vert.owner, TrSubscriptionObject):\n",
|
|
" owner_nodes = [vert.owner.subscription.node]\n",
|
|
" else:\n",
|
|
" owner_nodes = []\n",
|
|
"\n",
|
|
" if len(owner_nodes) > 1:\n",
|
|
" raise RuntimeError(f\"CB has owners {', '.join(map(lambda n: n.path, owner_nodes))}\")\n",
|
|
" elif not owner_nodes:\n",
|
|
" print(\"[WARN] CB has no owners\")\n",
|
|
" continue\n",
|
|
"\n",
|
|
" owner = owner_nodes[0]\n",
|
|
" if not owner in nodes_to_cbs: nodes_to_cbs[owner] = []\n",
|
|
" nodes_to_cbs[owner].append(vert)\n",
|
|
" if not owner.path in export_dict: export_dict[owner.path] = []\n",
|
|
"\n",
|
|
"for node, cbs in nodes_to_cbs.items():\n",
|
|
" with g.subgraph(name=f\"cluster_{node.path}\") as c:\n",
|
|
" c.attr(label=node.path)\n",
|
|
" c.attr(margin='0.0')\n",
|
|
" c.attr(bgcolor='lightgray')\n",
|
|
"\n",
|
|
" for cb in cbs:\n",
|
|
" cb: TrCallbackObject\n",
|
|
" pretty_sym = Ros2DataModelUtil._prettify(None, callback_symbols[cb.callback_object].symbol)\n",
|
|
" pretty_sym = re.sub(r\"std::shared_ptr<(.*?) *(const)?>\", r\"\\1*\", pretty_sym)\n",
|
|
" pretty_sym = pretty_sym.replace('{', '\\\\{').replace('}', '\\\\}')\n",
|
|
"\n",
|
|
" export_dict[node.path].append({cb.id: {\n",
|
|
" 'symbol': pretty_sym,\n",
|
|
" 'ins': sum(map(lambda k: k[1].id == cb.id, latency_graph.edges.keys())) + sum(\n",
|
|
" map(lambda k: k.id == cb.id, latency_graph.starts.keys())),\n",
|
|
" 'outs': sum(map(lambda k: k[0].id == cb.id, latency_graph.edges.keys())) + sum(\n",
|
|
" map(lambda k: k.id == cb.id, latency_graph.ends.keys()))\n",
|
|
" }})\n",
|
|
"\n",
|
|
" cb_durations = np.array(list(map(lambda inst: inst.duration.total_seconds(), cb.callback_instances)))\n",
|
|
" if len(cb_durations) == 0:\n",
|
|
" cb_durations = np.zeros(1)\n",
|
|
" cb_dur_stats = (cb_durations.min(), cb_durations.mean(), cb_durations.max())\n",
|
|
" c.node(str(cb.id), gv.nohtml(f\"{{<in> |{pretty_sym} |<out>}}\"),\n",
|
|
" tooltip=f\"{cb_dur_stats[0] * 1e6:.0f}µs, {cb_dur_stats[1] * 1e6:.0f}µs, {cb_dur_stats[2] * 1e6:.0f}µs\",\n",
|
|
" fontcolor=('red' if isinstance(cb.owner, TrTimer) else 'black'))\n",
|
|
"\n",
|
|
"for (c1, c2), (topic, lat_stats) in latency_graph.edges.items():\n",
|
|
" g.edge(f\"{c1.id}:out\", f\"{c2.id}:in\", tooltip=f\"{topic.name} ({lat_stats.mean() * 1000:.2f}ms)\")\n",
|
|
"\n",
|
|
"for c, t in latency_graph.starts.items():\n",
|
|
" g.edge(\"INPUT:out\", f\"{c.id}:in\", tooltip=t.name)\n",
|
|
"\n",
|
|
"for c, t in latency_graph.ends.items():\n",
|
|
" g.edge(f\"{c.id}:out\", \"OUTPUT:in\", tooltip=t.name)\n",
|
|
"\n",
|
|
"for n, deps in node_internal_deps.items():\n",
|
|
" for cb, dep_cbs in deps.items():\n",
|
|
" for dep_cb in dep_cbs:\n",
|
|
" g.edge(f\"{dep_cb.id}:out\", f\"{cb.id}:in\", tooltip=\"node-internal data dependency\", color='red')\n",
|
|
"\n",
|
|
"with open(\"settings/node-cbs.yaml\", \"w\") as f:\n",
|
|
" yaml = YAML()\n",
|
|
" yaml.dump(export_dict, f)\n",
|
|
"\n",
|
|
"g.save(\"latency_graph.gv\")\n",
|
|
"\n",
|
|
"g"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 10,
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%%\n"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"....................................*.......................................*.......................................*........................................*.......................................*...#*....................................*.......................................*.......................................*.........................................*.......................................*.......................................*.......................................*.....#*..........................................*.......................................*.......................................*.......................................*.....#**.....................................*.......................................*.......................................*.......................................*.......................................*.......................................*.......................................*.......................................*.......................................*.......#*....................................*.......................................*...#**#***.****.**.#**....................................*.......................................*........................................*.......................................*.......................................*.........................................*........................................*.......................................*.........................................*.......................................*.......................................*.......................................*.......................................*.......................................*.........................................*............................................*.......................................*.........................................*.........................................*.......................................*...........................................*........................................*.......................................*.........................................*.......................................*..........................................*.......................................*.........................................*...#**....................................*.....#****.........................................*.......................................*.....*.===== PATH 0 =====\n",
|
|
"===== PATH 1 =====\n",
|
|
"===== PATH 2 =====\n",
|
|
"===== PATH 3 =====\n",
|
|
"===== PATH 4 =====\n",
|
|
"/diagnostics 56.812ms 56.812ms\n",
|
|
"===== PATH 5 =====\n",
|
|
"===== PATH 6 =====\n",
|
|
"===== PATH 7 =====\n",
|
|
"===== PATH 8 =====\n",
|
|
"===== PATH 9 =====\n",
|
|
"===== PATH 10 =====\n",
|
|
"===== PATH 11 =====\n",
|
|
"===== PATH 12 =====\n",
|
|
"===== PATH 13 =====\n",
|
|
"===== PATH 14 =====\n",
|
|
"===== PATH 15 =====\n",
|
|
"===== PATH 16 =====\n",
|
|
"===== PATH 17 =====\n",
|
|
"===== PATH 18 =====\n",
|
|
"===== PATH 19 =====\n",
|
|
"===== PATH 20 =====\n",
|
|
"===== PATH 21 =====\n",
|
|
"===== PATH 22 =====\n",
|
|
"===== PATH 23 =====\n",
|
|
"===== PATH 24 =====\n",
|
|
"===== PATH 25 =====\n",
|
|
"===== PATH 26 =====\n",
|
|
"===== PATH 27 =====\n",
|
|
"===== PATH 28 =====\n",
|
|
"/diagnostics 56.812ms 56.812ms\n",
|
|
"===== PATH 29 =====\n",
|
|
"===== PATH 30 =====\n",
|
|
"===== PATH 31 =====\n",
|
|
"===== PATH 32 =====\n",
|
|
"===== PATH 33 =====\n",
|
|
"===== PATH 34 =====\n",
|
|
"===== PATH 35 =====\n",
|
|
"===== PATH 36 =====\n",
|
|
"===== PATH 37 =====\n",
|
|
"===== PATH 38 =====\n",
|
|
"===== PATH 39 =====\n",
|
|
"===== PATH 40 =====\n",
|
|
"===== PATH 41 =====\n",
|
|
"===== PATH 42 =====\n",
|
|
"===== PATH 43 =====\n",
|
|
"===== PATH 44 =====\n",
|
|
"===== PATH 45 =====\n",
|
|
"===== PATH 46 =====\n",
|
|
"===== PATH 47 =====\n",
|
|
"===== PATH 48 =====\n",
|
|
"===== PATH 49 =====\n",
|
|
"===== PATH 50 =====\n",
|
|
"===== PATH 51 =====\n",
|
|
"===== PATH 52 =====\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"\n",
|
|
"#################################################\n",
|
|
"# Transitively add latencies to get E2E latency\n",
|
|
"#################################################\n",
|
|
"\n",
|
|
"@dataclass\n",
|
|
"class PathElem:\n",
|
|
" src: TrCallbackObject\n",
|
|
" dst: TrCallbackObject\n",
|
|
" topic: TrTopic\n",
|
|
" latencies: LatencyStats\n",
|
|
"\n",
|
|
"\n",
|
|
"def get_latency_paths(cb1, cb_to_cb_to_lat_stats, goals, parent_path=[]) -> List[List[PathElem]]:\n",
|
|
" if cb1 in goals:\n",
|
|
" return [parent_path]\n",
|
|
"\n",
|
|
" if cb1 not in cb_to_cb_to_lat_stats:\n",
|
|
" return [parent_path]\n",
|
|
"\n",
|
|
" paths = []\n",
|
|
" for cb2 in cb_to_cb_to_lat_stats[cb1]:\n",
|
|
" for topic, lats in cb_to_cb_to_lat_stats[cb1][cb2].items():\n",
|
|
" new_paths = get_latency_paths(cb2, cb_to_cb_to_lat_stats, goals,\n",
|
|
" parent_path + [PathElem(cb1, cb2, topic, lats)])\n",
|
|
" paths += new_paths\n",
|
|
"\n",
|
|
" return paths\n",
|
|
"\n",
|
|
"\n",
|
|
"cb_to_cb_to_lat_stats = {}\n",
|
|
"for (pub, cb2), lat_stats in pub_cb_to_lat_stats.items():\n",
|
|
" if pub not in pub_to_scored_cb:\n",
|
|
" #print(f\"[WARN] Pub on topic {pub.topic.name} not in pub_to_scored_cb, skipping.\")\n",
|
|
" print(end='.')\n",
|
|
" continue\n",
|
|
" if len(pub_to_scored_cb[pub]) > 1:\n",
|
|
" #print(f\"[WARN] Pub on topic {pub.topic.name} has {len(pub_to_scored_cb[pub])} callbacks.\")\n",
|
|
" print(end='#')\n",
|
|
" for cb1, score in pub_to_scored_cb[pub]:\n",
|
|
" if score != 1.0:\n",
|
|
" #print(f\"[WARN] Callback for topic {pub.topic.name} only has a score of {score*100:.3f}%\")\n",
|
|
" print(end='*')\n",
|
|
" if cb1 in cb_to_cb_to_lat_stats and cb2 in cb_to_cb_to_lat_stats[cb1]:\n",
|
|
" #print(f\"[WARN] Pair of callbacks already in dict!\")\n",
|
|
" print(end='_')\n",
|
|
" else:\n",
|
|
" if cb1 not in cb_to_cb_to_lat_stats:\n",
|
|
" cb_to_cb_to_lat_stats[cb1] = {}\n",
|
|
" if cb2 not in cb_to_cb_to_lat_stats[cb1]:\n",
|
|
" cb_to_cb_to_lat_stats[cb1][cb2] = {}\n",
|
|
" cb_to_cb_to_lat_stats[cb1][cb2][pub.topic] = pub_cb_to_lat_stats[(pub, cb2)]\n",
|
|
"\n",
|
|
"latency_paths = []\n",
|
|
"for cb in inputs:\n",
|
|
" latency_paths += get_latency_paths(cb, cb_to_cb_to_lat_stats, outputs.keys())\n",
|
|
"\n",
|
|
"\n",
|
|
"def pl(l, lvl=0):\n",
|
|
" if isinstance(l, list):\n",
|
|
" print(\" \" * lvl, type(l), len(l))\n",
|
|
" for i in l:\n",
|
|
" pl(i, lvl + 1)\n",
|
|
" else:\n",
|
|
" print(\" \" * lvl, type(l))\n",
|
|
"\n",
|
|
"\n",
|
|
"#pl(latency_paths)\n",
|
|
"\n",
|
|
"for i, path in enumerate(latency_paths):\n",
|
|
" print(\"===== PATH\", i, \"=====\")\n",
|
|
" #print(type(path))\n",
|
|
" tot_lat = 0.0\n",
|
|
" for item in path:\n",
|
|
" tot_lat += item.latencies.mean()\n",
|
|
" print(f\"{item.topic.name:120s} {item.latencies.mean() * 1000:.3f}ms {tot_lat * 1000:.3f}ms\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%%\n"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"fig = plt.figure(figsize=(30, 10))\n",
|
|
"ax = fig.add_subplot()\n",
|
|
"\n",
|
|
"for lat_stats in pub_cb_to_lat_stats.values():\n",
|
|
" ax.plot(lat_stats.index, np.where(np.isnan(lat_stats), 0, lat_stats))\n",
|
|
"\n",
|
|
"ax.set_ylim(0, .1)\n",
|
|
"ax.set_xlim(655 + 1.652795e9, 660 + 1.652795e9)\n",
|
|
"None"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"pycharm": {
|
|
"name": "#%%\n"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": []
|
|
}
|
|
],
|
|
"metadata": {
|
|
"interpreter": {
|
|
"hash": "e7370f93d1d0cde622a1f8e1c04877d8463912d04d973331ad4851f04de6915a"
|
|
},
|
|
"kernelspec": {
|
|
"display_name": "Python 3.8.10 64-bit",
|
|
"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.8.10"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
} |