武汉网站建设百家号/西安百度搜索排名
背景
得物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 版本开始就被引入了。
相关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抽象类实现如下
因此可以通过设置自定义的SocketImpleFactory 返回一个包装过的SokcetImpl实例,从而对Socket进行监控。
//1.反射获取原始的Factory
SocketImplFactory oldFactory = ....
if (oldFacotry!=null){//反射修改 Socket.class 的facotry字段,设置为新的FactoryReflectionUtil.of(Socket.class).field("factory").set(null, new TrafficSocketImplFactory(oldFactory));
} else {//设置新的factorySocket.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 happendApmSafety.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 应用流量监控实践
背景 得物Apm系统本身包含网络接口性能监控的能力,但接口监控主要关注的是接口的耗时、异常率等信息,没有流量消耗相关维度的统计信息,并且一部分流量消耗可能来自于音视频等其他特殊场景,在接口监控的盲区外。 为了了解用户目前…...

并发前置知识一:线程基础
一、通用的线程生命周期:“五态模型” 二、java线程有哪几种状态? New:创建完线程Runable:start(),这里的Runnable包含操作的系统的Running(运行状态)和Ready(上面的可运行状态)Blo…...

计算机网络 物理层
文章目录 物理层物理层的基本概念数据通信的基础知识数据通信系统的模型有关信道的几个基本概念信道的极限容量 物理层下面的传输媒体导引型传输媒体非引导型传输媒体 信道复用技术波分复用码的复用 宽带接入技术ADSL 技术光纤同轴混合网 (HFC 网)FTTx 技术 物理层 …...

浅谈轻量级Kubernetes—K3s
1.什么是K3s K3s 被设计为小于 40MB 的单个二进制文件,完全实现了 Kubernetes API。为了实现这一目标,他们删除了许多不需要成为核心一部分的额外驱动程序,并且很容易被附加组件替换。 K3s 是完全 CNCF(云原生计算基金会&…...

Web APIs知识点讲解
学习目标: 能获取DOM元素并修改元素属性具备利用定时器间歇函数制作焦点图切换的能力 一.Web API 基本认知 1.作用和分类 作用: 就是使用 JS 去操作 html 和浏览器分类:DOM (文档对象模型)、BOM(浏览器对象模型) 2.DOM DOM(Document Ob…...

Python商业数据挖掘实战——爬取网页并将其转为Markdown
前言 「作者主页」:雪碧有白泡泡 「个人网站」:雪碧的个人网站 ChatGPT体验地址 文章目录 前言前言正则表达式进行转换送书活动 前言 在信息爆炸的时代,互联网上的海量文字信息如同无尽的沙滩。然而,其中真正有价值的信息往往埋…...

初识 Elasticsearch 应用知识,一文读懂 Elasticsearch 知识文集(1)
🏆作者简介,普修罗双战士,一直追求不断学习和成长,在技术的道路上持续探索和实践。 🏆多年互联网行业从业经验,历任核心研发工程师,项目技术负责人。 🎉欢迎 👍点赞✍评论…...

StampedLock详解
在现代的Java应用中,同步是一个核心问题,尤其是在高并发环境下。Java提供了多种同步机制,从基本的synchronized关键字到更高级的ReentrantLock。但在Java 8中,引入了一个新的同步原语——StampedLock,它旨在提供更高的…...

Linux中DCHP与时间同步
目录 一、DHCP (一)工作原理 1.获取 2.续约 (二)分配方式 (三)服务器配置 1.随机地址分配 2.固定地址分配 二、时间同步 (一)ntpdate (二)chrony …...

国产系统-银河麒麟桌面版V10安装字体-wps安装字体
安装系统:银河麒麟V10 demodemo-pc:~/桌面$ cat /proc/version Linux version 5.10.0-8-generic (builddfa379600e539) (gcc (Ubuntu 9.4.0-1kylin1~20.04.1) 9.4.0, GNU ld (GNU Binutils for Ubuntu) 2.34) #33~v10pro-KYLINOS SMP Wed Mar 22 07:21:49 UTC 20230.系统缺失…...

python 10常用自动化脚本收藏好
01、 图片优化器 使用这个很棒的自动化脚本,可以帮助把图像处理的更好,你可以像在 Photoshop 中一样编辑它们。 该脚本使用流行的是 Pillow 模块 # Image Optimizing # pip install Pillow import PIL # Croping im PIL.Image.open("Image1.jp…...

java物品检验管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目
一、源码特点 java Web 物品检验管理系统是一套完善的java web信息管理系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发,数据库为Mysq…...

Pandas实战100例 | 案例 2: 数据探索 - 查看和理解数据
案例 2: 数据探索 - 查看和理解数据 知识点讲解 在数据分析的早期阶段,对数据进行初步的探索是非常重要的。这包括查看数据的基本信息、统计摘要、以及数据的形状。 示例代码 查看数据的基本信息 # 显示 DataFrame 的基本信息,包括列名、非空值数量…...

c++qt-基本组件
1. Designer 设计师(掌握) Qt包含了一个Designer程序,用于通过可视化界面设计开发界面,保存的文件格式为.ui(界面文件)。界面文件内部使用xml语法的标签式语言。 在Qt Creator中创建项目时,选中…...

SpringBoot多环境配置Maven Profile组
Maven profile组 注意切换配置时 mvn clean下 或者 clean 加install 或者compile 编译 clean之后 install下 或者compile 编译 nohup java -Xms256m -Xmx512m -Dfile.encodingUTF-8 -jar demo.jar --spring.profiles.activeprod > system.log 2>&1 &...

服务器配置 ssh 密钥登录
服务器配置 ssh 密钥登录 配置 服务器安全组策略,开放 ssh 22 端口,以 root 用户登录服务器。 配置 ssh key 登录 ssh-keygen 生成公钥和私钥对 如果不需要其他设置,一直回车 可以在 ~/.ssh 目录下看到两个文件,即刚生成的私钥…...

使用递归将list转换成tree
在产品研发时遇到这样一个问题,对于省市区县这类三级联动的数据,前端插件需要一次把数据全部返回,单纯的使用接口查询字节的没办法满足要求。 如果一次把数据全部返回,前端使用起来很麻烦需要一条一条的进行查找。 常规的使用方…...

untiy使用http下载资源
文章目录 提醒下载一个资源并保存到本地下载一张图片 提醒 部分API需要将Unity的 Edit/PrejectSetting/Player/OtherSetttings/AConfiguration/ApiCompatibilityLevel 设为.NetFramework 才可以使用 下载一个资源并保存到本地 private IEnumerator DownloadFormServer_IE(st…...

03-编码篇-x264编译与介绍
使用FFMPEG作编码操作时,会涉及到将yuv数据编码成h264数据,FFmpeg的libavcodec中的libx264.c会调用x264库的源码作编码: 1.x264库编译 下载X264,地址为:http://www.videolan.org/developers/x264.html,并解…...

生活自来水厂污水处理设备需要哪些
生活自来水厂是确保我们日常用水质量安全的重要设施。在自来水的生产过程中,污水处理设备是不可或缺的环节。那么,生活自来水厂的污水处理设备都有哪些呢?本文将为您详细介绍。 首先,生活自来水厂的污水处理设备主要包括预处理设备…...

Full names for abbreviations of Linux Commands
synopsis Towards/On Full names for abbreviations of Linux Commands I) website addressII) Mapping between full names and abbreviations I) website address II) Mapping between full names and abbreviations su:Swith user 切换用户,切换到ro…...

