【FastDDS】FastDDS简易性能测试框架

DDS(Data Distribution Service)是一套规范,包含许多组件,其中,DCPS(Data-Centric Publish-Subscribe)是DDS的核心层,它定义了数据如何在发布者和订阅者之间交换。RTPS (Real-Time Publish-Subscribe)是DDS规范的网络层协议

FastDDS是DDS的一种实现,实现有两种模型,一种DDS模型,一种RTPS模型,RTPS能够更加精细化地操作底层。
也就是说,当使用 DDS 开发应用程序时,实际上是在利用 DCPS 模型,并且这个模型通常会通过 RTPS 协议来实现数据的传输。

本文档描述了使用FastDDS进行测试,对不同数据大小的数据进行测试,搭建了一套测试框架,并给出结论。
!!!本文不对FastDDS的安装进行说明。

1. 测试概述

本文从简单的发布订阅协议的FastDDS测试样例开始,到最终搭建出一套简单的可定制测试的发布订阅框架,方便进行测试。

1.1. 测试环境及指标

环境:
Win10主机下的VMWare虚拟机环境
OS:Ubuntu22.04LTS
CPU:4x2=8(核)
内存:8G RAM
磁盘:100G
FastDDS版本:2.14.2

软件需求:

  1. CMake
  2. FastDDS库
  3. gcc

指标:
CPU占用、内存占用

1.2. 测试阶段

测试分为4个阶段
简单的发布订阅->能够配置简单Qos->通过xml文件配置->搭建测试框架->完整测试框架+测试脚本
因为每一节基本都包含了大量代码,所以最好跳着看。
若读者对FastDDS的传输已经有了一定的理解,可以直接跳到1.2.5.节进行查看。
1.2.1.简单发布订阅
1.2.2.配置历史记录
1.2.3.xml文件配置
1.2.4.测试框架
1.2.5.完整测试框架

1.2.1.简单发布订阅

1)目录结构
. 
├── CMakeLists.txt
├── Readme.md(非必要)
├── restart.sh
└── src
    ├── HelloWorld.idl
    ├── HelloWorldPublisher.cpp
    └── HelloWorldSubscriber.cpp
2)测试需要执行
chmod +x ./restart.sh

# 在src下根据idl文件生成所需文件
fastddsgen src/HelloWorld.idl

# 编译文件
./restart.sh

	# 本窗口
	./build/DDSHelloWorldPublisher 
	# 另外一个窗口
	./build/DDSHelloWorldSubscriber
3)文件内容

CMakeLists.txt内容如下

cmake_minimum_required(VERSION 3.20)

project(DDSHelloWorld)

if(NOT fastrtps_FOUND)
    find_package(fastrtps REQUIRED)
endif()
if(NOT fastcdr_FOUND)
    find_package(fastcdr REQUIRED)
endif()


# Set C++11
include(CheckCXXCompilerFlag)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG OR
        CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    check_cxx_compiler_flag(-std=c++11 SUPPORTS_CXX11)
    if(SUPPORTS_CXX11)
        add_compile_options(-std=c++11)
    else()
        message(FATAL_ERROR "Compiler doesn't support C++11")
    endif()
endif()

message(STATUS "Configuring HelloWorld publisher/subscriber example...")
# 注意 将src/*.cxx添加到DDS_HELLOWORLD_SOURCES_CXX变量中
file(GLOB DDS_HELLOWORLD_SOURCES_CXX "src/*.cxx")


add_executable(DDSHelloWorldPublisher src/HelloWorldPublisher.cpp ${DDS_HELLOWORLD_SOURCES_CXX})
target_link_libraries(DDSHelloWorldPublisher fastrtps fastcdr)
add_executable(DDSHelloWorldSubscriber src/HelloWorldSubscriber.cpp ${DDS_HELLOWORLD_SOURCES_CXX})
target_link_libraries(DDSHelloWorldSubscriber fastrtps fastcdr)

restart.sh内容如下:

# /usr/bin
rm -rf ./build &&
mkdir build &&
cd build  &&
cmake .. &&
cmake --build .

HelloWorld.idl

struct HelloWorld
{
    unsigned long index;
    string message;
};

HelloWorldPublisher.cpp

/**
 * @file HelloWorldPublisher.cpp
 *
 */

#include "HelloWorldPubSubTypes.h"

#include <chrono>
#include <thread>

#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/publisher/DataWriter.hpp>
#include <fastdds/dds/publisher/DataWriterListener.hpp>
#include <fastdds/dds/publisher/Publisher.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>

using namespace eprosima::fastdds::dds;

class HelloWorldPublisher
{
private:

    HelloWorld hello_;

    DomainParticipant* participant_;

    Publisher* publisher_;

    Topic* topic_;

    DataWriter* writer_;

    TypeSupport type_;

    class PubListener : public DataWriterListener
    {
    public:

        PubListener()
            : matched_(0)
        {
        }

        ~PubListener() override
        {
        }

        void on_publication_matched(
                DataWriter*,
                const PublicationMatchedStatus& info) override
        {
            if (info.current_count_change == 1)
            {
                matched_ = info.total_count;
                std::cout << "Publisher matched." << std::endl;
            }
            else if (info.current_count_change == -1)
            {
                matched_ = info.total_count;
                std::cout << "Publisher unmatched." << std::endl;
            }
            else
            {
                std::cout << info.current_count_change
                        << " is not a valid value for PublicationMatchedStatus current count change." << std::endl;
            }
        }

        std::atomic_int matched_;

    } listener_;

public:

    HelloWorldPublisher()
        : participant_(nullptr)
        , publisher_(nullptr)
        , topic_(nullptr)
        , writer_(nullptr)
        , type_(new HelloWorldPubSubType())
    {
    }

    virtual ~HelloWorldPublisher()
    {
        if (writer_ != nullptr)
        {
            publisher_->delete_datawriter(writer_);
        }
        if (publisher_ != nullptr)
        {
            participant_->delete_publisher(publisher_);
        }
        if (topic_ != nullptr)
        {
            participant_->delete_topic(topic_);
        }
        DomainParticipantFactory::get_instance()->delete_participant(participant_);
    }

    //!Initialize the publisher
    bool init()
    {
        hello_.index(0);
        hello_.message("Firstfastddsdemo1");

        DomainParticipantQos participantQos;
        participantQos.name("Participant_publisher");
        participant_ = DomainParticipantFactory::get_instance()->create_participant(0, participantQos);

        if (participant_ == nullptr)
        {
            return false;
        }

        // Register the Type
        type_.register_type(participant_);

        // Create the publications Topic
        topic_ = participant_->create_topic("HelloWorldTopic", "HelloWorld", TOPIC_QOS_DEFAULT);

        if (topic_ == nullptr)
        {
            return false;
        }

        // Create the Publisher
        publisher_ = participant_->create_publisher(PUBLISHER_QOS_DEFAULT, nullptr);

        if (publisher_ == nullptr)
        {
            return false;
        }

        // Create the DataWriter
        writer_ = publisher_->create_datawriter(topic_, DATAWRITER_QOS_DEFAULT, &listener_);

        if (writer_ == nullptr)
        {
            return false;
        }
        return true;
    }

    //!Send a publication
    bool publish()
    {
        if (listener_.matched_ > 0)
        {
            hello_.index(hello_.index() + 1);
            writer_->write(&hello_);
            return true;
        }
        return false;
    }

    //!Run the Publisher
    void run(
            uint32_t samples)
    {
        uint32_t samples_sent = 0;
        while (samples_sent < samples)
        {
            if (publish())
            {
                samples_sent++;
                std::cout << "Message: " << hello_.message() << " with index: " << hello_.index()
                            << " SENT" << std::endl;
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        }
    }
};

int main(
        int argc,
        char** argv)
{
    std::cout << "Starting publisher." << std::endl;
    uint32_t samples = 10;

    HelloWorldPublisher* mypub = new HelloWorldPublisher();
    if(mypub->init())
    {
        mypub->run(samples);
    }

    delete mypub;
    return 0;
}

HelloWorldSubscriber.cpp

/**
 * @file HelloWorldSubscriber.cpp
 *
 */

#include "HelloWorldPubSubTypes.h"

#include <chrono>
#include <thread>

#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/subscriber/DataReader.hpp>
#include <fastdds/dds/subscriber/DataReaderListener.hpp>
#include <fastdds/dds/subscriber/qos/DataReaderQos.hpp>
#include <fastdds/dds/subscriber/SampleInfo.hpp>
#include <fastdds/dds/subscriber/Subscriber.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>

using namespace eprosima::fastdds::dds;

class HelloWorldSubscriber
{
private:

