Android 应用流量监控实践

news2024/9/25 1:25:47

背景

得物Apm系统本身包含网络接口性能监控的能力,但接口监控主要关注的是接口的耗时、异常率等信息,没有流量消耗相关维度的统计信息,并且一部分流量消耗可能来自于音视频等其他特殊场景,在接口监控的盲区外。 为了了解用户目前使用App时的流量消耗情况,并支持分析用户是存在异常的流量使用情况,还需要从流量消耗的角度,进行相关监控功能的建设。本文主要介绍流量监控实现过程中的一些技术实现及平台能力。

流量消耗信息获取

xt_qtaguid模块

xt_qtaguid 模块 是android 内核3.0 引入的流量统计模块,在Android系统9以下的版本,可以通过直接读取 /proc/net/xt_qtaguid/stats 文件获取对应应用的 流量消耗信息。其内容如下

nimdanoob:/ $ cat proc/net/xt_qtaguid/stats | grep 10027
idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
10 rmnet_data0 0x0 10027 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
11 rmnet_data0 0x0 10027 1 451662 2320 1193948 3888 451662 2320 0 0 0 0 1193948 3888 0 0 0 0
40 wlan0 0x0 10027 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
41 wlan0 0x0 10027 1 38829 166 77083 208 38829 166 0 0 0 0 77083 208 0 0 0 0

在结果中,根据 uid_tag_int 可以区分不同的应用程序,这个值对应代码中 android.os.Process.myUid()�获取的值。 第二列的 iface 表示所使用的网络接口,比如 wlan 表示Wi-Fi网络适配器的物理接口, rmnet_data 表示手机数据网络接口, l0表示本地回环接口 。 通过读取 rx_bytes、tx_bytes 就可以获取不同网络接口累计使用的上下行流量大小。

除了通过直接读取 xt_qtaguid/stats文件的方式,也可以直接通过调用系统封装TrafficStats类提供的相关Api获取流量消耗信息,该类在 Android API8 版本开始就被引入了。 [图片上传失败…(image-69eb39-1698138290833)]

相关Api 如下

  • getUidTxBytes(int uid) :返回指定uid的上行流量消耗
  • getUidRxBytes(int uid): 返回指定uid的下行流量
  • getMobileTxBytes(): 返回整机的上行流量消耗
  • getMobileRxBytes(): 返回整机下行流量消耗

TrafficStats的限制

在Android 9 版本开始,google 逐步取消了对 xt_qtaguid模块的支持,开始采用基于 eBPF的网络流量监控实现 ,所以在高版本设备中无法再通过读取xt_qtaguid文件获取流量使用信息。 而直接使用TrafficStats读取流量消耗信息又由于其提供的Api有限,存在一些限制,比如TrafficStats 虽然支持查看指定程序的整体流量消耗信息,但不支持根据iface端口进行区分,因而无法明确wifi和移动分别消耗了多少流量。 在一些开源项目的实现中 通过ConnectivityManager 获取当前网络连接状态,判断采样间隔内流量消耗来自于wifi还是移动。但是实际上在采样间隔内,设备可能同时在使用wifi和移动进行进行网络通信,因此这种方式采集的数据精准度有限。

另外采用基于TrafficStats Api的方案在接入得物后,还发现TrafficStats统计的流量消耗比实际的流量消耗大了非常多(实际流量消耗的5倍),最后定位发现是由于得物的音视频场景 使用了本地Socket提供媒体数据,而TrafficStats.getUidTxBytes接口包含了所有iface的数据,本地Socket的通信数据也会被包含在内。

NetworkStatsManager

在Android6.0 (API 23)版中,系统新增了NetworkStatsManager类查询网络历史使用信息,其提供的Api 能力相比TrafficStats 强大很多,比如其可以获取指定时间内的流量消耗,并且 通过指定networkType 可以区分流量消耗的类型。

NetworkStatsManager statsManager = (NetworkStatsManager) getSystemService(Context.NETWORK_STATS_SERVICE);
//查询自身进程 蜂窝网络消耗
statsManager.querySummary(ConnectivityManager.TYPE_MOBILE, "", startTime, endTime);
//查询整机 蜂窝网络消耗
statsManager.querySummaryForDevice(ConnectivityManager.TYPE_MOBILE, null, startTime, endTime);

