Android日志系统

目录

Android日志系统

不同类型的日志

Android日志系统

四个不同的日志缓冲区

日志接口介绍

日志格式

log的使用

Log状态图

日志文件位置

日志命令行工具

过滤模式

日志文件分析

Android 分析EventLog

查看EventLog

logtags生成

开机event log分析

1.获取开机时的event log:

2.boot_progress_start是哪里输出的?

应用启动event log分析

1.获取应用启动event log

2.am_create_activity在哪里输出的

3.输出log表示什么

dumpsys

获取dumpsys支持的命令

支持dumpsys的service

dumpsys power

TRACE

app/framework 加trace

Java Trace类说明

实战:在Camera App中添加Trace(添加了注释)

解释

实战:在Camera Java Framework中添加Trace(已添加注释)

解释

systrace实现

systrace的使用

主机systrace命令的实现(python)


Android使用一个集中式系统来记录所有的日志,应用开发者也可以编写自定义日志,也可以定义日志过滤器

日志分析是开发的核心阶段之一,开发人员经常会遇到这样那样的问题需要借助日志分析来解决。Bug日志有助于在开发阶段识别Android应用中的Bug。一旦应用发布到市场上,开发者(或者支持工程师)也要通过分析bug日志来解决问题。

  1. Android日志系统

  1. 不同类型的日志

在Android生态系统中有不同类型的日志:

  • 主日志:主日志用于应用程序

  • Android系统日志:system用于低级系统消息和调试

  • 事件日志:events用于系统事件信息

  • Radio日志:radio用于电话相关信息

  1. Android日志系统

Android日志系统包括一个内核驱动程序和用于存储Android日志消息内核缓冲区,用于创建日志条目和访问日志消息的C、c++和Java类,一个用于查看日志消息的独立程序(logcat),以及查看和过滤来自主机的日志消息的能力(通过Android Studio或ddms)。

  1. 四个不同的日志缓冲区

Linux内核中有四个不同的日志缓冲区,它们为系统的不同部分提供日志记录。所有的的缓冲区都在设备节点“在/dev/log”下,有

  • “/dev/log/main”, ——主日志

  • “/dev/log/radio”,——事件日志Radio日志

  • “/dev/log/event”,——事件日志

  • “/dev/log/system”,——系统日志。

日志中的每条消息都包含一个标记,表明消息来自系统或应用程序的哪个部分:

  • 一个时间戳(日志发生时间),

  • 日志级别(日志优先级)

  • 日志内容(错误、异常或信息的详细描述等)。

  1. 日志接口介绍

主日志使用android.util.Log打印,主要被应用使用。

系统日志使用android.util.Slog打印。许多android框架层的模块使用该工具打印日志,这样可以与应用日志区分开,避免其他日志干扰。

事件日志使用android.util.EventLog打印,输出二进制格式的日志。日志入口包含二进制tag code,后面跟二进制参数。二进制tag code存储在 /system/etc/event-log-tags

  1. 日志格式

时间戳 进程号 线程号 优先级 标签 日志内容

  • V - 详细(最低优先级)*

  • D - 调试*

  • I - 信息*

  • W - 警告*

  • E - 错误*

  • F - 致命的*

  • S - 沉默(最高优先级,不打印任何东西)

  1. log的使用

在Android开发中,我们可以通过Log类来输出log信息。Log类提供了以下几个方法来输出不同等级的log:

Log.v():输出Verbose等级的log。

Log.d():输出Debug等级的log。

Log.i():输出Info等级的log。

Log.w():输出Warning等级的log。

Log.e():输出Error等级的log。

Log.wtf():输出Assert等级的log。

演示如何在Android应用程序中输出log:

package com.example.myapp;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends Activity {

    private static final String TAG = "MyApp";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d(TAG, "onCreate() called");

        int a = 10;
        int b = 20;
        int sum = a + b;

        Log.i(TAG, "sum = " + sum);
    }
}

在上面的代码中,我们在onCreate()方法中分别使用Log.d()和Log.i()输出了Debug和Info等级的log信息。

  1. Log状态图

简单的log输出状态图,使用mermaid语法表示:

  1. 日志文件位置

Android的日志(包括崩溃的日志)存储在几个目录中,它不是标准化的(可能被特定手机厂家修改定制)。这里放了一些常见的。

  • /data/anr: Dalvik在anr上写堆栈跟踪,即“应用程序不响应”或“强制关闭”。

  • /data/dontpanic: 包含一些崩溃日志,包括跟踪。

  • /data/kernelpanics: —存储内核恐慌相关的日志。

  • /data/tombstones: 可以保存多个tombstone_nn文件(nn是一个从0到10的数字,在10之后再次重复它)。

  1. 日志命令行工具

从设备或模拟器捕获Android日志可通过一些命令行工具来进行。在实践中,还有一些日志捕获应用程序或工具用于捕获用户设备上的日志,并将其呈现给开发人员进行分析。

  • adb logcat: 显示当前android系统的所有类型日志

  • adb logcat -v threadtime: 显示当前android系统的所有类型日志, 包含日期和时间

  • adb logcat -v threadtime > logfile.txt: 将日志存储(覆盖重定向)在logfile.txt文件中

  1. 过滤模式

您可以在adb命令中使用过滤器来过滤日志。

  • adb logcat *:E: 获取所有错误和致命文件

  • adb logcat | grep -i "error": 过滤出日志中所有携带error的行

  • adb logcat MyTag:* *:S: 过滤所有标签为“MyTag”的行

  • adb logcat -b events gsm_service_state_change *:S: 获取所有GSM状态变化

  • adb logcat -b radio: 获取所有radio事件

