diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..bfa8015b9a --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.idea/ +.ipynb_checkpoints/ +cmake-build-debug/ +cmake-build-release/ +build/ +venv/ diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000000..d9052f289d --- /dev/null +++ b/NOTICE @@ -0,0 +1,5 @@ +rosbag2 + +Copyright Holders: +Copyright (c) 2018, Open Source Robotics Foundation, Inc. +Copyright (c) 2018, Bosch Software Innovations GmbH diff --git a/rosbag2_storage_evaluation/CMakeLists.txt b/rosbag2_storage_evaluation/CMakeLists.txt index 4c182fa6ac..08ebee029d 100644 --- a/rosbag2_storage_evaluation/CMakeLists.txt +++ b/rosbag2_storage_evaluation/CMakeLists.txt @@ -1,35 +1,81 @@ cmake_minimum_required(VERSION 3.5) -project(rosbag2_storage_evaluation) - -# Default to C99 -if(NOT CMAKE_C_STANDARD) - set(CMAKE_C_STANDARD 99) -endif() - -# Default to C++14 -if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 14) -endif() - -if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - add_compile_options(-Wall -Wextra -Wpedantic) -endif() - -# find dependencies -find_package(ament_cmake REQUIRED) -# uncomment the following section in order to fill in -# further dependencies manually. -# find_package( REQUIRED) - -if(BUILD_TESTING) - find_package(ament_lint_auto REQUIRED) - # the following line skips the linter which checks for copyrights - # remove the line when a copyright and license is present in all source files - set(ament_cmake_copyright_FOUND TRUE) - # the following line skips cpplint (only works in a git repo) - # remove the line when this package is a git repo - set(ament_cmake_cpplint_FOUND TRUE) - ament_lint_auto_find_test_dependencies() -endif() - -ament_package() + +project(ros2_rosbag_evaluation) + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_FLAGS "-O3") +set(BUILD_SHARED_LIBS ON) + +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) + +set(common_sources + src/common/strings.cpp) + +set(profiler_sources + src/profiler/profiler.cpp) + +set(sqlite_sources + src/writer/sqlite/sqlite.cpp + src/writer/sqlite/sqlite_writer.cpp + src/writer/sqlite/one_table_sqlite_writer.cpp + src/writer/sqlite/separate_topic_table_sqlite_writer.cpp) + +set(trivial_writer_benchmark_sources + src/benchmark/writer/trivial/trivial_writer_benchmark.cpp + src/benchmark/benchmark.cpp + src/writer/stream/message_stream_writer.cpp + src/generators/message_generator.cpp) + +set(sqlite_writer_benchmark_cmd_sources + src/benchmark/writer/sqlite/sqlite_writer_benchmark_cmd.cpp + src/benchmark/writer/sqlite/sqlite_writer_benchmark.cpp + src/benchmark/benchmark.cpp + src/generators/message_generator.cpp) + +set(small_messages_benchmark_sources + src/benchmark/small_messages_benchmark.cpp + src/benchmark/writer/sqlite/sqlite_writer_benchmark.cpp + src/benchmark/benchmark.cpp + src/generators/message_generator.cpp) + +set(big_messages_benchmark_sources + src/benchmark/big_messages_benchmark.cpp + src/benchmark/writer/sqlite/sqlite_writer_benchmark.cpp + src/benchmark/benchmark.cpp + src/generators/message_generator.cpp) + +set(mixed_messages_benchmark_sources + src/benchmark/mixed_messages_benchmark.cpp + src/benchmark/writer/sqlite/sqlite_writer_benchmark.cpp + src/benchmark/benchmark.cpp + src/generators/message_generator.cpp) + +add_library(common ${common_sources}) +target_include_directories(common PRIVATE src) + +add_library(profiler ${profiler_sources}) +target_include_directories(profiler PRIVATE src) + +add_library(sqlite ${sqlite_sources}) +target_include_directories(sqlite PRIVATE src) +target_link_libraries(sqlite sqlite3 common) + +add_executable(trivial_writer_benchmark ${trivial_writer_benchmark_sources}) +target_link_libraries(trivial_writer_benchmark profiler sqlite) +target_include_directories(trivial_writer_benchmark PRIVATE src) + +add_executable(sqlite_writer_benchmark_cmd ${sqlite_writer_benchmark_cmd_sources}) +target_link_libraries(sqlite_writer_benchmark_cmd profiler sqlite) +target_include_directories(sqlite_writer_benchmark_cmd PRIVATE src) + +add_executable(small_messages_benchmark ${small_messages_benchmark_sources}) +target_link_libraries(small_messages_benchmark profiler sqlite) +target_include_directories(small_messages_benchmark PRIVATE src) + +add_executable(big_messages_benchmark ${big_messages_benchmark_sources}) +target_link_libraries(big_messages_benchmark profiler sqlite) +target_include_directories(big_messages_benchmark PRIVATE src) + +add_executable(mixed_messages_benchmark ${mixed_messages_benchmark_sources}) +target_link_libraries(mixed_messages_benchmark profiler sqlite) +target_include_directories(mixed_messages_benchmark PRIVATE src) diff --git a/rosbag2_storage_evaluation/README.md b/rosbag2_storage_evaluation/README.md new file mode 100644 index 0000000000..1a8dfc0d13 --- /dev/null +++ b/rosbag2_storage_evaluation/README.md @@ -0,0 +1,84 @@ +# ROS 2.0 Rosbag Evaluation + +## Benchmarks + +This folder currently contains benchmarks which measure the write speed and disk usage of SQLite files. + +The single table schema +``` + sqlite::create_table(db, "MESSAGES", { + "TIMESTAMP INTEGER NOT NULL", + "TOPIC TEXT NOT NULL", + "DATA BLOB NOT NULL" + }); +``` +is compared with a foreign key schema storing topics in a separate table +``` + sqlite::create_table(db, "TOPICS", { + "ID INTEGER PRIMARY KEY", + "TOPIC TEXT NOT NULL" + }); + + sqlite::create_table(db, "MESSAGES", { + "TIMESTAMP INTEGER NOT NULL", + "TOPIC_ID INTEGER NOT NULL", + "DATA BLOB NOT NULL" + }, {sqlite::ForeignKeyDef{"TOPIC_ID", "TOPICS", "ID"}}); +``` + +It should be **easy to add additional bag file formats**, e.g. for writing directly to disk or writing the RosBag 2.0 format. + +### Build from command line + +The project is using cmake. The script `./build.sh` can be used to build it. + +This will generate benchmark binaries in `./build/bin/`. + +### Run + +Individual benchmarks in `./build/bin` can be run by hand. To run the complete suite the script `./run_all_benchmarks.sh` can be used. + +Each benchmark will generate a CSV file in `./build/bin` containing the measured data for further plotting with the Jupyter Notebook. + +## Jupyter Notebook + +It is used for data analysis and visualization. + +### Setup + +Prerequisites: Python 3.5+, [pip](https://pip.pypa.io/en/stable/), [virtualenv](https://virtualenv.pypa.io/en/stable/) + +``` +virtualenv -p python3 venv +. venv/bin/activate +pip install -r requirements.txt +``` + +### Usage + +``` +. venv/bin/activate +jupyter notebook data_analysis_and_visualization.ipynb +``` + +A browser window should open. Click `Cell -> Run All`. + +## Extending the benchmarks + +### Read Tests + +To measure retrieval time of the first message, extend the `MessageWriter` interface like so +``` + virtual MessageStream::Ptr selectAll() = 0; + virtual MessageStream::Ptr selectTopic(std::string const & topic) = 0; + virtual MessageStream::Ptr selectFromTo( + Message::Timestamp const & fromInclusive, Message::Timestamp const & toExclusive) = 0; +``` +where `MessageStream` is a lazy data structure +``` + virtual bool has_next() const = 0; + virtual MessagePtr next() = 0; +``` +implemented with a streaming SQLite `SELECT` statement. + +The desired timings can then be measured with the `Profiler`. diff --git a/rosbag2_storage_evaluation/build.sh b/rosbag2_storage_evaluation/build.sh new file mode 100755 index 0000000000..0a8e816013 --- /dev/null +++ b/rosbag2_storage_evaluation/build.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +# Copyright (c) 2018, Bosch Software Innovations 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. + +mkdir -p ./build + +cd build + +cmake .. +make + +cd .. + diff --git a/rosbag2_storage_evaluation/data_analysis_and_visualization.ipynb b/rosbag2_storage_evaluation/data_analysis_and_visualization.ipynb new file mode 100644 index 0000000000..63e380dc27 --- /dev/null +++ b/rosbag2_storage_evaluation/data_analysis_and_visualization.ipynb @@ -0,0 +1,557 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " Copyright (c) 2018, Bosch Software Innovations GmbH.\n", + "\n", + " Licensed under the Apache License, Version 2.0 (the \"License\");\n", + " you may not use this file except in compliance with the License.\n", + " You may obtain a copy of the License at\n", + "\n", + " http://www.apache.org/licenses/LICENSE-2.0\n", + "\n", + " Unless required by applicable law or agreed to in writing, software\n", + " distributed under the License is distributed on an \"AS IS\" BASIS,\n", + " WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + " See the License for the specific language governing permissions and\n", + " limitations under the License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Setup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Import of all the basic modules ([pandas](https://pandas.pydata.org/)). See [plotting](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.plot.html) for help on charts." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import os\n", + "import pandas as pd" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Common Settings" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "BASE_PATH = os.path.join('build', 'bin')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Common Code" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def read_csv(filepath, columns = None):\n", + " dataframe = pd.read_csv(os.path.join(BASE_PATH, filepath))\n", + " if columns is not None:\n", + " assert set(dataframe.columns) == set(columns)\n", + " return dataframe" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def calc_duration(dataframe, start_column, end_column, target_column):\n", + " dataframe[target_column] = dataframe.apply(lambda row: row[end_column] - row[start_column], axis=1)\n", + " dataframe.drop([start_column, end_column], axis=1, inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def delete_column(dataframe, column):\n", + " dataframe.drop([column], axis=1, inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def duration(dataframe, start_column, end_column, target_column):\n", + " dataframe[target_column] = dataframe.apply(lambda row: row[end_column] - row[start_column], axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def scale_value(dataframe, source_column, target_column, factor, ndigits=None):\n", + " dataframe[target_column] = dataframe.apply(lambda row: factor * row[source_column], axis=1)\n", + " if ndigits is not None:\n", + " dataframe[target_column] = dataframe.apply(lambda row: round(row[target_column], ndigits), axis=1)\n", + " if source_column != target_column:\n", + " dataframe.drop([source_column], axis=1, inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def progress_column(subject):\n", + " return lambda i: subject + '_' + str(i) + ' (ms)'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "percent = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]\n", + "def calc_write_progress(dataframe, progress_column, target_column):\n", + " for i in percent:\n", + " duration(dataframe, progress_column(i-10), progress_column(i), target_column(i))\n", + " for i in [0] + percent:\n", + " delete_column(dataframe, progress_column(i))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Benchmarks" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Write" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1) different SQLite modi: performance vs. \"stability\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#data1_columns = None\n", + "#data1 = read_csv('sqlite-db-settings-benchmark.csv', data1_columns)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2) small messages" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data2 = read_csv('small_messages_benchmark.csv', [\n", + " 'description',\n", + " 'number of messages',\n", + " 'message blob size (bytes)',\n", + " 'transaction size',\n", + " 'start writing time (ms)',\n", + " 'write_throughput_0 (ms)',\n", + " 'write_throughput_10 (ms)',\n", + " 'write_throughput_20 (ms)',\n", + " 'write_throughput_30 (ms)',\n", + " 'write_throughput_40 (ms)',\n", + " 'write_throughput_50 (ms)',\n", + " 'write_throughput_60 (ms)',\n", + " 'write_throughput_70 (ms)',\n", + " 'write_throughput_80 (ms)',\n", + " 'write_throughput_90 (ms)',\n", + " 'write_throughput_100 (ms)',\n", + " 'end writing time (ms)',\n", + " 'start indexing time (ms)',\n", + " 'end indexing time (ms)',\n", + " 'disk usage (bytes)'\n", + "])\n", + "\n", + "calc_duration(data2, 'start writing time (ms)', 'end writing time (ms)', 'writing time (ms)')\n", + "calc_duration(data2, 'start indexing time (ms)', 'end indexing time (ms)', 'indexing time (ms)')\n", + "scale_value(data2, 'disk usage (bytes)', 'disk usage (MB)', factor=1/1024/1024)\n", + "\n", + "data2['disk io (messages / s)'] = data2.apply(lambda row: row['number of messages'] / row['writing time (ms)'] * 1000, axis=1)\n", + "data2['disk io (MB / s)'] = data2.apply(lambda row: row['number of messages'] * row['message blob size (bytes)'] / 1024 / 1024 / row['writing time (ms)'] * 1000, axis=1)\n", + "\n", + "throughput_abs = progress_column('write_throughput')\n", + "throughput_duration = progress_column('write_duration')\n", + "calc_write_progress(data2, throughput_abs, throughput_duration)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "data2_aggregated = data2.groupby('description').median().sort_values(by='message blob size (bytes)')\n", + "data2_aggregated" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Throughput\n", + "This diagram shows how long it took to write each 10% slice of the messages." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "data2_aggregated[[throughput_duration(i) for i in percent]].T.plot(kind='bar')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data2_aggregated.plot(kind='bar', x='message blob size (bytes)', y='writing time (ms)');" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data2_aggregated.plot(kind='bar', x='message blob size (bytes)', y='disk usage (MB)');" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data2_aggregated.plot(kind='bar', x='message blob size (bytes)', y='disk io (messages / s)');" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data2_aggregated.plot(kind='bar', x='message blob size (bytes)', y='disk io (MB / s)');" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3) big messages" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data3 = read_csv('big_messages_benchmark.csv', [\n", + " 'description',\n", + " 'number of messages',\n", + " 'message blob size (bytes)',\n", + " 'transaction size',\n", + " 'start writing time (ms)',\n", + " 'write_throughput_0 (ms)',\n", + " 'write_throughput_10 (ms)',\n", + " 'write_throughput_20 (ms)',\n", + " 'write_throughput_30 (ms)',\n", + " 'write_throughput_40 (ms)',\n", + " 'write_throughput_50 (ms)',\n", + " 'write_throughput_60 (ms)',\n", + " 'write_throughput_70 (ms)',\n", + " 'write_throughput_80 (ms)',\n", + " 'write_throughput_90 (ms)',\n", + " 'write_throughput_100 (ms)',\n", + " 'end writing time (ms)',\n", + " 'start indexing time (ms)',\n", + " 'end indexing time (ms)',\n", + " 'disk usage (bytes)'\n", + "])\n", + "\n", + "calc_duration(data3, 'start writing time (ms)', 'end writing time (ms)', 'writing time (ms)')\n", + "calc_duration(data3, 'start indexing time (ms)', 'end indexing time (ms)', 'indexing time (ms)')\n", + "\n", + "scale_value(data3, 'disk usage (bytes)', 'disk usage (MB)', factor=1/1024/1024)\n", + "\n", + "data3['disk io (messages / s)'] = data3.apply(lambda row: row['number of messages'] / row['writing time (ms)'] * 1000, axis=1)\n", + "data3['disk io (MB / s)'] = data3.apply(lambda row: row['number of messages'] * row['message blob size (bytes)'] / 1024 / 1024 / row['writing time (ms)'] * 1000, axis=1)\n", + "\n", + "throughput_abs = progress_column('write_throughput')\n", + "throughput_duration = progress_column('write_duration')\n", + "calc_write_progress(data3, throughput_abs, throughput_duration)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data3_aggregated = data3.groupby('description').median().sort_values(by='message blob size (bytes)')\n", + "data3_aggregated" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Throughput\n", + "This diagram shows how long it took to write each 10% slice of the messages." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "data3_aggregated[[throughput_duration(i) for i in percent]].T.plot(kind='bar')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data3_aggregated.plot(kind='bar', x='message blob size (bytes)', y='writing time (ms)');" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data3_aggregated.plot(kind='bar', x='message blob size (bytes)', y='disk usage (MB)');" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data3_aggregated.plot(kind='bar', x='message blob size (bytes)', y='disk io (messages / s)');" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data3_aggregated.plot(kind='bar', x='message blob size (bytes)', y='disk io (MB / s)', sort_columns=True);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4) combination of small and big messages" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data4 = read_csv('mixed_messages_benchmark.csv', [\n", + " 'description',\n", + " 'number of small messages',\n", + " 'small message blob size (bytes)',\n", + " 'number of medium messages',\n", + " 'medium message blob size (bytes)',\n", + " 'number of big messages',\n", + " 'big message blob size (bytes)',\n", + " 'transaction size',\n", + " 'start writing time (ms)',\n", + " 'write_throughput_0 (ms)',\n", + " 'write_throughput_10 (ms)',\n", + " 'write_throughput_20 (ms)',\n", + " 'write_throughput_30 (ms)',\n", + " 'write_throughput_40 (ms)',\n", + " 'write_throughput_50 (ms)',\n", + " 'write_throughput_60 (ms)',\n", + " 'write_throughput_70 (ms)',\n", + " 'write_throughput_80 (ms)',\n", + " 'write_throughput_90 (ms)',\n", + " 'write_throughput_100 (ms)',\n", + " 'end writing time (ms)',\n", + " 'start indexing time (ms)',\n", + " 'end indexing time (ms)',\n", + " 'disk usage (bytes)'\n", + "])\n", + "\n", + "calc_duration(data4, 'start writing time (ms)', 'end writing time (ms)', 'writing time (ms)')\n", + "calc_duration(data4, 'start indexing time (ms)', 'end indexing time (ms)', 'indexing time (ms)')\n", + "\n", + "scale_value(data4, 'disk usage (bytes)', 'disk usage (MB)', factor=1/1024/1024)\n", + "\n", + "data4['disk io (messages / s)'] = data4.apply(lambda row: (row['number of small messages'] + row['number of medium messages'] + row['number of big messages']) / row['writing time (ms)'] * 1000, axis=1)\n", + "data4['disk io (MB / s)'] = data4.apply(lambda row: (row['number of small messages'] * row['small message blob size (bytes)'] + row['number of medium messages'] * row['medium message blob size (bytes)'] + row['number of big messages'] * row['big message blob size (bytes)']) / 1024 / 1024 / row['writing time (ms)'] * 1000, axis=1)\n", + "\n", + "\n", + "throughput_abs = progress_column('write_throughput')\n", + "throughput_duration = progress_column('write_duration')\n", + "calc_write_progress(data4, throughput_abs, throughput_duration)\n", + "\n", + "data4" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Throughput\n", + "This diagram shows how long it took to write each 10% slice of the messages." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "data4.median()[[throughput_duration(i) for i in percent]].T.plot(kind='bar')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 5) create index while or after writing file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#data5_columns = None\n", + "#data5 = read_csv('sqlite-index-benchmark.csv', data5_columns)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 6) huge file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#data6_columns = None\n", + "#data6 = read_csv('sqlite-huge-file-benchmark.csv', data6_columns)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "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.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/rosbag2_storage_evaluation/package.xml b/rosbag2_storage_evaluation/package.xml deleted file mode 100644 index 20e9cab9fc..0000000000 --- a/rosbag2_storage_evaluation/package.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - rosbag2_storage_evaluation - 0.0.0 - Evaluation and benchmark of possible rosbag2 storage formats - Karsten Knese - Apache License 2.0 - - ament_cmake - - ament_lint_auto - ament_lint_common - - - ament_cmake - - diff --git a/rosbag2_storage_evaluation/requirements.txt b/rosbag2_storage_evaluation/requirements.txt new file mode 100644 index 0000000000..704651840b --- /dev/null +++ b/rosbag2_storage_evaluation/requirements.txt @@ -0,0 +1,4 @@ +jupyter~=1.0.0 +pandas~=0.20.0 +numpy~=1.14.0 +matplotlib~=2.2.0 diff --git a/rosbag2_storage_evaluation/run_all_benchmarks.sh b/rosbag2_storage_evaluation/run_all_benchmarks.sh new file mode 100755 index 0000000000..5acd586f87 --- /dev/null +++ b/rosbag2_storage_evaluation/run_all_benchmarks.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env sh +# Copyright (c) 2018, Bosch Software Innovations 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. + +cd ./build/bin + +./small_messages_benchmark +./big_messages_benchmark +./mixed_messages_benchmark + +cd ../.. + diff --git a/rosbag2_storage_evaluation/src/benchmark/benchmark.cpp b/rosbag2_storage_evaluation/src/benchmark/benchmark.cpp new file mode 100644 index 0000000000..f6100dd1bf --- /dev/null +++ b/rosbag2_storage_evaluation/src/benchmark/benchmark.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#include "benchmark/benchmark.h" + +#include + +void ros2bag::write_csv_file( + std::string const & file_name, Benchmark const & benchmark, bool with_header) +{ + std::ofstream file; + if (with_header) { + std::remove(file_name.c_str()); + } + file.open(file_name, std::ofstream::out | std::ofstream::app); + benchmark.write_csv(file, with_header); + file.close(); +} diff --git a/rosbag2_storage_evaluation/src/benchmark/benchmark.h b/rosbag2_storage_evaluation/src/benchmark/benchmark.h new file mode 100644 index 0000000000..2792c050d2 --- /dev/null +++ b/rosbag2_storage_evaluation/src/benchmark/benchmark.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#ifndef ROS2_ROSBAG_EVALUATION_BENCHMARK_H +#define ROS2_ROSBAG_EVALUATION_BENCHMARK_H + +#include +#include + +namespace ros2bag +{ + +class Benchmark +{ +public: + Benchmark() = default; + + virtual ~Benchmark() = default; + + virtual void run() const = 0; + + virtual void write_csv(std::ostream & out_stream, bool with_header) const = 0; + +}; + +void write_csv_file( + std::string const & file_name, Benchmark const & benchmark, bool with_header); + +} + +#endif //ROS2_ROSBAG_EVALUATION_BENCHMARK_H diff --git a/rosbag2_storage_evaluation/src/benchmark/big_messages_benchmark.cpp b/rosbag2_storage_evaluation/src/benchmark/big_messages_benchmark.cpp new file mode 100644 index 0000000000..15d0f425cb --- /dev/null +++ b/rosbag2_storage_evaluation/src/benchmark/big_messages_benchmark.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#include "writer/sqlite/separate_topic_table_sqlite_writer.h" +#include "benchmark/writer/sqlite/sqlite_writer_benchmark.h" +#include "generators/message_generator.h" +#include "profiler/profiler.h" +#include "writer/sqlite/one_table_sqlite_writer.h" + +using namespace ros2bag; + +void run_benchmark( + std::string const & description, + std::shared_ptr writer, + std::string const & db_name, + unsigned int number_of_messages, + unsigned int message_blob_size, + unsigned int transaction_size, + bool with_header = false) +{ + + std::vector> meta_data = { + {"description", description}, + {"number of messages", std::to_string(number_of_messages)}, + {"message blob size (bytes)", std::to_string(message_blob_size)}, + {"transaction size", std::to_string(transaction_size)} + }; + + MessageGenerator::Specification specification = {std::make_tuple("topic", message_blob_size)}; + + SqliteWriterBenchmark benchmark( + std::make_unique(number_of_messages, specification), + std::move(writer), + std::make_unique(meta_data, db_name)); + + std::remove(db_name.c_str()); + benchmark.run(); + std::remove(db_name.c_str()); + + write_csv_file("big_messages_benchmark.csv", benchmark, with_header); +} + +void run_benchmark_repeatedly( + unsigned int times, + std::string const & description, + std::shared_ptr writer, + std::string const & db_name, + unsigned int number_of_messages, + unsigned int message_blob_size, + unsigned int transaction_size, + bool with_header = false) +{ + for (int i = 0; i < times; ++i) { + run_benchmark( + description, + writer, + db_name, + number_of_messages, + message_blob_size, + transaction_size, + with_header); + with_header = false; + } +} + +int main(int argc, char ** argv) +{ + /** + * We write the stream of a full HD camera to the Bagfile. + * We write about 10GB into the file + */ + std::string db_name = "big_messages_benchmark.db"; + unsigned int msg_size_bytes = 30000000; // 30MB == one full HD image + unsigned int msg_count = 300; + unsigned int transaction_size = 10; + + auto const write_header = true; + + run_benchmark_repeatedly(5, + "OneTableSqlite", + std::make_shared( + db_name, + transaction_size, + Indices({{"MESSAGES", "TIMESTAMP"}, + {"MESSAGES", "TOPIC"}}), + // Setting to "journal_mode" to "OFF" increases writing speed, but turns off transactions. + Pragmas({{"journal_mode", "MEMORY"}, + {"synchronous", "OFF"}}) + ), + db_name, + msg_count, + msg_size_bytes, + transaction_size, write_header); + + run_benchmark_repeatedly(5, + "SeparateTopicTableSqlite", + std::make_shared( + db_name, + transaction_size, + Indices({{"MESSAGES", "TIMESTAMP"}, + {"MESSAGES", "TOPIC_ID"}, + {"TOPICS", "TOPIC"}}), + // Setting to "journal_mode" to "OFF" increases writing speed, but turns off transactions. + Pragmas({{"foreign_keys", "ON"}, + {"journal_mode", "MEMORY"}, + {"synchronous", "OFF"}}) + ), + db_name, + msg_count, + msg_size_bytes, + transaction_size); + + return EXIT_SUCCESS; +} diff --git a/rosbag2_storage_evaluation/src/benchmark/mixed_messages_benchmark.cpp b/rosbag2_storage_evaluation/src/benchmark/mixed_messages_benchmark.cpp new file mode 100644 index 0000000000..e3f70ab709 --- /dev/null +++ b/rosbag2_storage_evaluation/src/benchmark/mixed_messages_benchmark.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#include "writer/sqlite/separate_topic_table_sqlite_writer.h" +#include "benchmark/writer/sqlite/sqlite_writer_benchmark.h" +#include "generators/message_generator.h" +#include "profiler/profiler.h" +#include "writer/sqlite/one_table_sqlite_writer.h" + +using namespace ros2bag; + +void run_benchmark( + std::string const & description, + std::shared_ptr writer, + std::string const & db_name, + unsigned int loop_count, + unsigned int number_of_small_messages, + unsigned int small_message_blob_size, + unsigned int number_of_medium_messages, + unsigned int medium_message_blob_size, + unsigned int number_of_big_messages, + unsigned int big_message_blob_size, + unsigned int transaction_size, + bool with_header = false) +{ + + std::vector> meta_data = { + {"description", description}, + {"number of small messages", std::to_string(number_of_small_messages * loop_count)}, + {"small message blob size (bytes)", std::to_string(small_message_blob_size)}, + {"number of medium messages", std::to_string(number_of_medium_messages * loop_count)}, + {"medium message blob size (bytes)", std::to_string(medium_message_blob_size)}, + {"number of big messages", std::to_string(number_of_big_messages * loop_count)}, + {"big message blob size (bytes)", std::to_string(big_message_blob_size)}, + {"transaction size", std::to_string(transaction_size)} + }; + + MessageGenerator::Specification specification; + for (auto i = 0; i < number_of_small_messages; ++i) { + specification.emplace_back("topic/small/" + std::to_string(i), small_message_blob_size); + } + for (auto i = 0; i < number_of_medium_messages; ++i) { + specification.emplace_back("topic/medium/" + std::to_string(i), medium_message_blob_size); + } + for (auto i = 0; i < number_of_big_messages; ++i) { + specification.emplace_back("topic/big/" + std::to_string(i), big_message_blob_size); + } + + SqliteWriterBenchmark benchmark( + std::make_unique(loop_count, specification), + std::move(writer), + std::make_unique(meta_data, db_name)); + + std::remove(db_name.c_str()); + benchmark.run(); + std::remove(db_name.c_str()); + + write_csv_file("mixed_messages_benchmark.csv", benchmark, with_header); +} + +void run_benchmark_repeatedly( + unsigned int times, + std::string const & description, + std::shared_ptr writer, + std::string const & db_name, + unsigned int loop_count, + unsigned int number_of_small_messages, + unsigned int small_message_blob_size, + unsigned int number_of_medium_messages, + unsigned int medium_message_blob_size, + unsigned int number_of_big_messages, + unsigned int big_message_blob_size, + unsigned int transaction_size, + bool with_header = false) +{ + for (int i = 0; i < times; ++i) { + run_benchmark( + description, + writer, + db_name, + loop_count, + number_of_small_messages, + small_message_blob_size, + number_of_medium_messages, + medium_message_blob_size, + number_of_big_messages, + big_message_blob_size, + transaction_size, + with_header); + with_header = false; + } +} + +int main(int argc, char ** argv) +{ + /** + * We write a total of 10GB to the Bagfile. + * Stream: + * * + */ + std::string db_name = "mixed_messages_benchmark.db"; + unsigned int const transaction_size = 10000; + + + auto const write_header = true; + + auto const small_messages = 1000; + auto const small_message_blob_size = 10; + + auto const medium_messages = 100; + auto const medium_message_blob_size = 1000; + + auto const big_messages = 1; + auto const big_message_blob_size = 30000000; // 30 MB + + auto const loop_count = 300; // gives roughly 10GB + + + + run_benchmark_repeatedly(3, + "OneTableSqlite", + std::make_shared( + db_name, + transaction_size, + Indices({{"MESSAGES", "TIMESTAMP"}, + {"MESSAGES", "TOPIC"}}), + // Setting to "journal_mode" to "OFF" increases writing speed, but turns off transactions. + Pragmas({{"journal_mode", "MEMORY"}, + {"synchronous", "OFF"}}) + ), + db_name, + loop_count, + small_messages, + small_message_blob_size, + medium_messages, + medium_message_blob_size, + big_messages, + big_message_blob_size, transaction_size, write_header); + + run_benchmark_repeatedly(5, + "SeparateTopicTableSqlite", + std::make_shared( + db_name, + transaction_size, + Indices({{"MESSAGES", "TIMESTAMP"}, + {"MESSAGES", "TOPIC_ID"}, + {"TOPICS", "TOPIC"}}), + // Setting to "journal_mode" to "OFF" increases writing speed, but turns off transactions. + Pragmas({{"foreign_keys", "ON"}, + {"journal_mode", "MEMORY"}, + {"synchronous", "OFF"}}) + ), + db_name, + loop_count, + small_messages, + small_message_blob_size, + medium_messages, + medium_message_blob_size, + big_messages, + big_message_blob_size, transaction_size, write_header); + + return EXIT_SUCCESS; +} + diff --git a/rosbag2_storage_evaluation/src/benchmark/small_messages_benchmark.cpp b/rosbag2_storage_evaluation/src/benchmark/small_messages_benchmark.cpp new file mode 100644 index 0000000000..1e7ee2686d --- /dev/null +++ b/rosbag2_storage_evaluation/src/benchmark/small_messages_benchmark.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#include "writer/sqlite/separate_topic_table_sqlite_writer.h" +#include +#include "benchmark/writer/sqlite/sqlite_writer_benchmark.h" +#include "generators/message_generator.h" +#include "profiler/profiler.h" +#include "writer/sqlite/one_table_sqlite_writer.h" + +using namespace ros2bag; + +void run_benchmark( + std::string const & description, + std::shared_ptr writer, + std::string const & db_name, + unsigned int number_of_messages, + unsigned int message_blob_size, + unsigned int transaction_size, + bool with_header = false) +{ + std::vector> meta_data = { + {"description", description}, + {"number of messages", std::to_string(number_of_messages)}, + {"message blob size (bytes)", std::to_string(message_blob_size)}, + {"transaction size", std::to_string(transaction_size)} + }; + + MessageGenerator::Specification specification = {std::make_tuple("topic", message_blob_size)}; + + SqliteWriterBenchmark benchmark( + std::make_unique(number_of_messages, specification), + std::move(writer), + std::make_unique(meta_data, db_name)); + + std::remove(db_name.c_str()); + benchmark.run(); + std::remove(db_name.c_str()); + + write_csv_file("small_messages_benchmark.csv", benchmark, with_header); +} + +void run_benchmark_repeatedly( + unsigned int times, + std::string const & description, + std::shared_ptr writer, + std::string const & db_name, + unsigned int number_of_messages, + unsigned int message_blob_size, + unsigned int transaction_size, + bool with_header = false) +{ + for (int i = 0; i < times; ++i) { + run_benchmark( + description, + writer, + db_name, + number_of_messages, + message_blob_size, + transaction_size, + with_header); + with_header = false; + } +} + +int main(int argc, char ** argv) +{ + /** + * We write a total of 1GB to the Bagfile + */ + std::string db_name = "small_messages_writer_benchmark.db"; + unsigned int msg_size_bytes = 10; + unsigned int msg_count = 100000000; + unsigned int transaction_size = 10000; + + auto const write_header = true; + + run_benchmark_repeatedly(5, + "OneTableSqlite", + std::make_shared( + db_name, + transaction_size, + Indices({{"MESSAGES", "TIMESTAMP"}, + {"MESSAGES", "TOPIC"}}), + // Setting to "journal_mode" to "OFF" increases writing speed, but turns off transactions. + Pragmas({{"journal_mode", "MEMORY"}, + {"synchronous", "OFF"}}) + ), + db_name, + msg_count, + msg_size_bytes, + transaction_size, write_header); + + run_benchmark_repeatedly(5, + "SeparateTopicTableSqlite", + std::make_shared( + db_name, + transaction_size, + Indices({{"MESSAGES", "TIMESTAMP"}, + {"MESSAGES", "TOPIC_ID"}, + {"TOPICS", "TOPIC"}}), + // Setting to "journal_mode" to "OFF" increases writing speed, but turns off transactions. + Pragmas({{"foreign_keys", "ON"}, + {"journal_mode", "MEMORY"}, + {"synchronous", "OFF"}}) + ), + db_name, + msg_count, + msg_size_bytes, + transaction_size); + + return EXIT_SUCCESS; +} diff --git a/rosbag2_storage_evaluation/src/benchmark/writer/sqlite/sqlite_writer_benchmark.cpp b/rosbag2_storage_evaluation/src/benchmark/writer/sqlite/sqlite_writer_benchmark.cpp new file mode 100644 index 0000000000..1327f0b5af --- /dev/null +++ b/rosbag2_storage_evaluation/src/benchmark/writer/sqlite/sqlite_writer_benchmark.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#include "benchmark/writer/sqlite/sqlite_writer_benchmark.h" + +#include + +#include "writer/sqlite/one_table_sqlite_writer.h" + +using namespace ros2bag; + +void SqliteWriterBenchmark::run() const +{ + generator_->reset(); + writer_->reset(); + + profiler_->take_time_for("start writing time"); + + Profiler::TickProgress throughput_tick = profiler_->measure_progress( + "write_throughput", generator_->total_msg_count()); + + writer_->open(); + while (generator_->has_next()) { + writer_->write(generator_->next()); + throughput_tick(); + } + + profiler_->take_time_for("end writing time"); + + profiler_->take_time_for("start indexing time"); + + writer_->create_index(); + writer_->close(); + + profiler_->take_time_for("end indexing time"); + profiler_->track_disk_usage(); +} + +void SqliteWriterBenchmark::write_csv(std::ostream & out_stream, bool with_header) const +{ + if (with_header) { + out_stream << profiler_->csv_header() << std::endl; + } + out_stream << profiler_->csv_entry() << std::endl; +} diff --git a/rosbag2_storage_evaluation/src/benchmark/writer/sqlite/sqlite_writer_benchmark.h b/rosbag2_storage_evaluation/src/benchmark/writer/sqlite/sqlite_writer_benchmark.h new file mode 100644 index 0000000000..0a0fb0e869 --- /dev/null +++ b/rosbag2_storage_evaluation/src/benchmark/writer/sqlite/sqlite_writer_benchmark.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#ifndef ROS2_ROSBAG_EVALUATION_SQLITE_WRITER_BENCHMARK_H +#define ROS2_ROSBAG_EVALUATION_SQLITE_WRITER_BENCHMARK_H + +#include + +#include "benchmark/benchmark.h" +#include "generators/message_generator.h" +#include "profiler/profiler.h" +#include "writer/message_writer.h" + +namespace ros2bag +{ + +class SqliteWriterBenchmark : public Benchmark +{ +public: + SqliteWriterBenchmark( + std::unique_ptr generator, + std::shared_ptr writer, + std::unique_ptr profiler) + : generator_(std::move(generator)), writer_(std::move(writer)), profiler_(std::move(profiler)) + {} + + ~SqliteWriterBenchmark() override = default; + + void run() const override; + + void write_csv(std::ostream & out_stream, bool with_header) const override; + +private: + std::unique_ptr generator_; + std::shared_ptr writer_; + std::unique_ptr profiler_; +}; + +} + +#endif //ROS2_ROSBAG_EVALUATION_SQLITE_WRITER_BENCHMARK_H diff --git a/rosbag2_storage_evaluation/src/benchmark/writer/sqlite/sqlite_writer_benchmark_cmd.cpp b/rosbag2_storage_evaluation/src/benchmark/writer/sqlite/sqlite_writer_benchmark_cmd.cpp new file mode 100644 index 0000000000..2d49424194 --- /dev/null +++ b/rosbag2_storage_evaluation/src/benchmark/writer/sqlite/sqlite_writer_benchmark_cmd.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#include "benchmark/writer/sqlite/sqlite_writer_benchmark.h" +#include "writer/sqlite/one_table_sqlite_writer.h" + +using namespace ros2bag; + +int main(int argc, char ** argv) +{ + if (argc != 5) { + std::cerr << "Usage: benchmark " + << "" + << std::endl; + return EXIT_FAILURE; + } + + std::string database_name = argv[1]; + unsigned int number_messages = static_cast(std::stol(argv[2])); + unsigned int message_blob_size = static_cast(std::stol(argv[3])); + unsigned int messages_per_transaction = static_cast(std::stol(argv[4])); + + MessageGenerator::Specification specification = {std::make_tuple("topic", message_blob_size)}; + + std::vector> meta_data = {}; + SqliteWriterBenchmark benchmark( + std::make_unique(number_messages, specification), + std::make_unique(database_name, messages_per_transaction), + std::make_unique(meta_data, database_name)); + + benchmark.run(); + + write_csv_file("sqlite3_writer_benchmark.csv", benchmark, true); + + return EXIT_SUCCESS; +} diff --git a/rosbag2_storage_evaluation/src/benchmark/writer/trivial/trivial_writer_benchmark.cpp b/rosbag2_storage_evaluation/src/benchmark/writer/trivial/trivial_writer_benchmark.cpp new file mode 100644 index 0000000000..9863d0f9ce --- /dev/null +++ b/rosbag2_storage_evaluation/src/benchmark/writer/trivial/trivial_writer_benchmark.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#include "trivial_writer_benchmark.h" + +#include +#include + +#include "writer/stream/message_stream_writer.h" + +using namespace ros2bag; + +void TrivialWriterBenchmark::run() const +{ + generator_->reset(); + writer_->reset(); + + + Profiler::TickProgress throughput_tick = profiler_->measure_progress( + "write_throughput", generator_->total_msg_count()); + profiler_->take_time_for("started writing messages"); + + writer_->open(); + while (generator_->has_next()) { + writer_->write(generator_->next()); + throughput_tick(); + } + writer_->close(); + + profiler_->take_time_for("finished writing messages"); + profiler_->track_disk_usage(); +} + +void TrivialWriterBenchmark::write_csv(std::ostream & out_stream, bool with_header) const +{ + if (with_header) { + out_stream << profiler_->csv_header() << std::endl; + } + out_stream << profiler_->csv_entry() << std::endl; +} + +int main(int argc, char ** argv) +{ + if (argc != 4) { + std::cerr << "Usage: benchmark " + << std::endl; + return EXIT_FAILURE; + } + + std::string file_name = argv[1]; + unsigned int number_messages = static_cast(std::stol(argv[2])); + unsigned int message_blob_size = static_cast(std::stol(argv[3])); + + std::vector> meta_data = {}; + MessageGenerator::Specification specification = {std::make_tuple("topic", message_blob_size)}; + std::ofstream file_stream(file_name); + + TrivialWriterBenchmark benchmark( + std::make_unique(number_messages, specification), + std::make_unique(file_stream), + std::make_unique(meta_data, file_name)); + + benchmark.run(); + + write_csv_file("trivial_writer_benchmark.csv", benchmark, true); + + return EXIT_SUCCESS; +} diff --git a/rosbag2_storage_evaluation/src/benchmark/writer/trivial/trivial_writer_benchmark.h b/rosbag2_storage_evaluation/src/benchmark/writer/trivial/trivial_writer_benchmark.h new file mode 100644 index 0000000000..41e0d37561 --- /dev/null +++ b/rosbag2_storage_evaluation/src/benchmark/writer/trivial/trivial_writer_benchmark.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#ifndef ROS2_ROSBAG_EVALUATION_TRIVIAL_WRITER_BENCHMARK_H +#define ROS2_ROSBAG_EVALUATION_TRIVIAL_WRITER_BENCHMARK_H + +#include + +#include "benchmark/benchmark.h" +#include "generators/message_generator.h" +#include "profiler/profiler.h" +#include "writer/message_writer.h" + +namespace ros2bag +{ + +class TrivialWriterBenchmark : public Benchmark +{ +public: + TrivialWriterBenchmark( + std::unique_ptr generator, + std::unique_ptr writer, + std::unique_ptr profiler) + : generator_(std::move(generator)), writer_(std::move(writer)), profiler_(std::move(profiler)) + {} + + ~TrivialWriterBenchmark() override = default; + + void run() const override; + + void write_csv(std::ostream & out_stream, bool with_header) const override; + +private: + std::unique_ptr generator_; + std::unique_ptr writer_; + std::unique_ptr profiler_; +}; + +} + +#endif //ROS2_ROSBAG_EVALUATION_TRIVIAL_WRITER_BENCHMARK_H diff --git a/rosbag2_storage_evaluation/src/common/strings.cpp b/rosbag2_storage_evaluation/src/common/strings.cpp new file mode 100644 index 0000000000..0ed1ffa9e1 --- /dev/null +++ b/rosbag2_storage_evaluation/src/common/strings.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#include "common/strings.h" + +#include +#include + +std::string ros2bag::strings::join( + std::vector const & strings, + std::string const & delimiter, + const std::string & prefix, + const std::string & suffix) +{ + if (strings.empty()) { + return ""; + } + + std::string joined = prefix; + for (auto i = 0; i < strings.size() - 1; ++i) { + joined += strings[i]; + joined += delimiter; + } + + joined += strings[strings.size() - 1]; + joined += suffix; + return joined; +} diff --git a/rosbag2_storage_evaluation/src/common/strings.h b/rosbag2_storage_evaluation/src/common/strings.h new file mode 100644 index 0000000000..3954bc46f8 --- /dev/null +++ b/rosbag2_storage_evaluation/src/common/strings.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#ifndef ROS2_ROSBAG_EVALUATION_STRINGS_H +#define ROS2_ROSBAG_EVALUATION_STRINGS_H + +#include +#include + +namespace ros2bag +{ +namespace strings +{ + +std::string join(std::vector const & strings, + std::string const & delimiter, std::string const & prefix = "", std::string const & suffix = ""); + +} +} + +#endif //ROS2_ROSBAG_EVALUATION_STRINGS_H diff --git a/rosbag2_storage_evaluation/src/common/vectors.h b/rosbag2_storage_evaluation/src/common/vectors.h new file mode 100644 index 0000000000..42ca891844 --- /dev/null +++ b/rosbag2_storage_evaluation/src/common/vectors.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#ifndef ROS2_ROSBAG_EVALUATION_VECTORS_H +#define ROS2_ROSBAG_EVALUATION_VECTORS_H + +#include + +namespace ros2bag +{ +namespace vectors +{ + +template +std::vector repeat(unsigned long times, T element) +{ + std::vector result(times); + for (auto & e : result) { + e = element; + } + return result; +} + +} +} + +#endif //ROS2_ROSBAG_EVALUATION_VECTORS_H diff --git a/rosbag2_storage_evaluation/src/generators/message.h b/rosbag2_storage_evaluation/src/generators/message.h new file mode 100644 index 0000000000..6e1de17e87 --- /dev/null +++ b/rosbag2_storage_evaluation/src/generators/message.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#ifndef ROS2_ROSBAG_EVALUATION_MESSAGE_H +#define ROS2_ROSBAG_EVALUATION_MESSAGE_H + +#include +#include +#include +#include +#include + +namespace ros2bag +{ + +using BlobPtr = std::shared_ptr const>; + +class Message +{ +public: + using Timestamp = std::chrono::system_clock::time_point; + + Message(Timestamp const & timestamp, std::string const & topic, BlobPtr blob) + : timestamp_(timestamp), topic_(topic), blob_(blob) + {} + + ~Message() = default; + + Timestamp timestamp() const + { + return timestamp_; + } + + std::string const topic() const + { + return topic_; + } + + BlobPtr blob() const + { + return blob_; + } + + + friend std::ostream & operator<<(std::ostream & out_stream, Message const & message) + { + return out_stream << "{timestamp_:" << message.timestamp().time_since_epoch().count() + << ",topic:" << message.topic() + << ",bytes:" << message.blob()->size() + << "}"; + } + +private: + Timestamp const timestamp_; + std::string const topic_; + BlobPtr const blob_; +}; + +using MessagePtr = std::shared_ptr; + +} + +#endif //ROS2_ROSBAG_EVALUATION_MESSAGE_H diff --git a/rosbag2_storage_evaluation/src/generators/message_generator.cpp b/rosbag2_storage_evaluation/src/generators/message_generator.cpp new file mode 100644 index 0000000000..dd9b5109c9 --- /dev/null +++ b/rosbag2_storage_evaluation/src/generators/message_generator.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#include "message_generator.h" + +#include +#include + +using namespace ros2bag; + +MessageGenerator::MessageGenerator(unsigned int loop_count, Specification const & msgs) : + loop_count_(loop_count) + , max_index_(msgs.size()) + , current_loop_(0) + , current_index_(0) + , total_msg_count_(loop_count * msgs.size()) +{ + topics_ = std::vector(msgs.size()); + blobs_ = std::vector(msgs.size()); + + std::string topic; + unsigned int blob_size; + for (int i = 0; i < msgs.size(); ++i) { + std::tie(topic, blob_size) = msgs[i]; + + topics_[i] = topic; + blobs_[i] = random_blob(blob_size); + } +} + +bool MessageGenerator::has_next() const +{ + return current_loop_ < loop_count_ && current_index_ < max_index_; +} + +MessagePtr MessageGenerator::next() +{ + auto topic = topics_[current_index_]; + auto blob = blobs_[current_index_]; + + auto timestamp = std::chrono::system_clock::now(); + auto msg = std::make_shared(timestamp, topic, blob); + + ++current_index_; + if (current_index_ >= max_index_) { + current_index_ = 0; + ++current_loop_; + } + + return msg; +} + +void MessageGenerator::reset() +{ + current_index_ = 0; + current_loop_ = 0; +} + +BlobPtr MessageGenerator::random_blob(unsigned int blob_size) const +{ + auto blobPtr = std::make_shared>(blob_size); + std::generate(blobPtr->begin(), blobPtr->end(), std::rand); + + return blobPtr; +} diff --git a/rosbag2_storage_evaluation/src/generators/message_generator.h b/rosbag2_storage_evaluation/src/generators/message_generator.h new file mode 100644 index 0000000000..97ab3077b4 --- /dev/null +++ b/rosbag2_storage_evaluation/src/generators/message_generator.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#ifndef ROS2_ROSBAG_EVALUATION_MESSAGE_GENERATOR_H +#define ROS2_ROSBAG_EVALUATION_MESSAGE_GENERATOR_H + +#include + +#include "generators/message.h" + +namespace ros2bag +{ + +class MessageGenerator +{ +public: + + using Specification = std::vector>; + + MessageGenerator(unsigned int loop_count, Specification const & msgs); + + bool has_next() const; + + MessagePtr next(); + + void reset(); + + unsigned long total_msg_count() + { + return total_msg_count_; + } + +private: + unsigned int const loop_count_; + unsigned int current_loop_; + unsigned long current_index_; + unsigned long const max_index_; + unsigned long const total_msg_count_; + std::vector topics_; + std::vector blobs_; + + BlobPtr random_blob(unsigned int blob_size) const; +}; + +} + +#endif //ROS2_ROSBAG_EVALUATION_MESSAGE_GENERATOR_H diff --git a/rosbag2_storage_evaluation/src/profiler/profiler.cpp b/rosbag2_storage_evaluation/src/profiler/profiler.cpp new file mode 100644 index 0000000000..6b20f9eb20 --- /dev/null +++ b/rosbag2_storage_evaluation/src/profiler/profiler.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#include "profiler/profiler.h" + +#include +#include + +using namespace ros2bag; +using namespace std::literals::chrono_literals; + +void Profiler::take_time_for(std::string const & task) +{ + time_points_.emplace_back(task, std::chrono::system_clock::now()); +} + +void Profiler::track_disk_usage() +{ + auto const mode = std::ifstream::binary | std::ifstream::ate; + std::ifstream file(file_name_, mode); + disk_usage_ = file.tellg(); +} + +std::string Profiler::csv_header() const +{ + std::ostringstream header; + + for (auto const & m : meta_data_) { + header << m.first << ","; + } + + for (auto const & t : time_points_) { + header << t.first << " (ms),"; + } + + header << "disk usage (bytes)"; + + return header.str(); +} + +std::string Profiler::csv_entry() const +{ + std::ostringstream entry; + + for (auto const & m : meta_data_) { + entry << m.second << ","; + } + + if (!time_points_.empty()) { + auto const start = time_points_.front().second; + for (auto const & t : time_points_) { + auto timestamp = std::chrono::duration_cast(t.second - start); + entry << timestamp.count() << ","; + } + } + + entry << disk_usage_; + + return entry.str(); +} + +Profiler::TickProgress Profiler::measure_progress( + const std::string & subject, + unsigned long const total, + const unsigned long increment) +{ + take_time_for(subject + "_0"/*%*/); + + return [this, next_level = 10, current = 0, subject, total, increment]() mutable { + current += increment; + const auto progress = static_cast(current) / total * 100; + if (progress >= next_level) { + this->take_time_for(subject + "_" + std::to_string(next_level)); + next_level += 10 /* % */; + } + }; +} diff --git a/rosbag2_storage_evaluation/src/profiler/profiler.h b/rosbag2_storage_evaluation/src/profiler/profiler.h new file mode 100644 index 0000000000..f44d33ba42 --- /dev/null +++ b/rosbag2_storage_evaluation/src/profiler/profiler.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#ifndef ROS2_ROSBAG_EVALUATION_PROFILER_H +#define ROS2_ROSBAG_EVALUATION_PROFILER_H + +#include +#include +#include + +#include "benchmark/benchmark.h" + +namespace ros2bag +{ + +class Profiler +{ +public: + Profiler( + std::vector> const & meta_data, + std::string const & file_name) + : meta_data_(meta_data), file_name_(file_name) + {} + + ~Profiler() = default; + + void take_time_for(std::string const & task); + + void track_disk_usage(); + + std::string csv_header() const; + + std::string csv_entry() const; + + using TickProgress = std::function; + + TickProgress measure_progress( + std::string const & subject, + unsigned long const total, + unsigned long const increment = 1 + ); + +private: + std::string file_name_; + long disk_usage_; + std::vector> meta_data_; + std::vector> time_points_; +}; + +} + +#endif //ROS2_ROSBAG_EVALUATION_PROFILER_H diff --git a/rosbag2_storage_evaluation/src/reader/message_reader.h b/rosbag2_storage_evaluation/src/reader/message_reader.h new file mode 100644 index 0000000000..ed0d58896e --- /dev/null +++ b/rosbag2_storage_evaluation/src/reader/message_reader.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#ifndef ROS2_ROSBAG_EVALUATION_MESSAGE_READER_H +#define ROS2_ROSBAG_EVALUATION_MESSAGE_READER_H + +#include + +#include "generators/message.h" + +namespace ros2bag +{ + +class MessageReader +{ +public: + MessageReader() = default; + + virtual ~MessageReader() = default; + + virtual void open() = 0; + + virtual void close() = 0; + + virtual std::vector read() = 0; +}; + +} +#endif //ROS2_ROSBAG_EVALUATION_MESSAGE_READER_H diff --git a/rosbag2_storage_evaluation/src/writer/message_writer.h b/rosbag2_storage_evaluation/src/writer/message_writer.h new file mode 100644 index 0000000000..ec03250a5b --- /dev/null +++ b/rosbag2_storage_evaluation/src/writer/message_writer.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#ifndef ROS2_ROSBAG_EVALUATION_MESSAGE_WRITER_H +#define ROS2_ROSBAG_EVALUATION_MESSAGE_WRITER_H + +#include + +#include "generators/message.h" + +namespace ros2bag +{ + +class MessageWriter +{ +public: + MessageWriter() = default; + + virtual ~MessageWriter() = default; + + virtual void open() = 0; + + virtual void close() = 0; + + virtual void write(MessagePtr message) = 0; + + virtual void create_index() = 0; + + virtual void reset() = 0; +}; + +} + +#endif //ROS2_ROSBAG_EVALUATION_MESSAGE_WRITER_H diff --git a/rosbag2_storage_evaluation/src/writer/sqlite/one_table_sqlite_writer.cpp b/rosbag2_storage_evaluation/src/writer/sqlite/one_table_sqlite_writer.cpp new file mode 100644 index 0000000000..4a2521546b --- /dev/null +++ b/rosbag2_storage_evaluation/src/writer/sqlite/one_table_sqlite_writer.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#include "writer/sqlite/one_table_sqlite_writer.h" + +#include "generators/message.h" +#include "writer/sqlite/sqlite.h" + +using namespace ros2bag; + + +void OneTableSqliteWriter::close() +{ + if (is_open()) { + sqlite::finalize(insert_message_stmt_); + SqliteWriter::close(); + } +} + +void OneTableSqliteWriter::write_to_database(MessagePtr message) +{ + sqlite3_bind_int64(insert_message_stmt_, + 1, message->timestamp().time_since_epoch().count()); + sqlite3_bind_text(insert_message_stmt_, + 2, message->topic().c_str(), static_cast(message->topic().size()), nullptr); + sqlite3_bind_blob(insert_message_stmt_, + 3, message->blob()->data(), static_cast(message->blob()->size()), nullptr); + + sqlite3_step(insert_message_stmt_); + sqlite3_reset(insert_message_stmt_); +} + +void OneTableSqliteWriter::initialize_tables(sqlite::DBPtr db) +{ + sqlite::create_table(db, "MESSAGES", { + "TIMESTAMP INTEGER NOT NULL", + "TOPIC TEXT NOT NULL", + "DATA BLOB NOT NULL" + }); +} + +void OneTableSqliteWriter::prepare_statements(sqlite::DBPtr db) +{ + insert_message_stmt_ = sqlite::new_insert_stmt(db, "MESSAGES", {"TIMESTAMP", "TOPIC", "DATA"}); +} diff --git a/rosbag2_storage_evaluation/src/writer/sqlite/one_table_sqlite_writer.h b/rosbag2_storage_evaluation/src/writer/sqlite/one_table_sqlite_writer.h new file mode 100644 index 0000000000..fb7f664404 --- /dev/null +++ b/rosbag2_storage_evaluation/src/writer/sqlite/one_table_sqlite_writer.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#ifndef ROS2_ROSBAG_EVALUATION_ONE_TABLE_SQLITE_WRITER_H +#define ROS2_ROSBAG_EVALUATION_ONE_TABLE_SQLITE_WRITER_H + +#include +#include + +#include "writer/message_writer.h" +#include "writer/sqlite/sqlite.h" +#include "writer/sqlite/sqlite_writer.h" + +namespace ros2bag +{ + +class OneTableSqliteWriter : public SqliteWriter +{ +public: + explicit OneTableSqliteWriter( + std::string const & filename, + unsigned int const messages_per_transaction = 0, + Indices const & indices = {{"MESSAGES", "TOPIC"}, + {"MESSAGES", "TIMESTAMP"}}, + Pragmas const & pragmas = {{"journal_mode", "MEMORY"}, + {"synchronous", "OFF"}} + ) : SqliteWriter(filename, messages_per_transaction, indices, pragmas) + {} + + ~OneTableSqliteWriter() override + { + OneTableSqliteWriter::close(); + } + + void close() override; + + void reset() override + {} + +protected: + void initialize_tables(sqlite::DBPtr db) final; + + void write_to_database(MessagePtr message) final; + + void prepare_statements(sqlite::DBPtr db) final; + +private: + sqlite::StatementPtr insert_message_stmt_; +}; + +} + +#endif //ROS2_ROSBAG_EVALUATION_ONE_TABLE_SQLITE_WRITER_H diff --git a/rosbag2_storage_evaluation/src/writer/sqlite/separate_topic_table_sqlite_writer.cpp b/rosbag2_storage_evaluation/src/writer/sqlite/separate_topic_table_sqlite_writer.cpp new file mode 100644 index 0000000000..21b76b88f6 --- /dev/null +++ b/rosbag2_storage_evaluation/src/writer/sqlite/separate_topic_table_sqlite_writer.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#include "writer/sqlite/separate_topic_table_sqlite_writer.h" +#include "writer/sqlite/sqlite.h" + +using namespace ros2bag; + +SeparateTopicTableSqliteWriter::SeparateTopicTableSqliteWriter( + std::string const & filename, + unsigned int const messages_per_transaction, + Indices const & indices, + Pragmas const & pragmas +) : SqliteWriter( + filename, messages_per_transaction, {{"MESSAGES", "TIMESTAMP"}, + {"MESSAGES", "TOPIC_ID"}, + {"TOPICS", "TOPIC"}}, + pragmas) +{} + +void SeparateTopicTableSqliteWriter::close() +{ + if (is_open()) { + sqlite::finalize(insert_message_stmt_); + sqlite::finalize(insert_topic_stmt_); + SqliteWriter::close(); + } +} + +void SeparateTopicTableSqliteWriter::initialize_tables(sqlite::DBPtr db) +{ + sqlite::create_table(db, "TOPICS", { + "ID INTEGER PRIMARY KEY", // This is an alias for ROWID + "TOPIC TEXT NOT NULL" + }); + + sqlite::create_table(db, "MESSAGES", { + "TIMESTAMP INTEGER NOT NULL", + "TOPIC_ID INTEGER NOT NULL", + "DATA BLOB NOT NULL" + }, {sqlite::ForeignKeyDef{"TOPIC_ID", "TOPICS", "ID"}}); +} + +void SeparateTopicTableSqliteWriter::write_to_database(MessagePtr message) +{ + std::string const topic = message->topic(); + long topic_id = topic_ids_[topic]; + if (!topic_id) { + topic_id = insert_topic(topic); + topic_ids_[topic] = topic_id; + } + + insert_message(message->timestamp(), topic_id, message->blob()); +} + +long SeparateTopicTableSqliteWriter::insert_topic(std::string const & topic) +{ + // Note: the »TOPIC_ID« is set automatically by SQLite + sqlite3_bind_text( + insert_topic_stmt_, 1, topic.c_str(), static_cast(topic.length()), nullptr); + + sqlite3_step(insert_topic_stmt_); + long id = sqlite3_last_insert_rowid(db()); + + sqlite3_reset(insert_topic_stmt_); + return id; +} + +void SeparateTopicTableSqliteWriter::insert_message( + Message::Timestamp timestamp, long topic_id, BlobPtr blob +) +{ + sqlite3_bind_int64(insert_message_stmt_, 1, timestamp.time_since_epoch().count()); + sqlite3_bind_int64(insert_message_stmt_, 2, topic_id); + sqlite3_bind_blob(insert_message_stmt_, 3, blob->data(), static_cast(blob->size()), nullptr); + + sqlite3_step(insert_message_stmt_); + sqlite3_reset(insert_message_stmt_); +} + + +void SeparateTopicTableSqliteWriter::prepare_statements(sqlite::DBPtr db) +{ + insert_message_stmt_ = sqlite::new_insert_stmt(db, "MESSAGES", {"TIMESTAMP", "TOPIC_ID", "DATA"}); + // Note: the »TOPIC_ID« is set automatically by SQLite + insert_topic_stmt_ = sqlite::new_insert_stmt(db, "TOPICS", {"TOPIC"}); +} + +void SeparateTopicTableSqliteWriter::reset() +{ + topic_ids_.clear(); +} diff --git a/rosbag2_storage_evaluation/src/writer/sqlite/separate_topic_table_sqlite_writer.h b/rosbag2_storage_evaluation/src/writer/sqlite/separate_topic_table_sqlite_writer.h new file mode 100644 index 0000000000..35bdcf47be --- /dev/null +++ b/rosbag2_storage_evaluation/src/writer/sqlite/separate_topic_table_sqlite_writer.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#ifndef ROS2_ROSBAG_EVALUATION_SEPARATE_TOPIC_TABLE_SQLITE_WRITER_H +#define ROS2_ROSBAG_EVALUATION_SEPARATE_TOPIC_TABLE_SQLITE_WRITER_H + +#include +#include "writer/sqlite/one_table_sqlite_writer.h" +#include "writer/sqlite/sqlite_writer.h" + +namespace ros2bag +{ +class SeparateTopicTableSqliteWriter : public SqliteWriter +{ +public: + SeparateTopicTableSqliteWriter( + std::string const & filename, + unsigned int const messages_per_transaction, + Indices const & indices, + Pragmas const & pragmas); + + ~SeparateTopicTableSqliteWriter() override + { + SeparateTopicTableSqliteWriter::close(); + } + + void close() override; + + void reset() override; + +private: + sqlite::StatementPtr insert_message_stmt_; + sqlite::StatementPtr insert_topic_stmt_; + std::map topic_ids_; + + void initialize_tables(sqlite::DBPtr db) final; + + void write_to_database(MessagePtr message) final; + + void prepare_statements(sqlite::DBPtr db) final; + + void insert_message(Message::Timestamp timestamp, long topic_id, BlobPtr blob); + + long insert_topic(std::string const & topic); +}; +} +#endif //ROS2_ROSBAG_EVALUATION_SEPARATE_TOPIC_TABLE_SQLITE_WRITER_H diff --git a/rosbag2_storage_evaluation/src/writer/sqlite/sqlite.cpp b/rosbag2_storage_evaluation/src/writer/sqlite/sqlite.cpp new file mode 100644 index 0000000000..84ef8de2b2 --- /dev/null +++ b/rosbag2_storage_evaluation/src/writer/sqlite/sqlite.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#include "writer/sqlite/sqlite.h" + +#include +#include +#include + +#include "common/strings.h" +#include "common/vectors.h" + +using namespace ros2bag::sqlite; + +DBPtr ros2bag::sqlite::open_db(std::string const & name) +{ + DBPtr db; + sqlite3_open(name.c_str(), &db); + return db; +} + +void ros2bag::sqlite::close_db(DBPtr db) +{ + sqlite3_close(db); +} + +void ros2bag::sqlite::set_pragma(DBPtr db, std::string const & pragma, std::string const & value) +{ + std::string statement = "PRAGMA " + pragma + "=" + value; + sqlite3_exec(db, statement.c_str(), nullptr, nullptr, nullptr); +} + +void ros2bag::sqlite::create_table( + DBPtr db, + std::string const & name, + std::vector const & fields, + std::vector const & foreign_keys +) +{ + std::vector definitions = fields; + + std::string foreign_key_column, parent_table, parent_column; + for (auto const & key : foreign_keys) { + std::tie(foreign_key_column, parent_table, parent_column) = key; + + std::ostringstream constraint; + constraint << "FOREIGN KEY (" << foreign_key_column << ") " + << "REFERENCES " << parent_table << " (" << parent_column << ")"; + + definitions.push_back(constraint.str()); + } + + std::string statement( + "CREATE TABLE IF NOT EXISTS " + + name + + ros2bag::strings::join(definitions, ",", "(", ")") + + ";"); + sqlite3_exec(db, statement.c_str(), nullptr, nullptr, nullptr); +} + +void ros2bag::sqlite::create_index(DBPtr db, std::string const & table, std::string const & key) +{ + std::string index_name = key + "_INDEX"; + std::string statement( + "CREATE INDEX IF NOT EXISTS " + index_name + " ON " + table + "(" + key + ");"); + sqlite3_exec(db, statement.c_str(), nullptr, nullptr, nullptr); +} + +StatementPtr ros2bag::sqlite::new_insert_stmt(DBPtr db, std::string const & table, + std::vector const & fields) +{ + StatementPtr stmt; + + std::string placeholder = "?"; + std::string sql = "INSERT INTO " + + table + + ros2bag::strings::join(fields, ",", "(", ")") + + " VALUES" + + ros2bag::strings::join(ros2bag::vectors::repeat(fields.size(), placeholder), ",", "(", ")") + + ";"; + + sqlite3_prepare_v2(db, sql.c_str(), static_cast(sql.size()), &stmt, nullptr); + + return stmt; +} + +void ros2bag::sqlite::finalize(StatementPtr statement) +{ + sqlite3_finalize(statement); +} + +void ros2bag::sqlite::exec(DBPtr db, std::string const & statement) +{ + sqlite3_exec(db, statement.c_str(), nullptr, nullptr, nullptr); +} diff --git a/rosbag2_storage_evaluation/src/writer/sqlite/sqlite.h b/rosbag2_storage_evaluation/src/writer/sqlite/sqlite.h new file mode 100644 index 0000000000..b56656166c --- /dev/null +++ b/rosbag2_storage_evaluation/src/writer/sqlite/sqlite.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#ifndef ROS2_ROSBAG_EVALUATION_SQLITE_H +#define ROS2_ROSBAG_EVALUATION_SQLITE_H + +#include +#include +#include + +namespace ros2bag +{ +namespace sqlite +{ + +using DBPtr = sqlite3 *; +using ForeignKeyDef = std::tuple; + +DBPtr open_db(std::string const & name); + +void close_db(DBPtr db); + +void set_pragma(DBPtr db, std::string const & pragma, std::string const & value); + +void create_table( + DBPtr db, + std::string const & name, + std::vector const & fields, + std::vector const & foreign_keys = {} +); + +void create_index(DBPtr db, std::string const & table, std::string const & key); + +using StatementPtr = sqlite3_stmt *; + +StatementPtr new_insert_stmt( + DBPtr db, std::string const & table, std::vector const & fields); + +void finalize(StatementPtr statement); + +void exec(DBPtr db, std::string const & statement); + +} +} + +#endif //ROS2_ROSBAG_EVALUATION_SQLITE_WRITER_H diff --git a/rosbag2_storage_evaluation/src/writer/sqlite/sqlite_writer.cpp b/rosbag2_storage_evaluation/src/writer/sqlite/sqlite_writer.cpp new file mode 100644 index 0000000000..f015f09c08 --- /dev/null +++ b/rosbag2_storage_evaluation/src/writer/sqlite/sqlite_writer.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#include "writer/sqlite/sqlite_writer.h" + +#include "generators/message.h" +#include "writer/sqlite/sqlite.h" + +using namespace ros2bag; + +void SqliteWriter::open() +{ + if (!is_open()) { + open_ = true; + db_ = sqlite::open_db(filename_); + initialize_tables(db_); + set_pragmas(); + prepare_statements(db_); + } +} + +void SqliteWriter::close() +{ + if (is_open()) { + open_ = false; + if (in_transaction_) { + end_transaction(); + } + sqlite::close_db(db_); + } +} + +void SqliteWriter::write(MessagePtr message) +{ + if (messages_per_transaction_ == 0) { + write_to_database(message); + return; + } + + ++number_of_message_in_current_transaction_; + + if (number_of_message_in_current_transaction_ == 1) { + begin_transaction(); + } + + write_to_database(message); + + if (number_of_message_in_current_transaction_ == messages_per_transaction_) { + end_transaction(); + number_of_message_in_current_transaction_ = 0; + } +} + +void SqliteWriter::begin_transaction() +{ + sqlite::exec(db_, "BEGIN TRANSACTION"); + in_transaction_ = true; +} + +void SqliteWriter::end_transaction() +{ + sqlite::exec(db_, "END TRANSACTION"); + in_transaction_ = false; +} + +void SqliteWriter::create_index() +{ + std::string table, index_column; + for (auto const & index : indices_) { + std::tie(table, index_column) = index; + sqlite::create_index(db_, table, index_column); + } +} + +void SqliteWriter::set_pragmas() +{ + std::string name, value; + for (auto const & pragma : pragmas_) { + std::tie(name, value) = pragma; + sqlite::set_pragma(db_, name, value); + } +} + diff --git a/rosbag2_storage_evaluation/src/writer/sqlite/sqlite_writer.h b/rosbag2_storage_evaluation/src/writer/sqlite/sqlite_writer.h new file mode 100644 index 0000000000..8a73ed258b --- /dev/null +++ b/rosbag2_storage_evaluation/src/writer/sqlite/sqlite_writer.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#ifndef ROS2_ROSBAG_EVALUATION_SQLITE_WRITER_H +#define ROS2_ROSBAG_EVALUATION_SQLITE_WRITER_H + +#include +#include +#include +#include + +#include "writer/message_writer.h" +#include "writer/sqlite/sqlite.h" + +namespace ros2bag +{ +using Pragmas = std::map; +using Indices = std::vector>; + +class SqliteWriter : public MessageWriter +{ +public: + explicit SqliteWriter( + std::string const & filename, + unsigned int const messages_per_transaction = 0, + Indices const & indices = {}, + Pragmas const & pragmas = {{"journal_mode", "MEMORY"}, + {"synchronous", "OFF"}} + ) : filename_(filename) + , messages_per_transaction_(messages_per_transaction) + , number_of_message_in_current_transaction_(0) + , db_(nullptr) + , insert_message_stmt_(nullptr) + , open_(false) + , in_transaction_(false) + , indices_(indices) + , pragmas_(pragmas) + {} + + ~SqliteWriter() override + { + SqliteWriter::close(); + } + + void open() final; + + void close() override; + + void write(MessagePtr message) final; + + void create_index() final; + +protected: + sqlite::DBPtr db() const + { + return db_; + } + + bool is_open() const + { + return open_; + } + + virtual void initialize_tables(sqlite::DBPtr db) = 0; + + virtual void write_to_database(MessagePtr message) = 0; + + virtual void prepare_statements(sqlite::DBPtr db) = 0; + +private: + std::string const filename_; + unsigned int const messages_per_transaction_; + unsigned int number_of_message_in_current_transaction_; + sqlite::DBPtr db_; + sqlite::StatementPtr insert_message_stmt_; + bool open_; + bool in_transaction_; + Pragmas pragmas_; + Indices indices_; + + void set_pragmas(); + + void begin_transaction(); + + void end_transaction(); +}; + +} + +#endif //ROS2_ROSBAG_EVALUATION_SQLITE_WRITER_H diff --git a/rosbag2_storage_evaluation/src/writer/stream/message_stream_writer.cpp b/rosbag2_storage_evaluation/src/writer/stream/message_stream_writer.cpp new file mode 100644 index 0000000000..69dfa22a38 --- /dev/null +++ b/rosbag2_storage_evaluation/src/writer/stream/message_stream_writer.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#include "message_stream_writer.h" + +using namespace ros2bag; + +void MessageStreamWriter::write(MessagePtr message) +{ + output_stream_ << *message << std::endl; +} + diff --git a/rosbag2_storage_evaluation/src/writer/stream/message_stream_writer.h b/rosbag2_storage_evaluation/src/writer/stream/message_stream_writer.h new file mode 100644 index 0000000000..0f4c60cf35 --- /dev/null +++ b/rosbag2_storage_evaluation/src/writer/stream/message_stream_writer.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018, Bosch Software Innovations 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. + */ + +#ifndef ROS2_ROSBAG_EVALUATION_MESSAGE_STREAM_WRITER_H +#define ROS2_ROSBAG_EVALUATION_MESSAGE_STREAM_WRITER_H + +#include + +#include "writer/message_writer.h" + +namespace ros2bag +{ + +class MessageStreamWriter : public MessageWriter +{ +public: + explicit MessageStreamWriter(std::ostream & output_stream) : output_stream_(output_stream) + {} + + ~MessageStreamWriter() override = default; + + void open() override + {} + + void close() override + {} + + void write(MessagePtr message) override; + + void create_index() override + {} + + void reset() override + {} + +private: + std::ostream & output_stream_; +}; + +} + +#endif //ROS2_ROSBAG_EVALUATION_MESSAGE_STREAM_WRITER_H