    DomainParticipant* participant_;

    Subscriber* subscriber_;

    DataReader* reader_;

    Topic* topic_;

    TypeSupport type_;

    class SubListener : public DataReaderListener
    {
    public:

        SubListener()
            : samples_(0)
        {
        }

        ~SubListener() override
        {
        }

        void on_subscription_matched(
                DataReader*,
                const SubscriptionMatchedStatus& info) override
        {
            if (info.current_count_change == 1)
            {
                std::cout << "Subscriber matched." << std::endl;
            }
            else if (info.current_count_change == -1)
            {
                std::cout << "Subscriber unmatched." << std::endl;
            }
            else
            {
                std::cout << info.current_count_change
                          << " is not a valid value for SubscriptionMatchedStatus current count change" << std::endl;
            }
        }

        void on_data_available(
                DataReader* reader) override
        {
            SampleInfo info;
            if (reader->take_next_sample(&hello_, &info) == ReturnCode_t::RETCODE_OK)
            {
                if (info.valid_data)
                {
                    std::cout <<"Only recieved: "<<hello_.index()<<" "<<hello_.message()<<std::endl;
                    samples_++;
                    std::cout << "Message: " << hello_.message() << " with index: " << hello_.index()
                              << " RECEIVED." << std::endl;
                }
            }
        }

        HelloWorld hello_;

        std::atomic_int samples_;

    }
    listener_;

public:

    HelloWorldSubscriber()
        : participant_(nullptr)
        , subscriber_(nullptr)
        , topic_(nullptr)
        , reader_(nullptr)
        , type_(new HelloWorldPubSubType())
    {
    }

    virtual ~HelloWorldSubscriber()
    {
        if (reader_ != nullptr)
        {
            subscriber_->delete_datareader(reader_);
        }
        if (topic_ != nullptr)
        {
            participant_->delete_topic(topic_);
        }
        if (subscriber_ != nullptr)
        {
            participant_->delete_subscriber(subscriber_);
        }
        DomainParticipantFactory::get_instance()->delete_participant(participant_);
    }

    //!Initialize the subscriber
    bool init()
    {
        DomainParticipantQos participantQos;
        participantQos.name("Participant_subscriber");
        participant_ = DomainParticipantFactory::get_instance()->create_participant(0, participantQos);

        if (participant_ == nullptr)
        {
            return false;
        }

        // Register the Type
        type_.register_type(participant_);

        // Create the subscriptions Topic
        topic_ = participant_->create_topic("HelloWorldTopic", "HelloWorld", TOPIC_QOS_DEFAULT);

        if (topic_ == nullptr)
        {
            return false;
        }

        // Create the Subscriber
        subscriber_ = participant_->create_subscriber(SUBSCRIBER_QOS_DEFAULT, nullptr);

        if (subscriber_ == nullptr)
        {
            return false;
        }

        // Create the DataReader
        reader_ = subscriber_->create_datareader(topic_, DATAREADER_QOS_DEFAULT, &listener_);

        if (reader_ == nullptr)
        {
            return false;
        }

        return true;
    }

    //!Run the Subscriber
    void run(
            uint32_t samples)
    {
        while (listener_.samples_ < samples)
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
    }

};

int main(
        int argc,
        char** argv)
{
    std::cout << "Starting subscriber." << std::endl;
    uint32_t samples = 10;

    HelloWorldSubscriber* mysub = new HelloWorldSubscriber();
    if (mysub->init())
    {
        mysub->run(samples);
    }

    delete mysub;
    return 0;
}
4)运行结果

简单测试结果

1.2.2.配置历史记录

通过对简单发布订阅代码的理解,摘出其中必要的部分,并对其进行Qos的扩展,再进行测试

1)目录结构
.
├── CMakeLists.txt
├── pub.sh
├── sub.sh
├── Readme.md(非必要)
├── restart.sh
└── src
    ├── Example.idl
    ├── HelloWorldPublisher.cpp
    └── HelloWorldSubscriber.cpp
2)测试时执行
chmod +x ./restart.sh
chmod +x ./pub.sh
chmod +x ./sub.sh

# 在src下根据idl文件生成所需文件
fastddsgen src/Example.idl

# 编译文件
./restart.sh

	# 本窗口
	./pub.sh
	# 另外一个窗口
	./sub.sh
3)文件内容

CMakeLists.txt和restart.sh与简单发布订阅一致,所以不再列出。
Example.idl

module Example {
    struct Message {
        unsigned long id;
        string<256> text;
    };
};

pub.sh

# /usr/bin
cd build
./DDSHelloWorldPublisher

sub.sh

# /usr/bin
cd build
./DDSHelloWorldSubscriber

HelloWorldPublisher.cpp

#include "ExamplePubSubTypes.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/publisher/Publisher.hpp>
#include <fastdds/dds/publisher/DataWriter.hpp>
#include <fastdds/dds/publisher/DataWriterListener.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
#include <fastdds/dds/core/policy/QosPolicies.hpp>
#include <fastrtps/transport/TCPv4TransportDescriptor.h>

using namespace eprosima::fastdds::dds;
using namespace eprosima::fastrtps::rtps;
class MyPubListener : public DataWriterListener
{
public:

    MyPubListener()
        : matched_(0)
    {
    }

    ~MyPubListener() override
    {
    }

    void on_publication_matched(
            DataWriter*,
            const PublicationMatchedStatus& info) override
    {
        if (info.current_count_change == 1)
        {
            matched_ = info.total_count;
            std::cout << "Publisher matched." << std::endl;
        }
        else if (info.current_count_change == -1)
        {
            matched_ = info.total_count;
            std::cout << "Publisher unmatched." << std::endl;
        }
        else
        {
            std::cout << info.current_count_change
                    << " is not a valid value for PublicationMatchedStatus current count change." << std::endl;
        }
    }

    std::atomic_int matched_;

};