(以上未完全包含,只列举常见的部分)

  1. 日志文件分析

在开发过程中我们并不总是能够实时查看日志,比如测试报告bug、者线上发生问题、或自动化测试时,会附加一份存储到文件中的android日志。

虽然在日志格式上命令行输出的日志和文件中的日志没有什么不同,但是在分析手段上,还是有明显差异。分析日志文件需要借助文本工具

这些文本工具需要具备执行普通和正则表达式过滤的能力。

满足条件的文本工具有很多,如IDE、notepad++、UltroEdit、Tilipa日志工具、sublime

如果需要打开超大日志文件(100MB以上),或者在非windows操作系统上打开,推荐使用Tilipa日志工具。

  1. Android 分析EventLog

  1. 查看EventLog

使用命令:
logcat -b events
  1. logtags生成

  1. 开机event log分析
1.获取开机时的event log:
logcat -b events | grep boot_progress

输出如下:

I boot_progress_start: 20726
I boot_progress_preload_start: 23255
I boot_progress_preload_end: 26806
I boot_progress_system_run: 27135
I boot_progress_pms_start: 27946
I boot_progress_pms_system_scan_start: 28447
I boot_progress_pms_data_scan_start: 29647
I boot_progress_pms_scan_end: 29958
I boot_progress_pms_ready: 30910
I boot_progress_ams_ready: 35811
I boot_progress_enable_screen: 38542

boot_progress_start表示Linux kernel启动到Zygote进程启动的时间,包含从kernel启动到Init启动Zygote的时间,接下来我们就以boot_progress_start为例来分析这条日志是如何输出的。

2.boot_progress_start是哪里输出的?

在/system/core/logcat/event.logtags找到了boot_progress_start的定义

# The entries in this file map a sparse set of log tag numbers to tag names.
# This is installed on the device, in /system/etc, and parsed by logcat.
  ...
# Device boot timings.  We include monotonic clock values because the
# intrinsic event log times are wall-clock.
#
# Runtime starts:
3000 boot_progress_start (time|2|3)

从注释可以看出这个logtags文件是用于日志标记号映射到标记名,并且安装在/system/etc下。在/system/etc下查找到event-log-tags文件,我们看下event-log-tags是如何生成的。

在build/soong/java/gen.go下:

pctx.SourcePathVariable("logtagsCmd", "build/tools/java-event-log-tags.py")
pctx.SourcePathVariable("mergeLogtagsCmd", "build/tools/merge-event-log-tags.py")

java-event-log-tags.py:生成一个java类,其中包含给定输入文件中每个事件日志标记的常量。

merge-event-log-tags.py:将零个或多个事件日志标记文件合并在一起,生成一个单独的输出文件,去掉注释。检查没有标签号冲突,如果有就失败。

我们知道boot_progress对应的id是3000,去framework下查找
grep -rn 3000
base/core/jni/AndroidRuntime.cpp:994:           const int LOG_BOOT_PROGRESS_START = 3000;

看下代码
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
        ...
        const int LOG_BOOT_PROGRESS_START = 3000;
    LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START,  ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
}

同样,我们在
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java找到了
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START, SystemClock.uptimeMillis());
  1. 应用启动event log分析
1.获取应用启动event log
logcat -b events | grep am_

输出:

I am_create_task: [0,364]
I am_create_activity: [0,165832734,364,com.android.settings/.Settings,android.intent.action.MAIN,NULL,NULL,270532608]
I am_focused_stack: [0,1,0,startedActivity setFocusedActivity]
I am_focused_activity: [0,com.android.settings/.Settings,startedActivity]
I am_pause_activity: [0,102587883,com.android.launcher3/.Launcher]
I am_on_paused_called: [0,com.android.launcher3.Launcher,handlePauseActivity]
I am_restart_activity: [0,165832734,364,com.android.settings/.Settings]
I am_on_resume_called: [0,com.android.settings.Settings,LAUNCH_ACTIVITY]
I am_activity_launch_time: [0,165832734,com.android.settings/.Settings,825,825]
I am_stop_activity: [0,102587883,com.android.launcher3/.Launcher]
I am_on_stop_called: [0,com.android.launcher3.Launcher,handleStopActivity]
I am_pss  : [5733,10023,com.android.launcher3,12664832,9179136,0]

接下来我们以am_create_activity为例,分析log的输出,以及log的含义。

2.am_create_activity在哪里输出的

前面讲到java-event-log-tags.py会将logtags文件转换成java文件

  • 1.先在/out/target/common/目录下查找am_create_activity

    find . -name EventLogTags.java | xargs grep am_create_activity
    找到了:
    ./obj/JAVA_LIBRARIES/services.core_intermediates/src/java/com/android/server/am/EventLogTags.java:  /** 30005 am_create_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5) */
    
    看下这个文件:
    /** 30005 am_create_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5) */
    public static final int AM_CREATE_ACTIVITY = 30005;
    
    public static void writeAmCreateActivity(int user, int token, int taskId, String componentName, String action, String mimeType, String uri, int flag    s) {
            android.util.EventLog.writeEvent(AM_CREATE_ACTIVITY, user, token, taskId, componentName, action, mimeType, uri, flags);
    }
  • 2.接下来去framework下查找哪里使用了writeAmCreateActivity

grep -rn writeAmCreateActivity

没有查找到,那就查AM_CREATE_ACTIVITY

grep -rn AM_CREATE_ACTIVITY
输出:
services/core/java/com/android/server/am/ActivityStarter.java:1242:                EventLogTags.AM_CREATE_ACTIVITY, mStartActivity, mStartActivity.task);

