OpenHarmony 源码解析之DFX子系统-Hiview(上) 原创 精华

发布于 2022-1-6 15:04
浏览
4收藏

作者:吴文璐

1 简介

DFX(Design for X)子系统是为了提升软件质量设计的工具集,目前包含的内容主要有:DFR(Design for Reliability,可靠性)和DFT(Design for Testability,可测试性)特性。
已实现以下功能:

  • HiLog:流水日志。
  • HiSysEvent:系统事件记录接口。
  • HiView:插件平台。
  • FaultLoggerd:应用故障订阅和收集。
  • HiAppEvent: js应用事件记录接口。

1.1 OpenHarmony架构图

OpenHarmony 源码解析之DFX子系统-Hiview(上)-开源基础软件社区

1.2 Hiview简介

Hiview是一个跨平台的终端设备维测服务集。目前开源部分仅包含插件管理平台和系统事件源。

Hiview架构图如下:
OpenHarmony 源码解析之DFX子系统-Hiview(上)-开源基础软件社区

Hiview由框架和插件组成,分别为:

  • 操作系统适配层(adapter),对使用的系统服务的接口进行适配。
  • Hiview基础定义(hiview base),包括插件基类、管道的定义,事件、事件队列定义以及一些工具类。
  • Hiview的核心模块(hiview core),包括插件配置,插件管理以及事件源。
  • Hiview服务(hiview services),目前仅包括hiview运行信息dump功能。
  • Hiview插件(plugins),为独立功能的业务模块。

Hiview维测服务是由事件驱动的,其核心为分布在系统各处的HiSysEvent桩点。
格式化的事件通过HiSysEvent API上报至hiview进行处理,如下图:
OpenHarmony 源码解析之DFX子系统-Hiview(上)-开源基础软件社区

  • 应用框架、系统服务使用HiSysEvent组件上报系统事件。
  • Hiview中SysEventSource获取消息,解析并组装成管道事件分发给插件处理。

1.3 Hiview代码目录

/base/hiviewdfx/hiview.
├── adapter             #平台适配
│   ├── service        #服务适配
│   └── system_service #系统接口适配
├── base                #模块定义,工具类
│   └── utility
├── build               #编译脚本
├── core                #插件管理核心代码
├── include             #公共定义
├── plugins             #插件
├── service             #HiviewService服务
└── utility             #工具类

2 源码分析

本文主要分析hiview插件管理平台的初始化,和事件处理流程。

hiview是个常驻服务,在开机阶段启动。
base/hiviewdfx/hiview/service/config/hiview.cfg定义如下

{
    "jobs" : [{
            "name" : "post-fs-data",
            "cmds" : [
                "mkdir /data/log/ 0770 system log",
                "mkdir /data/log/faultlog/ 0770 system system",
                "mkdir /data/log/faultlog/temp/ 0770 system system",
                "mkdir /data/log/faultlog/faultlogger/ 0770 system system",
                "start hiview"
            ]
        }
    ],
    "services" : [{
            "name" : "hiview",
            "path" : ["/system/bin/hiview"],
            "uid" : "system",
            "gid" : ["system", "log"],
            "writepid" : [
                "/dev/cpuset/system-background/tasks"
            ],
            "socket" : [
                "hisysevent dgram 0666 root system passcred"
            ]
        }
    ]
}

2.1 初始化

hiview的入口函数定义在base/hiviewdfx/hiview/main.cpp中

int main(int argc __UNUSED, char* argv[] __UNUSED)
{
    auto& hiview = OHOS::HiviewDFX::HiviewPlatform::GetInstance();
    // process cmdline
    hiview.ProcessArgsRequest(argc, argv);

    // 初始化环境,主要解析配置文件,加载插件
    if (!hiview.InitEnvironment()) {
        HIVIEW_LOGW("Fail to init plugin environment. exit");
        return -1;
    }

    // 启动HiviewService服务,该服务提供了hiview运行信息dump功能
    auto hiviewService = std::make_unique<OHOS::HiviewDFX::HiviewService>();
    hiviewService->StartService();
    return 0;
}

HiviewPlatform::InitEnvironment()函数实现如下:

bool HiviewPlatform::InitEnvironment(const std::string& defaultDir, const std::string& cloudUpdateDir,
                                     const std::string& workDir, const std::string& persistDir)
{
    // 创建工作目录,目前目录名使用了默认的空字符串,所以并未实际创建工作目录
    ValidateAndCreateDirectories(defaultDir, cloudUpdateDir, workDir, persistDir);

    // update beta config
    UpdateBetaConfigIfNeed();

    // check whether hiview is already started
    ExitHiviewIfNeed();

    // 解析"/system/etc/hiview/plugin_config"插件配置文件
    std::string cfgPath = GetPluginConfigPath();
    PluginConfig config(cfgPath);
    if (!config.StartParse()) { //...........注[1]
        HIVIEW_LOGE("Fail to parse plugin config. exit!");
        return false;
    }
    // 启动插件管理平台消息队列
    StartPlatformDispatchQueue();

    // init global context helper, remove in the future
    HiviewGlobal::CreateInstance(static_cast<HiviewContext&>(*this));
    //加载插件
    LoadBusinessPlugin(config);

    isReady_ = true;
    NotifyPluginReady();
    return true;
}