int main()
{
    
    DomainParticipantQos dq;
    
    // 初始化DDS域参与者
    DomainParticipant* participant = DomainParticipantFactory::get_instance()->create_participant(0, dq);

    // 注册类型
    TypeSupport type(new Example::MessagePubSubType());
    type.register_type(participant);

    // 创建发布者
    Publisher* publisher = participant->create_publisher(PUBLISHER_QOS_DEFAULT, nullptr);

    // 创建主题
    TopicQos topic_qos = TOPIC_QOS_DEFAULT;
    topic_qos.durability().kind = TRANSIENT_LOCAL_DURABILITY_QOS;

    Topic* topic = participant->create_topic("MessageTopic", type.get_type_name(), topic_qos);
    std::cout<<type.get_type_name()<<std::endl;


    // 创建数据写入者
    MyPubListener Mypl;
    DataWriterQos writer_qos = DATAWRITER_QOS_DEFAULT;
    writer_qos.durability().kind = TRANSIENT_LOCAL_DURABILITY_QOS;
    writer_qos.reliability().kind = RELIABLE_RELIABILITY_QOS;
    writer_qos.reliability().max_blocking_time = {5, 0};
    writer_qos.history().depth = 5;
    DataWriter* writer = publisher->create_datawriter(topic, writer_qos,&Mypl);

    // 发布数据
    Example::Message msg;
    msg.text("Hello");
    for (long i = 1; i <= 10; ++i) {
        while(Mypl.matched_==0)
        {
            std::cout<<"wait"<<std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
        msg.id(i);
        writer->write(&msg);
        std::cout << "Published message with id: " << i << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }

    // 清理资源
    publisher->delete_datawriter(writer);
    participant->delete_publisher(publisher);
    participant->delete_topic(topic);
    DomainParticipantFactory::get_instance()->delete_participant(participant);

    return 0;
}

HelloWorldSubscriber.cpp

#include "ExamplePubSubTypes.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/subscriber/DataReader.hpp>
#include <fastdds/dds/subscriber/DataReaderListener.hpp>
#include <fastdds/dds/subscriber/qos/DataReaderQos.hpp>
#include <fastdds/dds/subscriber/SampleInfo.hpp>
#include <fastdds/dds/subscriber/Subscriber.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
#include <fastdds/dds/core/policy/QosPolicies.hpp>
#include <fastrtps/transport/TCPv4TransportDescriptor.h>

using namespace eprosima::fastdds::dds;
using namespace eprosima::fastrtps::rtps;

class MyReaderListener : public DataReaderListener 
{
public:
    void on_subscription_matched(
            DataReader* reader,
            const SubscriptionMatchedStatus& info) override
    {
        if (info.current_count_change == 1)
        {
            std::cout << "Subscriber matched." << std::endl;
        }
        else if (info.current_count_change == -1)
        {
            std::cout << "Subscriber unmatched." << std::endl;
        }
        else
        {
            std::cout << info.current_count_change
                    << " is not a valid value for SubscriptionMatchedStatus current count change" << std::endl;
        }
    }
    void on_data_available(DataReader* reader) override {
        std::cout<<"data_available"<<std::endl;
        Example::Message msg;
        SampleInfo info;
        if (reader->take_next_sample(&msg, &info) == ReturnCode_t::RETCODE_OK) {
            if (info.valid_data) {
                std::cout << "Received message with id: " << msg.id() <<"text:"<<msg.text()<< std::endl;
            }
        }
    }
};

int main()
{
    // 设置TCP传输
    DomainParticipantQos dq;

    // 初始化DDS域参与者
    DomainParticipant* participant = DomainParticipantFactory::get_instance()->create_participant(0, dq);

    // 注册类型
    TypeSupport type(new Example::MessagePubSubType());
    type.register_type(participant);

    // 创建订阅者
    Subscriber* subscriber = participant->create_subscriber(SUBSCRIBER_QOS_DEFAULT);

    // 创建主题
    TopicQos topic_qos = participant->get_default_topic_qos();
    topic_qos.durability().kind = TRANSIENT_LOCAL_DURABILITY_QOS;
    Topic* topic = participant->create_topic("MessageTopic", type.get_type_name(), topic_qos);

    // 创建数据读取者
    DataReaderQos reader_qos = DATAREADER_QOS_DEFAULT;
    reader_qos.durability().kind = TRANSIENT_LOCAL_DURABILITY_QOS;
    reader_qos.reliability().kind = RELIABLE_RELIABILITY_QOS;
    reader_qos.reliability().max_blocking_time = {5, 0};
    reader_qos.history().depth = 5;
    DataReader* reader = subscriber->create_datareader(topic, reader_qos, new MyReaderListener());

    static int count = 0;
    // 等待接收消息
    while (count<=10) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        count++;
    }

    // 清理资源
    subscriber->delete_datareader(reader);
    participant->delete_subscriber(subscriber);
    participant->delete_topic(topic);
    DomainParticipantFactory::get_instance()->delete_participant(participant);

    return 0;
}
4)运行结果

历史记录测试结果

1.2.3.xml文件配置

通过xml进行配置,效果与配置历史记录相似

1)目录结构
.
├── CMakeLists.txt
├── pub.sh
├── sub.sh
├── Readme.md(非必要)
├── restart.sh
└── src
    ├── Example.idl
    ├── toal.xml
    ├── HelloWorldPublisher.cpp
    └── HelloWorldSubscriber.cpp
2)测试时执行
chmod +x ./restart.sh
chmod +x ./pub.sh
chmod +x ./sub.sh

# 在src下根据idl文件生成所需文件
fastddsgen src/Example.idl

# 编译文件
./restart.sh

	# 本窗口
	./pub.sh
	# 另外一个窗口
	./sub.sh
3)文件内容

CMakeLists.txt、restart.sh与配置历史记录一致。
pub.sh、sub.sh不一致
Example.idl

module Example {
    struct Message {
        unsigned long id;
        string<1024> text;
    };
};

pub.sh

# /usr/bin
./build/DDSHelloWorldPublisher $(pwd)/src/total.xml

sub.sh

# /usr/bin
./build/DDSHelloWorldSubscriber $(pwd)/src/total.xml

total.xml

<?xml version="1.0" encoding="UTF-8" ?>
<dds xmlns="http://www.eprosima.com">
    <profiles>
        <domainparticipant_factory profile_name="domainparticipant_factory_profile">
            <!-- ... -->
        </domainparticipant_factory>

        <participant profile_name="participant_profile">
            <!-- ... -->
        </participant>

        <publisher profile_name="publisher_profile">
            <!-- ... -->
        </publisher>

        <subscriber profile_name="subscriber_profile">
            <!-- ... -->
        </subscriber>

        <data_writer profile_name="datawriter_profile">
            <!-- ... -->
            <topic>
                <historyQos>
                    <kind>KEEP_LAST</kind>
                    <depth>5</depth>
                </historyQos>
            </topic>
            <qos>
                <reliability>
                    <kind>RELIABLE</kind>
                    <max_blocking_time>
                        <sec>2</sec>
                    </max_blocking_time>
                </reliability>
                <durability>
                    <kind>TRANSIENT_LOCAL</kind>
                </durability> 
            </qos>
        </data_writer>

        <data_reader profile_name="datareader_profile">
            <!-- ... -->
            <topic>
                <historyQos>
                    <kind>KEEP_LAST</kind>
                    <depth>5</depth>
                </historyQos>
            </topic>
            <qos>
                <reliability>
                    <kind>RELIABLE</kind>
                    <max_blocking_time>
                        <sec>2</sec>
                    </max_blocking_time>
                </reliability>
                <durability>
                    <kind>TRANSIENT_LOCAL</kind>
                </durability> 
            </qos>
        </data_reader>

        <topic profile_name="topic_profile">
            <!-- ... -->
        </topic>

        <transport_descriptors>
            <!-- ... -->
        </transport_descriptors>
    </profiles>

    <library_settings>
        <intraprocess_delivery>FULL</intraprocess_delivery> <!-- OFF | USER_DATA_ONLY | FULL -->
    </library_settings>

    <log>
        <!-- ... -->
    </log>

    <types>
        <!-- ... -->
    </types>
</dds>

HelloWorldPublisher.cpp

#include "ExamplePubSubTypes.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <string>
#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/publisher/Publisher.hpp>
#include <fastdds/dds/publisher/DataWriter.hpp>
#include <fastdds/dds/publisher/DataWriterListener.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
#include <fastdds/dds/core/policy/QosPolicies.hpp>
#include <fastrtps/transport/TCPv4TransportDescriptor.h>

using namespace eprosima::fastdds::dds;
using namespace eprosima::fastrtps::rtps;
class MyPubListener : public DataWriterListener
{
public:
    MyPubListener()
        : matched_(0)
    {
    }

    ~MyPubListener() override
    {
    }

    void on_publication_matched(
        DataWriter *,
        const PublicationMatchedStatus &info) override
    {
        if (info.current_count_change == 1)
        {
            matched_ = info.total_count;
            std::cout << "Publisher matched." << std::endl;
        }
        else if (info.current_count_change == -1)
        {
            matched_ = info.total_count;
            std::cout << "Publisher unmatched." << std::endl;
        }
        else
        {
            std::cout << info.current_count_change
                      << " is not a valid value for PublicationMatchedStatus current count change." << std::endl;
        }
    }

    std::atomic_int matched_;
};