看下代码:
ActivityStack.logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, mStartActivity, mStartActivity.task);

再来看下logStartActivity方法:

static final void logStartActivity(int tag, ActivityRecord r,
            TaskRecord task) {
      final Uri data = r.intent.getData();
      final String strData = data != null ? data.toSafeString() : null;

      EventLog.writeEvent(tag,
               r.userId, System.identityHashCode(r), task.taskId,
               r.shortComponentName, r.intent.getAction(),
               r.intent.getType(), strData, r.intent.getFlags());
}
3.输出log表示什么

由上面的输出行代码,再来看看

[0,165832734,364,com.android.settings/.Settings,android.intent.action.MAIN,NULL,NULL,270532608]
这就明白了
userId=0
identityHashCode=0,165832734
taskId=364
shortComponentName=com.android.settings/.Settings

上面的方式是通过查找代码来看出log含义。其实如果只是看log含义不需要这么麻烦,在/system/etc/event-log-tags可以看到

30005 am_create_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5)

这里的数字是什么意思呢?在framework下查找:

find . -name *.logtags | xargs grep am_create_activity
输出:
./services/core/java/com/android/server/am/EventLogTags.logtags:30005 am_create_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5)

这个文件开头就告诉我们去system/core/logcat/event.logtags看文件格式的描述

前面在分析开启启动log时,我们就看过这个文件。此时,我们再来看看。这个文件告诉我们描述格式是:

(<name>|data type[|data unit])
  • data type表示:

    1: int
    2: long
    3: string
    4: list
    5: float
  • data unit表示:

1: Number of objects
2: Number of bytes  # int/long类型数据的默认值是2(字节)。
3: Number of milliseconds
4: Number of allocations
5: Id
6: Percent
  • 实例分析 (User|1|5)表示:User是一个int类型的id (Component Name|3)表示是一个string

  1. dumpsys

dumpsys 是一种在 Android 设备上运行的工具,可提供有关系统服务的信息。可以使用 Android 调试桥 (adb) 从命令行调用 dumpsys,获取在连接的设备上运行的所有系统服务的诊断输出。

Android使用ServiceManager服务进程来管理系统所有的服务,在系统启动时,每个服务必须注册到ServiceManager进程中,那如何查看系统运行了那些服务呢?ServiceManager提供了listServices接口来罗列出系统注册的所有服务, dumpsys工具比较简单,就是调用ServiceManager服务的listServices来查询系统注册的所有服务,并且通过checkService接口来获取服务的Binder远程代理对象,使用每个服务的dump()函数来打印该服务的相关信息。

dumpsys源码在 frameworks\native\cmds\dumpsys\dumpsys.cpp

  1. 获取dumpsys支持的命令

执行

$ dumpsys

在输出的信息的开头, 列出了系统中正在运行的service,但是这样输出太多了, 因此, 可以使用如下命令来进行过滤

$ dumpsys | grep “DUMP OF SERVICE” 
  1. 支持dumpsys的service


  • acount 显示设备上所有用户的信息

  • Cpuinfo 可以显示每个进程在内核空间和用户空间的cpu占用率

  • Activity 用于查看 activity组件的信息,详细信息使用如下命令获取帮助

    $ dumpsys activity -h

    appwidget android窗口小部件的信息

    audio android音频组件信息

    battery android电池设备信息

    connectivity android网络连接状态及进程请求网络信息

    content 查看android content provider相关的信息

    device_policy android设备政策

    dropbox 系统崩溃的信息

    input_method Android输入法信息

    iphonesubinfo 手机制式及设备id

    location gps等位置信息

    meminfo android进程实际物理内存占用情况

    mount 加载的存储设备信息

    network_management android网络流量管理信息

    notification android通知栏广告信息

    package 通过l、perm、perf、p等参数dump出package信息,还能够直接跟包名来dump出该包的具体信息

    power 电源管理器的一些状态信息

    sensorservice android传感器的一些状态信息

    statusbar 显示状态栏相关的一些信息

    telephony.registry 电话服务相关的信息

    uimode ui mode service的状态信息

    usagestats 各个app使用情况统计

    wallpaper 壁纸信息

    wifi wifi连接及状态信息

  • dumpsys power

  • 这部分内容来自dumpsys power 字段含义介绍-CSDN博客,为防止连接内容丢失,以下为摘抄。

    在分析app测功耗以及亮灭屏的过程中,经常会执行dumpsys power来获取PowerManagerService的状态

    下面就关于dumpsys power的打印的部分字段的含义简要说明

    POWER MANAGER (dumpsys power)

    Power Manager State:

    Settings power_manager_constants:

    no_cached_wake_locks=true

    mDirty=0x0 (mDirty PMS中的核心参数,用来表示当前发生改变的事件类型)

    mWakefulness=Awake 当前所希望的屏幕状态,Awake 表示唤醒状态

    mWakefulnessChanging=false mWakefulnessChanging 表示上一个参数mWakefulness是否发生变化,如果变化这个参数会被改成true,然后会调用DIsplayManager来切换屏幕状态

    mIsPowered=true 是否充电,true表示在充电中

    mPlugType=2 充电类型

    mBatteryLevel=82 当前电量

    mBatteryLevelWhenDreamStarted=0

    mDockState=0

    mStayOn=false 屏幕是否需要长亮

    mProximityPositive=false 通话时候的距离感应传感器(打电话的时候,手机贴到脸上就灭屏)

    mBootCompleted=true 是否启动完毕

    mSystemReady=true 系统是否准备好

    mHalAutoSuspendModeEnabled=false HAL层是否自己进入Suspend模式

    mHalInteractiveModeEnabled=true 向HAL层设置的当前用户活动状态,true表示屏幕打开,用户在使用,false表示用户没有使用

    mWakeLockSummary=0x25 记录所有锁的类型

    mNotifyLongScheduled=+27s645ms

    mNotifyLongDispatched=-51s118ms

    mNotifyLongNextCheck=(none)

    mUserActivitySummary=0x1 用户活动事件标志

    mRequestWaitForNegativeProximity=false

    mSandmanScheduled=false

    mSandmanSummoned=false

    mBatteryLevelLow=false 电池是否是低电状态

    mLightDeviceIdleMode=false

    mDeviceIdleMode=false

    mDeviceIdleWhitelist=[1000, 2000, 10014] 设备在IDLE状态下的app白名单

    mDeviceIdleTempWhitelist=[1000]

    mLastWakeTime=577519 (32358 ms ago) 重要,表示上次屏幕点亮的事件 32358 ms ago 表示距离当前时间,屏幕在32秒之前点亮的

    mLastSleepTime=498525 (111352 ms ago) 上次屏幕灭屏时间,111352 ms ago 表示在111秒之之前息屏的

    mLastUserActivityTime=577519 (32358 ms ago) 最后一次用户的活动事件时间,点击,充电,等

    mLastUserActivityTimeNoChangeLights=64807 (545070 ms ago) 用户最后一次不影响屏幕亮度的事件

    mLastInteractivePowerHintTime=577519 (32358 ms ago)

    mLastScreenBrightnessBoostTime=0 (609877 ms ago) 最近一次自动调节屏幕亮度的时间(就是手机由黑暗的地方进入明亮的地方,手机屏幕会自动调整到最大亮度)

    mScreenBrightnessBoostInProgress=false 是否正在进行屏幕亮度调整

    mDisplayReady=true 显示器是否准备OK

    mHoldingWakeLockSuspendBlocker=true 是否持有阻止CPU休眠的锁

    mHoldingDisplaySuspendBlocker=true 是否持有阻止显示器息屏的锁

