360Apm 是什么?

ArgusAPM是360手机卫士客户端团队继RePlugin之后开源的又一个重量级开源项目。ArgusAPM是360移动端产品使用的可视化性能监控平台,为移动端APP提供性能监控与管理,可以迅速发现和定位各类APP性能和使用问题,帮助APP不断的提升用户体验。
项目地址:https://github.com/Qihoo360/ArgusAPM

apm 是如何收集卡顿(block)信息的?

package com.argusapm.android.core.job.block;/*** 卡顿模块Task** @author ArgusAPM Team*/
public class BlockTask extends BaseTask {private final String SUB_TAG = "BlockTask";private HandlerThread mBlockThread = new HandlerThread("blockThread");private Handler mHandler;private Runnable mBlockRunnable = new Runnable() {@Overridepublic void run() {if (!isCanWork()) {return;}StringBuilder sb = new StringBuilder();StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();for (StackTraceElement s : stackTrace) {sb.append(s.toString() + "\n");}if (DEBUG) {LogX.d(TAG, SUB_TAG, sb.toString());}saveBlockInfo(sb.toString());}};@Overridepublic void start() {super.start();if (!mBlockThread.isAlive()) { //防止多次调用mBlockThread.start();mHandler = new Handler(mBlockThread.getLooper());Looper.getMainLooper().setMessageLogging(new Printer() {private static final String START = ">>>>> Dispatching";private static final String END = "<<<<< Finished";@Overridepublic void println(String x) {if (x.startsWith(START)) {startMonitor();}if (x.startsWith(END)) {removeMonitor();}}});}}public void startMonitor() {mHandler.postDelayed(mBlockRunnable, ArgusApmConfigManager.getInstance().getArgusApmConfigData().funcControl.blockMinTime);}public void removeMonitor() {mHandler.removeCallbacks(mBlockRunnable);}}

com.argusapm.android.core.job.block.BlockTask 是收集卡顿信息的类,可以看到,他是拿到了MainLooper(), 然后设置了一个setMessageLogging。Looper.getMainLooper().setMessageLogging
mainLooper 里面执行的都是主线程里面执行的代码。

那为什么设置了一个messageLogging 就可以拿到卡顿信息呢?
我们看下Looper 的loop 方法:

    public static void loop() {final Looper me = myLooper();final MessageQueue queue = me.mQueue;for (;;) {Message msg = queue.next(); // might blockfinal Printer logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();final long end;try {msg.target.dispatchMessage(msg);end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);}}}if (logging != null) {logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);}}}

看可以看到,每一个msg 执行之前都会调用logging.println(">>>>> Dispatching to " + msg.target + " " +,每一个msg 结束都会执行logging.println("<<<<< Finished to "

所以,360收集卡顿是设置了一个mainLooper 的logging,然后如果log 信息 x.startsWith(“>>>>> Dispatching"),那么我就开启一个延时一定时间的runable,如果msg 里面的执行完成了x.startsWith(“<<<<< Finished”),那么表示结束了,那就移除掉这个延迟的runable, 如果延时的runable 到时间了,还没有打印<<<<< Finished,则表示你在主线程里面,操作的时间太长了,那么这时候,这个延时的runable 里面就会记录当前的堆栈。

这样就做到了收集卡顿的信息。

注:如果是我,我真的想不到可以这样做,反思下:
1.对源码不够了解 – 以后要多看源码
2.思考的太少–以后要多思考

如何收集Okhttp信息?

集成apm 的过程中,有一个gradle 插件。
这个插件里面,回去注册一个android transform

            android.registerTransform(ArgusAPMTransform(project))

这样,android 编译apk的时候,会把所有的jar 和 class 都会回调给自己写的transform处理。
会在编译期间,读取所有的class 文件,如果发现class 文件是Okhttp 的类的时候,在 方法里,拿到interceptors 字段,添加上自己的拦截器,就完成了用asm写入一段字节码。 这样就做到了可以采集OkHttp 信息。
具体可以参看源码。

如何收集帧(fps)信息?

package com.argusapm.android.core.job.fps;public class FpsTask extends BaseTask implements Choreographer.FrameCallback {private final String SUB_TAG = ApmTask.TASK_FPS;private long mLastFrameTimeNanos = 0; //最后一次时间private long mFrameTimeNanos = 0; //本次的当前时间private int mCurrentCount = 0; //当前采集条数private int mFpsCount = 0;private FpsInfo fpsInfo = new FpsInfo();private JSONObject paramsJson = new JSONObject();//定时任务private Runnable runnable = new Runnable() {@Overridepublic void run() {if (!isCanWork()) {mCurrentCount = 0;return;}calculateFPS();mCurrentCount++;//实现分段采集if (mCurrentCount < ArgusApmConfigManager.getInstance().getArgusApmConfigData().onceMaxCount) {AsyncThreadTask.executeDelayed(runnable, TaskConfig.FPS_INTERVAL);} else {AsyncThreadTask.executeDelayed(runnable, ArgusApmConfigManager.getInstance().getArgusApmConfigData().pauseInterval > TaskConfig.FPS_INTERVAL ? ArgusApmConfigManager.getInstance().getArgusApmConfigData().pauseInterval : TaskConfig.FPS_INTERVAL);mCurrentCount = 0;}}};private void calculateFPS() {if (mLastFrameTimeNanos == 0) {mLastFrameTimeNanos = mFrameTimeNanos;return;}float costTime = (float) (mFrameTimeNanos - mLastFrameTimeNanos) / 1000000.0F;if (mFpsCount <= 0 && costTime <= 0.0F) {return;}int fpsResult = (int) (mFpsCount * 1000 / costTime);if (fpsResult < 0) {return;}if (fpsResult <= TaskConfig.DEFAULT_FPS_MIN_COUNT) {fpsInfo.setFps(fpsResult);try {paramsJson.put(FpsInfo.KEY_STACK, CommonUtils.getStack());} catch (JSONException e) {e.printStackTrace();}fpsInfo.setParams(paramsJson.toString());fpsInfo.setProcessName(ProcessUtils.getCurrentProcessName());save(fpsInfo);}if (AnalyzeManager.getInstance().isDebugMode()) {if (fpsResult > TaskConfig.DEFAULT_FPS_MIN_COUNT) {fpsInfo.setFps(fpsResult);}AnalyzeManager.getInstance().getParseTask(ApmTask.TASK_FPS).parse(fpsInfo);}mLastFrameTimeNanos = mFrameTimeNanos;mFpsCount = 0;}@Overridepublic void start() {super.start();AsyncThreadTask.executeDelayed(runnable, (int) (Math.round(Math.random() * TaskConfig.TASK_DELAY_RANDOM_INTERVAL)));Choreographer.getInstance().postFrameCallback(this);}@Overridepublic void doFrame(long frameTimeNanos) {mFpsCount++;mFrameTimeNanos = frameTimeNanos;if (isCanWork()) {//注册下一帧回调Choreographer.getInstance().postFrameCallback(this);} else {mCurrentCount = 0;}}
}

上面这个是收集fps 的任务类。Choreographer.getInstance().postFrameCallback(this);这句话,把当前的任务类,添加到绘制一帧frame 的callback 里面。

我们看下这个方法:

/*** Posts a frame callback to run on the next frame.* <p>* The callback runs once then is automatically removed.* </p>** @param callback The frame callback to run during the next frame.** @see #postFrameCallbackDelayed* @see #removeFrameCallback*/public void postFrameCallback(FrameCallback callback) {postFrameCallbackDelayed(callback, 0);}

可以看到,到了绘制的时候,就会调用doFrame 方法。这个callback 执行一次之后,就会被移除掉。所以,他们在doFrame 里面又执行了Choreographer.getInstance().postFrameCallback(this);继续监听。
当到了时间执行采集的runable,就会去计算绘制了多少帧,花费了多少时间。如果算出来每帧的时间大于一个值,就会记录下来。

如果收集Activity信息?

package com.argusapm.android.core.job.activity;public class InstrumentationHooker {private static boolean isHookSucceed = false;//是否已经hook成功public static void doHook() {try {hookInstrumentation();isHookSucceed = true;} catch (Exception e) {if (DEBUG) {LogX.e(TAG, "InstrumentationHooker", e.toString());}}}static boolean isHookSucceed() {return isHookSucceed;}private static void hookInstrumentation() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {Class<?> c = Class.forName("android.app.ActivityThread");Method currentActivityThread = c.getDeclaredMethod("currentActivityThread");boolean acc = currentActivityThread.isAccessible();if (!acc) {currentActivityThread.setAccessible(true);}Object o = currentActivityThread.invoke(null);if (!acc) {currentActivityThread.setAccessible(acc);}Field f = c.getDeclaredField("mInstrumentation");acc = f.isAccessible();if (!acc) {f.setAccessible(true);}Instrumentation currentInstrumentation = (Instrumentation) f.get(o);Instrumentation ins = new ApmInstrumentation(currentInstrumentation);f.set(o, ins);if (!acc) {f.setAccessible(acc);}}
}

会去hook 当前currentActivityThread 的 mInstrumentation。 每次执行activity 的任何生命周期,都会先调用到Instrumentation 里面。把Instrumentation 设置成自己的Instrumentation,把原来的Instrumentation 保存起来。相当于代理了原来的Instrumentation。

package com.argusapm.android.core.job.activity;/*** 对activity启动各个阶段的消耗的时间进行监控** @author ArgusAPM Team*/
public class ApmInstrumentation extends Instrumentation {private static final String SUB_TAG = "traceactivity";private Instrumentation mOldInstrumentation = null;public ApmInstrumentation(Instrumentation oldInstrumentation) {if (oldInstrumentation instanceof Instrumentation) {mOldInstrumentation = oldInstrumentation;}}@Overridepublic void callApplicationOnCreate(Application app) {ActivityCore.appAttachTime = System.currentTimeMillis();if (DEBUG) {LogX.d(TAG, SUB_TAG, "callApplicationOnCreate time" + ActivityCore.appAttachTime);}if (mOldInstrumentation != null) {mOldInstrumentation.callApplicationOnCreate(app);} else {super.callApplicationOnCreate(app);}}@Overridepublic void callActivityOnCreate(Activity activity, Bundle icicle) {if (!isActivityTaskRunning()) {if (mOldInstrumentation != null) {mOldInstrumentation.callActivityOnCreate(activity, icicle);} else {super.callActivityOnCreate(activity, icicle);}return;}if (DEBUG) {LogX.d(TAG, SUB_TAG, "callActivityOnCreate");}long startTime = System.currentTimeMillis();if (mOldInstrumentation != null) {mOldInstrumentation.callActivityOnCreate(activity, icicle);} else {super.callActivityOnCreate(activity, icicle);}ActivityCore.startType = ActivityCore.isFirst ? ActivityInfo.COLD_START : ActivityInfo.HOT_START;ActivityCore.onCreateInfo(activity, startTime);}}

这样,就统计了你的activity 每个onXXX 方法执行的用时,而没有嵌入任何代码。

如果收集内存信息?

package com.argusapm.android.core.job.memory;public class MemoryTask extends BaseTask {private static final String SUB_TAG = "MemoryTask";//定时任务private Runnable runnable = new Runnable() {@Overridepublic void run() {if ((!isCanWork()) || (!checkTime())) {return;}MemoryInfo memoryInfo = getMemoryInfo();save(memoryInfo);updateLastTime();}}};/*** 获取当前内存信息*/private MemoryInfo getMemoryInfo() {// 注意:这里是耗时和耗CPU的操作,一定要谨慎调用Debug.MemoryInfo info = new Debug.MemoryInfo();Debug.getMemoryInfo(info);if (DEBUG) {LogX.d(TAG, SUB_TAG,"当前进程:" + ProcessUtils.getCurrentProcessName()+ ",内存getTotalPss:" + info.getTotalPss()+ " nativeSize:" + info.nativePss+ " dalvikPss:" + info.dalvikPss+ " otherPss:" + info.otherPss);}return new MemoryInfo(ProcessUtils.getCurrentProcessName(), info.getTotalPss(), info.dalvikPss, info.nativePss, info.otherPss);}@Overridepublic void start() {super.start();AsyncThreadTask.executeDelayed(runnable, ArgusApmConfigManager.getInstance().getArgusApmConfigData().funcControl.getMemoryDelayTime() + (int) (Math.round(Math.random() * 1000)));}}

关键是getMemoryInfo 这个方法。其他的定时去调用getMemoryInfo获取内存信息,都是没有什么的。

开源框架学习总结:

1.学到一些新的思路。
2.学到一些新的设计模式。
3.扩展知识面
4.学以致用,自己以后有其他的想法,会这个技术,就可以写开源框架。

360Apm源码解析相关推荐

  1. 谷歌BERT预训练源码解析(二):模型构建

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/weixin_39470744/arti ...

  2. 谷歌BERT预训练源码解析(三):训练过程

    目录 前言 源码解析 主函数 自定义模型 遮蔽词预测 下一句预测 规范化数据集 前言 本部分介绍BERT训练过程,BERT模型训练过程是在自己的TPU上进行的,这部分我没做过研究所以不做深入探讨.BE ...

  3. 谷歌BERT预训练源码解析(一):训练数据生成

    目录 预训练源码结构简介 输入输出 源码解析 参数 主函数 创建训练实例 下一句预测&实例生成 随机遮蔽 输出 结果一览 预训练源码结构简介 关于BERT,简单来说,它是一个基于Transfo ...

  4. Gin源码解析和例子——中间件(middleware)

    在<Gin源码解析和例子--路由>一文中,我们已经初识中间件.本文将继续探讨这个技术.(转载请指明出于breaksoftware的csdn博客) Gin的中间件,本质是一个匿名回调函数.这 ...

  5. Colly源码解析——结合例子分析底层实现

    通过<Colly源码解析--框架>分析,我们可以知道Colly执行的主要流程.本文将结合http://go-colly.org上的例子分析一些高级设置的底层实现.(转载请指明出于break ...

  6. libev源码解析——定时器监视器和组织形式

    我们先看下定时器监视器的数据结构.(转载请指明出于breaksoftware的csdn博客) /* invoked after a specific time, repeatable (based o ...

  7. libev源码解析——定时器原理

    本文将回答<libev源码解析--I/O模型>中抛出的两个问题.(转载请指明出于breaksoftware的csdn博客) 对于问题1:为什么backend_poll函数需要指定超时?我们 ...

  8. libev源码解析——I/O模型

    在<libev源码解析--总览>一文中,我们介绍过,libev是一个基于事件的循环库.本文将介绍其和事件及循环之间的关系.(转载请指明出于breaksoftware的csdn博客) 目前i ...

  9. libev源码解析——调度策略

    在<libev源码解析--监视器(watcher)结构和组织形式>中介绍过,监视器分为[2,-2]区间5个等级的优先级.等级为2的监视器最高优,然后依次递减.不区分监视器类型和关联的文件描 ...

最新文章

  1. keep健身软件电脑版_keep下载安装2020官方版-keep健身软件免费版下载v6.125.0 安卓最新版...
  2. python是c语言写的吗-C语言和python的区别
  3. 1. 金融数学中的随机变分法-Wiener空间与Wiener泛函
  4. 【分享】Oracle 常用运维命令大全
  5. Spring反转控制
  6. mpvue 中使用 wx-f2 tooltip 失效解决
  7. 文档基本结构标签的作用
  8. jedispool redis哨兵_通过java哨兵JedisSentinelPool代码示例连接对配置的redis哨兵主从模式进行测试验证...
  9. 【新冠疫情】5G到底能为抗疫做点啥,这篇文章终于讲清楚了
  10. 二、Get和Post的区别
  11. 测试用例怎么写_如何高效组织自动化测试用例
  12. C#AutoResetEvent和ManualResetEvent的区别
  13. 《Android游戏开发详解》——导读
  14. 大量的免费电子书下载地址
  15. hp6960无法连接计算机,惠普6960驱动
  16. 常用的富文本编辑器插件
  17. 阿里矢量图iconfont的两种使用方法
  18. keras入门 ---在小数据集上训练神经网络
  19. 第三方登录 人人php,php 使用curl模拟登录人人(校内)网的简单实例
  20. [038]量化交易]显示所有股票名称及股票代码

热门文章

  1. G41显卡Linux驱动,Intel最新G41/G43/G45集成显卡驱动下载
  2. keil git 编译文件_keil下的STM32程序开发部署(一)
  3. CUDA简易安装教程
  4. 计算机编码技术ppt,计算机编码技术.ppt
  5. c++排查线程hang住_Kafka学习笔记之kafka高版本Client连接0.9Server引发的血案排查 - 时光飞逝,逝者如斯...
  6. phpcurl 请求Chunked-Encoded data 遇到的一个问题
  7. 自己搭建自动化巡检系统(五) 抖动告警
  8. Android中Handler的使用
  9. MIPS中的异常处理和系统调用【转】
  10. wpf 绘制rectangle 代码