然而,在实际的测试过程中,以10s为采样间隔为例,经常会发现采样间隔内统计的数值未发生变化,在测试TrafficStats相关Api时发现没有类似的限制,其返回的数值是是实时的。

通过跟踪 NetworkStatsService 相关的实现后发现,原因可能是2个方面的问题导致的。 当应用层通过本地NetworkStatsManager调用相应Api获取消耗大小时,会创建一个NetworkStats对象,而NetworkStats对象构造时,会创建一个本地的 INetworkStatsSession对象

首先需要了解,NetworkStatsService 主要提供的是历史流量消耗记录的能力,它的计算方式是周期性的读取ebpf相应的虚拟文件中,从中获得最新的流量消耗大小。 NetworkStatsService为了尽量保证应用层获取的是最新的数据在每次openSession时,会尝试进行一次poll,不过这里的poll动作触发存在一定限制,15秒内只能触发一次。

另一个原因是,其底层实际负责流量记录的 NetworkStatsRecorder 会先将变化的流量信息统计在一个mPending对象中,当pending 记录超出一定阈值后,才会将这个数据落盘,而这个阈值默认情况下为2MB。

为了验证这个问题,保证每次采样时获取到的wifi及移动消耗数据是最新的,我们可以创建一组LocalSocket进行本地的端对端通信,在每次采样之前,进行一次数据数据通信,从而触发系统底层的数据合并操作,保证我们获取的数值是最新的。 当然这种方案会消耗不必要的系统资源,包括Socket资源的浪费并且会导致触发系统流量统计服务的数据合并及持久化操作,从节省系统资源的角度并不是一个好的方式,如果对数据采集结果的失效及准确性要求不高的话,不建议进行此类行为。

跟踪流量消耗明细

当发现用户流量消耗不合理时,我们还需要提供流量消耗的明细。比如接口的流量消耗、图片、音视频的流量消耗、直连Socket的流量消耗。

网络库Hook

大部分的网络请求发起依赖于现成的网络框架如 OkHttp、HttpURLConnection,因此需要Hook这些网络库追踪具体的流量消耗信息。

OkHttp

针对OkHttp库,OkHttp本身提供了EventListener机制,通过注册EventListner可以监听获取每个请求的阶段过程,以及请求响应信息。 因此这里不做过多阐述。

HttpURLConnection

针对HttpURLConnection进行的网络请求,这里参考的是AndroidStudio中网络监控的方案,具体代码详见 aosp ,这里简单做下原理阐述。 首先需要字节码插桩代理 URLConnection的 openConnection函数,代理原始URL.openConnection返回的URLConnection对象。这里需要注意由于Https 和Http协议会返回不同的URLConnection类型实例,因此需要做下判断。

以HttpURLConnectionWrapper实现为例,具体跟踪代码

其内部会再创建一个专门负责流量统计的TrackHttpURLConnection实例,这里又包装了一层是因为 该类的实现 可以在HttpsURLConnection的代理中进行复用。 继续跟踪TrackedHttpURLConnection� 实现。 针对请求体、响应体流量的跟踪,其主要实现是返回一个代理包装类型的InputStream或OutputStream

以OutputStream为例,继续代理内部的write函数,当write函数被调用时记录相应的流量消耗。

在整个对HttpURLConnection Hook实现的过程中有一些需要额外注意的点。 拿到URLConnection对象后,业务层会调用connect触发实际的网络请求调用,我们在connect函数中可以进行请求头和请求体流量的记录,但实际上业务层不主动调用connect函数,比如直接调用getInputStream()或getOutputStream时 底层也会触发connect操作,但此时它直接触发的是被代理的原始对象的connect函数,而我们自身的connect函数是不会被触发的,因此如果只在connect函数中记录信息,就可能出现遗漏的情况。 因此AS在实现这块逻辑时,会包装一个trackPreConnect�()函数,并同时在这3处进行调度。

类似的直接调用获取响应内容的一些函数,比如获取响应头信息也会触发底层的conenct操作,因此其也封装了一个trackResponse函数,内部会调用conenct函数(如果判断没调用过的话),并记录响应头的一些信息

