Skip to content

Commit

Permalink
Samples: Added perftool (#1241)
Browse files Browse the repository at this point in the history
  • Loading branch information
FlorianReimold authored Nov 6, 2023
1 parent b7ae815 commit dca98c4
Show file tree
Hide file tree
Showing 9 changed files with 1,104 additions and 0 deletions.
52 changes: 52 additions & 0 deletions samples/cpp/benchmarks/perftool/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# ========================= eCAL LICENSE =================================
#
# Copyright (C) 2023 Continental Corporation
#
# 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.
#
# ========================= eCAL LICENSE =================================

cmake_minimum_required(VERSION 3.13)
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON)

project(perftool)

find_package(eCAL REQUIRED)

set(source_files
src/main.cpp
src/publisher.cpp
src/publisher.h
src/publisher_statistics.h
src/subscriber.cpp
src/subscriber.h
src/subscriber_statistics.h
)

#add_executable(${PROJECT_NAME} ${source_files})
ecal_add_sample(${PROJECT_NAME} ${source_files})

target_link_libraries(${PROJECT_NAME}
eCAL::core
)

target_include_directories(${PROJECT_NAME} PRIVATE src)

target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)

source_group(TREE "${CMAKE_CURRENT_LIST_DIR}"
FILES
${source_files}
)

ecal_install_sample(${PROJECT_NAME})
52 changes: 52 additions & 0 deletions samples/cpp/benchmarks/perftool/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# ecal-perftool

The ecal-perftool is a simple application to estimate the performance of eCAL pub-sub connections using dummy-data being published a at a constant frequency.

## Usage

```
Usage:
ecal_sample_perftool pub <topic_name> <frequency_hz> <payload_size_bytes> [options]
or:
ecal_sample_perftool sub <topic_name> [callback_delay_ms] [options]
Options:
-q, --quiet: Do not print any output
-v, --verbose: Print all measured times for all messages
--busy-wait: Busy wait when receiving messages (i.e. burn CPU). For subscribers only.
--hickup <after_ms> <delay_ms>: Further delay a single callback. For subscribers only.
```

## Output

**Publisher**:

```
[ 78436.510] | cnt: 9 | loop_dt(ms) mean: 99.954 [ 99.587, 100.001] | loop_freq(Hz): 10.0 | snd_dt(ms) mean: 0.001 [ 0.001, 0.001]
[ 78437.525] | cnt: 10 | loop_dt(ms) mean: 100.000 [ 99.999, 100.001] | loop_freq(Hz): 10.0 | snd_dt(ms) mean: 0.001 [ 0.000, 0.001]
[ 78438.538] | cnt: 10 | loop_dt(ms) mean: 100.001 [ 99.999, 100.013] | loop_freq(Hz): 10.0 | snd_dt(ms) mean: 0.001 [ 0.000, 0.001]
[ 78439.545] | cnt: 10 | loop_dt(ms) mean: 99.999 [ 99.987, 100.002] | loop_freq(Hz): 10.0 | snd_dt(ms) mean: 0.001 [ 0.001, 0.001]
[ 78440.551] | cnt: 10 | loop_dt(ms) mean: 100.000 [ 99.999, 100.001] | loop_freq(Hz): 10.0 | snd_dt(ms) mean: 0.001 [ 0.001, 0.001]
```

- `[ xxx]`: Log system time
- `cnt`: Amount of messages sent since last log output
- `loop_dt`: Duration of publishing loop, consisting of mean `mean [min, max]` in milliseconds
- `loop_freq`: computed loop frequency in Hz
- `snd_dt`: Duration of the eCAL `CPublisher::Send()` call only, consisting of `mean [min, max]` in milliseconds

**Subscriber**

```
[ 78927.089] | cnt: 10 | lost: 0 | msg_dt(ms) mean: 99.997 [ 99.967, 100.019] | msg_freq(Hz): 10.0
[ 78928.103] | cnt: 10 | lost: 0 | msg_dt(ms) mean: 100.000 [ 99.964, 100.031] | msg_freq(Hz): 10.0
[ 78929.104] | cnt: 10 | lost: 0 | msg_dt(ms) mean: 99.998 [ 99.966, 100.039] | msg_freq(Hz): 10.0
[ 78930.117] | cnt: 10 | lost: 0 | msg_dt(ms) mean: 100.001 [ 99.993, 100.010] | msg_freq(Hz): 10.0
[ 78931.132] | cnt: 10 | lost: 0 | msg_dt(ms) mean: 100.000 [ 99.966, 100.030] | msg_freq(Hz): 10.0
```

- `[ xxx]`: Log system time
- `cnt`: Amount of received sent since last log output
- `lost`: Amount of dropped messages since the last log output. Determined by comparing the native eCAL message counter of each message to the previous.
- `msg_dt`: Duration between the received messages, consisting of `mean [min, max]` in milliseconds
- `msg_freq`: Computed message frequency in Hz
249 changes: 249 additions & 0 deletions samples/cpp/benchmarks/perftool/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
/* ========================= eCAL LICENSE =================================
*
* Copyright (C) 2023 Continental Corporation
*
* 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.
*
* ========================= eCAL LICENSE =================================
*/

#include <algorithm>
#include <chrono>
#include <cstddef>
#include <ecal/ecal.h> // IWYU pragma: keep

#include "publisher.h"
#include "subscriber.h"

#include <exception>
#include <iostream>
#include <iterator>
#include <ratio>
#include <string>
#include <thread>
#include <vector>

#ifdef WIN32

#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#endif


