android IO Prefetch原始碼分析

yooooooo發表於2024-08-01

I/O Prefetcher是高通本身提供的一套最佳化方案,可以用在Android手機App冷啟動的時候。本文基於android Q

主要分libqti-iopd、vendor.qti.hardware.iop@2.0-impl、libqti-iopd-client_system、libqti-perfd-client_system、libperfconfig、libqti_performance,編譯後在/vendor/lib/目錄下,其中libqti-iopd、vendor.qti.hardware.iop@2.0-impl為服務端

  • libqti-perfd-client_system

主要提供perf_get_prop、perf_wait_get_prop等方法,這裡主要呼叫libperfconfig中的方法去讀取配置檔案中的資訊

程式碼路徑:vendor/qcom/proprietary/commonsys-intf/android-perf/mp-ctl/client.cpp

  • libperfconfig

主要是讀取/vendor/etc/perf/perfconfigstore.xml檔案獲取配置資訊,此檔案時在編譯時複製

vendor/qcom/proprietary/android-perf/configs/XXX/perfconfigstore.xml到vendor/etc/perf/目錄下

程式碼路徑:vendor/qcom/proprietary/android-perf/perf-hal/Perf.h

  • libqti-iopd-client_system

主要提供perf_io_prefetch_start、perf_io_prefetch_stop、perf_ux_engine_events、perf_ux_engine_trigger方法,這些方法主要呼叫vendor.qti.hardware.iop@2.0-impl中的iopStart、iopStop、uxEngine_events、uxEngine_trigger

程式碼路徑: vendor/qcom/proprietary/commonsys-intf/android-perf/io-p/client.cpp

  • libqti_performance

vendor/qcom/proprietary/commonsys/android-perf/QPerformance/src/com/qualcomm/qti/Performance.java的jni實現

  • vendor.qti.hardware.iop@2.0-impl

主要提供iopStart、iopStop和uxEngine_events、uxEngine_trigger方法,是對libqti-iopd的方法的封裝uxEngine_XX會判斷perfconfigstore.xml中vendor.iop.enable_uxe屬性是否為1來判斷是否啟用,如果未啟用則直接返回程式碼路徑: vendor/qcom/proprietary/android-perf/iop-hal/Iop.cpp

  • libqti-iopd

主要服務端實現庫,見下文分析

程式碼路徑:vendor/qcom/proprietary/android-perf/io-p/io-p.cpp

首先看服務端的實現

IO Prefetcher的初始化

vendor.qti.hardware.iop的實現比較簡單,主要呼叫libqti-iopd中對應方法。在初始化時,開啟libqti-iopd.so庫,然後找到iop_server_init、iop_server_exit、iop_server_submit_request、uxe_server_submit_request四個方法,並呼叫iop_server_init進行初始化。

對應關係如下:

  • 在呼叫iopStart、iopStop和uxEngine_events時,呼叫libqti-iopd中iop_server_submit_request,並傳遞cmd分別為IOP_CMD_PERFLOCK_IOPREFETCH_START、IOP_CMD_PERFLOCK_IOPREFETCH_STOP和UXENGINE_CMD_PERFLOCK_EVENTS的訊息,並放入IOPevqueue佇列中

  • 在呼叫uxEngine_trigger時,呼叫libqti-iopd中uxe_server_submit_request,並傳遞cmd為UXENGINE_CMD_PERFLOCK_TRIGGER的訊息,並放入UXEevqueue佇列中

* vendor/qcom/proprietary/android-perf/iop-hal/Iop.cpp
IIop* HIDL_FETCH_IIop(const char* /* name */) {
    ALOGE("IOP-HAL: inside HIDL_FETCH_IIop");
    Iop *iopObj = new (std::nothrow) Iop();
    ALOGE("IOP-HAL: boot Address of iop object");
    if (iopObj != NULL) {
        iopObj->LoadIopLib();
        ALOGE("IOP-HAL: loading library is done");
        if (iopObj->mHandle.iop_server_init != NULL ) {
            (*(iopObj->mHandle.iop_server_init))();
        }
    }
    return iopObj;
}

//載入libqti-iopd庫,並找到對應的方法
void Iop::LoadIopLib() {
    const char *rc = NULL;
    char buf[PROPERTY_VALUE_MAX];

    if (!mHandle.is_opened) {
         mHandle.dlhandle = dlopen("libqti-iopd.so", RTLD_NOW | RTLD_LOCAL);
         ...
         *(void **) (&mHandle.iop_server_init) = dlsym(mHandle.dlhandle, "iop_server_init");
         ...
         *(void **) (&mHandle.iop_server_exit) = dlsym(mHandle.dlhandle, "iop_server_exit");
         ...
         *(void **) (&mHandle.iop_server_submit_request) = dlsym(mHandle.dlhandle, "iop_server_submit_request");
         ...
         *(void **) (&mHandle.uxe_server_submit_request) = dlsym(mHandle.dlhandle, "uxe_server_submit_request");
         ...
         mHandle.is_opened = true;
    }

    return;
}

真正的初始化:

* vendor/qcom/proprietary/android-perf/io-p/io-p.cpp
int iop_server_init() {
    ...
    //建立IOP服務,進入無限迴圈來等待訊息
    rc1 = pthread_create(&iop_server_thread, NULL, iop_server, NULL);
    ...
    //讀取vendor.iop.enable_uxe是否為1來判斷是否啟用,1則啟用
    if (uxe_disabled()) {
        uxe_prop_disable = 1;
    } else {
        uxe_prop_disable = 0;
        ...
        //建立UXE服務,進入無限迴圈來等待訊息
        rc2 = pthread_create(&uxe_server_thread, NULL, uxe_server, NULL);
        uba_rc = init_uba();
        ...
        uxe_init = true;
    }

    return 1;
...
}