基于Socket Api Hook

在部分业务场景中,可能会存在使用Socket进行网络通信的情况,比如自研长连协议,此时需要能够实现直接对原生Java Socket层数据通信的监控。 这里介绍2中针对Java Socket通信的监控方式。

基于字节码替换

和URLConnection监控的方案类似,Socket的数据读写是通过其InputStream和OutputStream对象实现的,因此通过ASM全局替换Socket的getInputStream和getOutPutStream对象,对其数据读写进行监控即可。

自定义SocketImplFactory

在Java层, 创建Socket的方式是直接构造 java.net.Socket对象,我们先跟踪其构造函数的实现。

在构造函数中,会调用setImpl()函数,继续跟踪setImple()实现

setImpl()函数先判断 factory是否为空,如果不为空则 使用factory创建SocketImpl实例,否则直接通过 SocksSocketImple()构造函数创建。 观察整个 java.net.Socket类的代码可以发现,其有关Socket的实际操作其实都是转发到 内部的SocksSocketImple对象中,以getInputStream()为例,在java.net.Socket实现中只是简单做了下状态的判断,最后直接调用impl的getInputStream函数。

java.net.Socket 类的factory是一个静态常量,SocketImpleFactory接口的实现如下

而 SocketImple抽象类实现如下 [图片上传失败…(image-ec6ff0-1698138290833)]# Android 应用流量监控实践

因此可以通过设置自定义的SocketImpleFactory 返回一个包装过的SokcetImpl实例,从而对Socket进行监控。

//1.反射获取原始的Factory
SocketImplFactory oldFactory = ....
if (oldFacotry!=null){
    //反射修改 Socket.class 的facotry字段,设置为新的Factory
	ReflectionUtil.of(Socket.class).field("factory").set(null, new TrafficSocketImplFactory(oldFactory));
} else {
    //设置新的factory
	Socket.setSocketImplFactory(new TrafficSocketImplFactory(null));
}

public class TrafficSocketImplFactory implements SocketImplFactory {
	public TrafficSocketImplFactory(SocketImplFactory originalFactory) {
        this.mOldFactory = originalFactory;
    }
     // ... 
     public SocketImpl createSocketImpl() {
        if (this.mOldFactory != null){
            return new TrafficSocketImpl(this.mOldFactory.createSocketImpl());
        }
        try {
            if (isCreateServerSocket())
                return (SocketImpl)sConstructor_SocketImpl.newInstance(new Object[0]);
            return new TrafficSocketImpl((SocketImpl)sConstructor_SocketImpl.newInstance(new Object[0]));
        } catch (Throwable tr) {
            //should never happend
            ApmSafety.handleException(tr);
            return null;
        }
    }
}
基于Native Sokcet Api Hook

除了Java层 Hook Sokcet Api的方式,也可以在Native 层使用 PLT Hook Socket 的相关读写API,包括

  • ssize_t send(int sockfd, const void *buf, size_t len, int flags)
  • ssize_t write(int fd, const void *buf, size_t count);
  • ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • ssize_t read(int fd, void *buf, size_t count);

具体实现可以参考开源项目 matrix 中的代码

上报数据内容及数据处理

在每次采样间隔内,会上报流量消耗的以下信息

  • 总流量消耗
  • 上/下行流量消耗、移动网络上/下行流量消耗、wifi网络上/下行流量消耗
  • 网络库请求信息 及上下行流量消耗
  • 按照域名维度分组统计的流量消耗信息

另外,针对部分直接在Java层无法监控到的流量消耗,比如音视频、直播等业务域的网络请求是在native层直接触发的,要求相应业务提供流量统计接口,在每次采样上报时,附带这些特殊场景的流量消耗信息。

当发现用户流量使用异常时,为了方便回溯用户操作,并判断流量消耗是否合理,在每次采样周期的埋点上报中还包含以下信息,

  • 页面路径信息,包括打开页面、页面可见、页面不可见状态变化
  • 视频播放行为,包括播放视频的url、播放时长、视频总时长

流量监控平台设计

流量消耗趋势

在流量监控首页,展示线上用户的流量平均及分位值的流量消耗趋势及接口请求次数。