void printUsage(const std::string& arg0)
{
std::cout << "Usage:" << std::endl;
std::cout << " " << arg0 << " pub <topic_name> <frequency_hz> <payload_size_bytes> [options]" << std::endl;
std::cout << "or:" << std::endl;
std::cout << " " << arg0 << " sub <topic_name> [callback_delay_ms] [options]" << std::endl;
std::cout << std::endl;
std::cout << "Options:" << std::endl;
std::cout << " -q, --quiet: Do not print any output" << std::endl;
std::cout << " -v, --verbose: Print all measured times for all messages" << std::endl;
std::cout << " --busy-wait: Busy wait when receiving messages (i.e. burn CPU). For subscribers only." << std::endl;
std::cout << " --hickup <after_ms> <delay_ms>: Further delay a single callback. For subscribers only." << std::endl;

}

int main(int argc, char** argv)
{
#ifdef WIN32
SetConsoleOutputCP(CP_UTF8);
#endif // WIN32

bool quiet_arg = false;
bool verbose_print_times = false;
bool busy_wait_arg = false;

bool hickup_arg = false;
std::chrono::steady_clock::duration hickup_time (0);
std::chrono::steady_clock::duration hickup_delay(0);

// convert argc, argv to vector of strings
std::vector<std::string> args;
args.reserve(static_cast<size_t>(argc));
for (int i = 0; i < argc; ++i)
{
args.emplace_back(argv[i]);
}

// Check for -h / --help
if (args.size() < 2
|| std::find(args.begin(), args.end(), "-h") != args.end()
|| std::find(args.begin(), args.end(), "--help") != args.end())
{
printUsage(args[0]);
return 0;
}

// find "--hickup" argument and remove it from args
{
auto hickup_arg_it = std::find(args.begin(), args.end(), "--hickup");
if (hickup_arg_it != args.end())
{
hickup_arg = true;

// Check if there are enough arguments for the time and delay after the hickup_arg_it and parse those as doubles
if (args.size() < static_cast<size_t>(std::distance(args.begin(), hickup_arg_it) + 3))
{
std::cerr << "Invalid number of parameters after --hickup" << std::endl;
printUsage(args[0]);
return 1;
}
else
{
try
{
// Parse the next two arguments as double
const double hickup_time_ms = std::stod(*(std::next(hickup_arg_it, 1)));
const double hickup_delay_ms = std::stod(*(std::next(hickup_arg_it, 2)));

hickup_time = std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<double, std::milli>(hickup_time_ms));
hickup_delay = std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<double, std::milli>(hickup_delay_ms));
}
catch (const std::exception& e)
{
std::cerr << "Failed parsing parameters after --hickup: " << e.what() << std::endl;
printUsage(args[0]);
return 1;
}

// Remove all 3 parameters
args.erase(hickup_arg_it, std::next(hickup_arg_it, 3));
}
}
}

// find "--quiet" argument and remove it from args
{
auto quiet_arg_it = std::find(args.begin(), args.end(), "--quiet");
if (quiet_arg_it != args.end())
{
quiet_arg = true;
args.erase(quiet_arg_it);
}
}

// find "-q" argument and remove it from args
{
auto q_arg_it = std::find(args.begin(), args.end(), "-q");
if (q_arg_it != args.end())
{
quiet_arg = true;
args.erase(q_arg_it);
}
}

// find "--verbose" argument and remove it from args
{
auto verbose_arg_it = std::find(args.begin(), args.end(), "--verbose");
if (verbose_arg_it != args.end())
{
verbose_print_times = true;
args.erase(verbose_arg_it);
}
}

// find "-v" argument and remove it from args
{
auto v_arg_it = std::find(args.begin(), args.end(), "-v");
if (v_arg_it != args.end())
{
verbose_print_times = true;
args.erase(v_arg_it);
}
}

// Validate quite and verbose args
if (quiet_arg && verbose_print_times)
{
std::cerr << "Invalid arguments: Cannot use \"quiet\" and \"verbose\" simultaneously" << std::endl;
printUsage(argv[0]);
return 1;
}

// find "--busy-wait" argument and remove it from args
{
auto busy_wait_arg_it = std::find(args.begin(), args.end(), "--busy-wait");
if (busy_wait_arg_it != args.end())
{
busy_wait_arg = true;
args.erase(busy_wait_arg_it);
}
}

if (args[1] == "pub")
{
if (args.size() != 5)
{
std::cerr << "Invalid number of parameters" << std::endl;
printUsage(args[0]);
return 1;
}
const std::string topic_name = args[2];
const double frequency_hz = std::stod(args[3]);
const unsigned long long payload_size_bytes = std::stoull(args[4]);

// Initialize eCAL
eCAL::Initialize(argc, argv, "ecal-perftool");
eCAL::Util::EnableLoopback(true);

const Publisher publisher(topic_name, frequency_hz, payload_size_bytes, quiet_arg, verbose_print_times);

// Just don't exit
while (eCAL::Ok())
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}

// finalize eCAL API
eCAL::Finalize();
}
else if (args[1] == "sub")
{
if (args.size() < 3 || args.size() > 4)
{
std::cerr << "Invalid number of parameters" << std::endl;
printUsage(args[0]);
return 1;
}

const std::string topic_name = args[2];
const std::chrono::milliseconds callback_delay((args.size() >= 4 ? std::stoull(args[3]) : 0));


// Initialize eCAL
eCAL::Initialize(argc, argv, "ecal-perftool");
eCAL::Util::EnableLoopback(true);

const Subscriber subscriber(topic_name, callback_delay, busy_wait_arg, hickup_arg, hickup_time, hickup_delay, quiet_arg, verbose_print_times);

// Just don't exit
while (eCAL::Ok())
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}

// finalize eCAL API
eCAL::Finalize();
}
else
{
std::cerr << "Invalid parameter: " << args[1] << std::endl;
printUsage(args[0]);
return 1;
}

return 0;
}
Loading

0 comments on commit dca98c4

Please sign in to comment.