IOP服務訊息處理,從IOPevqueue訊息佇列中獲取訊息並處理:

* vendor/qcom/proprietary/android-perf/io-p/io-p.cpp
static void *iop_server(void *data)
{
    ...
    /* Main loop */
    for (;;) {
       //wait for perflock commands
        EventData *evData = IOPevqueue.Wait();
        ...
        //判斷vendor.post_boot.parsed表示是否為1來判斷系統是否啟動完成
        if(!is_boot_complete())
        {
            QLOGE("io prefetch is disabled waiting for boot_completed");
            continue;
        }
        //建立DB
        if(is_db_init == false)
        {
            if(create_database() == 0)
            {
                //Success
                is_db_init = true;
            }
        }
        ...
        switch (cmd) {
            //uxEngine_events方法實現
            case UXENGINE_CMD_PERFLOCK_EVENTS:
            {
                ...
                break;
            }
            //iopStart方法實現
            case IOP_CMD_PERFLOCK_IOPREFETCH_START:
            {
                ...
                break;
            }
            //iopStop方法實現
            case IOP_CMD_PERFLOCK_IOPREFETCH_STOP:
            {
                stop_capture();
                break;
            }
        }
   }
}

UXE訊息處理,從UXEevqueue佇列中獲取一個訊息,並處理

* vendor/qcom/proprietary/android-perf/io-p/io-p.cpp
static void *uxe_server(void *data)
{
    for (;;) {
        QLOGI("UXEngine: Waiting on uxe_server_submit_req in uxe_server\n");
        EventData *evData = UXEevqueue.Wait();
        if (!evData || !evData->mEvData) {
            continue;
        }
        cmd = evData->mEvType;
        msg = (iop_msg_t *)evData->mEvData;

        switch (cmd) {
            //uxEngine_trigger方法實現
            case UXENGINE_CMD_PERFLOCK_TRIGGER:
            {
                ...
                break;
            }
       }
   }
}

所以可以看出先是建立了一個database,及4個表,原始碼在dblayer.cppDB儲存路徑為/data/vendor/iop/io-prefetcher.db

* vendor/qcom/proprietary/android-perf/io-p/io-prefetch/dblayer.cpp
/******************************************************************************
  DESCRIPTION

     pkg_file_tbl               pkg_tbl
  |-----------------|      |-----------------|
  |  pkg_name       |      |  pkg_name       |
  |  file_name      |      |-----------------|
  |-----------------|      | pkg_use_time    |
  |                 |      | num_of_launches |
  | file_use_ctr    |      |-----------------|
  | file_time_stamp |
  | file_size       |
  | mark_for_delete |
  | file_modify_time|
  | file_iop_size   |
  | study_finish    |
  | mincore_array   |
  | cache_dropped   |
  | disabled        |
  |-----------------|
******************************************************************************/

/******************************************************************************
      ux_pkg_tbl                 ux_lat_tbl
  |--------------------|        |-----------------|
  |  pkg_name          |        |    pkg_name     |
  |  week_day          |        |-----------------|
  |--------------------|        |   bindApp_dur   |
  | pkg_last_use       |        |   disAct_dur    |
  | num_of_launches    |        |  wakeLock_dur   |
  | num_of_sublaunches |        |    memory_st    |
  | timeslot_1_count   |        |    bindApp_ct   |
  | timeslot_2_count   |        | predict_success |
  | timeslot_3_count   |        |   predict_fail  |
  | timeslot_4_count   |        |    not_game     |
  |--------------------|        |-----------------|
******************************************************************************/

iopStart

* vendor/qcom/proprietary/android-perf/io-p/io-p.cpp
case IOP_CMD_PERFLOCK_IOPREFETCH_START:
{
    static bool is_in_recent_list = false;
    char enable_prefetch_property[PROPERTY_VALUE_MAX];
    char enable_prefetch_ofr_property[PROPERTY_VALUE_MAX];
    int enable_prefetcher = 0;
    int enable_prefetcher_ofr = 0;
    //IOP是否啟用
    strlcpy(enable_prefetch_property,perf_get_prop("vendor.enable.prefetch" , "0").value, PROPERTY_VALUE_MAX);
    enable_prefetch_property[PROPERTY_VALUE_MAX-1]='\0';

    enable_prefetcher = strtod(enable_prefetch_property, NULL);

    strlcpy(enable_prefetch_ofr_property,perf_get_prop("vendor.iop.enable_prefetch_ofr" , "0").value, PROPERTY_VALUE_MAX);
    enable_prefetch_ofr_property[PROPERTY_VALUE_MAX-1]='\0';

    enable_prefetcher_ofr = strtod(enable_prefetch_ofr_property, NULL);

    // if PID < 0 consider it as playback operation
    if(msg->pid < 0)
    {
        int ittr = 0;
        char *week_day = NULL;
        week_day = (char *) malloc(6*sizeof(char));
        // Insert package into the table
        if (week_day == NULL) {
           //Malloc failed. Most-probably low on memory.
           break;
        }
        strlcpy(pkg_info.pkg_name,msg->pkg_name,PKG_NAME_LEN);
        strlcpy(tmp_pkg_name,pkg_info.pkg_name,PKG_NAME_LEN);
        bindApp_dur = 0;
        disAct_dur = 0;
        launching = true;
        //更新DB資訊
        time(&pkg_info.last_time_launched);
           //計算week_day:如果時週二~週六,則為true,否則為false
           //time_slot:4~12時,為1
           //12~17時,為2
           //17~21時,為3
           //0~4,21~24,為4
        compute_time_day_slot(week_day, &time_slot);
        QLOGI("UXEngine Updating Details: pkg_name: %s, week_day: %s, time_slot: %d %s\n", pkg_info.pkg_name, week_day, time_slot, tmp_pkg_name);
        //更新ux_pkg_tbl表資訊
        update_ux_pkg_details(pkg_info, week_day, time_slot, 0);
        //更新tracker資訊,當啟啟動應用個數達到12個時,更新ux_lat_tbl資訊,並設定權重為10
        update_palm_table(msg->pkg_name, 0, 1);

        QLOGI("UXEngine finished ux_pkg_details update \n");
        free(week_day);
        is_in_recent_list = false;
        if(!enable_prefetcher)
        {
            QLOGE("io prefetch is disabled");
            break;
        }

        //檢測應用是否在應用歷史列表中
        for(ittr = 0; ittr < IOP_NO_RECENT_APP; ittr++)
        {
            if(0 == strcmp(msg->pkg_name,recent_list[ittr]))
            {
                is_in_recent_list = true;
                QLOGE("is_in_recent_list is TRUE");
                break;
            }
        }
        // 如果在應用歷史列表中,則退出
        if(true == is_in_recent_list)
        {
            QLOGE("io prefetch is deactivate");
            break;
        }
        //假如在應用歷史個數為7,則重置為0
        if(recent_list_cnt == IOP_NO_RECENT_APP)
            recent_list_cnt = 0;

        //複製包名到應用歷史列表中
        strlcpy(recent_list[recent_list_cnt],msg->pkg_name,PKG_LEN);
        recent_list_cnt++;

        stop_capture();
        stop_playback();
        start_playback(msg->pkg_name);
    }
    // if PID > 0 then consider as capture operation
    if(msg->pid > 0)
    {
        if(!enable_prefetcher)
        {
            QLOGE("io prefetch is disabled");
            break;
        }
        if(true == is_in_recent_list)
        {
            QLOGE("io prefetch Capture is deactivated ");
            break;
        }
        stop_capture();
        //關鍵函式,開始進行capture抓取,啟動一個執行緒,呼叫capture_thread
        start_capture(msg->pid,msg->pkg_name,msg->code_path,enable_prefetcher_ofr);
    }

    break;
}

