Android statsd 埋点简析

news2024/11/16 2:28:56

源码基于:Android U

0. 前言

最近在研究 Android 自带的系统数据指标采集功能,框架依旧很严谨、完美,这里做个分享。

1. Android S 之后变化

stats 的代码从 framework 或 system/core 中转移到了 packages/modules/StatsD 目录中。

2. 框架图

大的框架分两层:

  • system_sever;

  • statsd;

Java 层创建两个 SystemService:

  • StatsCompanion.Lifecycle:会启动两个Service 用以与 native daemon 通信;

  • StatsPullAtomService:SystemServer 中用来采集数据;

StatsPullAtomService 用以采集数据,并将数据填充到参数中,在StatsManager.PullAtomCallbackInternal 中会转换成 Parcel 格式传入到 native。

frameworks/base/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java

    private class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback {
        @Override
        public int onPullAtom(int atomTag, List<StatsEvent> data) {
            ...
            try {
                switch (atomTag) {
                ...
                case FrameworkStatsLog.PROCESS_MEMORY_STATE:
                case FrameworkStatsLog.PROCESS_MEMORY_HIGH_WATER_MARK:
                case FrameworkStatsLog.PROCESS_MEMORY_SNAPSHOT:
                case FrameworkStatsLog.SYSTEM_ION_HEAP_SIZE:
                case FrameworkStatsLog.ION_HEAP_SIZE:
                case FrameworkStatsLog.PROCESS_SYSTEM_ION_HEAP_SIZE:
                case FrameworkStatsLog.PROCESS_DMABUF_MEMORY:
                case FrameworkStatsLog.SYSTEM_MEMORY:
                case FrameworkStatsLog.VMSTAT:
                ...
                }
            } finally {}
        }

该函数是针对不同类型的 puller 的回调处理,在此之前会调用 registerPullers 函数将所有的puller 注册到 StatsManager 中:

    private void registerProcessMemoryHighWaterMark() {
        int tagId = FrameworkStatsLog.PROCESS_MEMORY_HIGH_WATER_MARK;
        mStatsManager.setPullAtomCallback(
                tagId,
                null, // use default PullAtomMetadata values
                DIRECT_EXECUTOR,
                mStatsCallbackImpl
        );
    }

StatsCallbackPuller::PullInternal 中会回调StatsManager.PullAtomCallbackInternal 的onPullAtom,该函数中会回调StatsCallbackPuller::PullInternal 中定义的PullResultReceiverpullFinished函数,并将 StatsPullAtomService 端采集的数据存入StatsCallbackPuller::PullInternal的入参中;

packages/modules/StatsD/framework/java/android/app/StatsManager.java

        public void onPullAtom(int atomTag, IPullAtomResultReceiver resultReceiver) {
            final long token = Binder.clearCallingIdentity();
            try {
                mExecutor.execute(() -> {
                    List<StatsEvent> data = new ArrayList<>(); //上层的采集数据存入data
                    int successInt = mCallback.onPullAtom(atomTag, data); //callback,开始采集
                    boolean success = successInt == PULL_SUCCESS;
                    StatsEventParcel[] parcels = new StatsEventParcel[data.size()];
                    for (int i = 0; i < data.size(); i++) {
                        parcels[i] = new StatsEventParcel();
                        parcels[i].buffer = data.get(i).getBytes(); //转换成parcel,传入native
                    }
                    try {
                        resultReceiver.pullFinished(atomTag, success, parcels); //receiver回调
                    } catch (RemoteException e) {
                        ...
                    }
                });
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }
packages/modules/StatsD/statsd/src/external/StatsCallbackPuller.cpp

PullErrorCode StatsCallbackPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
    ...

    shared_ptr<vector<shared_ptr<LogEvent>>> sharedData =
            make_shared<vector<shared_ptr<LogEvent>>>();

    shared_ptr<PullResultReceiver> resultReceiver = SharedRefBase::make<PullResultReceiver>(
            [cv_mutex, cv, pullFinish, pullSuccess, sharedData]( //receiver回调
                    int32_t atomTag, bool success, const vector<StatsEventParcel>& output) {
                {
                    lock_guard<mutex> lk(*cv_mutex);
                    for (const StatsEventParcel& parcel: output) {
                        shared_ptr<LogEvent> event = make_shared<LogEvent>(/*uid=*/-1, /*pid=*/-1);
                        bool valid = event->parseBuffer((uint8_t*)parcel.buffer.data(),
                                                        parcel.buffer.size());
                        if (valid) {
                            sharedData->push_back(event); //解析上层采集数据,存入sharedData
                        } else {
                            StatsdStats::getInstance().noteAtomError(event->GetTagId(),
                                                                     /*pull=*/true);
                        }
                    }
                    *pullSuccess = success;
                    *pullFinish = true;
                }
                cv->notify_one();
            });

    // Initiate the pull. This is a oneway call to a different process, except
    // in unit tests. In process calls are not oneway.
    Status status = mCallback->onPullAtom(mTagId, resultReceiver); //callback,等待receiver回调
    ...

    {
        unique_lock<mutex> unique_lk(*cv_mutex);
        // Wait until the pull finishes, or until the pull timeout.
        cv->wait_for(unique_lk, chrono::nanoseconds(mPullTimeoutNs),
                     [pullFinish] { return *pullFinish; });
        if (!*pullFinish) {
            ...
        } else {
            if (*pullSuccess) {
                *data = std::move(*sharedData); //数据最终填充到入参data中
            }
            ...
    }
}

StatsPullerManager::OnAlarmFired 最终会调用 StatsCallbackPuller::PullInternal,参数也是这里传入的。所以采集的数据最终会回到 StatsPullerManager::OnAlarmFired 中,最终会将这些数据通过注册在StatsPullerManager中的 mReceivers 的回调函数 onDataPulled处理。

packages/modules/StatsD/statsd/src/external/StatsPullerManager.cpp

void StatsPullerManager::OnAlarmFired(int64_t elapsedTimeNs) {
    std::lock_guard<std::mutex> _l(mLock);
    int64_t wallClockNs = getWallClockNs();

    int64_t minNextPullTimeNs = NO_ALARM_UPDATE;

    vector<pair<const ReceiverKey*, vector<ReceiverInfo*>>> needToPull;
    for (auto& pair : mReceivers) { //最终数据的处理都是在mReceivers中
        vector<ReceiverInfo*> receivers;
        if (pair.second.size() != 0) {
            for (ReceiverInfo& receiverInfo : pair.second) {
                sp<PullDataReceiver> receiverPtr = receiverInfo.receiver.promote();
                const bool pullNecessary = receiverPtr != nullptr && receiverPtr->isPullNeeded();
                if (receiverInfo.nextPullTimeNs <= elapsedTimeNs && pullNecessary) {
                    receivers.push_back(&receiverInfo);
                } else {
                    if (receiverInfo.nextPullTimeNs <= elapsedTimeNs) {
                        receiverPtr->onDataPulled({}, PullResult::PULL_NOT_NEEDED, elapsedTimeNs);
                        int numBucketsAhead = (elapsedTimeNs - receiverInfo.nextPullTimeNs) /
                                              receiverInfo.intervalNs;
                        receiverInfo.nextPullTimeNs +=
                                (numBucketsAhead + 1) * receiverInfo.intervalNs;
                    }
                    minNextPullTimeNs = min(receiverInfo.nextPullTimeNs, minNextPullTimeNs);
                }
            }
            if (receivers.size() > 0) {
                needToPull.push_back(make_pair(&pair.first, receivers));
            }
        }
    }
    for (const auto& pullInfo : needToPull) {
        vector<shared_ptr<LogEvent>> data; //采集的数据根本是存在这里的临时变量
        PullResult pullResult =
                PullLocked(pullInfo.first->atomTag, pullInfo.first->configKey, elapsedTimeNs, &data)  //调用PullLocked 将数据采集回来
                        ? PullResult::PULL_RESULT_SUCCESS
                        : PullResult::PULL_RESULT_FAIL;
        if (pullResult == PullResult::PULL_RESULT_FAIL) {
            VLOG("pull failed at %lld, will try again later", (long long)elapsedTimeNs);
        }

        // Convention is to mark pull atom timestamp at request time.
        // If we pull at t0, puller starts at t1, finishes at t2, and send back
        // at t3, we mark t0 as its timestamp, which should correspond to its
        // triggering event, such as condition change at t0.
        // Here the triggering event is alarm fired from AlarmManager.
        // In ValueMetricProducer and GaugeMetricProducer we do same thing
        // when pull on condition change, etc.
        for (auto& event : data) {
            event->setElapsedTimestampNs(elapsedTimeNs);
            event->setLogdWallClockTimestampNs(wallClockNs);
        }

        for (const auto& receiverInfo : pullInfo.second) {
            sp<PullDataReceiver> receiverPtr = receiverInfo->receiver.promote();
            if (receiverPtr != nullptr) {
                receiverPtr->onDataPulled(data, pullResult, elapsedTimeNs); //发送给receiver处理
                // We may have just come out of a coma, compute next pull time.
                int numBucketsAhead =
                        (elapsedTimeNs - receiverInfo->nextPullTimeNs) / receiverInfo->intervalNs;
                receiverInfo->nextPullTimeNs += (numBucketsAhead + 1) * receiverInfo->intervalNs;
                minNextPullTimeNs = min(receiverInfo->nextPullTimeNs, minNextPullTimeNs);
            } else {
                VLOG("receiver already gone.");
            }
        }
    }

    VLOG("mNextPullTimeNs: %lld updated to %lld", (long long)mNextPullTimeNs,
         (long long)minNextPullTimeNs);
    mNextPullTimeNs = minNextPullTimeNs; //alarm时间点更新
    updateAlarmLocked();  //调用updateAlarmLocked 告知Java service 中的AlarmManagerService
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1950030.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

结构拼图的艺术——组合模式(Python实现)

大家好&#xff0c;今天我们继续来讲结构型设计模式&#xff0c;上一期我们介绍了桥接模式&#xff0c;帮助大家理解了如何通过分离抽象部分和实现部分来实现代码的解耦。 今天&#xff0c;我们将介绍另一个非常实用的设计模式——组合模式&#xff0c;这个模式特别适合用于处…

Vue3 + Vite 打包引入图片错误

1. 具体报错 报错信息 报错代码 2. 解决方法 改为import引入&#xff0c;注意src最好引用为符引入&#xff0c;不然docker部署的时候可能也会显示不了 <template><img :src"loginBg" alt""> </template><script langts setup> …

ili9341数据手册中的常用命令

一.设置液晶显示窗口 根据液晶屏的要求&#xff0c;在发送显示数据前&#xff0c;需要先设置显示窗口确定后面发送的像素数据的显示区域。下面的0x2A和0x2B分别对应的是y轴与x轴的命令。 /********** ILI934 命令 ********************************/ #define CMD_SetCoor…

keil调试SH79F7416

仿真器JET51A, 调试设置 选择器件 再次点击调试就一切正常啦

使用moco 完成挡板测试

这里写自定义目录标题 背景使用 moco 工具完成mock挡板功能1. 下载jar包2. 简单启动2.1 准备一个简单的json文件2.2 启动 高级运用同一接口的不同返回字段部分匹配 SONPath参数结构匹配 SON Struct JSON分模块 背景 mock测试&#xff08;挡板测试&#xff09;就是在测试过程中…

在jmeter中使用javascript脚本

工作上遇到一个压力测试的需求&#xff0c;需要测试几个考试相关的接口。其中有一个获取试题详情的接口&#xff0c;和一个提交答题信息的接口。后一个接口以上一接口的返回内容为参数&#xff0c;添加上用户的答案即可。jmeter提供了非常多的方式可以实现该需求&#xff0c;这…

保障企业数据主权:安全可控的爬虫工具与管理平台

摘要 在数据驱动的时代&#xff0c;企业对数据的需求日益增长&#xff0c;但如何在保障数据主权的前提下高效采集数据&#xff1f;本文深入探讨了选择安全可控爬虫工具与管理平台的重要性&#xff0c;分析了关键特性&#xff0c;并提出实用建议&#xff0c;助力企业维护数据安…

AWT200-HPLC-M载波通讯模块/智能网关

安科瑞AWT200-HPLC-M载波通讯模块适用于对数据实时性要求不高的系统&#xff0c;数据刷新速度大于1分钟&#xff0c;比如Acrel-5000能耗管理系统 电力线载波通讯模块AWT200-HPLC-M具备载波接收和网关通讯功能&#xff0c;支持三相载波数据采集&#xff0c;协议转换和数据上传平…

【Plotly-驯化】一文教你通过plotly画出动态可视化多变量分析:create_scatterplotmatrix

【Plotly-驯化】一文教你通过plotly画出动态可视化多变量分析&#xff1a;create_scatterplotmatrix 本次修炼方法请往下查看 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我工作、学习、实践 IT领域、真诚分享 踩坑集合&#xff0c;智慧小天地&#xff01; &am…

windows USB 设备驱动开发- WinUSB 简介

WinUSB 是 Windows 随附的 USB 设备的通用驱动程序。WinUSB 包括&#xff1a; 内核模式驱动程序 (Winusb.sys)&#xff1b;公开 winusb.h 中所述的 WinUSB 函数的用户模式动态链接库 (Winusb.dll)。 借助这些函数&#xff0c;你可以使用用户模式软件管理 USB 设备&#xff1b;…

软件测试产教融合高质量发展论坛举办,开源网安受邀解读国家标准

近年来&#xff0c;在国家政策引导下&#xff0c;横跨教育体系内外的产教融合改革正在进行&#xff0c;推动了教育与产业深度融合、学校与企业协同发展。在软件安全领域&#xff0c;开源网安也一直走在产教融合发展的最前线&#xff0c;与各大高校建立了深度合作&#xff0c;双…

Cuppa CMS v1.0 任意文件读取漏洞(CVE-2022-25578)

前言 春秋云镜靶场是一个专注于网络安全培训和实战演练的平台&#xff0c;旨在通过模拟真实的网络环境和攻击场景&#xff0c;提升用户的网络安全防护能力和实战技能。这个平台主要提供以下功能和特点&#xff1a; 实战演练&#xff1a; 提供各种网络安全攻防演练场景&#…

mysql字符类型字段设置默认值为当前时间

-- 2024-07-22 10:22:20 select (DATE_FORMAT(CURRENT_TIMESTAMP, %Y-%m-%d %H:%i:%s)); ALTER TABLE tablename MODIFY COLUNN CREATE_DATE varchar (23) DEFAULT(DATE_FORMAT(CURRENT_TIMESTAMP, %Y-%m-%d %H:%i:%s)) COMMENT "创建日期;

新校区,新视野——广州六中以太彩光打造智慧教育新高地

广州市第六中学总务处 林继青 广州市第六中学是拥有85年办学历史的著名市重点中学,也是广东省首批一级学校和国家级示范性高中。作为广州市首批智慧校园建设示范单位,广州第六中学在从化、花都新建校区的智慧校园建设中“破旧立新”,让先进的以太全光技术与学校新校区建设同频共…

CVE-2024-39700 (CVSS 9.9):JupyterLab 模板中存在严重漏洞

在广泛使用的 JupyterLab 扩展模板中发现了一个严重漏洞&#xff0c;编号为CVE-2024-39700 。此漏洞可能使攻击者能够在受影响的系统上远程执行代码&#xff0c;从而可能导致大范围入侵和数据泄露。 该漏洞源于在扩展创建过程中选择“测试”选项时自动生成“update-integratio…

基于 HTML+ECharts 实现智慧安防数据可视化大屏(含源码)

构建智慧安防数据可视化大屏&#xff1a;基于 HTML 和 ECharts 的实现 随着科技的不断进步&#xff0c;智慧安防系统已经成为保障公共安全的重要工具。通过数据可视化&#xff0c;安防管理人员可以实时监控关键区域的安全状况、人员流动以及设备状态&#xff0c;从而提高应急响…

TikTok批量养号方法

想要在TikTok平台上批量养号&#xff0c;确保账号的健康与活跃度非常重要&#xff0c;不然等于白干。下面&#xff0c;我们就来详细探讨一下TikTok养号的几个关键步骤。 首先&#xff0c;新注册的账号必须严格遵守一机一号一IP的原则。随着TikTok平台在识别IP技术方面的不断进步…

叶再豪老师-降龙精英课程

文章目录 1.思维认知1.1 稻盛和夫成功公式1.2 龙头主升模式1.3 龙头主升-两种路径1.4 股市新手的炒股思路1.5 龙头案例1.6 降龙心法 2.情绪周期2.1 情绪周期2.1 情绪演绎周期2.2 情绪的四个部分2.2.1 指数的情绪周期2.2.3 热点情绪周期2.2.4 热点情绪演绎周期2.2.5 大热点支线2…

CDGA|数据治理:安全如何贯穿数据供给、流通、使用全过程

随着信息技术的飞速发展&#xff0c;数据已经成为企业运营、社会管理和经济发展的核心要素。然而&#xff0c;数据在带来巨大价值的同时&#xff0c;也伴随着诸多安全风险。因此&#xff0c;数据治理的重要性日益凸显&#xff0c;它不仅仅是对数据的简单管理&#xff0c;更是确…

JAVA基础知识4(static、继承)

JAVA基础知识4 static静态成员变量静态成员方法解释注意点 继承父类&#xff08;基类或超类&#xff09;子类&#xff08;派生类&#xff09;使用继承的示例解释运行结果 成员变量的访问特点成员方法的访问特点构造方法的访问特点 Java项目&#xff0c;包和类之间的联系包的概念…