美文网首页
云米客户端APM性能监控组件分析(一)

云米客户端APM性能监控组件分析(一)

作者: 捉影T_T900 | 来源:发表于2019-08-28 23:39 被阅读0次

    想做这个组件很久了,企业是一个盈利机构,业务的稳定快速发展才能支撑一个公司正常运营。但很多时候这个理由被滥用,被拿来当做搪塞的借口。忽略流程、质量,以致很多时候绝大部分人都疲于奔命,而且产出效益一点都不高。只有为数不多的企业真正做到科学管控、各司其职。

    作为一个技术人员,核心职责是通过技术手段,交付质量高、稳定性好、扩展性强的功能,满足企业的业务需求。那怎样才能衡量交付的内容满足以上条件?看谁的声音大?还是看谁的资历老?【摊手】。显然不是,通过各种各样的监控手段,获得整个系统的运行数据,量化统计出各项运行指标,制定合适的标准,才能判断交付的内容是否符合标准。

    出于这个目的,我在公司内部提出了设计并实现一个APP监控组件,可以方便地集成到公司的各个大大小小Android应用程序中,时时刻刻监控用户运行时的程序各项数据,并将获取到的数据上传至公司数据库,用于分析运行时的程序性能,并得出后续迭代的依据。

    目前组件已经上线运行一段时间,目测数据比较正常,有需要可以拿去研究学习,已去除公司域名,哈哈哈哈哈哈
    https://github.com/andyliu900/ideacode-apm-lib

    以下内容高能,非专业人士选择性阅读
    

    想法很美好,那么问题来了

    问题一:一个手机APP,应该监控什么?

    首先,这里实现的是Android原生程序性能监控方案,讨论的只是Android原生的范围。

    一个Android程序由一个又一个界面组成,每一个界面都有各自的画面,界面的打开、显示、渲染等等都需要时间进行处理,所以监控这个过程的耗时数据是个不错的idea。

    每个人用手机程序的时候都有过卡成狗的体验,所以监控为什么会卡成狗又是一个不错的idea。

    现在的手机程序界面为了吸引用户,多多少少都会用到各种动画,计算机是通过绘制一帧又一帧的画面来实现画面动起来的效果,玩过走马灯的都知道这个原理,人眼对于绘制速度超过每秒60帧的画面认为流畅不卡顿,又是一个监控的point。

    计算机都需要内存来进行缓冲,程序跑起来都会占用一定的内存空间,内存越少运行程序越卡,所以监控一定时间内的内存使用情况,也是一个很好的idea。

    现在想找一个不用联网的单机程序真的难,网络通信本质上市IO操作,有上行和下行的数据,那么获取上行、下行数据,并统计整个IO操作的耗时,不失为一个聪明的idea。

    另外现代操作系统是支持多进程的,一个程序允许开启多个进程来处理任务,可以监控各个进程的信息,判断这个程序的进程设计是否科学......

    问题二:好吧,你说的都对。但我不知道去哪里拿这些内容

    以上可以统称为监控【APP启动】、【Activity生命周期】、【卡顿时线程堆栈】、【绘制帧率】、【运行时内存使用情况】、【网络通信】、【进程信息】。接下来会分别从以上7个方面获取我们想要的信息,并记录在案。

    以下内容更高能,非战斗人员尽早离场
    
    监控【APP启动】、【Activity生命周期】

    这两个本质上都是监控Activity的相关信息,这里要做一个很重要的事情,就是“hook”,中文译名是“钩子”。实际上是用一个假的东西替换掉系统原来的东西,让系统傻傻地拿着这个假的内容去完成任务,而这个假的内容已经被我们所接管,我们可以监控系统的整个任务过程。
    说到Activity,大家都知道Activity和Activity之间是可以跳转的,真正控制跳转的是Instrumentation这个对象。那我们可以用我们自己指定的假的Instrumentation来替换系统原来的Instrumentation。

        /**
         * 插桩实现
         *
         * @throws ClassNotFoundException
         * @throws NoSuchMethodError
         * @throws InvocationTargetException
         * @throws IllegalAccessException
         * @throws NoSuchFileException
         */
        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);
            }
        }
    

    ApmInstrumentation就是我说的假的Instrumentation。通过重写callApplicationOnCreate、callActivityOnXXX等方法,达到拦截生命周期事件的目的,并在整个过程中记录时间节点,统计出耗时信息。
    详见代码:
    https://github.com/andyliu900/ideacode-apm-lib/blob/master/apmlibrary/src/main/java/com/ideacode/apm/library/core/job/activity/ApmInstrumentation.java

    注意点:监控FirstFrame耗时

    一个Activity页面的最外层实际是一个DecorView,可以指定发送一个Runnable对象,并在其中计算出FirstFrame的渲染耗时。

    activity.getWindow().getDecorView().post(new FirstFrameRunnable(activity, startType, startTime));
    .
    . 省略代码
    .
       static class FirstFrameRunnable implements Runnable {
    
            private Activity activity;
            private int startType;
            private long startTime;
    
            public FirstFrameRunnable(Activity activity, int startType, long startTime) {
                this.activity = activity;
                this.startType = startType;
                this.startTime = startTime;
            }
    
            @Override
            public void run() {
                long firstFrameTime = System.currentTimeMillis() - startTime;
                if (Manager.isDebug()) {
                    ApmLogX.d(APM_TAG, SUB_TAG, "FirstFrameRunnable time:" + firstFrameTime);
                }
                if (firstFrameTime >= TaskConfig.DEFAULT_ACTIVITY_FIRST_MIN_TIME) {
                    saveActivityInfo(activity, startType, System.currentTimeMillis() - startTime, ActivityInfo.TYPE_FIRST_FRAME);
                }
    
                if (Manager.isDebug()) {
                    ApmLogX.d(APM_TAG, SUB_TAG, "FirstFrameRunnable time:" + String.format("[%s, %s]", ActivityCore.isFirst,
                            TimeUtils.getFormatTime(ActivityCore.appAttachTime, TimeUtils.DATETIMESECOND_SPLIT)));
                }
                // 保存应用冷启动时间
                if (ActivityCore.isFirst) {
                    ActivityCore.isFirst = false;
                    if (ActivityCore.appAttachTime <= 0) {
                        return;
                    }
                    int t = (int)(System.currentTimeMillis() - ActivityCore.appAttachTime);
                    if (Manager.isDebug()) {
                        ApmLogX.d(APM_TAG, SUB_TAG, "AppStartTime time: " + t);
                    }
                    AppStartInfo info = new AppStartInfo(t);
                    ITask task = Manager.getInstance().getTaskManager().getTask(ApmTask.TASK_APP_START);
                    if (task != null) {
                        if (Manager.isDebug()) {
                            ApmLogX.d(APM_TAG, SUB_TAG, "AppStartInfo saveAppStartInfo");
                        }
                        task.save(info);
                    } else {
                        if (Manager.isDebug()) {
                            ApmLogX.d(APM_TAG, SUB_TAG, "AppStartInfo task == null");
                        }
                    }
                }
            }
        }
    
    

    详见代码:
    https://github.com/andyliu900/ideacode-apm-lib/blob/master/apmlibrary/src/main/java/com/ideacode/apm/library/core/job/activity/ActivityCore.java

    未完待续......

    相关文章

      网友评论

          本文标题:云米客户端APM性能监控组件分析(一)

          本文链接:https://www.haomeiwen.com/subject/rgdvectx.html