主要在應用啟動時候呼叫IopStart

  • 假如pid小於0,則進行playback操作
  1. 生成tmp_pkg_name(在binderApplication中甬道)和pkg_info

  2. 計算week_day:如果時週二~週六,則為true,否則為false

  3. 計算time_slot:4 ~ 12時,為1; 12 ~ 17時,為2; 17 ~ 21時,為3; 0~4或者21~24,為4

  4. 呼叫update_ux_pkg_details更新ux_pkg_tbl表資訊,pkg_use_count和timeslot_count_欄位+1

  5. 呼叫update_palm_table更新tracker資訊,當應用kill,則cached為false,當和上次啟動時間小於2s,do_not_launch為true,當應用launch,則cached為true當啟啟動應用個數達到12個時,更新ux_lat_tbl資訊,並設定權重為10,這裡的權重暫未使用

  6. 檢測應用是否在應用歷史列表中,如果在應用歷史列表中,則退出;否則,假如應用歷史個數為7,則重置為0,複製包名到應用歷史列表中

  7. 呼叫start_playback,啟動一個執行緒,呼叫start_playback_thread假如pid大於0,則進行start_capture操作,這裡主要啟動一個執行緒,然後執行capture_thread

從第6步開始,為IOP流程,1~5為iop和uxe共通

IOP流程關鍵函式為capture_thread和start_playback_thread

* vendor/qcom/proprietary/android-perf/io-p/io-prefetch/list_capture.cpp
void * capture_thread(void * arg) {
    ...
    //獲取polling_enable標示是否啟用,預設為啟用
    property_get("vendor.polling_enable", property, "0");
    polling_enable = atoi(property);
    ATRACE_BEGIN("capture_thread");
    capture_thread_arg * arg_bundle = (capture_thread_arg *)arg;

    //獲取包名:之前的字元
    pkg_len = strlen(arg_bundle->pkg_name);
    i = 0;
    while(i < pkg_len && arg_bundle->pkg_name[i] != ':')
    {
        i++;
    }
    arg_bundle->pkg_name[i] = '\0';
    strlcpy(list_pkg_name,arg_bundle->pkg_name,PKG_NAME_LEN);

    QLOGI("pkg_name  = %s",arg_bundle->pkg_name);

    if(polling_enable)
    {
        //假如開啟輪詢,則迴圈3000/50次get_snapshot,每次休眠50秒
        while (duration_counter < halt_counter) {
            if (halt_thread) goto cleanup;
            QLOGI("Getting snapshot %d\n", arg_bundle->pid);
            //獲取當前pid開啟的檔案描述符,並放入file_list中
            get_snapshot(list_pkg_name,arg_bundle->pid);
            duration_counter++;
            usleep(read_fd_interval_ms * 1000);
        }
    }
    //獲取到程式碼apk、oat位置,路徑為/data/app/××,這裡會掃描並開啟檔案並放入file_list中
    get_priv_code_files(arg_bundle->pkg_name);
    //獲取檔案位置,路徑為/data/user/0/pkg和/data/data/pkg
    get_priv_files(arg_bundle);
    QLOGI("pkg_name = %s total_files = %d ",arg_bundle->pkg_name,total_files);

    // Insert package into the table
    strlcpy(pkg_info.pkg_name,arg_bundle->pkg_name,PKG_NAME_LEN);
    time(&pkg_info.last_time_launched);
    // Update Mincore data
    if(arg_bundle->ofr)
    {
        for(index = 0; index < total_files;index++)
        {
           if(update_mincore_data(file_list[index]) != 0)
           {
               file_list[index]->mincore_array = NULL;
           }
        }
    }
    //更新在database中,更新io_pkg_tbl,主要更新應用最後啟動時間
    update_pkg_details(pkg_info);
    //更新io_pkg_file_tbl,將開啟的檔案資訊存入資料庫中
    update_file_details(arg_bundle->pkg_name, file_list, total_files);
    delete_mark_files();
cleanup:
    //log a report about how many files need insert or update this time
    QLOGE("# Final entry : pkg_name file_name file_time_stamp filesize file_iop_size");
    for(i = 0; i < total_files; i++) {
        QLOGE("%d. Final entry : %s %s %d %d %d\n",i
                ,arg_bundle->pkg_name,file_list[i]->file_name
                , file_list[i]->file_time_stamp, file_list[i]->filesize, file_list[i]->file_iop_size);
        if (file_list[i]->mincore_array) {
            free(file_list[i]->mincore_array);
            file_list[i]->mincore_array = NULL;
        }
        free(file_list[i]);
    }
    ATRACE_END();

    free(arg_bundle->pkg_name);
    free(arg_bundle);
    QLOGI("Exit capture_thread");
    return NULL;
}
  • 獲取polling_enable標示是否啟用,預設未啟用,如果啟用,則迴圈3000/50次get_snapshot,每次休眠50秒,獲取/proc/pid/fd開啟的檔案描述符,並放入file_list中

  • 獲取包名:之前的字元

  • 獲取到程式碼apk、oat位置,路徑為/data/app/××,這裡會掃描並開啟檔案並放入file_list中

  • 獲取檔案位置,路徑為/data/user/0/pkg和/data/data/pkg,並放入file_list中

  • 更新io_pkg_tbl,主要更新應用最後啟動時間

  • 更新io_pkg_file_tbl,將開啟的檔案資訊存入資料庫中

