APM框架Matrix源码分析(九)AppMethodBeat源码分析

在编译期间,AppMethodBeat的i、o、at会插桩到相应的位置(i在方法入口,o在方法出口,at在Activity的onWindowFocusChanged)

当执行第一个函数(Application的attachBaseContext)记录时间,会执行AppMethodBeat.i

i方法

java 复制代码
public static void i(int methodId) {

    if (status <= STATUS_STOPPED) {
        return;
    }
    if (methodId >= METHOD_ID_MAX) {
        return;
    }

    if (status == STATUS_DEFAULT) {
        synchronized (statusLock) {
            if (status == STATUS_DEFAULT) {
                //【realExecute】只有第一个方法进来才会执行(Application的attachBaseContext)
                realExecute();
                status = STATUS_READY;
            }
        }
    }
    //主线程才会执行此逻辑
    if (threadId == sMainThreadId) {
        if (assertIn) {
          android.util.Log.e(TAG, "ERROR!!! AppMethodBeat.i Recursive calls!!!");
          return;
        }
        assertIn = true;
        if (sIndex < Constants.BUFFER_SIZE) {
          //【mergeData】存方法信息,传true表示方法开始
          mergeData(methodId, sIndex, true);
        } else {
          //超过数组长度,重新从0开始存
          sIndex = 0;
          mergeData(methodId, sIndex, true);
        }
        //自增
        ++sIndex;
        assertIn = false;
    }
}

status用来控制状态,第一个i方法进来会执行realExecute(),只会执行一次。

realExecute
java 复制代码
private static void realExecute() {
    //1.sUpdateDiffTimeRunnable更新时间间隔用于计算方法执行耗时
    sHandler.postDelayed(sUpdateDiffTimeRunnable, Constants.TIME_UPDATE_CYCLE_MS);
    //2.checkStartExpiredRunnable超时检测(10s)将AppMethodBeat置为不可用状态
    sHandler.postDelayed(checkStartExpiredRunnable = new Runnable() {
        @Override
        public void run() {
            synchronized (statusLock) {
                MatrixLog.i(TAG, "[startExpired] timestamp:%s status:%s", System.currentTimeMillis(), status);
                if (status == STATUS_DEFAULT || status == STATUS_READY) {
                    status = STATUS_EXPIRED_START;
                }
            }
        }
    }, Constants.DEFAULT_RELEASE_BUFFER_DELAY);
    ActivityThreadHacker.hackSysHandlerCallback();
    //监听消息开始和消息结束
    LooperMonitor.register(looperMonitorListener);
}
1)sUpdateDiffTimeRunnable

如果在方法首尾直接用SystemClock.uptimeMillis()计算方法耗时,大量方法执行这个操作时,对性能的影响就会明显放大,所以这里采用了循环线程,每隔5ms(不包含消息队列休眠时间,同时5ms的误差对于耗时分析可忽略不计)更新一次sCurrentDiffTime

java 复制代码
private static volatile boolean isPauseUpdateTime = false;

private static Runnable sUpdateDiffTimeRunnable = new Runnable() {
    @Override
    public void run() {
        try {
            while (true) {
                //满足条件则5ms更新一次时间,isPauseUpdateTime是一个重要控制条件
                while (!isPauseUpdateTime && status > STATUS_STOPPED) {
                    sCurrentDiffTime = SystemClock.uptimeMillis() - sDiffTime;
                    SystemClock.sleep(Constants.TIME_UPDATE_CYCLE_MS);
                }
                //不满足条件进入挂起状态,等待唤醒
                synchronized (updateTimeLock) {
                    updateTimeLock.wait();
                }
            }
        } catch (Exception e) {
            MatrixLog.e(TAG, "" + e.toString());
        }
    }
};

//消息执行开始
private static void dispatchBegin() {
    sCurrentDiffTime = SystemClock.uptimeMillis() - sDiffTime;
    //isPauseUpdateTime置为false,满足循环条件
    isPauseUpdateTime = false;
		//唤醒循环线程继续更新时间
    synchronized (updateTimeLock) {
      updateTimeLock.notify();
    }
}
//消息执行结束
private static void dispatchEnd() {
  //isPauseUpdateTime置为true,停止循环,进入挂起状态
   isPauseUpdateTime = true;
}

sUpdateDiffTimeRunnable是一个循环线程,isPauseUpdateTime是一个重要循环控制条件,当主线程的当前消息执行结束,下一个消息还未执行期间sUpdateDiffTimeRunnable会进入挂起状态(减少cpu占用), 否则每隔5ms更新一次时间。

2)checkStartExpiredRunnable

checkStartExpiredRunnable超时检测线程,i方法第一次执行10s后,status状态仍为STATUS_DEFAULTSTATUS_READY,即认为超时,将状态置为STATUS_EXPIRED_START(即AppMethodBeat不可用,如i方法和o方法不工作,sUpdateDiffTimeRunnable不更新时间等)

那么这个status又是在哪里更改的呢