用户维度Top流量消耗

在单用户维度,直接根据设备、用户单次启动等维度进行数据聚合,统计单用户的流量消耗。

用户流量消耗分析

当发现用户流量消耗和用户使用时长及行为不符时,提供流量消耗的深入分析。 首先会将用户这段时间内的流量进行一些统计, 包括不同host维度的流量消耗 及用户行为

流量日志详情分析

定位具体的流量消耗时,可以根据采样样本进行排序,找出较高流量消耗的样本。

点击日志 ,可以查看日志收集的详细信息,具体的流量消耗接口、用户行为

Android 学习笔录

Android 性能优化篇:https://qr18.cn/FVlo89
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

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

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

相关文章

EPB功能开发与测试(基于ModelBase实现)

ModelBase是经纬恒润开发的车辆仿真软件,包含两个大版本:动力学版本、智能驾驶版本。动力学版包含高精度动力学模型,能很好地复现车辆在实际道路中运行的各种状态变化,可用于乘用车、商用车动力底盘系统算法开发、控制器仿真测试&…

嵌入式Linux_学习路线+基础知识

嵌入式Linux_学习路线基础知识 一、学习路线 说明:u-boot是一大块学起来需要三到六个月比较耗时,也属于比较落后的知识点,所以暂时不学习,内核也是如此暂时不学习,从应用层入手,先入门再深入 二、Shell指令…

企业CRM系统选型时的一些建议

随着市场环境的变化,越来越多的企业有了使用CRM客户管理系统的需求。市面上那么多的CRM品牌,我们该如何选择呢?下面我们就来说说企业CRM系统选型时的一些建议。 1、明确自身需求 企业要厘清使用CRM的目的:是遇到了只有CRM才能解…

友宝赴港上市能否成为无人零售业领头羊?

北京友宝在线科技股份有限公司(以下简称“友宝”)近日宣布赴港上市,拟募集资金2.35亿港元,这引起了业界和投资者的广泛关注。据了解这已经是友宝第三次冲击港股IPO了,但是至今仍在门外徘徊。 据招股说明书显示&#x…

zfile环境搭建(存储引擎为minio)

zfile项目地址 minio环境搭建参考 本地运行 修改zfile项目配置文件,更改数据库为MySQL(注释掉灰色高亮部分,启用下面的 MySQL 信息并修改为你的数据库信息) ## sqlite #spring.datasource.driver-class-nameorg.sqlite.JDBC #s…

【C++面向对象】3. 友元

文章目录 【 1. 友元函数 】【 2. 友元类 】 友元可以是一个函数,该函数被称为 友元函数;友元也可以是一个类,该类被称为 友元类。友元可以理解为 原类 的 朋友,友元(友元函数和友元类)不是原类的成员&…

【vue3】状态过渡-GSAP插件实现

效果图&#xff1a; 实现代码 安装库&#xff1a;npm install --save-dev gsap 引入&#xff1a;import gsap from gsap <template><div><el-input v-model"num.currNum" type"number" step"20" style"width: 120px;"…

基于多尺度分形残差注意力网络的超分辨率重建算法

1.引言 深度神经网络可以显著提高超分辨率的质量&#xff0c;但现有方法难以充分利用低分辨率尺度特征和通道信息&#xff0c;从而阻碍了卷积神经网络的表达能力。针对此类问题&#xff0c;本章提出了一种多尺度分形残差注意力网络&#xff08;Multi-scale Fractal Residual A…

js快速回顾