至此,可以看到,在開啟了iop之後,在開啟應用的時候,會檢視該pid開啟的檔案描述符列表,將一些需要開啟的檔案先快取到資料庫中。那麼快取到資料庫中,該怎麼使用?其實就是呼叫start_playback_thread,從資料庫中查詢到對應的檔案,然後進行開啟操作

start的呼叫流程為:

  1. 任務歷史中切換應用ActivityTaskManagerService::moveTaskToFrontLocked

---ActivityStackSupervisor::findTaskToMoveToFront--------ActivityStackSupervisor::acquireAppLaunchPerfLock(當TopActivity為空或者DESTROYED)時被呼叫BoostFramework::perfIOPrefetchStart

  1. 應用啟動RootActivityContainer::findTask----ActivityDisplay::findTaskLocked--------ActivityDisplay::acquireAppLaunchPerfLock------------BoostFramework::perfIOPrefetchStar

最終呼叫com.qualcomm.qti.Performance.perfIOPrefetchStart,直接呼叫hidl的iopStart方法及com.qualcomm.qti.UxPerformance.perfIOPrefetchStart 這裡會透過MappedByteBuffer和FileChannel開啟/data/app/下的檔案對映到虛擬記憶體中

uxEngine_events

呼叫流程

  1. ActivityThread::handleBindApplication完成後,呼叫ux_perf.perfUXEngine_events(BoostFramework.UXE_EVENT_BINDAPP, 0,pkg_name,bindApp_dur,//handleBindApplication執行時間pkgDir);//data/app/pck/base.apk

  2. ActivityManagerService::appDiedLockedmUxPerf.perfUXEngine_events(BoostFramework.UXE_EVENT_KILL, 0, app.processName, 0);

  3. ProcessRecord::killux_perf.perfUXEngine_events(BoostFramework.UXE_EVENT_KILL, 0, this.processName, 0);activity顯示完成,呼叫ActivityMetricsLogger.logAppDisplayed

  4. mUxPerf.perfUXEngine_events(BoostFramework.UXE_EVENT_DISPLAYED_ACT, 0, info.packageName, info.windowsDrawnDelayMs);

case UXENGINE_CMD_PERFLOCK_EVENTS:
 {
     if(uxe_prop_disable || !is_boot_complete())
     {
         QLOGE("UXEngine is disabled");
         break;
     }

     int opcode = msg->opcode;
     char in_pkg_name[PKG_NAME_LEN];
     strlcpy(in_pkg_name, msg->pkg_name, PKG_NAME_LEN);

     //Opcode = 2 : Client is sending bindApp duration
     if(opcode == UXE_EVENT_BINDAPP)
     {
         ...
         bindApp_dur = msg->lat;
         //獲取bindApplication次數
         bindApp_count = get_total_pkg_bindapp_count(in_pkg_name);
         QLOGI("UXEngine: Received bindApp duration: %d pkg_name %s tmp_pkg_name: %s\n",
                                                 bindApp_dur, in_pkg_name, tmp_pkg_name);
         if (bindApp_count) {
             int lat_thres = 0;
             //獲取ux_lat_tbl表中資訊
             get_ux_lat_pkg(in_pkg_name, &ux_lat);
             QLOGI("UXEngine: Average bindApp before: %d bindApp_count : %d\n",
                                            ux_lat.bindApp_dur, bindApp_count);
             //計算lat_thres,為當前binderApplication時間和上次時間百分比
             if (ux_lat.bindApp_dur != 0)
                  lat_thres = (bindApp_dur*100)/ux_lat.bindApp_dur;
             //如果lat_thres為[50, 500]之間,且小於4s,則計算平均啟動時間
             if (ux_lat.bindApp_dur != 0 && (LAT_LOW_THRESHOLD < lat_thres && LAT_HIGH_THRESHOLD > lat_thres) && bindApp_dur < 4000) {
                 avg_bindApp = ((bindApp_count) * ux_lat.bindApp_dur + bindApp_dur) / (bindApp_count+1);
             } else {
                 avg_bindApp = ux_lat.bindApp_dur;
                 if (ux_lat.bindApp_dur == 0)
                     avg_bindApp = bindApp_dur;
             }
             avg_disAct = ux_lat.disAct_dur;
             QLOGI("UXEngine: Average bindApp: %d for app: %s bindApp_count: %d\n",
                                          avg_bindApp, in_pkg_name, bindApp_count);
             bindApp_dur = avg_bindApp;
         } else {
             avg_disAct = 0;
         }
         //這種情況下為沒有呼叫過iopStart,即為啟動empty app情況下,
         //比如清除任務後,透過uxEngine_trigger獲取資訊後呼叫startActivityAsUserEmpty情況下
         if (!pkg_match)
         {
             // Empty app launch. Update db table.
             QLOGI("UXEngine: Updating bindApp duration: %d pkg_name %s\n",
                                                 bindApp_dur, in_pkg_name);
             update_ux_lat_details(in_pkg_name, bindApp_dur, avg_disAct, 0, 1);
             bindApp_dur = 0;
             avg_bindApp = 0;
             avg_disAct = 0;
         }
     }

     //Opcode = 3 : Client is sending DisplayedActivity
     if (opcode == UXE_EVENT_DISPLAYED_ACT)
     {
                int non_empty_launch = 1;
                bool pkg_match = true;
                if (tmp_pkg_name[0] == 0)
                    goto disAct_cleanup;
                //比較in_pkg_name和tmp_pkg_name(在IopStart獲取)是否一致 ,launching(IopStart時賦為true)是否為true
                if (!strncmp(in_pkg_name, tmp_pkg_name, PKG_NAME_LEN)) {
                    pkg_match = true;
                } else {
                    //Received unexpected displayed activity.
                    //Update regardless, but for the correct app.
                    //skip update
                    QLOGI("UXEngine: Skip. Received weird DA: %s\n", in_pkg_name);
                    goto disAct_cleanup;
                }

                disAct_dur = msg->lat;
                //呼叫get_total_pkg_use_count查詢ux_pkg_tbl表中pkg_use_count,即應用activity開啟次數
                pkg_count = get_total_pkg_use_count(in_pkg_name);
                // Empty app launch
                //呼叫iopStart,但是未執行binderApplication流程,則bindApp_dur為0,launching為true
                if ((bindApp_dur == 0 && launching) || !pkg_match) {
                    QLOGI("UXEngine: Empty app launch. Just update DA. pkg_name: %s \n", in_pkg_name);
                    bindApp_count = get_total_pkg_bindapp_count(in_pkg_name);
                    get_ux_lat_pkg(in_pkg_name, &ux_lat);
                    // Launching process would have definitely had a bindApp.
                    // If count=0, something wrong. Skip update.
                    if(bindApp_count)
                        bindApp_dur = ux_lat.bindApp_dur;
                    else
                        goto disAct_cleanup;
                    avg_disAct = ux_lat.disAct_dur;
                    //空app啟動流程,曾經被uxe拉起過
                    non_empty_launch = 0;
                }

                QLOGI("UXEngine: bindApp duration: %d, DisplayedActivity: %d\n", bindApp_dur, disAct_dur);
                //代表應用activity被開啟過兩次以上
                if (pkg_count - 1) {
                    QLOGI("UXEngine: Average displayed activity before: %d pkg_count :%d\n", avg_disAct, pkg_count);
                    int lat_thres = 0;
                    if (avg_disAct != 0)
                        lat_thres = (disAct_dur*100)/avg_disAct;
                    if (avg_disAct != 0 && pkg_count != 0 && (LAT_LOW_THRESHOLD < lat_thres && LAT_HIGH_THRESHOLD > lat_thres) && disAct_dur < 5000) {
                        avg_disAct = ((pkg_count - 1) * avg_disAct + disAct_dur) / pkg_count;
                        disAct_dur = avg_disAct;
                    }
                    QLOGI("UXEngine: Average displayed activity after: %d, bindApp_count: %d\n", avg_disAct);
                    if(avg_disAct != 0)
                        disAct_dur = avg_disAct;
                }
                QLOGI("UXEngine: Updating bindApp & DA duration: %d %d pkg_name %s\n",
                                                     bindApp_dur, disAct_dur, in_pkg_name);
                update_ux_lat_details(in_pkg_name, bindApp_dur, disAct_dur, 0, non_empty_launch);

                disAct_cleanup:
                // Refresh preferred apps & palm table after launch.
                        char *final_out[PREFERRED_APP_COUNT];
                        int uba_return = 0, i = 0;
                        for (i = 0; i < PREFERRED_APP_COUNT; i++) {
                            final_out[i] = (char*) malloc(PKG_NAME_LEN);
                            final_out[i][0] = '\0';
                        }
                        uba_return = get_preferred_apps(final_out, 0, NULL, -1, true);
                        update_palm_table(in_pkg_name, 0, 1);
                        //Cleanup
                        for (i = 0; i < PREFERRED_APP_COUNT; i++) {
                            free(final_out[i]);
                        }
                        disAct_dur = 0;
                        bindApp_dur = 0;
                        avg_bindApp = 0;
                        avg_disAct = 0;
                        pkg_count = 0;
                        launching = false;
                        memset(tmp_pkg_name, 0, PKG_NAME_LEN);
                        QLOGI("UXEngine: Finished ux_lat update & reset \n");
     }
     //應用被殺,則更新tracker及資訊ux_lat_tbl資訊
     if(opcode == UXE_EVENT_KILL)
     {
         QLOGI("UXEngine: Received app no-AM kill: %s\n", in_pkg_name);
         update_palm_table(in_pkg_name, 1, 0);
     }

     if(opcode == UXE_EVENT_GAME)
     {
          char *week_day = NULL;
          week_day = (char *) malloc(6*sizeof(char));
           if (week_day == NULL) {
              //Malloc failed. Most-probably low on memory.
              break;
           }
           strlcpy(pkg_info.pkg_name,in_pkg_name,PKG_NAME_LEN);
           time(&pkg_info.last_time_launched);
           compute_time_day_slot(week_day, &time_slot);
           QLOGI("UXEngine Updating sub_launch details: \
                  pkg_name: %s, week_day: %s, time_slot: %d %s\n",
                  pkg_info.pkg_name, week_day, time_slot, tmp_pkg_name);
           update_ux_pkg_details(pkg_info, week_day, time_slot, 1);
           update_palm_table(msg->pkg_name, 0, 1);
           free(week_day);
     }
     if(opcode == UXE_EVENT_SUB_LAUNCH)
     {
         ...
     }
     //應用解除安裝,刪除db資訊
     if(opcode == UXE_EVENT_PKG_UNINSTALL)
     {
         QLOGI("UXEngine: Received pkg uninstall - %s\n", in_pkg_name);
         int userId = msg->lat;
         update_palm_table(in_pkg_name, 1, 0);
         uninstall_pkg(in_pkg_name);
     }
     if(opcode == UXE_EVENT_PKG_INSTALL)
     {
         ...
     }
     break;
 }

UXE_EVENT_BINDAPP 流程,在應用bindApplication完成後呼叫

  1. 比較in_pkg_name和tmp_pkg_name(在IopStart獲取)是否一致 ,launching(IopStart時賦為true)是否為true

  2. 呼叫get_total_pkg_bindapp_count查詢ux_lat_tbl表中bindApp_ct獲取bindApplication次數

  3. 呼叫get_ux_lat_pkg獲取ux_lat_tbl表中資訊

  4. 計算lat_thres,為當前binderApplication時間和上次時間百分比,如果lat_thres為[50, 500]之間,且小於4s,則計算平均啟動時間bindApp_dur

  5. 沒有呼叫過iopStart(即tmp_pkg_name為上次iopStart時應用包名),即為啟動empty app情況下,比如清除任務後,透過uxEngine_trigger獲取資訊後呼叫startActivityAsUserEmpty情況下,則呼叫update_ux_lat_details更新ux_lat_tbl表中bindApp_dur(平均binderApplication時間)和disAct_dur(如果第一次為0,否則為db中儲存的)資訊,並使bindApp_ct+1

UXE_EVENT_DISPLAYED_ACT流程,在應用activity啟動完成後,類似activity啟動的displayed時間

  1. 比較in_pkg_name和tmp_pkg_name(在IopStart獲取)是否一致 ,launching(IopStart時賦為true)是否為true

  2. 呼叫get_total_pkg_use_count查詢ux_pkg_tbl表中pkg_use_count,即應用activity開啟次數

  3. 呼叫iopStart,但是未執行binderApplication流程,則bindApp_dur為0,launching為true,即為空app啟動流程,曾經被uxe拉起過。呼叫get_total_pkg_bindapp_count獲取ux_lat_tbl中bindApp_ct,即binderApplication次數,呼叫get_ux_lat_pkg獲取ux_lat_tbl中對應包名的資訊,這裡貌似有點雞肋,get_total_pkg_bindapp_count為多餘的。並之non_empty_launch為0,代表空app啟動流程

  4. pkg_count - 1為真,代表應用activity被開啟過兩次以上,

  5. 計算lat_thres,為當前displayed activity時間和上次時間百分比,如果lat_thres為[50, 500]之間,且小於5s,則計算平均啟動時間disAct_dur

  6. 呼叫update_ux_lat_details更新ux_lat_tbl表中bindApp_dur(平均binderApplication時間)和disAct_dur(如果第一次為0,否則為db中儲存的)資訊,並使bindApp_ct+1(曾經被uxe拉起過,則non_empty_launch為0,不+1)

  7. 狀態重置:呼叫get_preferred_apps更新tracker列表呼叫update_palm_table設定為以啟動模式(cached為true)

UXE_EVENT_KILL流程,應用被殺死後的流程

  1. 呼叫update_palm_table,設定tracker列表的cached和empty為false

  2. 如果kill時間距離displayed activity時間或者返回home時間間隔2s內,則do_not_launch為true,在下次返回home或者displayed時,從返回列表中移除

uxEngine_trigger

獲取需要預啟動的app資訊

case UXENGINE_CMD_PERFLOCK_TRIGGER:
{
    static int back_to_home = 1;
    pa_ready = false;
    //判斷是否啟用
    if(uxe_prop_disable || !is_boot_complete())
    {
        QLOGI("UXEngine is disabled");
        pthread_cond_signal(&ux_trigger_cond);
        break;
    }
    ...
    // get_preferred_apps.
    if(msg->opcode == UXE_TRIGGER || msg->opcode == UXE_ULMK_TRIGGER)
    {
        ...
        QLOGE("UXEngine, Received back to home");
           //final_out個數為12
        char *final_out[PREFERRED_APP_COUNT];
        int uba_return = 0, i = 0;

        for (i = 0; i < PREFERRED_APP_COUNT; i++) {
            final_out[i] = (char*) malloc(PKG_NAME_LEN);
            final_out[i][0] = '\0';
        }

        if (msg->opcode == UXE_TRIGGER) {
            uba_return = get_preferred_apps(final_out, 0, NULL, -1, false);
        } else {
            uba_return = get_preferred_apps(final_out, 1, NULL, -1, false);
        }
        QLOGE("Final OUTPUT: Preferred Apps returned : %d\n", uba_return);

        for (i = 0; i < uba_return ; i++) {
            if (final_out[i][0] != 0) {
                QLOGI("Final OUTPUT: Apps returned: %s\n", final_out[i]);
                strlcat(preferred_apps,final_out[i],strcat_sz);
                strlcat(preferred_apps,"/",strcat_sz);
            }
        }
        pa_ready = true;
        // return to F/W once HIDL is implemented
        back_to_home = 1;
        for (i = 0; i < PREFERRED_APP_COUNT; i++) {
            free(final_out[i]);
        }
        QLOGE("UXEngine: Set preferred apps in uxe_server: %s\n", preferred_apps);
        pthread_cond_signal(&ux_trigger_cond);
    }
    else if (msg->opcode == UXE_TRIGGER_LIST_FAV_APPS) {
        //暫未使用
        ...
    }
    break;
}

* vendor/qcom/proprietary/android-perf/io-p/io-prefetch/uba.cpp
int get_preferred_apps(char **out_list, int disable_palm, char * all_week_day, int all_time_slot, bool launch)
{
    long cur_time = (long) now_secs();
    char *week_day = NULL;
    pkg_ux_top_details *cur_list = NULL;
    int total_pkgs = 0, index = 0, table_size = 0, num_pkgs = 0;
    //臨時個數為18
    int tmp_size = PREFERRED_APP_COUNT + PREFERRED_APP_COUNT/2;
    //week_day為true或者false
    week_day = (char *) malloc(6*sizeof(char));
    if (week_day == NULL) {
        //Malloc failed. Most-probably low on memory.
        goto out;
    }
    //計算week_day:如果時週二~週六,則為true,否則為false
    //time_slot:4~12時,為1
    //12~17時,為2
    //17~21時,為3
    //0~4,21~24,為4
    compute_time_day_slot(week_day, &time_slot);
    //獲取ux_pkg_tbl中應用個數
    total_pkgs = get_total_ux_pkgs(0);

    if (total_pkgs == 0) {
        goto out;
    }
    cur_list = (pkg_ux_top_details *) malloc ((tmp_size+1) * sizeof(pkg_ux_top_details));

    if (cur_list == NULL) {
        goto out;
    }

    QLOGI("Get_Preferred_Apps: Current Time stats: Week_day: %s, Time_slot: %d, Total_pkgs: %d\n", week_day, time_slot, total_pkgs);
    //根據time_slot獲取ux_pkg_tbl中應用列表,即所有記錄的應用的binderApplication時間,開啟次數等資訊
    if (disable_palm == 1 && all_week_day != NULL && all_time_slot != -1)
        num_pkgs = get_top_ux_pkg_list(cur_list, all_week_day, all_time_slot, tmp_size, 1);
    else
        num_pkgs = get_top_ux_pkg_list(cur_list, week_day, time_slot, tmp_size, 1);
    QLOGI("Get_Preferred_Apps: Get Top pkg list Returned pkgs: %d\n", num_pkgs);

    if (num_pkgs == 0) {
        goto out;
    }

    for (index = 0 ; (index < PREFERRED_APP_COUNT && index < num_pkgs) ; index++) {
         int total_count = cur_list[index].num_launches;
         int tslot_count = cur_list[index].timeslot_count_select;
         QLOGI("Get_Preferred_Apps: Accessing cur list: %s pkg_launch_count: %d bindApp_dur: %d disAct_dur%d\n", cur_list[index].pkg_name, cur_list[index].num_launches,
                                                                                                                cur_list[index].bindApp_dur, cur_list[index].disAct_dur);
         strlcpy(out_list[index], cur_list[index].pkg_name, PKG_NAME_LEN);
         QLOGI("Get_Preferred_Apps: Accessing copied list: %s\n", out_list[index]);
         table_size++;
    }

    if (disable_palm == 0) {
        QLOGI("Get_Preferred_Apps: Accessing table_size %d\n", table_size);
        set_palm_table(out_list, table_size, cur_time, launch);
    }
    else if (disable_palm == 1) {
        QLOGI("Get_Preferred_Apps: Return all Fav apps List with size = %d\n", table_size);
    }

out:
    if (cur_list != NULL) {
        free(cur_list);
    }
    if (week_day != NULL) {
        free(week_day);
    }
    return table_size;
}

呼叫邏輯為:清除後臺任務

ActivityStackSupervisor::removeTaskByIdLocked----ActivityStackSupervisor::cleanUpRemovedTaskLocked-------PreferredAppsTask::doInBackground-------------BoostFramework::perfUXEngine_trigger

切換到launcher介面

ActivityStack::resumeTopActivityInnerLocked----PreferredAppsTask::doInBackground

切換到home或者進入任務列表

ActivityRecord::completeResumeLocked----PreferredAppsTask::doInBackground

get_preferred_apps根據應用的啟動時間和星期幾,來預測要啟動的應用,DB中只儲存12個應用當透過perfUXEngine_trigger獲取到應用資訊後,呼叫startActivityAsUserEmpty來啟動一個空的應用,在再次啟動時,直接跳過binderApplication階段,從而加快啟動速度,這裡startActivityAsUserEmpty直接呼叫startProcessLocked建立一個程序,並執行bindApplication流程,因為startProcessLocked時,指定啟動空activity(傳入new HostingRecord(null)),從而在RootActivityContainer::attachApplication時,不在呼叫realStartActivityLocked

* frameworks/base/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
class PreferredAppsTask extends AsyncTask<Void, Void, Void> {
        @Override
        protected Void doInBackground(Void... params) {
            String res = null;
            final Intent intent = new Intent(Intent.ACTION_MAIN);
            int trimLevel = 0;
            try {
                trimLevel = ActivityManager.getService().getMemoryTrimLevel();
            } catch (RemoteException e) {
                return null;
            }
            if (mUxPerf != null
                   && trimLevel < ProcessStats.ADJ_MEM_FACTOR_CRITICAL) {
                res = mUxPerf.perfUXEngine_trigger(BoostFramework.UXE_TRIGGER); 
                if (res == null)
                    return null;
                String[] p_apps = res.split("/");
                if (p_apps.length != 0) {
                    ArrayList<String> apps_l = new ArrayList(Arrays.asList(p_apps));
                    Bundle bParams = new Bundle();
                    if (bParams == null)
                        return null;
                    bParams.putStringArrayList("start_empty_apps", apps_l);
                    final Message msg = PooledLambda.obtainMessage(
                                            ActivityManagerInternal::startActivityAsUserEmpty, mService.mAmInternal, bParams);
                    mService.mH.sendMessage(msg);
                }
            }
            return null;
        }
    }

get_preferred_apps的主要流程為:

  1. 呼叫compute_time_day_slot計算當前是否工作日和時間段

  2. 根據工作日和時間段呼叫get_top_ux_pkg_list獲取ux_pkg_tbl和ux_lat_tbl中的應用啟動次數(pkg_use_count),啟動時間(bindApp_dur、disAct_dur)、時間段啟動次數(timeslot_count_)等資訊

  3. 獲取到最大PREFERRED_APP_COUNT(12)個應用資訊,放入out_list

  4. 呼叫set_palm_table進行應用資訊過濾,並更新tracker列表

    int set_palm_table(char **list, int tb_size, long cur_time, bool launch) {
    //Compare old and new list, and send update to PALM
    //Check for empty conditions.
    int i = 0, j = 0, match = 0, match_cached = 0;

     palm_table *cur_tracker;
     palm_table *tmp_tracker;
     cur_tracker = (palm_table *) malloc (tb_size * sizeof(palm_table));
    
     if (cur_tracker == NULL) {
         QLOGE("Set_Palm_Table: No memory to set temp table. Return");
         return 0;
     }
    
     QLOGI("Set_Palm_Table: Entry, Table size: %d, old table size: %d, Current time: %ld \n", tb_size, old_tbl_size, cur_time);
     for(i = 0; i < tb_size; i++)
     {
          strlcpy(cur_tracker[i].pkg_name, list[i], PKG_NAME_LEN);
          cur_tracker[i].cached = launch;
          cur_tracker[i].empty = !launch;
          cur_tracker[i].memory_rss = 0; //Use getMemory or db call later on.
          cur_tracker[i].last_empty_time = cur_time;
          cur_tracker[i].do_not_launch = false;
          cur_tracker[i].hit = false;
          cur_tracker[i].comp = false;
          QLOGI("Set_Palm_Table: %s", cur_tracker[i].pkg_name);
          for(j = 0; j < old_tbl_size; j++)
          {
              if(!strncmp(tracker[j].pkg_name, list[i], PKG_NAME_LEN))
              {
                  cur_tracker[i].memory_rss = tracker[j].memory_rss;
                  cur_tracker[i].last_empty_time = tracker[j].last_empty_time;
                  cur_tracker[i].cached = tracker[j].cached;
                  cur_tracker[i].empty = tracker[j].empty;
                  cur_tracker[i].do_not_launch = tracker[j].do_not_launch;
                  cur_tracker[i].hit = tracker[j].hit;
                  tracker[j].comp = true;
                  strlcpy(cur_tracker[i].pkg_name, tracker[j].pkg_name, PKG_NAME_LEN);
                  //
                  if(launch || tracker[j].empty || tracker[j].cached) {
                      list[i][0] = 0;
                      list[i][1] = '\0';
                      QLOGI("Set_Palm_Table: Matched empty from previous table. Pkg_name: %s Pkg State--Cached: %d, Empty: %d\n", tracker[j].pkg_name, tracker[j].cached, tracker[j].empty);
                      strlcpy(cur_tracker[i].pkg_name, tracker[j].pkg_name, PKG_NAME_LEN);
                      match++;
                  }
                 //如果應用2s內被殺兩次(do_not_launch為true)
                  else if(tracker[j].do_not_launch) {
                      QLOGI("Set_Palm_Table: Not allowed for launch due to looping: %s\n", tracker[j].pkg_name);
                      list[i][0] = 0;
                      list[i][1] = '\0';
                      cur_tracker[i].cached = false;
                      cur_tracker[i].empty = false;
                      cur_tracker[i].do_not_launch = true;
                      if ((cur_time - tracker[j].last_empty_time) > empty_restart_threshold) {
                          cur_tracker[i].do_not_launch = false;
                          QLOGE("Set_Palm_Table: Timeout expired. Allowed to start as empty: %s\n", tracker[j].pkg_name);
                      }
                  }
                  //應用被殺後,再次返回home會執行此流程,被拉起,將empty付為空
                  else {
                      cur_tracker[i].empty = true;
                      cur_tracker[i].cached = false;
                      QLOGI("Set_Palm_Table: Matched, but app was killed. Resetting. Pkg_name: %s Pkg State--Cached: %d, Empty: %d\n", cur_tracker[i].pkg_name, cur_tracker[i].cached, cur_tracker[i].empty);
                  }
              }
          }
     }
     old_tbl_size = tb_size;
     //memcpy(tracker, cur_tracker, (tb_size*sizeof(palm_table)));
     tmp_tracker = tracker;
     tracker = cur_tracker;
     free(tmp_tracker);
     if (match == tb_size)
         return 0;
     else
         return (tb_size - match);
    

    }

set_palm_table

  1. 根據最新的可能要啟動應用列表,構建一個最新的tracker

  2. 如果應用displayed完成,lunch為true,返回home,lunch為false

  3. 當應用kill時,被置為true,當返回home時,進入set_palm_table,然後走else流程,將empty置為true,然後被拉起,empty為true

  4. 如果應用2s內被殺兩次,則do_not_launch為true

  5. 如果應用已啟動,則cached為true

  6. 上述四種情況,則從out列表移除

  7. 如果是當前時間段不再tracker的應用(comp為false),(比如工作日/非工作日切換,常用應用個數大於12個時候,使用次數變更到底等),則如果已經啟動,更新預言成功(predict_success)權重+10,否則更新預言失敗(predict_fail)權重pr_launch/PREFERRED_APP_COUNT*10。這裡的predict_success和predict_fail暫未使用,在android R中不在使用

相關文章