注[1]处PluginConfig::StartParse()函数会按照特定规则去解析插件配置文件:

       if (field == "plugins") {
           ParsePlugin(strTmp);
       } else if (field == "pipelines") {
           ParsePipeline(strTmp);
       } else if (field == "pipelinegroups") {
           ParsePipelineGroup(strTmp);
       }

目前使用的插件配置文件/system/etc/hiview/plugin_config内容如下:

plugins:3
SysEventSource[thread:sysevent_source]:0 static
Faultlogger[]:0 static
SysEventService[thread:sysevent_service]:0 static
pipelines:1
SysEventPipeline:SysEventService Faultlogger
pipelinegroups:1
SysEventSource:SysEventPipeline
  • 注:base/hiviewdfx/hiview/plugins 下有eventlogger eventservice faultlogger freeze_detector hicollie_collector五个插件目录,而目前插件配置文件里实际只用到了Faultlogger和SysEventService。

PluginConfig::StartParse()解析完之后会把相关信息保存到列表中。类图如下:
OpenHarmony 源码解析之DFX子系统-Hiview(上)-开源基础软件社区

PluginConfig::ParsePipelineGroup()代码如下

void PluginConfig::ParsePipelineGroup(const std::string& pipelineGroup)
{
    std::smatch result;
    // EventSourceExample:FromTwo2Three FromThree2Two
    if (!regex_search(pipelineGroup, result, std::regex("(\\S+)\\s*:(.+)"))) {
        HIVIEW_LOGW("Fail to match pipeline group expression.");
        return;
    }

    const int pipelineGroupNameField = 1;
    const int pipelineNameListField = 2;
    std::string eventSourceName = StringUtil::TrimStr(result.str(pipelineGroupNameField));
    auto ret = std::find_if(pluginInfoList_.begin(), pluginInfoList_.end(), [&](PluginInfo& info) {
        if (info.name == eventSourceName) {
            info.isEventSource = true;
            info.pipelineNameList = StringUtil::SplitStr(result.str(pipelineNameListField));
            return true;
        }
        return false;
    });
    if (ret != std::end(pluginInfoList_)) {
        HIVIEW_LOGD("%s is an event source.", eventSourceName.c_str());
    }
}

说明:

  • 在解析pipelinegroups时,如果发现pipelineGroupName和pluginInfoList中某个插件的name一致,则把该插件PluginInfo.isEventSource置为true并且把pipelineNameList赋值给PluginInfo.pipelineNameList。
  • 结合/system/etc/hiview/plugin_config配置文件,可以看到SysEventSource插件是带管道(SysEventPipeline)的,插件SysEventService和Faultlogger隶属于管道SysEventPipeline,用于处理SysEventSource扔给管道的事件。

解析完插件配置信息之后会调用HiviewPlatform::LoadBusinessPlugin(const PluginConfig& config)去装载插件和管道。
代码如下

void HiviewPlatform::LoadBusinessPlugin(const PluginConfig& config)
{
    // start to load plugin
    // 1. 遍历pluginInfoList,根据插件名创建插件。因为目前配置的插件加载延时(loadDelay)为0,所以直接走[2],调用CreatePlugin()创建插件并添加到pluginMap_中。
    auto const& pluginInfoList = config.GetPluginInfoList();
    for (auto const& pluginInfo : pluginInfoList) {
        HIVIEW_LOGI("Start to create plugin %{public}s delay:%{public}d", pluginInfo.name.c_str(),
                    pluginInfo.loadDelay);
        if (pluginInfo.loadDelay > 0) { //.............[1]
            auto task = std::bind(&HiviewPlatform::ScheduleCreateAndInitPlugin, this, pluginInfo);
            sharedWorkLoop_->AddTimerEvent(nullptr, nullptr, task, pluginInfo.loadDelay, false);
        } else {       //...............[2]
            CreatePlugin(pluginInfo);
        }
    }
    // 2. 遍历pipelineInfoList,调用CreatePipeline()创建管道并添加到pipelines_中。
    auto const& pipelineInfoList = config.GetPipelineInfoList();
    for (auto const& pipelineInfo : pipelineInfoList) {
        HIVIEW_LOGI("Start to create pipeline %{public}s", pipelineInfo.name.c_str());
        CreatePipeline(pipelineInfo);
    }

    // 3. 遍历pluginInfoList,调用InitPlugin()初始化插件
    for (auto const& pluginInfo : pluginInfoList) {
        HIVIEW_LOGI("Start to Load plugin %{public}s", pluginInfo.name.c_str());
        InitPlugin(config, pluginInfo); //............注[1]
    }

    CleanupUnusedResources();
}