int main(int argc, char *argv[])
{
    std::string xml_filename = argv[1];
    MyPubListener Mypl;
    DomainParticipant *participant;
    Topic *topic;
    Publisher *publisher;
    DataWriter *writer;
    if (ReturnCode_t::RETCODE_OK ==
        DomainParticipantFactory::get_instance()->load_XML_profiles_file(xml_filename))
    {
        participant =
            DomainParticipantFactory::get_instance()->create_participant_with_profile(
                0, "participant_profile");

        TypeSupport type(new Example::MessagePubSubType());
        type.register_type(participant);
        topic = participant->create_topic("MessageTopic", type.get_type_name(), TOPIC_QOS_DEFAULT);
        publisher = participant->create_publisher_with_profile("publisher_profile");
        writer = publisher->create_datawriter_with_profile(topic, "datawriter_profile", &Mypl);
    }
    else
    {
        std::cout << "load_XML_profiles_file failed" << std::endl;
    }

    // 发布数据
    Example::Message msg;
    std::string str;
    str.assign(1024, 'A');
    msg.text(str);
    for (long i = 1; i <= 10; ++i)
    {
        while (Mypl.matched_ == 0)
        {
            std::cout << "wait" << std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
        msg.id(i);
        writer->write(&msg);
        std::cout << "Published message with id: " << i << std::endl;
        // 稍微带点时延,否则容易直接丢弃部分数据,历史记录来不及存储
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }

    // 清理资源
    publisher->delete_datawriter(writer);
    participant->delete_publisher(publisher);
    participant->delete_topic(topic);
    DomainParticipantFactory::get_instance()->delete_participant(participant);

    return 0;
}

HelloWorldSubscriber.cpp

#include "ExamplePubSubTypes.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/subscriber/DataReader.hpp>
#include <fastdds/dds/subscriber/DataReaderListener.hpp>
#include <fastdds/dds/subscriber/qos/DataReaderQos.hpp>
#include <fastdds/dds/subscriber/SampleInfo.hpp>
#include <fastdds/dds/subscriber/Subscriber.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
#include <fastdds/dds/core/policy/QosPolicies.hpp>
#include <fastrtps/transport/TCPv4TransportDescriptor.h>

const long PORT = 12345;

using namespace eprosima::fastdds::dds;
using namespace eprosima::fastrtps::rtps;

class MyReaderListener : public DataReaderListener
{
public:
    void on_subscription_matched(
        DataReader *reader,
        const SubscriptionMatchedStatus &info) override
    {
        if (info.current_count_change == 1)
        {
            std::cout << "Subscriber matched." << std::endl;
        }
        else if (info.current_count_change == -1)
        {
            std::cout << "Subscriber unmatched." << std::endl;
        }
        else
        {
            std::cout << info.current_count_change
                      << " is not a valid value for SubscriptionMatchedStatus current count change" << std::endl;
        }
    }
    void on_data_available(DataReader *reader) override
    {
        Example::Message msg;
        SampleInfo info;
        if (reader->take_next_sample(&msg, &info) == ReturnCode_t::RETCODE_OK)
        {
            if (info.valid_data)
            {
                std::cout << "Received message with id: " << msg.id() << "text size:" << msg.text().size() << std::endl;
            }
        }
    }
};

int main(int argc, char *argv[])
{
    std::string xml_filename = argv[1];
    DomainParticipant *participant;
    Topic *topic;
    Subscriber *subscriber;
    DataReader *reader;
    if (ReturnCode_t::RETCODE_OK ==
        DomainParticipantFactory::get_instance()->load_XML_profiles_file(xml_filename))
    {
        participant =
            DomainParticipantFactory::get_instance()->create_participant_with_profile(
                0, "participant_profile");

        TypeSupport type(new Example::MessagePubSubType());
        type.register_type(participant);
        topic = participant->create_topic("MessageTopic", type.get_type_name(), TOPIC_QOS_DEFAULT);
        subscriber = participant->create_subscriber_with_profile("subscriber_profile");
        reader = subscriber->create_datareader_with_profile(topic, "datareader_profile", new MyReaderListener());
    }
    else
    {
        std::cout << "load_XML_profiles_file failed" << std::endl;
    }
    static int count = 0;
    // 等待接收消息
    while (count <= 10)
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        count++;
    }

    // 清理资源
    subscriber->delete_datareader(reader);
    participant->delete_subscriber(subscriber);
    participant->delete_topic(topic);
    DomainParticipantFactory::get_instance()->delete_participant(participant);

    return 0;
}
4)运行结果

xml测试结果

1.2.4.测试框架

在上一个基础上增加了自定义生成不同大小idl的工作

1)目录结构
.
├── CMakeLists.txt
├── pub.sh
├── sub.sh
├── gen.sh
├── Readme.md(非必要)
├── restart.sh
└── src
    ├── Example.idl
    ├── toal.xml
    ├── HelloWorldPublisher.cpp
    └── HelloWorldSubscriber.cpp
2)测试时执行
chmod +x ./restart.sh
chmod +x ./pub.sh
chmod +x ./sub.sh

# 修改Example.idl中的sequence的大小
# 修改pub.sh中的PACK变量

# 在src下根据idl文件生成所需文件
./gen.sh

	# 本窗口
	./pub.sh
	# 另外一个窗口
	./sub.sh
3)文件内容

CMakeLists.txt、restart.sh与配置历史记录一致。
Example.idl

module Example {
    struct Message {
        unsigned long id;
        sequence<char,1024*1024*1024> text;
    };
};

gen.sh

#!/bin/bash
cd src &&
rm Example.h Example.cxx ExampleC* ExampleP*
fastddsgen Example.idl &&
cd .. &&
./restart.sh 

config.ini

[settings]

debug = true

pub.sh

#!/bin/bash

# 配置文件路径
CONFIG_FILE="config.ini"

# 读取配置文件中的 debug 选项
DEBUG=$(grep -E '^\s*debug\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')

if $DEBUG -eq true; then
    echo "debug: $DEBUG"
fi

# 检查参数个数
TOPIC=topic_default
NUM_PACKETS=10
PACK=$((1024*1024*1024)) #根据Example.idl更改
if [ "$#" -ne 3 ]; then
    echo "Usage: $0 <topic> <num_packets> <packet_size>"
else
    # 获取参数
    TOPIC=$1
    NUM_PACKETS=$2
    PACK=$3
fi

echo "topic: $TOPIC, num_packets: $NUM_PACKETS, packet_size: $PACK"

# 启动发布者
./build/DDSHelloWorldPublisher $(pwd)/src/total.xml $TOPIC $NUM_PACKETS $PACK

sub.sh

#!/bin/bash

# 检查参数个数
TOPIC=topic_default
NUM_PACKETS=10
if [ "$#" -ne 2 ]; then
    echo "Usage: $0 <topic> <num_packets>"
else
    # 获取参数
    TOPIC=$1
    NUM_PACKETS=$2
fi

# 启动订阅者
./build/DDSHelloWorldSubscriber $(pwd)/src/total.xml $TOPIC $NUM_PACKETS

HelloWorldPublisher.cpp

#include "ExamplePubSubTypes.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
// #include <string>

#include <signal.h>
#include <unistd.h>

#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/publisher/Publisher.hpp>
#include <fastdds/dds/publisher/DataWriter.hpp>
#include <fastdds/dds/publisher/DataWriterListener.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
#include <fastdds/dds/core/policy/QosPolicies.hpp>
#include <fastrtps/transport/TCPv4TransportDescriptor.h>

using namespace eprosima::fastdds::dds;
using namespace eprosima::fastrtps::rtps;
class MyPubListener : public DataWriterListener
{
public:
    MyPubListener()
        : matched_(0)
    {
    }

    ~MyPubListener() override
    {
    }

    void on_publication_matched(
        DataWriter *,
        const PublicationMatchedStatus &info) override
    {
        if (info.current_count_change == 1)
        {
            matched_ = info.current_count;
            std::cout << "matched: " << matched_ << std::endl;
            std::cout << "Publisher matched." << std::endl;
        }
        else if (info.current_count_change == -1)
        {
            matched_ = info.current_count;
            std::cout << "matched: " << matched_ << std::endl;
            std::cout << "Publisher unmatched." << std::endl;
        }
        else
        {
            std::cout << info.current_count_change
                      << " is not a valid value for PublicationMatchedStatus current count change." << std::endl;
        }
    }

    std::atomic_int matched_;
};

class Me_
{
public:
    Me_()
    {
        std::cout << "before main " << std::endl;
    }
    ~Me_()
    {
        std::cout << "after main" << std::endl;
    }
    char get_a()
    {
        return a;
    }
    void set_a(char a_)
    {
        a = a_;
    }

private:
    char a;
};
Me_ me;
DomainParticipant *participant = nullptr;
Topic *topic = nullptr;
Publisher *publisher = nullptr;
DataWriter *writer = nullptr;
void signal_int_handler(int signum)
{
    if (signum == SIGINT)
    {
        if (writer != nullptr)
            publisher->delete_datawriter(writer);
        if (publisher != nullptr)
            participant->delete_publisher(publisher);
        if (topic != nullptr)
            participant->delete_topic(topic);
        if (participant != nullptr)
            DomainParticipantFactory::get_instance()->delete_participant(participant);
        exit(0);
    }
}