java 复制代码
public class TracePlugin extends Plugin {
    @Override
    public void start() {
        //...省略其它代码
        if (traceConfig.isAppMethodBeatEnable()) {
          AppMethodBeat.getInstance().onStart();
        } else {
          AppMethodBeat.getInstance().forceStop();
        }
    }

    @Override
    public void stop() {
        //...省略其它代码
        AppMethodBeat.getInstance().onStop();
    }
}
java 复制代码
public class AppMethodBeat implements BeatLifecycle {
    @Override
    public void onStart() {
        synchronized (statusLock) {
            if (status < STATUS_STARTED && status >= STATUS_EXPIRED_START) {
                sHandler.removeCallbacks(checkStartExpiredRunnable);
                MatrixHandlerThread.getDefaultHandler().removeCallbacks(realReleaseRunnable);
                if (sBuffer == null) {
                    throw new RuntimeException(TAG + " sBuffer == null");
                }
                MatrixLog.i(TAG, "[onStart] preStatus:%s", status, Utils.getStack());
                //状态置为STATUS_STARTED 
                status = STATUS_STARTED;
            } else {
                MatrixLog.w(TAG, "[onStart] current status:%s", status);
            }
        }
    }
}

AppMethodBeat是跟随TracePlugin的生命周期的,TracePlugin启动了AppMethodBeat只才能工作,AppBeatMethod的i、o等方法被外部调用,场景多,这样做可以使TracePlugin真正控制AppBeatMethod。

mergeData

结合下图(来自官方)来看,mergeData将methodId、i/o、方法耗时合成一个long数据,long二进制有64位,0 ~ 42存时间戳偏移量,43 ~ 62存methodId,最高位存i/o,这样一个long存储3条信息,减少内存占用。

java 复制代码
private static void mergeData(int methodId, int index, boolean isIn) {
  	//消息分发开始和结束传入METHOD_ID_DISPATCH,更新时间偏移量
    if (methodId == AppMethodBeat.METHOD_ID_DISPATCH) {
        sCurrentDiffTime = SystemClock.uptimeMillis() - sDiffTime;
    }

    try {
        //long最高位存i/o,0表示退出方法,1表示进入方法
        long trueId = 0L;
        if (isIn) {
            //如果是方法开始,最高位存1
            trueId |= 1L << 63;
        }
        //43 ~ 62存methodId
        trueId |= (long) methodId << 43;
        //将sCurrentDiffTime转为二进制存入0 ~ 42
        //7对应二进制111,F对应1111,0x7FFFFFFFFFFL表示111...1111,共43位,取& 得到sCurrentDiffTime二进制原值
        trueId |= sCurrentDiffTime & 0x7FFFFFFFFFFL;
        //此时trueId包含i/o、methodId、sCurrentDiffTime,存入sBuffer
        sBuffer[index] = trueId;
        //处理堆积状态,sBuffer装满后从0开始覆盖老数据,标记老数据为无效数据
        checkPileup(index);
        sLastIndex = index;
    } catch (Throwable t) {
        MatrixLog.e(TAG, t.getMessage());
    }
}

o方法

java 复制代码
public static void o(int methodId) {
    if (status <= STATUS_STOPPED) {
        return;
    }
    if (methodId >= METHOD_ID_MAX) {
        return;
    }
    if (Thread.currentThread().getId() == sMainThreadId) {
        if (sIndex < Constants.BUFFER_SIZE) {
            //mergeData存方法信息,传false表示方法结束
            mergeData(methodId, sIndex, false);
        } else {
            sIndex = 0;
            mergeData(methodId, sIndex, false);
        }
        ++sIndex;
    }
}

和i方法一样,调用mergeData存方法信息,传入false表示方法结束。

at方法

java 复制代码
public static void at(Activity activity, boolean isFocus) {
    String activityName = activity.getClass().getName();
    if (isFocus) {
        //sFocusActivitySet去重
        if (sFocusActivitySet.add(activityName)) {
            synchronized (listeners) {
                for (IAppMethodBeatListener listener : listeners) {
                    //回调给StartupTracer计算Activity启动耗时
                    listener.onActivityFocused(activity);
                }
            }
            MatrixLog.i(TAG, "[at] visibleScene[%s] has %s focus!", getVisibleScene(), "attach");
        }
    } else {
        if (sFocusActivitySet.remove(activityName)) {
            MatrixLog.i(TAG, "[at] visibleScene[%s] has %s focus!", getVisibleScene(), "detach");
        }
    }
}

at方法在Activity执行onWindowFocusChanged时回调给StartupTracer计算Activity启动耗时。

maskIndex

maskIndex用来标记获取堆栈信息的起点,便于回溯。