说明:

注[1]InitPlugin()这一步中,如果插件的workHandlerType为thread,则绑定工作线程EventLoop。如果插件是EventSource类型,则绑定管道Pipeline,并且调用StartEventSource开启消息监听。代码如下:

void HiviewPlatform::InitPlugin(const PluginConfig& config __UNUSED, const PluginConfig::PluginInfo& pluginInfo)
{
    auto& plugin = pluginMap_[pluginInfo.name];
    if (plugin == nullptr) {
        return;
    }
    // 如果插件的workHandlerType为thread,则绑定工作线程EventLoop。
    if (pluginInfo.workHandlerType == "thread") {
        auto workLoop = GetAvaliableWorkLoop(pluginInfo.workHandlerName);
        plugin->BindWorkLoop(workLoop);
    }

    auto begin = TimeUtil::GenerateTimestamp();
    plugin->OnLoad();
    // 如果插件是EventSource类型,则添加管道Pipeline,并且调用StartEventSource开启消息监听
    if (pluginInfo.isEventSource) {
        auto sharedSource = std::static_pointer_cast<EventSource>(plugin);
        if (sharedSource == nullptr) {
            HIVIEW_LOGE("Fail to cast plugin to event source!");
            return;
        }
        for (auto& pipelineName : pluginInfo.pipelineNameList) {
            sharedSource->AddPipeline(pipelines_[pipelineName]);
        }
        StartEventSource(sharedSource);
    }
    auto end = TimeUtil::GenerateTimestamp();
    HIVIEW_LOGI("Plugin %{public}s loadtime:%{public}" PRIu64 ".", pluginInfo.name.c_str(), end - begin);
}

至此,插件管理平台初始化工作已完成。

2.2 事件处理流程

结合插件配置文件,目前实际使用的插件类图如下:
OpenHarmony 源码解析之DFX子系统-Hiview(上)-开源基础软件社区

说明:

  • 前文最后的代码段提到,如果插件是EventSource类型,则绑定管道,并且调用StartEventSource开启消息监听。结合类图,只有SysEventSource是EventSource类型的插件,所以只有SysEventSource持有管道。
  • EventSource类型的插件会监听HiSysEvent接口发来的消息,解析并组装成管道事件,分发给管道中的插件处理。

SysEventSource::StartEventSource()函数实现如下。

void SysEventSource::StartEventSource()
{
    HIVIEW_LOGI("SysEventSource start");
    std::shared_ptr<EventReceiver> sysEventReceiver = std::make_shared<SysEventReceiver>(*this);
    eventServer.AddReceiver(sysEventReceiver);

    eventServer.Start();
}

SysEventSource有个成员变量eventServer,EventServer会开启socketserver端用于接收HiSysEvent接口发来的socket消息。

类图如下:
OpenHarmony 源码解析之DFX子系统-Hiview(上)-开源基础软件社区
OpenHarmony 源码解析之DFX子系统-Hiview(上)-开源基础软件社区

说明:

  • 1.SysEventSource::StartEventSource()中把SysEventReceiver对象加入到EventServer的receivers_容器中。
  • 2.EventServer收到socket消息之后,调用SysEventReceiver::HandlerEvent(const std::string& rawMsg)方法处理接收到的消息。
  • 3.SysEventReceiver::HandlerEvent()方法中会调用SysEventParser::Parser(const std::string& rawMsg)方法解析消息并组装成SysEvent对象,然后调用EventSource::PublishPipelineEvent(std::shared_ptr<PipelineEvent> event)方法把管道事件发布出去。

EventSource::PublishPipelineEvent()代码如下:

bool EventSource::PublishPipelineEvent(std::shared_ptr<PipelineEvent> event)
{
    HIVIEW_LOGD("EventSource PublishPipelineEvent");
    if (event == nullptr) {
        return false;
    }

    if (Audit::IsEnabled()) {
        auto digest = GetPluginInfo() + Audit::DOMAIN_DELIMITER + event->GetEventInfo();
        Audit::WriteAuditEvent(Audit::StatsEvent::PIPELINE_EVENT_CREATE, event->createTime_, digest);
    }

    for (auto& pipeline : listeners_) {
        if ((pipeline != nullptr) && (pipeline->CanProcessEvent(event))) {
            // one event can only be processed by one pipeline
            pipeline->ProcessEvent(event);
            return true;
        }
    }
    return false;
}