int main(int argc, char *argv[])
{
    // Ctrl+C处理
    std::cout << "Entering main function" << std::endl;
    std::cout << "argc: " << argc << std::endl;
    for (int i = 0; i < argc; ++i)
    {
        std::cout << "argv[" << i << "]: " << argv[i] << std::endl;
    }
    signal(SIGINT, signal_int_handler);

    std::string xml_filename = argv[1];
    std::string topic_name = argv[2];
    long NUM_PACKAGE = std::stoi(argv[3]);
    long PACKAGE_SIZE = std::stoi(argv[4]);
    long COUNT = 0;
    MyPubListener Mypl;
    // 未做错误处理
    if (ReturnCode_t::RETCODE_OK ==
        DomainParticipantFactory::get_instance()->load_XML_profiles_file(xml_filename))
    {
        participant =
            DomainParticipantFactory::get_instance()->create_participant_with_profile(
                0, "participant_profile");

        TypeSupport type(new Example::MessagePubSubType());
        type.register_type(participant);
        topic = participant->create_topic(topic_name, type.get_type_name(), TOPIC_QOS_DEFAULT);
        publisher = participant->create_publisher_with_profile("publisher_profile");
        writer = publisher->create_datawriter_with_profile(topic, "datawriter_profile", &Mypl);
    }
    else
    {
        std::cout << "load_XML_profiles_file failed" << std::endl;
    }

    // 发布数据
    Example::Message msg;
    for (long i = 1; i <= NUM_PACKAGE; ++i)
    {
        while (Mypl.matched_ <= 0)
        {
            std::cout << "wait" << std::endl;
            COUNT++;
            if (COUNT >= 20)
            {
                std::cout << "timeout" << std::endl;
                goto out;
            }
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
        std::cout << "mypl.matched_: " << Mypl.matched_ << std::endl;
        msg.id(i);
        me.set_a('A' + i % 26);
        std::vector<char> str;
        if(PACKAGE_SIZE>=i) {
            str = std::vector<char>(PACKAGE_SIZE - i, me.get_a());
        }
        msg.text(str);
        writer->write(&msg);
        std::cout<<(msg.text())[0]<<std::endl;
        std::cout << "Published message with id: " << i << std::endl;
        // 稍微带点时延,否则容易直接丢弃部分数据,历史记录来不及存储
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
out:
    // 清理资源
    publisher->delete_datawriter(writer);
    participant->delete_publisher(publisher);
    participant->delete_topic(topic);
    DomainParticipantFactory::get_instance()->delete_participant(participant);

    return 0;
}

HelloWorldSubscriber.cpp

#include "ExamplePubSubTypes.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <string>

#include <signal.h>
#include <unistd.h>

#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/subscriber/DataReader.hpp>
#include <fastdds/dds/subscriber/DataReaderListener.hpp>
#include <fastdds/dds/subscriber/qos/DataReaderQos.hpp>
#include <fastdds/dds/subscriber/SampleInfo.hpp>
#include <fastdds/dds/subscriber/Subscriber.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
#include <fastdds/dds/core/policy/QosPolicies.hpp>
#include <fastrtps/transport/TCPv4TransportDescriptor.h>

using namespace eprosima::fastdds::dds;
using namespace eprosima::fastrtps::rtps;

class MyReaderListener : public DataReaderListener
{
public:
    MyReaderListener()
        : messageCount(0), matched_(0)
    {
    }
    void on_subscription_matched(
        DataReader *reader,
        const SubscriptionMatchedStatus &info) override
    {
        if (info.current_count_change == 1)
        {
            matched_ = info.current_count;
            std::cout << "Subscriber matched." << std::endl;
        }
        else if (info.current_count_change == -1)
        {
            matched_ = info.current_count;
            std::cout << "Subscriber unmatched." << std::endl;
        }
        else
        {
            std::cout << info.current_count_change
                      << " is not a valid value for SubscriptionMatchedStatus current count change" << std::endl;
        }
    }
    void on_data_available(DataReader *reader) override
    {
        Example::Message msg;
        SampleInfo info;
        if (reader->take_next_sample(&msg, &info) == ReturnCode_t::RETCODE_OK)
        {
            if (info.valid_data)
            {
                messageCount++;
                std::cout << "Received message with id: " << msg.id() << "text size:" << msg.text().size() << std::endl;
                std::cout << "First char: " << (msg.text())[0] << "..." << std::endl;
            }
        }
    }
    std::atomic_int messageCount;
    std::atomic_int matched_;
};
DomainParticipant *participant;
Topic *topic;
Subscriber *subscriber;
DataReader *reader;
MyReaderListener *listener = new MyReaderListener();
TypeSupport type(new Example::MessagePubSubType());

void cleanup_resources()
{
    if (reader != nullptr)
        subscriber->delete_datareader(reader);
    if (subscriber != nullptr)
        participant->delete_subscriber(subscriber);
    if (topic != nullptr)
        participant->delete_topic(topic);
    if (participant != nullptr)
        DomainParticipantFactory::get_instance()->delete_participant(participant);
    delete listener;
}
void signal_int_handler(int signum)
{
    if (signum == SIGINT)
    {
        cleanup_resources();
        exit(0);
    }
}
int main(int argc, char *argv[])
{
    std::cout << "Entering main function" << std::endl;
    std::cout << "argc: " << argc << std::endl;
    for (int i = 0; i < argc; ++i)
    {
        std::cout << "argv[" << i << "]: " << argv[i] << std::endl;
    }
    if (argc < 4)
    {
        std::cerr << "Usage: " << argv[0] << " <xml_filename>" << std::endl;
        return 0; // 返回错误码
    }
    // Ctrl+C处理
    signal(SIGINT, signal_int_handler);

    std::string xml_filename = argv[1];
    std::string topic_name = argv[2];
    int NUM_PACKAGE = std::stoi(argv[3]);
    int COUNT = 0;

    if (ReturnCode_t::RETCODE_OK ==
        DomainParticipantFactory::get_instance()->load_XML_profiles_file(xml_filename))
    {
        participant =
            DomainParticipantFactory::get_instance()->create_participant_with_profile(
                0, "participant_profile");

        type.register_type(participant);
        topic = participant->create_topic_with_profile(topic_name, type.get_type_name(), "topic_profile");
        if (topic == nullptr)
        {
            std::cout << "create_topic failed" << std::endl;
        }
        subscriber = participant->create_subscriber_with_profile("subscriber_profile");
        if (subscriber == nullptr)
        {
            std::cout << "create_subscriber failed" << std::endl;
        }
        reader = subscriber->create_datareader_with_profile(topic, "datareader_profile", listener);
        if (reader == nullptr)
        {
            std::cout << "create_datareader failed" << std::endl;
        }
    }
    else
    {
        std::cout << "load_XML_profiles_file failed" << std::endl;
    }
    std::cout << "Waiting for Data..." << std::endl;
    // 等待接收消息
    while (listener->messageCount < NUM_PACKAGE && COUNT <= 20)
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        if (listener->matched_ == 0)
        {
            COUNT++;
        }
        std::cout << "wait" << std::endl;
    }

    // 清理资源
    cleanup_resources();
    return 0;
}

total.xml

<?xml version="1.0" encoding="UTF-8" ?>
<dds xmlns="http://www.eprosima.com">
    <profiles>
        <domainparticipant_factory profile_name="domainparticipant_factory_profile">
            <!-- ... -->
        </domainparticipant_factory>

        <participant profile_name="participant_profile">
            <!-- ... -->
        </participant>

        <publisher profile_name="publisher_profile">
            <!-- ... -->
        </publisher>

        <subscriber profile_name="subscriber_profile">
            <!-- ... -->
        </subscriber>

        <data_writer profile_name="datawriter_profile">
            <!-- ... -->
        </data_writer>

        <data_reader profile_name="datareader_profile">
            <!-- ... -->
        </data_reader>

        <topic profile_name="topic_profile">
            <!-- ... -->
        </topic>

        <transport_descriptors>
            <!-- ... -->
        </transport_descriptors>
    </profiles>

    <library_settings>
        <intraprocess_delivery>OFF</intraprocess_delivery> <!-- OFF | USER_DATA_ONLY | FULL(default) -->
    </library_settings>

    <log>
        <!-- ... -->
    </log>

    <types>
        <!-- ... -->
    </types>
</dds>
4)测试结果

测试框架测试结果

1.2.5.完整测试框架

这一项需要Python环境
在测试框架的基础上,加上CPU占用,内存占用数据的提取,并可以实现自定义数据包大小的填充,
因为这是一个完整的框架,所以在此基础上会附上所有文件的代码,
因为需要自定义数据包,Example.idl会在start_test.sh中自动生成,故目录结构中不再列出