kafka下载安装部署
Apache kafka 是一个分布式的基于push-subscribe的消息系统,它具备快速、可扩展、可持久化的特点。它现在是Apache旗下的一个开源系统,作为hadoop生态系统的一部分,被各种商业公司广泛应用。它的最大的特性就是可以实时的处理大量数据以满足各…...

python包管理工具:pipenv的基本使用
很多语言都提供了环境隔离的支持,例如nodejs的node_module,golang的go mod,python也有virtualenv和pyvenv等机制。 为了建立依赖快照,通常会用pip freeze > requirements.txt 命令生成一个requirements.txt文件,在…...

AI系统ChatGPT网站系统源码AI绘画详细搭建部署教程,支持GPT语音对话+DALL-E3文生图+GPT-4多模态模型识图理解
一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统,支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美,可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作Ch…...

MC-4/11/03/400步进电机驱动器的主要驱动方式有哪些?
MC-4/11/03/400步进电机驱动器的主要驱动方式有哪些? 步进电机驱动器是一种将电脉冲转化为角位移的执行机构。当步进驱动器接收到一个脉冲信号,它就驱动步进电机按设定的方向转动一个固定的角度,这个固定的角度被称为“步距角”。步进电机不能…...

大数据技术原理与应用期末复习(林子雨)
大数据技术原理与应用期末复习(林子雨) Hadoop的特性HBase编程实践NoSQL的四大类型键值数据库优点:缺点: 列族数据库优点:缺点: 文档数据库优点:缺点: 图数据库优点:缺点…...

C练习——魔术师猜三位数
题目: 有一种室内互动游戏,魔术师要每位观众心里想一个三位数abc(a、b、c分别是百位、十位和个位数字),然后魔术师让观众心中记下acb、bac、bca、cab、cba五个数以及这5个数的和值。只要观众说出这个和是多少…...

three.js 使用 tweenjs绘制相机运动动画
效果: 代码: <template><div><el-container><el-main><div class"box-card-left"><div id"threejs" style"border: 1px solid red"></div><div class"box-right"…...

Oracle VARCHAR和VARCHAR2区别
在Oracle数据库中,VARCHAR和VARCHAR2是两种不同的数据类型,它们的区别如下: 1.存储空间 VARCHAR和VARCHAR2在存储空间上有所不同。在Oracle 7及以下版本中,VARCHAR类型的长度是固定的,如果存储的数据长度小于定义的长…...

HarmonyOS 开发基础(八)Row和Column
HarmonyOS 开发基础(八)Row和Column 一、Column 容器 1、容器说明: 纵向容器主轴方向:从上到下纵向交叉轴方向:从左到右横向 2、容器属性: justifyContent:设置子元素在主轴方向的对齐格式…...