结合上面的类图,EventSource持有管道列表对象listeners_,EventSource::PublishPipelineEvent()函数中会遍历该列表,调用Pipeline::ProcessEvent(std::shared_ptr<PipelineEvent> event)函数去处理管道事件。(Pipeline对象是在HiviewPlatform::InitPlugin()函数中被加入到EventSource.listeners_中的。)
Pipeline类型有个成员变量std::list<std::weak_ptr<Plugin>> processors_,
在插件平台初始化的时候,HiviewPlatform::CreatePipeline()函数中已经把Plugin插件对象加入到了processors_列表中。
接下来分析Pipeline::ProcessEvent()函数做了什么。

void Pipeline::ProcessEvent(std::shared_ptr<PipelineEvent> event)
{
    event->SetPipelineInfo(name_, processors_);
    event->OnContinue();
}

把Pipeline的processors_对象赋给了PipelineEvent的processors_对象,然后调用
PipelineEvent::OnContinue()函数。
接下来分析PipelineEvent::OnContinue()函数:

bool PipelineEvent::OnContinue()
{
    // 判断hasFinish_标志位和processors_列表是否已经为空,如已为空,结束本次事件传递
    if ((!hasFinish_) && processors_.empty()) {
        return OnFinish();
    }

    // once the event start delivering
    // the call OnContinue means one has done the processing of the event
    // this may be called by upstream event processor or the framework
    if (Audit::IsEnabled() && startDeliver_) {
        Audit::WriteAuditEvent(Audit::StatsEvent::PIPELINE_EVENT_HANDLE_OUT,
            createTime_, std::to_string(Thread::GetTid()));
    }

    // the framework will call OnContinue when the event is assigned to a pipeline
    if (!startDeliver_) {
        startDeliver_ = true;
    }

    // 取出processors_列表中第一个Plugin元素,并从列表弹出
    std::weak_ptr<Plugin> plugin = processors_.front();
    processors_.pop_front();
    if (auto pluginPtr = plugin.lock()) {
        if (!pluginPtr->CanProcessMoreEvents()) {
            handler_->PauseDispatch(plugin);
        }

        if (Audit::IsEnabled()) {
            Audit::WriteAuditEvent(Audit::StatsEvent::PIPELINE_EVENT_HANDLE_IN, createTime_,
                                   pluginPtr->GetHandlerInfo());
        }
        // 判断当前Plugin是否开启了事件工作队列,如有则加入事件队列处理,如没有直接调用OnEventProxy
        if (auto workLoop = pluginPtr->GetWorkLoop()) {
            workLoop->AddEvent(pluginPtr, shared_from_this()); //............[1]
        } else {
            pluginPtr->OnEventProxy(shared_from_this()); //..........[2]
        }
    } else {
        return OnContinue();
    }
    return true;
}

不管插件是否使用了事件队列,最终都会调用到Plugin::OnEventProxy()函数来处理管道事件。

Plugin::OnEventProxy()函数实现如下:

bool Plugin::OnEventProxy(std::shared_ptr<Event> event)
{
    if (event == nullptr) {

        return false;
    }

    std::shared_ptr<Event> dupEvent = event;
    auto processorSize = dupEvent->GetPendingProcessorSize();
    dupEvent->ResetPendingStatus();
    bool ret = OnEvent(dupEvent); //..............注[1]

    if (!dupEvent->IsPipelineEvent()) {
        if (Audit::IsEnabled()) {
            Audit::WriteAuditEvent(Audit::StatsEvent::QUEUE_EVENT_OUT, dupEvent->createTime_,
                std::to_string(Thread::GetTid()));
        }
    } else {
        if ((!dupEvent->HasFinish() && !dupEvent->HasPending()) &&
            (processorSize == dupEvent->GetPendingProcessorSize())) {
            dupEvent->OnContinue();//.............注[2]
        }
    }
    return ret;
}

先调用注[1]OnEvent()函数处理事件,再判断管道事件传递是否已结束,如未结束则调用注[2]PipelineEvent::OnContinue()函数继续把管道事件传递给后面的
插件处理。

结合目前的插件配置文件,整个插件管理平台的消息处理流程大致如下:

EventServer接收到HiSysEvent组件接口发来的消息 --> SysEventReceiver::HandlerEvent() --> SysEventParser::Parser() --> EventSource::PublishPipelineEvent() --> Pipeline::ProcessEvent() --> PipelineEvent::OnContinue() --> SysEventService::OnEventProxy --> SysEventService::OnEvent --> PipelineEvent::OnContinue() --> Faultlogger::OnEventProxy --> Faultlogger::OnEvent --> 结束

至此,事件处理流程已分析完。

3 总结

以上内容首先分析了hiview插件管理平台的初始化,然后对事件的处理流程做了分析。后续会详细讲解base/hiviewdfx/hiview/plugins目录下每个插件的源码。

更多原创内容请关注:深开鸿技术团队

入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2022-1-6 15:04:14修改
6
收藏 4
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