/** * @file service_benchmark_node.cpp * @brief A ROS2 node to benchmark Orbbec camera service calls. * * Features: * - Benchmark a single service call (latency, success rate) * - Benchmark multiple services defined in a YAML configuration file * - Optionally save results to a CSV file * * Usage: * 1.Benchmark a single service: * ros2 run orbbec_camera service_benchmark_node \ --ros-args \ -p service_name:=/camera/get_depth_gain \ -p service_type:=orbbec_camera_msgs/srv/GetInt32 \ -p count:=10 * 2.Benchmark multiple services from a YAML config file: * ros2 run orbbec_camera service_benchmark_node \ --ros-args \ -p yaml_file:=/path/to/default_service_cpp.yaml */ #include "orbbec_camera/ob_camera_node_driver.h" #include #include #include #include #include #include #include #include #include #include #include class SingleServiceBenchmark { public: SingleServiceBenchmark(rclcpp::Node::SharedPtr nh, const std::string &service_name, const std::string &service_type, int count, const YAML::Node &request_data) : nh_(nh), service_name_(service_name), service_type_(service_type), count_(count), request_data_(request_data) { service_map_["orbbec_camera_msgs/srv/GetDeviceInfo"] = [this](std::vector &durations, int &success) { runTyped(durations, success); }; service_map_["orbbec_camera_msgs/srv/GetString"] = [this](std::vector &durations, int &success) { runTyped(durations, success); }; service_map_["orbbec_camera_msgs/srv/GetBool"] = [this](std::vector &durations, int &success) { runTyped(durations, success); }; service_map_["orbbec_camera_msgs/srv/GetInt32"] = [this](std::vector &durations, int &success) { runTyped(durations, success); }; service_map_["std_srvs/srv/Empty"] = [this](std::vector &durations, int &success) { runTyped(durations, success); }; service_map_["orbbec_camera_msgs/srv/SetInt32"] = [this](std::vector &durations, int &success) { runTyped(durations, success); }; service_map_["orbbec_camera_msgs/srv/SetArrays"] = [this](std::vector &durations, int &success) { runTyped(durations, success); }; service_map_["std_srvs/srv/SetBool"] = [this](std::vector &durations, int &success) { runTyped(durations, success); }; service_map_["orbbec_camera_msgs/srv/SetFilter"] = [this](std::vector &durations, int &success) { runTyped(durations, success); }; service_map_["orbbec_camera_msgs/srv/SetString"] = [this](std::vector &durations, int &success) { runTyped(durations, success); }; } template typename rclcpp::Client::SharedPtr getClient() { auto it = client_cache_.find(service_name_); if (it != client_cache_.end()) { return std::dynamic_pointer_cast>(it->second); } auto client = nh_->create_client(service_name_); client_cache_[service_name_] = client; return client; } int run(std::vector &durations_out) { int success = 0; auto it = service_map_.find(service_type_); if (it != service_map_.end()) { it->second(durations_out, success); } else { RCLCPP_ERROR(nh_->get_logger(), "Unsupported service type: %s", service_type_.c_str()); } return success; } void printSummary(const std::vector &durations, int success) { if (durations.empty()) { RCLCPP_WARN(nh_->get_logger(), "No successful calls!"); return; } double avg = std::accumulate(durations.begin(), durations.end(), 0.0) / durations.size(); double minv = *std::min_element(durations.begin(), durations.end()); double maxv = *std::max_element(durations.begin(), durations.end()); double success_rate = 100.0 * success / count_; std::cout << std::string(64, '=') << std::endl; std::cout << std::setw(7) << "Calls" << std::setw(10) << "Success" << std::setw(11) << "Rate(%)" << std::setw(12) << "Avg(ms)" << std::setw(12) << "Min(ms)" << std::setw(12) << "Max(ms)" << std::endl; std::cout << std::string(64, '-') << std::endl; std::cout << std::setw(5) << count_ << std::setw(10) << success << std::setw(12) << std::fixed << std::setprecision(2) << success_rate << std::setw(11) << std::fixed << std::setprecision(2) << avg << std::setw(12) << std::fixed << std::setprecision(2) << minv << std::setw(12) << std::fixed << std::setprecision(2) << maxv << std::endl; std::cout << std::string(64, '=') << std::endl; } private: rclcpp::Node::SharedPtr nh_; std::string service_name_; std::string service_type_; int count_; YAML::Node request_data_; std::map &, int &)>> service_map_; std::map client_cache_; template struct has_success : std::false_type {}; template struct has_success().success)>> : std::true_type {}; template void fillRequest(typename ServiceT::Request &request); template void runTyped(std::vector &durations_out, int &success_out) { auto client = getClient(); std::vector durations; int success = 0; if (!client->wait_for_service(std::chrono::seconds(2))) { RCLCPP_ERROR(nh_->get_logger(), "Service %s not available", service_name_.c_str()); durations_out.clear(); success_out = 0; return; } for (int i = 0; i < count_; ++i) { auto Request = std::make_shared(); fillRequest(*Request); auto start = std::chrono::steady_clock::now(); bool call_ok = false; try { auto result_future = client->async_send_request(Request); auto status = rclcpp::spin_until_future_complete(nh_, result_future); call_ok = (status == rclcpp::FutureReturnCode::SUCCESS); if (call_ok) { auto end = std::chrono::steady_clock::now(); double dt = std::chrono::duration_cast(end - start).count() / 1000.0; durations.push_back(dt); if constexpr (has_success::value) { auto response = result_future.get(); if (response->success) { success++; RCLCPP_INFO(nh_->get_logger(), "Call %s %d/%d succeeded (cost: %.2f ms)", service_name_.c_str(), i + 1, count_, dt); } else { RCLCPP_WARN( nh_->get_logger(), "Call %s %d/%d (cost: %.2f ms) responded with success=false, message='%s'", service_name_.c_str(), i + 1, count_, dt, response->message.c_str()); } } else { success++; } } } catch (const std::exception &e) { RCLCPP_ERROR(nh_->get_logger(), "Exception calling service %s: %s", service_name_.c_str(), e.what()); } if (!call_ok) { std::string request_str; try { if (request_data_ && !request_data_.IsNull()) { request_str = YAML::Dump(request_data_); } else { request_str = "{error 6}"; } } catch (...) { request_str = "{error null}"; } RCLCPP_WARN(nh_->get_logger(), "Call %d/%d failed for service '%s' (type: '%s') with request: %s", i + 1, count_, service_name_.c_str(), service_type_.c_str(), request_str.c_str()); } } if (success == 0 || durations.empty()) { RCLCPP_WARN(nh_->get_logger(), "No successful calls for service %s (%s)", service_name_.c_str(), service_type_.c_str()); durations_out.clear(); success_out = 0; return; } durations_out = durations; success_out = success; } }; template void SingleServiceBenchmark::fillRequest(typename ServiceT::Request &request) { (void)request; } // SetInt32 template <> void SingleServiceBenchmark::fillRequest( orbbec_camera::SetInt32::Request &request) { if (request_data_ && request_data_["data"] && request_data_["data"].IsScalar()) { request.data = request_data_["data"].as(); } } // SetBool template <> void SingleServiceBenchmark::fillRequest( std_srvs::srv::SetBool::Request &request) { if (request_data_ && request_data_["data"] && request_data_["data"].IsScalar()) { request.data = request_data_["data"].as(); } } // SetString template <> void SingleServiceBenchmark::fillRequest( orbbec_camera_msgs::srv::SetString::Request &request) { if (!request_data_) { return; } if (request_data_.IsScalar()) { request.data = request_data_.as(); } else if (request_data_["data"]) { request.data = request_data_["data"].as(); } } // SetArrays template <> void SingleServiceBenchmark::fillRequest( orbbec_camera::SetArrays::Request &request) { if (request_data_ && request_data_["data_param"] && request_data_["data_param"].IsSequence()) { const YAML::Node &arr = request_data_["data_param"]; request.data_param.clear(); for (const auto &v : arr) request.data_param.push_back(v.as()); } } // SetFilter template <> void SingleServiceBenchmark::fillRequest( orbbec_camera::SetFilter::Request &request) { if (!request_data_ || request_data_.IsNull()) return; if (request_data_["filter_name"] && request_data_["filter_name"].IsScalar()) request.filter_name = request_data_["filter_name"].as(); if (request_data_["filter_enable"] && request_data_["filter_enable"].IsScalar()) request.filter_enable = request_data_["filter_enable"].as(); if (request_data_["filter_param"] && request_data_["filter_param"].IsSequence()) { request.filter_param.clear(); for (const auto &v : request_data_["filter_param"]) request.filter_param.push_back(v.as()); } } class MultiServiceBenchmark { public: MultiServiceBenchmark(rclcpp::Node::SharedPtr nh, const YAML::Node &services_config, int default_count, const std::string &csv_file) : nh_(nh), services_config_(services_config), default_count_(default_count), csv_file_(csv_file) { if (!csv_file_.empty()) { csv_stream_.open(csv_file_, std::ios::out); if (csv_stream_.is_open()) { csv_stream_ << "Service,Type,Calls,Success,Rate,Avg(ms),Min(ms),Max(ms)\n"; } else { RCLCPP_WARN(nh_->get_logger(), "Failed to open CSV file: %s", csv_file_.c_str()); } } } ~MultiServiceBenchmark() { if (csv_stream_.is_open()) csv_stream_.close(); RCLCPP_INFO(nh_->get_logger(), "Benchmark results saved to CSV file: %s", csv_file_.c_str()); } void run() { if (!services_config_ || !services_config_.IsSequence()) { RCLCPP_ERROR(nh_->get_logger(), "No services found in YAML config."); return; } for (size_t i = 0; i < services_config_.size(); ++i) { YAML::Node svc = services_config_[i]; std::string service_name = svc["name"] ? svc["name"].as() : ""; std::string service_type = svc["type"] ? svc["type"].as() : ""; YAML::Node request_data = svc["request"]; int count = svc["count"] ? svc["count"].as() : default_count_; RCLCPP_INFO(nh_->get_logger(), "Running benchmark for service %s (%s)", service_name.c_str(), service_type.c_str()); SingleServiceBenchmark bench(nh_, service_name, service_type, count, request_data); std::vector durations; int success = bench.run(durations); if (csv_stream_.is_open()) { if (durations.empty() || success == 0) { csv_stream_ << service_name << "," << service_type << "," << count << "," << success << ",0.00%,0.00,0.00,0.00\n"; } else { double avg = std::accumulate(durations.begin(), durations.end(), 0.0) / durations.size(); double minv = *std::min_element(durations.begin(), durations.end()); double maxv = *std::max_element(durations.begin(), durations.end()); double success_rate = 100.0 * success / count; csv_stream_ << service_name << "," << service_type << "," << count << "," << success << "," << std::fixed << std::setprecision(2) << success_rate << "%" << "," << avg << "," << minv << "," << maxv << "\n"; } } } } private: rclcpp::Node::SharedPtr nh_; YAML::Node services_config_; int default_count_; std::string csv_file_; std::ofstream csv_stream_; }; int main(int argc, char **argv) { rclcpp::init(argc, argv); auto nh = std::make_shared("service_benchmark_node"); std::string service_name, service_type, request_str; YAML::Node request_data; int count = 10; std::string yaml_file, csv_file; nh->declare_parameter("yaml_file", ""); nh->declare_parameter("csv_file", "multi_service_results_log_cpp.csv"); nh->declare_parameter("service_name", ""); nh->declare_parameter("service_type", ""); nh->declare_parameter("request_data", ""); nh->declare_parameter("count", 10); nh->get_parameter("yaml_file", yaml_file); nh->get_parameter("csv_file", csv_file); nh->get_parameter("service_name", service_name); nh->get_parameter("service_type", service_type); nh->get_parameter("request_data", request_str); nh->get_parameter("count", count); if (yaml_file.empty()) { if (service_name.empty() || service_type.empty()) { RCLCPP_ERROR(nh->get_logger(), "service_name or service_type is empty! Please set parameters."); rclcpp::shutdown(); return 1; } if (!request_str.empty()) { try { request_data = YAML::Load(request_str); } catch (const YAML::Exception &e) { RCLCPP_ERROR(nh->get_logger(), "Failed to parse request_data: %s", e.what()); rclcpp::shutdown(); return 1; } } SingleServiceBenchmark bench(nh, service_name, service_type, count, request_data); std::vector durations_out; int success = bench.run(durations_out); bench.printSummary(durations_out, success); } else { YAML::Node config = YAML::LoadFile(yaml_file); YAML::Node services = config["services"]; int global_count = config["default_count"] ? config["default_count"].as() : 1; MultiServiceBenchmark multi_bench(nh, services, global_count, csv_file); multi_bench.run(); } rclcpp::shutdown(); return 0; }