组成 书写位置 声明 ![在这里插入图片描述](https://img-blog.csdnimg.cn/83e92ae533714d5bb45d0cbb00d19e20.png)数据类型

【HyperQuest】调参

1 前言 平台网址链接 平台介绍&#xff1a;这是一个网络应用程序&#xff0c;专门为机器学习的初学者设计&#xff0c;可以轻松地通过直觉来选择正确的超参数。 以此篇文章记录我的调参结果和个人总结&#xff0c;如果大家有更好的效果&#xff0c;欢迎留言交流~ 2 调参思路 …

C语言行列式转置

#include<stdio.h> void main() {int a[3][4] { {1,2,3},{4,5,6},{7,8,9} };int b[4][3] {0};int zf sizeof(a) / sizeof(a[0][0]);//总长int row_len sizeof(a) / sizeof(a[0]);//行数int col_len zf / row_len;//列数int i, j, k0,count0;for (i 0; i <row_le…

福建三明大型工程机械3D扫描测量工程零件开模加工逆向抄数-CASAIM中科广电

高精度3D扫描测量技术已经在大型工件制造领域发挥着重要作用&#xff0c;可以高精度高效率实现全尺寸三维测量&#xff0c;本期&#xff0c;CASAIM要分享的应用是大型工程机械3D扫描测量案例。 铣轮是深基础施工领域内工法先进、技术复杂程度高、高附加值的地连墙设备&#xff…

【万字长文】基于阿里云PAI平台搭建知识库检索增强的大模型对话系统

作者&#xff1a;施晨、尹丰彬、张晓雯、李林杨、黄俊 等 写在前面 本方案已在阿里云线上多个场景落地&#xff0c;将覆盖阿里云官方答疑群聊、研发答疑机器人、钉钉技术服务助手等。线上工单拦截率提升10%&#xff0c;答疑采纳率70%&#xff0c;显著提升答疑效率。 本方案…

onebound电商API接口商品数据采集平台:让数据成为生产力!

随着数字化商业时代的到来&#xff0c;API接口已成为电商资源连接利器&#xff0c;也是全球传统互联网企业转型的基础。 2021年 Google Cloud 研究显示&#xff0c;全球互联网企业近3/4的企业持续投入数字化转型&#xff0c;2/3的企业在持续增加投入&#xff0c;从这组数据可以…

内裤洗衣机有用吗?最好用的四款内衣洗衣机测评

相信很多小伙伴往往会因为懒而不想洗内衣&#xff0c;又或者洗内衣时经常会洗不干净&#xff01;这时就很有必要入手一台内衣洗衣机了&#xff0c;当我们洗完澡时&#xff0c;顺手把内衣放入洗衣机内&#xff0c;一键启动即可把我们的内衣洗得干干净净&#xff01;同时还可以为…

企业财务数字化转型怎么才能落地?_光点科技

企业财务数字化转型一直是企业发展中的一个重要议题。在当今数字化的时代&#xff0c;将财务流程纳入数字化转型计划中&#xff0c;不仅能够提高工作效率&#xff0c;还能为企业带来更多的商业机会。那么&#xff0c;企业财务数字化转型如何才能真正落地呢&#xff1f; 企业需要…

Postman环境配置

Postman环境配置 安装Postman安装node.js安装newman安装htmlextra安装git注册163邮箱用163邮箱注册gitee在pycharm中安装gitee详细文档 安装Postman 网址&#xff1a;https://www.postman.com/downloads/ 注册一个账号即可 安装node.js 安装newman npm install -g newman …

【2021集创赛】Arm杯三等奖:基于FPGA的人脸检测SoC设计

本作品参与极术社区组织的有奖征集|秀出你的集创赛作品风采,免费电子产品等你拿~活动。 团队介绍 参赛单位&#xff1a;合肥工业大学 队伍名称&#xff1a;芯创之家 指导老师&#xff1a;邓红辉、尹勇生 参赛杯赛&#xff1a;Arm杯 参赛人员&#xff1a;王亮 李嘉燊 金京 获奖情…

Cn2线路异常采用Nginx反代灾备解决方案

香港机房因出口带宽较小&#xff0c;抗攻击能力相对 较差。这是绝大部分香港机房的通病。CN2的稳定性要好一些&#xff0c;163线路价格相对便宜&#xff0c;稳定性较CN2要差一些。在受到扫段攻击&#xff08; https://anquan.baidu.com/article/1343 &#xff09;等特殊情况下&…

ASO优化之我们该如何本地化应用

ASO优化涉及优化各种元素&#xff0c;包括应用标题、关键词、描述、屏幕截图和预览视频&#xff0c;能够提高应用的排名、吸引相关用户并最大限度地提高特定区域内的下载量。在竞争激烈的应用商店中&#xff0c;本地化是确保应用成功的关键因素&#xff0c;首先是了解目标受众的…