Settings and Configuration: 下面这部分是系统的原始配置参数
  mDecoupleHalAutoSuspendModeFromDisplayConfig=false
  mDecoupleHalInteractiveModeFromDisplayConfig=false
  mWakeUpWhenPluggedOrUnpluggedConfig=true
  mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig=false
  mTheaterModeEnabled=false
  mSuspendWhenScreenOffDueToProximityConfig=true
  mDreamsSupportedConfig=true
  mDreamsEnabledByDefaultConfig=true
  mDreamsActivatedOnSleepByDefaultConfig=false
  mDreamsActivatedOnDockByDefaultConfig=true
  mDreamsEnabledOnBatteryConfig=false
  mDreamsBatteryLevelMinimumWhenPoweredConfig=-1
  mDreamsBatteryLevelMinimumWhenNotPoweredConfig=15
  mDreamsBatteryLevelDrainCutoffConfig=5
  mDreamsEnabledSetting=true
  mDreamsActivateOnSleepSetting=false
  mDreamsActivateOnDockSetting=true
  mDozeAfterScreenOff=false
  mMinimumScreenOffTimeoutConfig=10000
  mMaximumScreenDimDurationConfig=7000
  mMaximumScreenDimRatioConfig=0.20000005
  mScreenOffTimeoutSetting=60000
  mSleepTimeoutSetting=-1
  mMaximumScreenOffTimeoutFromDeviceAdmin=9223372036854775807 (enforced=false)
  mStayOnWhilePluggedInSetting=0
  mScreenBrightnessSetting=0
  mScreenBrightnessModeSetting=0
  mScreenBrightnessOverrideFromWindowManager=-1
  mUserActivityTimeoutOverrideFromWindowManager=-1
  mUserInactiveOverrideFromWindowManager=false
  mDozeScreenStateOverrideFromDreamManager=0
  mDrawWakeLockOverrideFromSidekick=false
  mDozeScreenBrightnessOverrideFromDreamManager=-1
  mScreenBrightnessSettingMinimum=10
  mScreenBrightnessSettingMaximum=255
  mScreenBrightnessSettingDefault=102
  mDoubleTapWakeEnabled=false
  mIsVrModeEnabled=false
  mForegroundProfile=0

Sleep timeout: -1 ms

Screen off timeout: 60000 ms 用户设置的手机灭屏实现(从最后一个用户事件到屏幕彻底熄灭,这里是60秒)

Screen dim duration: 7000 ms 屏幕熄灭之前变暗所持续的事件

应用的状态信息

UID states (changing=false changed=false):

1000 表示应用的UDI,ACTIVE 表示应用是否是活动状态,count表示此应用申请的锁的数量 state 表示应用的运行等级

应用的运行等级可以百度搜索 PROCESS_STATE_NONEXISTENT 来看看具体都有哪些已经各个状态下的运行状态。

  UID 1000:   ACTIVE  count=0 state=0
  UID 1001:   ACTIVE  count=0 state=0
  UID 1027:   ACTIVE  count=0 state=0
  UID 1068:   ACTIVE  count=0 state=0
  UID u0a8: INACTIVE  count=0 state=18
  UID u0a13: INACTIVE  count=0 state=18
  UID u0a14: INACTIVE  count=0 state=9
  UID u0a19: INACTIVE  count=0 state=18
  UID u0a21:   ACTIVE  count=0 state=18
  UID u0a24:   ACTIVE  count=0 state=0
  UID u0a25:   ACTIVE  count=0 state=4
  UID u0a34: INACTIVE  count=0 state=18
  UID u0a38: INACTIVE  count=0 state=18
  UID u0a40: INACTIVE  count=0 state=18
  UID u0a43: INACTIVE  count=0 state=18
  UID u0a44:   ACTIVE  count=0 state=5
  UID u0a53:   ACTIVE  count=0 state=13
  UID u0a54:   ACTIVE  count=2 state=2