java 复制代码
public IndexRecord maskIndex(String source) {
    //AppMethodBeat.getInstance().maskIndex("ApplicationCreateBeginMethodIndex")
    if (sIndexRecordHead == null) {
        //首次调用时创建一个链表,sIndex是当前方法在sBuffer中的位置
        sIndexRecordHead = new IndexRecord(sIndex - 1);
        //IndexRecord封装了source标记,和当前方法做映射
        sIndexRecordHead.source = source;
        return sIndexRecordHead;
    } else {
        IndexRecord indexRecord = new IndexRecord(sIndex - 1);
        indexRecord.source = source;
        IndexRecord record = sIndexRecordHead;
        IndexRecord last = null;
	     //链表存在,将新的indexRecord节点按照index从小到大排序插入到链表中
        while (record != null) {
            if (indexRecord.index <= record.index) {
                //last为null表示第一次循环,也就是标记点index <= 链表中最小,也就是第一个index
                if (null == last) {
                    //新节点插到链表头部
                    IndexRecord tmp = sIndexRecordHead;
                    sIndexRecordHead = indexRecord;
                    indexRecord.next = tmp;
                } else {
                    //新节点插到 (链表中第一个 >= 标记index) 的前面
                    IndexRecord tmp = last.next;
                    last.next = indexRecord;
                    indexRecord.next = tmp;
                }
                return indexRecord;
            }
            //双指针操作,保存上一个节点,为了指向新的节点
            last = record;
            //原链表遍历
            record = record.next;
        }
        //到这里说明没走if (indexRecord.index <= record.index),因为这里会return,也就是标记点index > 链表中最大index,直接插到末尾
        last.next = indexRecord;
	     //返回新插入的节点
        return indexRecord;
    }
}

index是当前方法在sBuffer中的位置,这里维护了一个按照index从小到大顺序的链表,链表节点IndexRecord封装了source标记和index, sIndexRecordHead是链表的头节点。

copyData

maskIndex标记起点就是为了copyData取出两个index之间的方法信息。

java 复制代码
public long[] copyData(IndexRecord startRecord) {
    return copyData(startRecord, new IndexRecord(sIndex - 1));
}
//startRecord就是之前maskIndex标记的IndexRecord,endRecord当前IndexRecord
private long[] copyData(IndexRecord startRecord, IndexRecord endRecord) {
    long current = System.currentTimeMillis();
    long[] data = new long[0];
    try {
        if (startRecord.isValid && endRecord.isValid) {
            int length;
            int start = Math.max(0, startRecord.index);
            int end = Math.max(0, endRecord.index);

            if (end > start) {
                //end > start 拷贝 start到end 之间的数据
                length = end - start + 1;
                data = new long[length];
                System.arraycopy(sBuffer, start, data, 0, length);
            } else if (end < start) {
                // 方法数超过100w,index会从0开始,拷贝 start到sBuffer末尾 + 0到end 
                length = 1 + end + (sBuffer.length - start);
                data = new long[length];
                System.arraycopy(sBuffer, start, data, 0, sBuffer.length - start);
                System.arraycopy(sBuffer, 0, data, sBuffer.length - start, end + 1);
            }
            return data;
        }
        return data;
    } catch (Throwable t) {
        MatrixLog.e(TAG, t.toString());
        return data;
    } finally {
        MatrixLog.i(TAG, "[copyData] [%s:%s] length:%s cost:%sms", Math.max(0, startRecord.index), endRecord.index, data.length, System.currentTimeMillis() - current);
    }
}

copyData返回了一个long数组,至于怎么处理的后面再分析。

小结

AppMethodBeat的主要功能:

  1. 方法中插入i和o记录方法信息
  2. 维护了时间偏移量sCurrentDiffTime,用一个循环线程,每隔5ms(不包含消息队列休眠时间,同时5ms的误差对于耗时分析可忽略不计)更新一次sCurrentDiffTime
  3. 方法运行时将methodId、i/o、方法耗时合成一个long数据,节省内存
  4. maskIndex用来标记获取堆栈信息的起点(便于回溯),维护了一个顺序链表,用于映射标记和方法index(index是当前方法在sBuffer中的位置)
  5. copyData取出从maskIndex标记开始当前的方法信息
相关推荐
编程洪同学1 小时前
Spring Boot 中实现自定义注解记录接口日志功能
android·java·spring boot·后端
氤氲息4 小时前
Android 底部tab,使用recycleview实现
android
Clockwiseee4 小时前
PHP之伪协议
android·开发语言·php
小林爱4 小时前
【Compose multiplatform教程08】【组件】Text组件
android·java·前端·ui·前端框架·kotlin·android studio
小何开发5 小时前
Android Studio 安装教程
android·ide·android studio
开发者阿伟6 小时前
Android Jetpack LiveData源码解析
android·android jetpack
HL_LOVE_C6 小时前
Linux编程中的性能优化方法和工具
性能优化
weixin_438150996 小时前
广州大彩串口屏安卓/linux触摸屏四路CVBS输入实现同时显示!
android·单片机
CheungChunChiu6 小时前
Android10 rk3399 以太网接入流程分析
android·framework·以太网·eth·net·netd
木头没有瓜7 小时前
ruoyi 请求参数类型不匹配,参数[giftId]要求类型为:‘java.lang.Long‘,但输入值为:‘orderGiftUnionList
android·java·okhttp