1)目录结构
.
├── CMakeLists.txt
├── config.ini
├── idls
│   └── gen_idl.sh
├── monitor.py
├── pub.sh
├── Readme.md(非必要)
├── restart.sh
├── result
│   ├── img
│   └── log
├── src
│   ├── HelloWorldPublisher.cpp
│   ├── HelloWorldSubscriber.cpp
│   └── total.xml
├── start_test.sh
└── sub.sh
2)测试时执行
./start_test.sh
# 执行过程中需要根据需求按y/n继续
# 更改config.ini中的数据量时,需要重新覆盖,并重新编译
Do you want to overwrite it? (y/n): y
Do you want to recompile it? (recommend)(y/n):y
3)文件内容

CMakeLists.txt

cmake_minimum_required(VERSION 3.20)

project(DDSHelloWorld)

# 添加可调式选项
set(CMAKE_BUILD_TYPE Debug)

if(NOT fastrtps_FOUND)
    find_package(fastrtps REQUIRED)
endif()
if(NOT fastcdr_FOUND)
    find_package(fastcdr REQUIRED)
endif()

# Set c++11
include(CheckCXXCompilerFlag)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG OR
        CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    check_cxx_compiler_flag(-std=c++11 SUPPORTS_CXX11)
    if(SUPPORTS_CXX11)
        add_compile_options(-std=c++11)
    else()
        message(FATAL_ERROR "Compiler doesn't support C++11")
    endif()
endif()

message(STATUS "Configuring HelloWorld publisher/subscriber example...")
# 注意 将src/*.cxx添加到DDS_HELLOWORLD_SOURCES_CXX变量中
file(GLOB DDS_HELLOWORLD_SOURCES_CXX "src/*.cxx")


add_executable(DDSHelloWorldPublisher src/HelloWorldPublisher.cpp ${DDS_HELLOWORLD_SOURCES_CXX})
target_link_libraries(DDSHelloWorldPublisher fastrtps fastcdr)
add_executable(DDSHelloWorldSubscriber src/HelloWorldSubscriber.cpp ${DDS_HELLOWORLD_SOURCES_CXX})
target_link_libraries(DDSHelloWorldSubscriber fastrtps fastcdr)

config.ini

# 配置文件中大项一律大写,子项一律小写

[DEBUG]
debug=true

[LAUNCH]
num_group=1
num_pub=1
num_packets_pub=50
num_sub=1
num_packets_sub=50
# SIZE_PACKET 1024 1024*100 1024*1024 1024*1024*100 1024*1024*1024 可自定义
size_packet=1024*1024*100
xml_file=src/total.xml
# ROLE pub sub 二选一
role=sub

gen_idl.sh

fastddsgen idl_*/Example.idl

montior.py

import os
import time
import psutil
import matplotlib.pyplot as plt
import argparse
from concurrent.futures import ThreadPoolExecutor

def get_process_info(pid):
    try:
        process = psutil.Process(pid)
        cpu_percent = process.cpu_percent(interval=1)
        memory_info = process.memory_info()
        memory_usage = memory_info.rss / (1024 * 1024)  # Convert to MB
        return cpu_percent, memory_usage
    except psutil.NoSuchProcess:
        return None, None

def monitor_process(pid, interval=1, duration=60):
    cpu_data = []
    memory_data = []
    timestamps = []

    start_time = time.time()
    while time.time() - start_time < duration:
        current_time = time.strftime('%H:%M:%S', time.localtime())
        timestamps.append(current_time)
        cpu, memory = get_process_info(pid)
        if cpu is not None and memory is not None:
            cpu_data.append(cpu)
            memory_data.append(memory)
        else:
            cpu_data.append(None)
            memory_data.append(None)
        time.sleep(interval)

    return timestamps, {pid: cpu_data}, {pid: memory_data}  # 返回字典

def plot_data(timestamps, cpu_data, memory_data, pid, output_file):
    fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))

    # 过滤掉 None 值
    filtered_timestamps = [t for t, c in zip(timestamps, cpu_data[pid]) if c is not None]
    filtered_cpu_data = [c for c in cpu_data[pid] if c is not None]
    filtered_memory_data = [m for m in memory_data[pid] if m is not None]

    ax1.plot(filtered_timestamps, filtered_cpu_data)
    ax1.set_title(f'CPU Usage Over Time (PID {pid})')
    ax1.set_xlabel('Time')
    ax1.set_ylabel('CPU Usage (%)')

    ax2.plot(filtered_timestamps, filtered_memory_data)
    ax2.set_title(f'Memory Usage Over Time (PID {pid})')
    ax2.set_xlabel('Time')
    ax2.set_ylabel('Memory Usage (MB)')

    plt.tight_layout()
    plt.savefig(output_file)
    plt.close(fig)

def get_pids_with_keyword(keyword, process_type):
    pids = []
    for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
        try:
            if keyword in proc.name() or (proc.cmdline() and keyword in ' '.join(proc.cmdline())):
                if process_type in proc.name() or (proc.cmdline() and process_type in ' '.join(proc.cmdline())):
                    pids.append(proc.pid)
        except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
            continue
    return pids

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Monitor CPU and memory usage of DDS processes.")
    parser.add_argument("--interval", type=int, default=1, help="Sampling interval in seconds (default: 1)")
    parser.add_argument("--total_time", type=int, default=10, help="Total monitoring time in seconds (default: 10)")
    parser.add_argument("--size", type=int, default=10, help="package_size ONLY USED TO mkdir (default: 10)")

    args = parser.parse_args()

    # 获取包含 DDS 关键字的 pub 和 sub 进程的 PID
    pub_pids = get_pids_with_keyword('DDS', 'Pub')
    sub_pids = get_pids_with_keyword('DDS', 'Sub')
    all_pids = pub_pids + sub_pids

    if not all_pids:
        print("No DDS processes found.")
        exit(1)

    # 使用 ThreadPoolExecutor 并行监控进程
    with ThreadPoolExecutor(max_workers=len(all_pids)) as executor:
        futures = {executor.submit(monitor_process, pid, args.interval, args.total_time): pid for pid in all_pids}
        results = {pid: future.result() for future, pid in futures.items()}

    img_dir = os.path.join('./result/img', str(args.size))
    os.makedirs(img_dir, exist_ok=True)

    # 绘制并保存数据
    for pid, (timestamps, cpu_data, memory_data) in results.items():
        if pid in pub_pids:
            output_file = os.path.join(img_dir, f'pub_{pid}.png')
            plot_data(timestamps, cpu_data, memory_data, pid, output_file)
        elif pid in sub_pids:
            output_file = os.path.join(img_dir, f'sub_{pid}.png')
            plot_data(timestamps, cpu_data, memory_data, pid, output_file)

pub.sh

#!/bin/bash

# 配置文件路径
CONFIG_FILE="config.ini"
# 检查配置文件是否存在
if [ ! -f "$CONFIG_FILE" ]; then
    echo "Config file not found: $CONFIG_FILE"
    exit 1
fi
# 读取配置文件中的 debug 选项
DEBUG=$(grep -E '^\s*debug\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')

