Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added rosdiscover input #28

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
metrics/*
__pycache__/
graphs/*
42 changes: 39 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,55 @@ The following figure illustrates the 3-phases approach to extract computation gr
```

## Installation
Note that it is not necessary for our architecture extractor, which is independent of platforms. However, it requires a few dependencies, solved by the following commands:
### Installing ROSDiscover
Installing rosdiscover has some prerequisites needed for the static code analysis we need to install these first.

#### Build docker-llvm
Build the [docker-llvm](https://github.com/ChrisTimperley/docker-llvm) image image. This will build the llvm compiler used to build rosdicover-extract-cxx. This might take a while.

#### Build rosdiscover-extract-cxx
* Clone [the repo](https://github.com/cmu-rss-lab/rosdiscover-cxx-recover)
* Add submodules with their correct versions
* `cd extern`
* `git clone https://github.com/fmtlib/fmt && cd fmt && git checkout d141cdbeb0fb422a3fb7173b285fd38e0d1772dc && cd ..`
* `git clone https://github.com/nlohmann/json nlohmann_json && cd nlohmann_json && git checkout db78ac1d7716f56fc9f1b030b715f872f93964e4 && cd ..`
* `git clone https://github.com/mapbox/variant/ && cd variant && git checkout a5a79a594f39d705a7ef969f54a0743516f0bc6d && cd ../../`
* Build the image
* `cd docker && sudo make install`

After following these steps you will have a docker volume called rosdiscover-cxx-extract-opt ready to be mounted and used by rosdiscover. You can check if it was properly installed by running sudo docker volume ls.

#### Install the rosdiscover python package
* `pip3 install roswire`
* `git clone https://github.com/cmu-rss-lab/rosdiscover rosdiscover`
* `cd rosdiscover`
* `pip install -e .`

#### Install the rosbag-extractor dependencies:

```
$ pip3 install -r ./requirements.txt
$ sudo apt install graphviz
```
* If the requirements list is/becomes broken, do not hesitate to pull request the necessary updates.

Then, just run the extraction script on a bag file:

Now you are finally ready to use the extraction script on a bag file:

```
$ python3 extractor.py [-h] <-v ROS_VERSION> [-s START_TIME] [-e END_TIME] <-f FILE_PATH> <-ft FILE_TYPE> [-i INPUT] [-ts TIME_SPACE]
```

##### Example
parser.add_argument('-rd', '--use-ros-discover', help='Option to run rosdiscover', required=False, type=bool)
parser.add_argument('-rdc', '--ros-discover-config', help='Path to the rosdiscover config', required=False, type=str)
parser.add_argument('-rds', '--node-input-strategy', help='either node-provider or graph-merge', required=False, type=str)

If you would like to use rosbag extractor with rosdiscover support please use the following flags:
* `-rd / --use-ros-discover` - Enable the usage of rosdiscover for node input
* `-rdc / --ros-discover-config` - Path to the configuration for rosdiscover. Check out [this example](https://github.com/cristidragomir97/rosbag-extractor/blob/main/rosdiscover_configs/turtlebot3-slam.yml)
* `-rds / --ros-discover-strategy` - The strategy used for integrationg the rosdiscover output. `graph-merge` creates an architecture graph using rosart and another one using rosdiscover and merges them, while `node-provider` uses the input from rosdiscover as an input for rosbag-extractor.

## Example

Here, we provide an example with a very simple ROS 2 bag file:
```
Expand Down Expand Up @@ -87,3 +122,4 @@ The corresponding metric as follows can be found in the ``metrics`` directory.
```



105 changes: 93 additions & 12 deletions extractor.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import os, sys
import argparse
from src.extractor import main

from rosdiscover_wrapper import *
import pandas as pd
from graph_merger import *
from graphviz import Source

def check_files_extension(folder_path, extension):
for filename in os.listdir(folder_path):
Expand All @@ -10,7 +13,7 @@ def check_files_extension(folder_path, extension):
return False


def run_extractor(ros_version, start_time, end_time, file_path, filetype, input, time_space):
def run_extractor(ros_version, start_time, end_time, file_path, filetype, input, time_space, rosdiscover):
print(">>>> Bag File Extractor <<<<")

if start_time is None:
Expand All @@ -20,19 +23,19 @@ def run_extractor(ros_version, start_time, end_time, file_path, filetype, input,

if ros_version == 'ros1':
if filetype == 'bag':
main.extractor(start_time, end_time, file_path, filetype, input, time_space)
graph_path = main.extractor(start_time, end_time, file_path, filetype, input, time_space, rosdiscover = rosdiscover)
else:
print("ROS1 only has filetype `bag`")
sys.exit()
elif ros_version == 'ros2':
if filetype == 'db3':
if check_files_extension(file_path, '.db3'):
main.extractor(start_time, end_time, file_path, filetype, input, time_space)
graph_path = main.extractor(start_time, end_time, file_path, filetype, input, time_space, rosdiscover = rosdiscover)
else:
print("Cannot find the correct file with the input filetype: " + filetype)
elif filetype == 'mcap':
if check_files_extension(file_path, '.mcap'):
main.extractor(start_time, end_time, file_path, filetype, input, time_space)
graph_path = main.extractor(start_time, end_time, file_path, filetype, input, time_space, rosdiscover = rosdiscover)
else:
print("Cannot find the correct file with the input filetype: " + filetype)
else:
Expand All @@ -41,7 +44,30 @@ def run_extractor(ros_version, start_time, end_time, file_path, filetype, input,
print("ROS version is unknown")

print(">>>>>>>>>>>>>><<<<<<<<<<<<<<<")
return graph_path


def convert_dot_to_pdf(dot_file_path, pdf_output_path):
"""
Reads a DOT file, displays the graph, and saves it as a PDF.

Parameters:
- dot_file_path: str, the path to the DOT file.
- pdf_output_path: str, the path where the PDF file will be saved.
"""
print(pdf_output_path)
# Read the DOT file
with open(dot_file_path, 'r') as file:
dot_content = file.read()

# Create a Source object from the DOT content
dot_graph = Source(dot_content)

# Optionally, view the graph (this will open a viewer window if supported)
dot_graph.view()

# Save the graph to a PDF file (cleanup=True removes intermediate files)
dot_graph.render(pdf_output_path, format='pdf', cleanup=True)

def run():
# Arguments definition and management
Expand All @@ -53,15 +79,70 @@ def run():
parser.add_argument('-ft', '--file_type', help='Bag file type: bag, db3 or mcap', required=True, type=str)
parser.add_argument('-i', '--input', help='Path to file containing nodes information', required=False, type=str)
parser.add_argument('-ts', '--time_space', help='Time in second to generate a series of interconnected graphs', required=False, type=str)
parser.add_argument('-rd', '--use-ros-discover', help='Option to run rosdiscover', required=False, type=bool)
parser.add_argument('-rdc', '--ros-discover-config', help='Path to the rosdiscover config', required=False, type=str)
parser.add_argument('-rds', '--node-input-strategy', help='either node-provider or graph-merge', required=False, type=str)
options = parser.parse_args()

run_extractor(options.ros_version,
options.start_time,
options.end_time,
options.file_path,
options.file_type,
options.input,
options.time_space)
if options.use_ros_discover is not None:
if options.ros_discover_config is not None:
if options.node_input_strategy is not None:
csv_output_file = f"{options.file_path}/rosdiscover_output.csv"
print("Running ROS Discover",options.ros_discover_config, "with the", options.node_input_strategy, "strategy")
run_rosdiscover(options.ros_discover_config, csv_output_file)


if str(options.node_input_strategy) == "graph-merge":
base_graph_path = run_extractor(options.ros_version,
options.start_time,
options.end_time,
options.file_path,
options.file_type,
options.input,
options.time_space,
rosdiscover = True)

rosdiscover_graph_path = create_graph(path=csv_output_file, format= "dot")
convert_dot_to_pdf(rosdiscover_graph_path, f"{rosdiscover_graph_path}.pdf")
rosdiscover_graph_path = rosdiscover_graph_path + ".dot"

print("Base graph path: ", base_graph_path)
graph_folder_path = os.path.dirname(os.path.realpath(base_graph_path))
print("Rosdiscover graph path: ", rosdiscover_graph_path)

base_graph = parse_dotfile(base_graph_path)
rosdiscover_graph = parse_dotfile(rosdiscover_graph_path)
merged_graph = merge_and_highlight(base_graph, rosdiscover_graph)
export_to_dot(merged_graph, graph_folder_path + "/merged_graph.dot")
convert_dot_to_pdf(graph_folder_path + "/merged_graph.dot", graph_folder_path + "/merged_graph.pdf")


elif str(options.node_input_strategy) == "node-provider":
graph_path = run_extractor(options.ros_version,
options.start_time,
options.end_time,
options.file_path,
options.file_type,
csv_output_file,
options.time_space,
rosdiscover = True)
print(graph_path)

else:
print("Node strategy is not node-provider or graph-merge")
else:
print("Please provide a strategy for the node input, can be either node-provider or graph-merge")
else:
run_extractor(options.ros_version,
options.start_time,
options.end_time,
options.file_path,
options.file_type,
options.input,
options.time_space,
rosdiscover = False)




if __name__ == "__main__":
Expand Down
43 changes: 43 additions & 0 deletions graph_merger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import pydot

def parse_dotfile(file_path):
graphs = pydot.graph_from_dot_file(file_path)
if graphs:
return graphs[0]
else:
return None

def merge_and_highlight(graph1, graph2):
# Initialize the merged graph
merged_graph = pydot.Dot(graph_type='digraph')

# Add all nodes and edges from the first graph
for node in graph1.get_nodes():
merged_graph.add_node(node)
for edge in graph1.get_edges():
merged_graph.add_edge(edge)

# Keep track of existing nodes and edges for comparison
existing_nodes = set(node.get_name() for node in merged_graph.get_nodes())
existing_edges = set((edge.get_source(), edge.get_destination()) for edge in merged_graph.get_edges())

# Add nodes from the second graph
for node in graph2.get_nodes():
if node.get_name() not in existing_nodes:
node.set_color('red') # Highlight new nodes in red
merged_graph.add_node(node)

# Add edges from the second graph, highlight new edges or edges between existing nodes
for edge in graph2.get_edges():
edge_tuple = (edge.get_source(), edge.get_destination())
if edge_tuple not in existing_edges:
edge.set_color('red') # Highlight new edges in red
merged_graph.add_edge(edge)
elif edge.get_color() != 'red': # For existing edges not highlighted, ensure they're not colored red
edge.set_color('') # Reset color to default if re-adding an existing edge that's not new

return merged_graph

def export_to_dot(merged_graph, output_file):
merged_graph.write_dot(output_file)
print(f"Exported merged graph to {output_file}")
3 changes: 3 additions & 0 deletions requirement.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ bagpy>=0.4.8
graphviz>=0.20
catkin_pkg>0.5.2
rosbags
pandas=2.2.1
pydot=2.0.0
ruamel.yaml=0.18.6
9 changes: 9 additions & 0 deletions rosdiscover_configs/autorally.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
image: therobotcooperative/autorally
sources:
- /opt/ros/melodic/setup.bash
- /ros_ws/devel/setup.bash
- /ros_ws/src/autorally/autorally_util/setupEnvLocal.sh
launches:
- /ros_ws/src/autorally/autorally_gazebo/launch/autoRallyTrackGazeboSim.launch
- /ros_ws/src/autorally/autorally_control/launch/waypointFollower.launch
- /ros_ws/src/autorally/autorally_control/launch/constantSpeedController.launch
7 changes: 7 additions & 0 deletions rosdiscover_configs/fetch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
image: therobotcooperative/fetch
sources:
- /opt/ros/melodic/setup.bash
- /ros_ws/devel/setup.bash
launches:
- /ros_ws/src/fetch_gazebo/fetch_gazebo/launch/playground.launch
- /ros_ws/src/fetch_gazebo/fetch_gazebo_demo/launch/demo.launch
13 changes: 13 additions & 0 deletions rosdiscover_configs/turtlebot3-slam.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
image: cristidragomir97/turtlebot3-melodic
sources:
- /opt/ros/melodic/setup.bash
- /turtlebot3_ws/devel/setup.bash
environment:
TURTLEBOT3_MODEL: burger
launches:
- filename: /turtlebot3_ws/src/turtlebot3_simulations/turtlebot3_gazebo/launch/turtlebot3_house.launch
- filename: /turtlebot3_ws/src/turtlebot3/turtlebot3_teleop/launch/turtlebot3_teleop_key.launch
- filename: /turtlebot3_ws/src/turtlebot3/turtlebot3_slam/launch/turtlebot3_slam.launch
slam_methods: gmapping


Loading