Looper state:

待处理的handl消息

Looper (PowerManagerService, tid 34) {6ca4820}

Message 0: { when=+20s642ms what=1 target=com.android.server.power.PowerManagerService$PowerManagerHandler } Message 1: { when=+27s645ms what=4 target=com.android.server.power.PowerManagerService$PowerManagerHandler }

(Total messages: 2, polling=true, quitting=false)

Wake Locks: size=2 应用申请的锁的数量

锁的类型 锁的tag 当前锁所持有的时间 申请锁的app的UID以及PID

PARTIAL_WAKE_LOCK 'My_Tag_PARTIAL_WAKE_LOCK' ACQ=-24s518ms (uid=10054 pid=15301)

SCREEN_DIM_WAKE_LOCK 'My_Tag_SCREEN_DIM_WAKE_LOCK' ACQ=-24s512ms (uid=10054 pid=15301)

Suspend Blockers: size=4

PowerManagerService.WakeLocks: ref count=1

PowerManagerService.Display: ref count=1

PowerManagerService.Broadcasts: ref count=0

PowerManagerService.WirelessChargerDetector: ref count=0

Display Power: state=ON

Battery saving stats:

Battery Saver is currently: OFF

Last OFF time: 2020-10-30 09:36:09.545 -6h5m54s767ms 距离上次充满电的时间

Times enabled: 0

Drain stats:

Battery saver OFF ON

NonDoze NonIntr: 358m 29mAh( 1%) 4.9mAh/h 0m 0mAh( 0%) 0.0mAh/h

Intr: 1m 0mAh( 0%) 0.0mAh/h 0m 0mAh( 0%) 0.0mAh/h

Deep NonIntr: 0m 0mAh( 0%) 0.0mAh/h 0m 0mAh( 0%) 0.0mAh/h

Intr: 0m 0mAh( 0%) 0.0mAh/h 0m 0mAh( 0%) 0.0mAh/h

Light NonIntr: 0m 0mAh( 0%) 0.0mAh/h 0m 0mAh( 0%) 0.0mAh/h

Intr: 0m 0mAh( 0%) 0.0mAh/h 0m 0mAh( 0%) 0.0mAh/h

Battery saver policy (*NOTE* they only apply when battery saver is ON):

Settings: battery_saver_constants

value: null

Settings: (overlay)

value:

mAccessibilityEnabled=false

vibration_disabled:config=true

vibration_disabled:effective=true

animation_disabled=false

fullbackup_deferred=true

keyvaluebackup_deferred=true

firewall_disabled=false

datasaver_disabled=true

launch_boost_disabled=true

adjust_brightness_disabled=true

adjust_brightness_factor=0.5

gps_mode=2

force_all_apps_standby=true

force_background_check=true

optional_sensors_disabled=true

aod_disabled=true

send_tron_log=false

Interactive File values:

Noninteractive File values:

Battery saver state machine:

Enabled=false

mLastChangedIntReason=0

mLastChangedStrReason=null

mBootCompleted=true

mSettingsLoaded=true

mBatteryStatusSet=true

mBatterySaverSnoozing=false

mIsPowered=true

mBatteryLevel=82

mIsBatteryLevelLow=false

mSettingBatterySaverEnabled=false

mSettingBatterySaverEnabledSticky=false

mSettingBatterySaverTriggerThreshold=0

Profile power states: size=0

Wireless Charger Detector State:

mGravitySensor={Sensor name="Gravity Sensor", vendor="AOSP", version=3, type=9, maxRange=19.6133, resolution=0.0012, power=0.002, minDelay=10000}

mPoweredWirelessly=false

mAtRest=false

mRestX=0.0, mRestY=0.0, mRestZ=0.0

mDetectionInProgress=false

mDetectionStartTime=0 (never)

mMustUpdateRestPosition=false

mTotalSamples=0

mMovingSamples=0

mFirstSampleX=0.0, mFirstSampleY=0.0, mFirstSampleZ=0.0

mLastSampleX=0.0, mLastSampleY=0.0, mLastSampleZ=0.0

  1. TRACE

参考如下分析应用性能  |  Android Studio  |  Android Developers

  1. app/framework 加trace

  1. Java Trace类说明

  2. 实战:在Camera App中添加Trace

  3. 实战:在Camera Java Framework中添加Trace

  1. Java Trace类说明

android.os.Trace类

描述

beginSection(String sectionName)

在一个函数中打印Trace开始标记,会显示在某个线程中

endSection()

在一个函数中打印Trace结束标记,会显示在某个线程中

beginAsyncSection(String methodName, int cookie)

打印异步Trace开始标记,cookie用来区分相同methodName不同的异步Trace,会独立成一行Trace显示

endAsyncSection(String methodName, int cookie)

打印异步Trace结束标记,cookie用来区分相同methodName不同的异步Trace,会独立成一行Trace显示

setCounter(String counterName, long counterValue)

以给定计数器的值打印Trace

isEnabled()