# 检查参数个数
XML_FILE=$(pwd)/src/total.xml
TOPIC=topic_default
NUM_PACKETS=10
PACK=$((1024))
echo "$#"
MAX_PARAMS=10
if [ "$#" -ne 4 ]; then
    if  $DEBUG -eq true; then
        echo "Usage: $0 <xml_file> <topic> <num_packets> <packet_size>"
        # 打印传入参数
        for ((i=1; i<=MAX_PARAMS && i<=$#; i++)); do
            eval param=\$$i
            echo "Parameter $i: $param"
        done
    fi
else
    # 获取参数
    XML_FILE=$1
    TOPIC=$2
    NUM_PACKETS=$3
    PACK=$4
fi

if  $DEBUG -eq true; then
    echo "xml: $XML_FILE, topic: $TOPIC, num_packets: $NUM_PACKETS, packet_size: $PACK"
fi

# 切换到 build 目录
cd build

# 启动发布者
./DDSHelloWorldPublisher $XML_FILE $TOPIC $NUM_PACKETS $PACK

restart.sh

# /usr/bin
rm -rf ./build
mkdir build
cd build 
cmake ..
cmake --build .

start_test.sh

#!/bin/bash

# 读取配置文件中的参数
CONFIG_FILE="config.ini"
# 检查配置文件是否存在
if [ ! -f "$CONFIG_FILE" ]; then
    echo "Config file not found: $CONFIG_FILE"
    exit 1
fi
# 默认参数
DEBUG=$(grep -E '^\s*debug\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')
NUM_GROUP=$(grep -E '^\s*num_group\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')
NUM_PUB=$(grep -E '^\s*num_pub\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')
NUM_PACKETS_PUB=$(grep -E '^\s*num_packets_pub\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')
NUM_SUB=$(grep -E '^\s*num_sub\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')
NUM_PACKETS_SUB=$(grep -E '^\s*num_packets_sub\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')
SIZE_PACKET=$(grep -E '^\s*size_packet\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')
XML_FILE=$(pwd)/$(grep -E '^\s*xml_file\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')
ROLE=$(grep -E '^\s*role\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')


# 迁移idl库并重新编译
M_SIZE=$((SIZE_PACKET))

if $DEBUG -eq true; then
    echo "DEBUG: $DEBUG"
    echo "NUM_GROUP: $NUM_GROUP"
    echo "NUM_PUB: $NUM_PUB"
    echo "NUM_PACKETS_PUB: $NUM_PACKETS_PUB"
    echo "NUM_SUB: $NUM_SUB"
    echo "NUM_PACKETS_SUB: $NUM_PACKETS_SUB"
    echo "SIZE_PACKET: $SIZE_PACKET"
    echo "XML_FILE: $XML_FILE"
    echo "ROLE: $ROLE"
    echo "M_SIZE: $M_SIZE"
fi

SOURCE_FILE="$(pwd)/idls/idl_$M_SIZE"
DEST_FILE="$(pwd)/src/"
# 循环询问用户是否覆盖
while true; do
    read -p "Destination file already exists: $DEST_FILE. Do you want to overwrite it? (y/n): " choice
    case "$choice" in
        y|Y )
            CONTENT="module Example {\n\t
                struct Message {\n\t\t
                    unsigned long id;\n\t\t
                    sequence<char,$M_SIZE> text;\n\t
                };\n
            };\n
            "
            mkdir -p "$SOURCE_FILE"
            echo -e $CONTENT > "$SOURCE_FILE"/Example.idl
            fastddsgen "$SOURCE_FILE/Example.idl"
            cp -f "$SOURCE_FILE"/* "$DEST_FILE"
            echo "Files copied and overwritten."
            break
            ;;
        n|N )
            echo "Files not copied.Use previous config."
            break
            ;;
        * )
            echo "Invalid input. Please enter 'y' or 'n'."
            ;;
    esac
done
# 重新编译
while true; do
    read -p "Do you want to recompile it? (recommend)(y/n): " choice
    case "$choice" in
        y|Y )
            ./restart.sh
            echo "Files recompiled."
            break
            ;;
        n|N )
            echo "Warning: No files recompiled.Files copied but not make sense."
            break
            ;;
        * )
            echo "Invalid input. Please enter 'y' or 'n'."
            ;;
    esac
done

# 检查参数个数
if $DEBUG -eq true; then
    if [ "$#" -ne 6 ]; then
        echo "Usage: $0 <num_group> <num_pub> <num_packets_pub> <num_sub> <num_packets_sub> <size_packet>"
        echo "use default params:"
        echo "Please find in config.ini."
    fi
        echo "----------"
fi


mkdir -p ./result/log/$((SIZE_PACKET))
echo $ROLE
for ((i=1; i<=NUM_GROUP; i++)); do
    if [[ "$ROLE" == "pub" ]]; then
        # 启动发布者
        for ((j=1; j<=NUM_PUB; j++)); do
            (./pub.sh $XML_FILE "Topic_${i}" $NUM_PACKETS_PUB $((SIZE_PACKET))) > ./result/log/$((SIZE_PACKET))/pub_${i}_${j}.log &
        done
    elif [[ "$ROLE" == "sub" ]]; then
        # 启动订阅者
        for ((j=1; j<=NUM_SUB; j++)); do
            (./sub.sh $XML_FILE "Topic_${i}" $NUM_PACKETS_SUB) > ./result/log/$((SIZE_PACKET))/sub_${i}_${j}.log &
        done
    fi
done

echo "Running ... Result in result directory."
python3 monitor.py --total_time=$(($NUM_PACKETS_PUB*2)) --size=$((SIZE_PACKET)) &

# 等待所有子进程完成
wait

echo "All processes have finished."

sub.sh

#!/bin/bash

# 配置文件路径
CONFIG_FILE="config.ini"
# 检查配置文件是否存在
if [ ! -f "$CONFIG_FILE" ]; then
    echo "Config file not found: $CONFIG_FILE"
    exit 1
fi
# 读取配置文件中的 debug 选项
DEBUG=$(grep -E '^\s*debug\s*=' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d '[:space:]')

# 检查参数个数
TOPIC=topic_default
NUM_PACKETS=10
XML_FILE=$(pwd)/src/total.xml
if [ "$#" -ne 3 ]; then
    if  $DEBUG -eq true; then
        echo "Usage: $0 <xml_file> <topic> <num_packets>"
    fi
else
    # 获取参数
    XML_FILE=$1
    TOPIC=$2
    NUM_PACKETS=$3
fi

# 切换到 build 目录
cd build

# 启动订阅者
./DDSHelloWorldSubscriber $XML_FILE $TOPIC $NUM_PACKETS

HelloWorldPublisher.cpp

#include "ExamplePubSubTypes.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>

#include <signal.h>
#include <unistd.h>

#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/publisher/Publisher.hpp>
#include <fastdds/dds/publisher/DataWriter.hpp>
#include <fastdds/dds/publisher/DataWriterListener.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
#include <fastdds/dds/core/policy/QosPolicies.hpp>
#include <fastrtps/transport/TCPv4TransportDescriptor.h>

using namespace eprosima::fastdds::dds;
using namespace eprosima::fastrtps::rtps;
class MyPubListener : public DataWriterListener
{
public:

    MyPubListener()
        : matched_(0)
    {
    }

    ~MyPubListener() override
    {
    }

    void on_publication_matched(
            DataWriter*,
            const PublicationMatchedStatus& info) override
    {
        if (info.current_count_change == 1)
        {
            matched_ = info.current_count;
            std::cout << "matched: " << matched_ << std::endl;
            std::cout << "Publisher matched." << std::endl;
        }
        else if (info.current_count_change == -1)
        {
            matched_ = info.current_count;
            std::cout << "matched: " << matched_ << std::endl;
            std::cout << "Publisher unmatched." << std::endl;
        }
        else
        {
            std::cout << info.current_count_change
                    << " is not a valid value for PublicationMatchedStatus current count change." << std::endl;
        }
    }

    std::atomic_int matched_;

};

class Me_{
public:
    Me_()
    {
        std::cout<<"before main "<<std::endl;
    }
    ~Me_()
    {
        std::cout<<"after main"<<std::endl;
    }
    char get_a()
    {
        return a;
    }
    void set_a(char a_)
    {
        a=a_;
    }
private:
    char a;
};
Me_ me;
DomainParticipant* participant=nullptr;
Topic* topic=nullptr;
Publisher* publisher=nullptr;
DataWriter* writer=nullptr;
void signal_int_handler(int signum)
{
    if(signum == SIGINT)
    {
        if(writer!=nullptr)
            publisher->delete_datawriter(writer);
        if(publisher!=nullptr)
            participant->delete_publisher(publisher);
        if(topic!=nullptr)
            participant->delete_topic(topic);
        if(participant!=nullptr)
            DomainParticipantFactory::get_instance()->delete_participant(participant);
        exit(0);
    }
}

int main(int argc, char* argv[])
{
    //Ctrl+C处理
    std::cout << "Entering main function" << std::endl;
    std::cout << "argc: " << argc << std::endl;
    for (int i = 0; i < argc; ++i) {
        std::cout << "argv[" << i << "]: " << argv[i] << std::endl;
    }
    signal(SIGINT, signal_int_handler);

    std::string xml_filename = argv[1];
    std::string topic_name = argv[2];
    long NUM_PACKAGE = std::stoi(argv[3]);
    long PACKAGE_SIZE = std::stoi(argv[4]);
    long COUNT = 0;
    MyPubListener Mypl;
    //未做错误处理
    if (ReturnCode_t::RETCODE_OK ==
            DomainParticipantFactory::get_instance()->load_XML_profiles_file(xml_filename))
    {
        participant =
                DomainParticipantFactory::get_instance()->create_participant_with_profile(
            0, "participant_profile");

        TypeSupport type(new Example::MessagePubSubType());
        type.register_type(participant);
        topic = participant->create_topic(topic_name, type.get_type_name(), TOPIC_QOS_DEFAULT);
        publisher = participant->create_publisher_with_profile("publisher_profile");
        writer = publisher->create_datawriter_with_profile(topic, "datawriter_profile",&Mypl);
    }
    else
    {
        std::cout << "load_XML_profiles_file failed" << std::endl;
    }

    // 发布数据
    Example::Message msg;
    for (long i = 1; i <= NUM_PACKAGE; ++i) {
        while(Mypl.matched_<=0)
        {
            std::cout<<"wait"<<std::endl;
            COUNT++;
            if(COUNT>=100)
            {
                std::cout<<"timeout"<<std::endl;
                goto out;
            }
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
        std::cout<<"mypl.matched_: "<<Mypl.matched_<<std::endl;
        msg.id(i);
        me.set_a('A'+i%26);
        std::vector<char> str;
        if(PACKAGE_SIZE>=i) {
            str = std::vector<char>(PACKAGE_SIZE - i, me.get_a());
        }
        msg.text(str);
        writer->write(&msg);
        std::cout << "Published message with id: " << i << std::endl;
        //稍微带点时延,否则容易直接丢弃部分数据,历史记录来不及存储
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
out:
    // 清理资源
    publisher->delete_datawriter(writer);
    participant->delete_publisher(publisher);
    participant->delete_topic(topic);
    DomainParticipantFactory::get_instance()->delete_participant(participant);

    return 0;
}

HelloWorldSubscriber.cpp

#include "ExamplePubSubTypes.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <string>

#include <signal.h>
#include <unistd.h>

#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/subscriber/DataReader.hpp>
#include <fastdds/dds/subscriber/DataReaderListener.hpp>
#include <fastdds/dds/subscriber/qos/DataReaderQos.hpp>
#include <fastdds/dds/subscriber/SampleInfo.hpp>
#include <fastdds/dds/subscriber/Subscriber.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
#include <fastdds/dds/core/policy/QosPolicies.hpp>
#include <fastrtps/transport/TCPv4TransportDescriptor.h>


using namespace eprosima::fastdds::dds;
using namespace eprosima::fastrtps::rtps;

class MyReaderListener : public DataReaderListener 
{
public:

    MyReaderListener()
        : messageCount(0)
        , matched_(0)
    {
    }
    void on_subscription_matched(
            DataReader* reader,
            const SubscriptionMatchedStatus& info) override
    {
        if (info.current_count_change == 1)
        {
            matched_=info.current_count;
            std::cout << "Subscriber matched." << std::endl;
        }
        else if (info.current_count_change == -1)
        {
            matched_=info.current_count;
            std::cout << "Subscriber unmatched." << std::endl;
        }
        else
        {
            std::cout << info.current_count_change
                    << " is not a valid value for SubscriptionMatchedStatus current count change" << std::endl;
        }
    }
    void on_data_available(DataReader* reader) override {
        Example::Message msg;
        SampleInfo info;
        if (reader->take_next_sample(&msg, &info) == ReturnCode_t::RETCODE_OK) {
            if (info.valid_data) {
                messageCount++;
                std::cout << "Received message with id: " << msg.id() <<"text size:"<<msg.text().size()<< std::endl;
                std::cout << "First char: "<<(msg.text())[0]<<"..."<<std::endl;
            } else {
                std::cout<<"invalid_data"<<std::endl;
            }
        } else {
            std::cout<<"take_next_sample ERROR"<<std::endl;
        }
    }
    std::atomic_int messageCount;
    std::atomic_int matched_;
};
DomainParticipant* participant;
Topic* topic;
Subscriber* subscriber;
DataReader* reader;
MyReaderListener* listener = new MyReaderListener();
TypeSupport type(new Example::MessagePubSubType());

void cleanup_resources()
{
    if(reader != nullptr)
        subscriber->delete_datareader(reader);
    if(subscriber != nullptr)
        participant->delete_subscriber(subscriber);
    if(topic != nullptr)
        participant->delete_topic(topic);
    if(participant != nullptr)
        DomainParticipantFactory::get_instance()->delete_participant(participant);
    delete listener;
}
void signal_int_handler(int signum)
{
    if(signum == SIGINT)
    {
        cleanup_resources();
        exit(0);
    }
}
int main(int argc, char* argv[])
{
    std::cout << "Entering main function" << std::endl;
    std::cout << "argc: " << argc << std::endl;
    for (int i = 0; i < argc; ++i) {
        std::cout << "argv[" << i << "]: " << argv[i] << std::endl;
    }
    if (argc < 4) {
        std::cerr << "Usage: " << argv[0] << " <xml_filename>" << std::endl;
        return 0;  // 返回错误码
    }
    //Ctrl+C处理
    signal(SIGINT, signal_int_handler);

    std::string xml_filename = argv[1];
    std::string topic_name = argv[2];
    int NUM_PACKAGE = std::stoi(argv[3]);
    int COUNT=0;
    
    if (ReturnCode_t::RETCODE_OK ==
            DomainParticipantFactory::get_instance()->load_XML_profiles_file(xml_filename))
    {
        participant =
                DomainParticipantFactory::get_instance()->create_participant_with_profile(
            0, "participant_profile");

        type.register_type(participant);
        topic = participant->create_topic_with_profile(topic_name, type.get_type_name(), "topic_profile");
        if(topic==nullptr)
        {
            std::cout << "create_topic failed" << std::endl;
        }
        subscriber = participant->create_subscriber_with_profile("subscriber_profile");
        if(subscriber==nullptr)
        {
            std::cout << "create_subscriber failed" << std::endl;
        }
        reader = subscriber->create_datareader_with_profile(topic, "datareader_profile",listener);
        if(reader==nullptr)
        {
            std::cout << "create_datareader failed" << std::endl;
        }
    }
    else
    {
        std::cout << "load_XML_profiles_file failed" << std::endl;
    }
    std::cout << "Waiting for Data..." << std::endl;
    // 等待接收消息
    while (listener->messageCount<NUM_PACKAGE&&COUNT<=100) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        if(listener->matched_==0)
        {
            COUNT++;
        }
        else{
            COUNT = 0;
        }
        std::cout<<"wait"<<COUNT<<std::endl;
    }

    // 清理资源
    cleanup_resources();
    return 0;
}

total.xml

<?xml version="1.0" encoding="UTF-8" ?>
<dds xmlns="http://www.eprosima.com">
    <profiles>
        <domainparticipant_factory profile_name="domainparticipant_factory_profile">
            <!-- ... -->
        </domainparticipant_factory>

        <participant profile_name="participant_profile">
            <!-- ... -->
        </participant>

        <publisher profile_name="publisher_profile">
            <!-- ... -->
        </publisher>

        <subscriber profile_name="subscriber_profile">
            <!-- ... -->
        </subscriber>

        <data_writer profile_name="datawriter_profile">
            <!-- ... -->
        </data_writer>

        <data_reader profile_name="datareader_profile">
            <!-- ... -->
        </data_reader>

        <topic profile_name="topic_profile">
            <!-- ... -->
        </topic>

        <transport_descriptors>
            <!-- ... -->
        </transport_descriptors>
    </profiles>

    <library_settings>
        <intraprocess_delivery>FULL</intraprocess_delivery>
    </library_settings>

    <log>
        <!-- ... -->
    </log>

    <types>
        <!-- ... -->
    </types>
</dds>
4) 测试结果

/log/<数据量大小>下的两个日志文件的内容如下表

pub_1_1sub_1_1
xml: /home/gh/workspace/Testfastdds5_test_v3_bak2/src/total.xml, topic: Topic_1, num_packets: 50, packet_size: 104857600
before main
Entering main function
argc: 5
argv[0]: ./DDSHelloWorldPublisher
argv[1]: /home/gh/workspace/Testfastdds5_test_v3_bak2/src/total.xml
argv[2]: Topic_1
argv[3]: 50
argv[4]: 104857600
matched: 1
Publisher matched.
mypl.matched_: 1
Published message with id: 1
mypl.matched_: 1
Published message with id: 2
mypl.matched_: 1
Entering main function
argc: 4
argv[0]: ./DDSHelloWorldSubscriber
argv[1]: /home/gh/workspace/Testfastdds5_test_v3_bak/src/total.xml
argv[2]: Topic_1
argv[3]: 50
Waiting for Data…
wait1
wait2
wait3
Subscriber matched.
wait0
Received message with id: 1text size:104857599
First char: B…
wait0
Received message with id: 2text size:104857598
First char: C…

/img/<数据量大小>下有测试图像
Sub的测试结果
Sub测试结果

Pub的测试结果

Pub测试结果

2.参考

官方文档

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值