判断是否Trace打开了,建议打印Trace前都判断下避免创建一些无用的临时对象

  1. 实战:在Camera App中添加Trace(添加了注释)
    public class GeekCamera2Trace {
        // 常量定义,用于标识不同的跟踪事件
        public static final String OPEN_CAMERA = "GC2_openCamera";
        public static final String CREATE_CAPTURE_SESSION = "GC2_createCaptureSession";
        public static final String FRAME_NUMBER = "GC2_FrameNumber";
    
        /**
         * 开始一个异步跟踪区段。
         * @param methodName 跟踪区段的名称
         * @param cookie 唯一标识异步操作的cookie
         */
        public static void beginAsyncSection(String methodName, int cookie) {
            // 仅在系统版本大于等于Android Q(API 29)且跟踪功能启用时执行
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Trace.isEnabled()) {
                Trace.beginAsyncSection(methodName, cookie);
            }
        }
    
        /**
         * 结束一个异步跟踪区段。
         * @param methodName 跟踪区段的名称
         * @param cookie 唯一标识异步操作的cookie
         */
        public static void endAsyncSection(String methodName, int cookie) {
            // 仅在系统版本大于等于Android Q(API 29)且跟踪功能启用时执行
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Trace.isEnabled()) {
                Trace.endAsyncSection(methodName, cookie);
            }
        }
    
        /**
         * 开始一个同步跟踪区段。
         * @param sectionName 跟踪区段的名称
         */
        public static void beginSection(String sectionName) {
            // 仅在系统版本大于等于Android Q(API 29)且跟踪功能启用时执行
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Trace.isEnabled()) {
                Trace.beginSection(sectionName);
            }
        }
    
        /**
         * 结束一个同步跟踪区段。
         */
        public static void endSection() {
            // 仅在系统版本大于等于Android Q(API 29)且跟踪功能启用时执行
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Trace.isEnabled()) {
                Trace.endSection();
            }
        }
    
        /**
         * 设置一个计数器的值。
         * @param counterName 计数器的名称
         * @param counterValue 计数器的值
         */
        public static void setCounter(String counterName, long counterValue) {
            // 仅在系统版本大于等于Android Q(API 29)且跟踪功能启用时执行
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Trace.isEnabled()) {
                Trace.setCounter(counterName, counterValue);
            }
        }
    }

    解释

  2. 常量定义

    1. OPEN_CAMERA, CREATE_CAPTURE_SESSION, FRAME_NUMBER: 这些是用于标识不同跟踪事件的常量字符串。

  3. beginAsyncSection

    1. 参数 methodName 表示异步跟踪区段的名称。

    2. 参数 cookie 是一个唯一标识异步操作的整数。

    3. 只有在系统版本大于等于Android Q(API 29)且跟踪功能启用时,才会调用 Trace.beginAsyncSection 方法。

  4. endAsyncSection

    1. 参数 methodName 表示异步跟踪区段的名称。

    2. 参数 cookie 是一个唯一标识异步操作的整数。

    3. 只有在系统版本大于等于Android Q(API 29)且跟踪功能启用时,才会调用 Trace.endAsyncSection 方法。

  5. beginSection

    1. 参数 sectionName 表示同步跟踪区段的名称。

    2. 只有在系统版本大于等于Android Q(API 29)且跟踪功能启用时,才会调用 Trace.beginSection 方法。

  6. endSection

    1. 没有参数。

    2. 只有在系统版本大于等于Android Q(API 29)且跟踪功能启用时,才会调用 Trace.endSection 方法。

  7. setCounter

    1. 参数 counterName 表示计数器的名称。

    2. 参数 counterValue 表示计数器的值。

    3. 只有在系统版本大于等于Android Q(API 29)且跟踪功能启用时,才会调用 Trace.setCounter 方法。

  8. 实战:在Camera Java Framework中添加Trace(已添加注释)
/**
 * 打开指定的相机设备,为特定UID进行跟踪。
 *
 * @param cameraId 相机设备的ID
 * @param callback 相机设备状态的回调
 * @param executor 执行回调的Executor
 * @param clientUid 客户端的UID
 * @param oomScoreOffset OOM(Out-Of-Memory)分数偏移量
 * @throws CameraAccessException 如果相机访问失败
 */
public void openCameraForUid(@NonNull String cameraId,
        @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor,
        int clientUid, int oomScoreOffset) throws CameraAccessException {
    // 记录日志,输出相机ID
    Log.i(TAG, "v.deepinout.com openCameraForUid cameraId:" + cameraId);
    
    // 开始一个名为 "GC2_FWK_openCameraForUid" 的同步跟踪区段
    Trace.beginSection("GC2_FWK_openCameraForUid");

    // 检查cameraId是否为空,如果为空则抛出IllegalArgumentException
    if (cameraId == null) {
        throw new IllegalArgumentException("cameraId was null");
    } 
    // 检查callback是否为空,如果为空则抛出IllegalArgumentException
    else if (callback == null) {
        throw new IllegalArgumentException("callback was null");
    }
    
    // 检查相机服务是否被禁用,如果禁用则抛出IllegalArgumentException
    if (CameraManagerGlobal.sCameraServiceDisabled) {
        throw new IllegalArgumentException("No cameras available on device");
    }

    // 异步打开相机设备
    openCameraDeviceUserAsync(cameraId, callback, executor, clientUid, oomScoreOffset);
    
    // 结束同步跟踪区段
    Trace.endSection();
}

解释

  1. 方法签名

    1. @NonNull String cameraId: 相机设备的ID,不能为空。

    2. @NonNull CameraDevice.StateCallback callback: 回调接口,用于通知相机设备的状态变化,不能为空。

    3. @NonNull Executor executor: 用于执行回调的Executor,不能为空。

    4. int clientUid: 客户端的UID,用于标识哪个应用在使用相机。

    5. int oomScoreOffset: OOM(Out-Of-Memory)分数偏移量,用于调整进程的内存优先级。

    6. throws CameraAccessException: 可能抛出的异常,表示相机访问失败。

  2. 日志记录

    1. Log.i(TAG, "v.deepinout.com openCameraForUid cameraId:" + cameraId);:记录日志,输出相机ID。

  3. 开始跟踪区段

    1. Trace.beginSection("GC2_FWK_openCameraForUid");:开始一个名为 "GC2_FWK_openCameraForUid" 的同步跟踪区段。

  4. 参数检查

    1. 检查 cameraId 是否为空,如果为空则抛出 IllegalArgumentException

    2. 检查 callback 是否为空,如果为空则抛出 IllegalArgumentException

  5. 相机服务检查

    1. 检查 CameraManagerGlobal.sCameraServiceDisabled 是否为 true,如果为 true 则抛出 IllegalArgumentException

  6. 异步打开相机设备

    1. openCameraDeviceUserAsync(cameraId, callback, executor, clientUid, oomScoreOffset);:异步打开相机设备。

  7. 结束跟踪区段

    1. Trace.endSection();:结束同步跟踪区段。

  1. systrace实现

systrace的框架

1、systrace调用atrace抓取目标机的trace数据;

2、systrace把trace数据和’prefix.html’、‘suffix.html’、'systrace_trace_viewer.html’合成一个’trace.html’文件;

3、使用chrome浏览器打开’trace.html’就可以非常方便的以图形化的形式来查看和分析trace数据。背后是Trace-Viewer的脚本在运行;

内核态和用户态的存储trace数据的实现:

1、内核trace信息,通过trace event记录到ftrace的buffer中;

2、用户态(app/java framework/native)是通过使用Trace类来记录trace信息的,也是记录到内核ftrace buffer当中的,是通过"/sys/kernel/debug/tracing/trace_marker"接口记录的。

  1. systrace的使用

在定位Android性能问题的时候,我们经常会用到systrace工具。配置到adb,连接上目标手机,在主机侧使用类似命令启动systrace:

ASOP_ROOT/external/chromium-trace/catapult/systrace/bin$ ./systrace -o trace.html -t 10 gfx view wm am dalvik input sched freq idle
Starting tracing (10 seconds)
Tracing completed. Collecting output...
Outputting Systrace results...
Tracing complete, writing results

Wrote trace HTML file: file:///ASOP_ROOT/external/chromium-trace/catapult/systrace/bin/trace.html

systrace命令在ASOP源码包的ASOP_ROOT/external/chromium-trace/catapult/systrace/bin路径下。上述命令的参数:

  • ‘-t 10’,指定了抓取trace的时长为10s;

  • ‘-o trace.html’,指定了trace的输出文件;

  • ‘gfx view wm am dalvik input sched freq idle’,指定了需要抓取的事件;

在命令启动以后,我们就可以在目标机上进行滑动、启动app等一系列操作,10s内的这些操作都会被记录下来最后dump进trace.html文件。我们可以通过google的chrome浏览器来查看、分析trace.html:‘google-chrome trace.html’。

可以通过’-l’选项来查看目标机支持的systrace事件全集:

$ ./systrace -l
         gfx - Graphics
       input - Input
        view - View System
     webview - WebView
          wm - Window Manager
          am - Activity Manager
          sm - Sync Manager
       audio - Audio
       video - Video
      camera - Camera
         hal - Hardware Modules
         app - Application
         res - Resource Loading
      dalvik - Dalvik VM
          rs - RenderScript
      bionic - Bionic C Library
       power - Power Management
          pm - Package Manager
          ss - System Server
    database - Database
     network - Network
         adb - ADB
         pdx - PDX services
       sched - CPU Scheduling
         irq - IRQ Events
        freq - CPU Frequency
        idle - CPU Idle
        disk - Disk I/O
       workq - Kernel Workqueues
  memreclaim - Kernel Memory Reclaim
  regulators - Voltage and Current Regulators
  binder_driver - Binder Kernel driver
  binder_lock - Binder global lock trace
   pagecache - Page cache

NOTE: more categories may be available with adb root

还可以通过’–help’选项来查看systrace命令的详细选项:

$ ./systrace --help
Usage: systrace [options] [category1 [category2 ...]]

Example: systrace -b 32768 -t 15 gfx input view sched freq

Options:
  -h, --help            show this help message and exit
  -o FILE               write trace output to FILE
  -j, --json            write a JSON file
  --link-assets         (deprecated)
  --asset-dir=ASSET_DIR
                        (deprecated)
  -e DEVICE_SERIAL_NUMBER, --serial=DEVICE_SERIAL_NUMBER
                        adb device serial number
  --timeout=TIMEOUT     timeout for start and stop tracing (seconds)
  --collection-timeout=COLLECTION_TIMEOUT
                        timeout for data collection (seconds)
  -t N, --time=N        trace for N seconds
  --target=TARGET       choose tracing target (android or  linux)
  -b N, --buf-size=N    use a trace buffer size  of N KB
  -l, --list-categories
                        list the available categories and exit

  Atrace options:
    --atrace-categories=ATRACE_CATEGORIES
                        Select atrace categories with a comma-delimited list,
                        e.g. --atrace-categories=cat1,cat2,cat3
    -k KFUNCS, --ktrace=KFUNCS
                        specify a comma-separated list of kernel functions to
                        trace
    --no-compress       Tell the device not to send the trace data in
                        compressed form.
    -a APP_NAME, --app=APP_NAME
                        enable application-level tracing for comma-separated
                        list of app cmdlines
    --from-file=FROM_FILE
                        read the trace from a file (compressed) rather than
                        running a live trace

  BattOr trace options:
    --battor-categories=BATTOR_CATEGORIES
                        Select battor categories with a comma-delimited list,
                        e.g. --battor-categories=cat1,cat2,cat3
    --serial-map=SERIAL_MAP
                        File containing pregenerated map of phone serial
                        numbers to BattOr serial numbers.
    --battor-path=BATTOR_PATH
                        specify a BattOr path to use
    --battor            Use the BattOr tracing agent.

  Ftrace options:
    --ftrace-categories=FTRACE_CATEGORIES
                        Select ftrace categories with a comma-delimited list,
                        e.g. --ftrace-categories=cat1,cat2,cat3

  WALT trace options:
    --walt              Use the WALT tracing agent. WALT is a device for
                        measuring latency of physical sensors on phones and
                        computers. See https://github.com/google/walt
  1. 主机systrace命令的实现(python)

systrace实质上是一个python文件,整个python相关包在ASOP_ROOT/external/chromium-trace/catapult/路径下,以此路径为根(root=ASOP_ROOT/external/chromium-trace/catapult),我们来分析它的实现过程:

./systrace/bin/systrace:

import os
import sys

# (1) 将'./systrace/'路径加入到python的搜索路径 #
_SYSTRACE_DIR = os.path.abspath(
    os.path.join(os.path.dirname(__file__), os.path.pardir))
sys.path.insert(0, _SYSTRACE_DIR)

# (2) 这样就能找到'./systrace/systrace/run_systrace.py'模块并导入 #
from systrace import run_systrace

# (3) 调用run_systrace.py模块中的main()函数 #
if __name__ == '__main__':
  sys.exit(run_systrace.main())

./systrace/systrace/run_systrace.py:


# (3.1) main()函数继续调用main_impl()函数 #
def main():
  main_impl(sys.argv)

↓

def main_impl(arguments):
  # Parse the command line options.
  
  # (3.1.1) 将用户输入的参数解析为options和categories #
  options, categories = parse_options(arguments)

  # Override --atrace-categories and --ftrace-categories flags if command-line
  # categories are provided.
  
  # (3.1.2) 如果解析出了trace事件categories #
  # 根据options.target指定的平台'android'/'linux',来给options中的选项赋值 #
  # 平台是使用sytrace命令的'--target=TARGET'选项来指定的,如果没有指定默认值是'android' #
  if categories:
    if options.target == 'android':
      options.atrace_categories = categories
    elif options.target == 'linux':
      options.ftrace_categories = categories
    else:
      raise RuntimeError('Categories are only valid for atrace/ftrace. Target '
                         'platform must be either Android or Linux.')
↓

./systrace/systrace/run_systrace.py:
  # Include atrace categories by default in Systrace.
  # (3.1.3) 如果平台是'android',且没有指定trace事件,给出默认的trace事件 #
  if options.target == 'android' and not options.atrace_categories:
    options.atrace_categories = atrace_agent.DEFAULT_CATEGORIES

  # (3.1.4) 如果平台是'android',且不是从文件中读取数据,那么就是从实际的目标机中读取数据了 #
  if options.target == 'android' and not options.from_file:
    # 初始化adb #
    initialize_devil()
    # 如果没有指定目标机的serialnumber,尝试读取 #
    if not options.device_serial_number:
      devices = [a.GetDeviceSerial() for a in adb_wrapper.AdbWrapper.Devices()]
      if len(devices) == 0:
        raise RuntimeError('No ADB devices connected.')
      elif len(devices) >= 2:
        raise RuntimeError('Multiple devices connected, serial number required')
      options.device_serial_number = devices[0]

  # If list_categories is selected, just print the list of categories.
  # In this case, use of the tracing controller is not necessary.
  # (3.1.5) 如果当前是'systrace -l'命令,列出目标机支持的所有trace事件后直接返回 #
  if options.list_categories:
    if options.target == 'android':
      # 调用'systrace/systrace/tracing_agents/atrace_agent.py'文件中的list_categories()函数 #
      # 最后调用的是`adb shell atrace --list_categories'命令 #
      atrace_agent.list_categories(options)
    elif options.target == 'linux':
      ftrace_agent.list_categories(options)
    return

  # Set up the systrace runner and start tracing.
  # (3.1.6) 如果是普通的trace命令,根据'systrace/systrace/systrace_runner.py'模块中的SystraceRunner类来创建对象 #
  controller = systrace_runner.SystraceRunner(
      os.path.dirname(os.path.abspath(__file__)), options)
      
  # (3.1.6.1) 开始tracing #
  controller.StartTracing()

  # Wait for the given number of seconds or until the user presses enter.
  # pylint: disable=superfluous-parens
  # (need the parens so no syntax error if trying to load with Python 3)
  if options.from_file is not None:
    print('Reading results from file.')
  elif options.trace_time:
    print('Starting tracing (%d seconds)' % options.trace_time)
    time.sleep(options.trace_time)
  else:
    raw_input('Starting tracing (stop with enter)')

  # Stop tracing and collect the output.
  print('Tracing completed. Collecting output...')
  
  # (3.1.6.2) 停止tracing #
  controller.StopTracing()
  
  print('Outputting Systrace results...')
  
  # (3.1.6.3) 输出tracing结果到文件中 #
  controller.OutputSystraceResults(write_json=options.write_json)

我们可以看到trace过的重点最后落在SystraceRunner对象的创建,以及.StartTracing()/.StopTracing()/.OutputSystraceResults()几个方法上。

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值