gradle中如何統(tǒng)計(jì)每個(gè)task的執(zhí)行時(shí)間 ?

一. 背景

假設(shè)我們有這樣一個(gè)需求:
我們想要知道哪些task是耗時(shí)較多的, 這樣就可以針對這些task進(jìn)行優(yōu)化, 以此來節(jié)省構(gòu)建時(shí)間!

構(gòu)建, 對于一個(gè)開發(fā)者來說, 是一個(gè)痛苦的等待過程, 相信開發(fā)者都深有體會.
當(dāng)然對于安卓開發(fā)者來說, 已經(jīng)有不少非常優(yōu)秀的加速構(gòu)建過程的工具了, 如: Instant Run, Freeline 等.
但是對于其他使用gradle作為構(gòu)建工具的項(xiàng)目, 可能會缺少這樣的工具, 因此會有自行優(yōu)化構(gòu)建過程的必要. 至于如后優(yōu)化, 就留給讀者發(fā)揮自己的智慧了. 下面主要講講如何收集構(gòu)建過程的各個(gè)步驟所花費(fèi)的時(shí)間.

二. 解決思路

1. 收集各個(gè)task所花費(fèi)的時(shí)間

  • 此過程主要用到兩個(gè)gradle的關(guān)鍵接口以及一個(gè)方法:
    • org.gradle.api.execution.TaskExecutionListener
      此接口定義了每個(gè)task執(zhí)行前后的回調(diào): beforeExecute()afterExecute()
    • org.gradle.BuildListener
      此接口主要定義了構(gòu)建開始和構(gòu)建完成的回調(diào) (當(dāng)然還有一些其他的回: 調(diào)配置完成, 所有項(xiàng)目加載完成等): buildStarted()buildFinished()
    • org.gradle.api.Project對象的gradle屬性的addListener()方法
      1. 每個(gè)項(xiàng)目(父項(xiàng)目和子項(xiàng)目)都有自己的配置, 一般是用項(xiàng)目根目錄下的build.gradle腳本來進(jìn)行配置. 每個(gè)項(xiàng)目都會創(chuàng)建一個(gè)org.gradle.api.Project對象來代表該項(xiàng)目. Project對象中的gradle屬性代表的是build.gradle腳本. gradle屬性的類型是org.gradle.api.invocation.Gradle.
      2. 值得一提的是org.gradle.api.invocation.Gradle類的addListener()是一個(gè)比較特殊的方法, 它的參數(shù)是Object類型, 此方法的原型如下:
             /**
             * Adds the given listener to this build. The listener may implement any of the given listener interfaces:
             *
             * <ul>
             * <li>{@link org.gradle.BuildListener}
             * <li>{@link org.gradle.api.execution.TaskExecutionGraphListener}
             * <li>{@link org.gradle.api.ProjectEvaluationListener}
             * <li>{@link org.gradle.api.execution.TaskExecutionListener}
             * <li>{@link org.gradle.api.execution.TaskActionListener}
             * <li>{@link org.gradle.api.logging.StandardOutputListener}
             * <li>{@link org.gradle.api.tasks.testing.TestListener}
             * <li>{@link org.gradle.api.tasks.testing.TestOutputListener}
             * <li>{@link org.gradle.api.artifacts.DependencyResolutionListener}
             * </ul>
             *
             * @param listener The listener to add. Does nothing if this listener has already been added.
             */
            void addListener(Object listener);
      
      可以看到, 其可以添加的類型非常多. 上面我們使用的就是其中的兩個(gè).

2. 上傳上一步收集的數(shù)據(jù) (用于統(tǒng)計(jì)分析)

  • 使用groovy的擴(kuò)展庫http-builderhttp-builder-ng(最新版本的http-builder)做統(tǒng)計(jì)數(shù)據(jù)的上傳工作.

三. 具體實(shí)踐

1. 在父項(xiàng)目的build.gradle文件中將http-builder-ng庫加入到classpath中 (腳本文件中用的的類庫必須添加到classpath中), 如下:
buildscript {
   repositories {
       google()
       jcenter()
   }
   dependencies {
       classpath 'com.android.tools.build:gradle:3.0.1'
       //添加http-builder-ng的依賴
       classpath 'io.github.http-builder-ng:http-builder-ng-core:1.0.3'
   }
}

如果用http-builder, classpath處改成:
classpath "org.codehaus.groovy.modules.http-builder:http-builder:0.7.2"

2. 在父項(xiàng)目的build.gradle文件頂部導(dǎo)入http-builder-ng相關(guān)的類 (上傳數(shù)據(jù)時(shí)用到)

如下:
import groovyx.net.http.HttpBuilder
如果是http-builder, 導(dǎo)入語句如下:

import groovyx.net.http.HTTPBuilder
import static groovyx.net.http.ContentType.*
3. 在父項(xiàng)目的build.gradle內(nèi)容底部, 自定義監(jiān)聽CollectTaskTimeListener, 如下:
class CollectTaskTimeListener implements TaskExecutionListener, BuildListener {
    private Clock clock                 //用于記錄每個(gè)task執(zhí)行所花的時(shí)間
    private Clock start = new Clock()   //用于記錄所有task執(zhí)行所花的時(shí)間
    private def timings = new HashMap<String, Long>() //存儲所有task和其所發(fā)時(shí)間的對應(yīng)關(guān)系
    private def final MIN_COST = 5      //展示統(tǒng)計(jì)數(shù)據(jù)的下限 (小于此值時(shí)不輸出統(tǒng)計(jì)數(shù)據(jù))

    //每個(gè)task執(zhí)行之前調(diào)用
    @Override
    void beforeExecute(Task task) {
        clock = new Clock()
    }

    //每個(gè)task執(zhí)行后調(diào)用
    @Override
    void afterExecute(Task task, TaskState state) {
        long ms = clock.timeInMs
        timings.put(task.path, ms)
        task.project.logger.warn "${task.path} took ${ms}ms"
    }

    //build結(jié)束時(shí)調(diào)用 (所有task結(jié)束時(shí)調(diào)用)
    @Override
    void buildFinished(BuildResult result) {
        //輸出統(tǒng)計(jì)數(shù)據(jù)
        outputHeader("Task timings(no sort): ")
        outputProfile(timings.iterator())
        //輸出排序后的統(tǒng)計(jì)數(shù)據(jù)
        outputHeader("Task timings(sorted): ")
        outputProfile(sortProfileData(timings).iterator())
        println("\n")
        uploadReport()
    }

    void outputHeader(String headerMessage) {
        println("\n======================================================")
        println(headerMessage)
    }

    //輸出收集的數(shù)據(jù)
    void outputProfile(Iterator<Map.Entry<String, Long>> it) {
        for (entry in it) {
            if (entry.value >= MIN_COST) {
                printf("%-50s  %-15s\n", entry.key, entry.value + "ms")
            }
        }
    }

    //對task所花費(fèi)的時(shí)間進(jìn)行排序
    List<Map<String, Long>> sortProfileData(Map<String, Long> profileData) {
        List<Map.Entry<String, Long>> data = new ArrayList<>()
        for (timing in profileData) data.add(timing)
        Collections.sort(data, new Comparator<Map.Entry<String, Long>>() {
            @Override
            int compare(Map.Entry<String, Long> o1, Map.Entry<String, Long> o2) {
                if (o1.value > o2.value) return 1
                else if (o1.value < o2.value) return -1
                return 0
            }
        })
        return data
    }

    //將收集的數(shù)據(jù)上傳到服務(wù)器做分析
    //org.codehaus.groovy.modules.http-builder:http-builder:0.7.2
    /*
    void uploadReport() {
        def http = new HTTPBuilder('http://10.249.23.63:8080', "application/json")
        try {
            http.post(path: '/time', body: ['timings': timings, 'user.name': System.getProperty("user.name"), "total_time": start.timeInMs],
                    requestContentType: JSON) { resp ->
                println "POST Success: ${resp.statusLine}"
            }
        } catch (Exception e) {
            e.printStackTrace()
        }
    }*/

    //將收集的數(shù)據(jù)上傳到服務(wù)器做分析
    //io.github.http-builder-ng:http-builder-ng-core:1.0.3
    void uploadReport() {
        HttpBuilder.configure {
            request.uri = "http://10.249.23.72:8080"
        }.postAsync {
            request.uri.path = '/time'
            request.body = ['timings': timings, 'user.name': System.getProperty("user.name"), "total_time": start.timeInMs]
            request.contentType = 'application/json'
            response.success { formServer, body -> //body => groovy.json.internal.LazyMap  (服務(wù)端相應(yīng)類型Content-Type為application/json)
                println "POST Success: ${formServer.statusCode}, ${formServer.message}, ${body.getClass()}; code=${body.get('code')}, message=${body.get('message')}"
            }
            response.failure { formServer, errorMessage -> //errorMessage => byte[]
                println "POST Failure: ${formServer.statusCode}, ${formServer.message}, errorMessage=${new String(errorMessage)}"
            }
        }
    }

    @Override
    void buildStarted(Gradle gradle) {}

    @Override
    void settingsEvaluated(Settings settings) {}

    @Override
    void projectsLoaded(Gradle gradle) {}

    @Override
    void projectsEvaluated(Gradle gradle) {}
}
4. 在上一步定義的CollectTaskTimeListener的最后面, 將自定義的監(jiān)聽添加到gradle對象中, 如下:
//添加自定義的監(jiān)聽
gradle.addListener(new CollectTaskTimeListener())
完整build.gradle文件內(nèi)容如下:
import groovyx.net.http.HttpBuilder

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'
        classpath 'io.github.http-builder-ng:http-builder-ng-core:1.0.3'
    }
}

allprojects {
    repositories {
        google()
        jcenter()
        mavenCentral()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

class CollectTaskTimeListener implements TaskExecutionListener, BuildListener {
    private Clock clock                 //用于記錄每個(gè)task執(zhí)行所花的時(shí)間
    private Clock start = new Clock()   //用于記錄所有task執(zhí)行所花的時(shí)間
    private def timings = new HashMap<String, Long>() //存儲所有task和其所發(fā)時(shí)間的對應(yīng)關(guān)系
    private def final MIN_COST = 5      //展示統(tǒng)計(jì)數(shù)據(jù)的下限 (小于此值時(shí)不輸出統(tǒng)計(jì)數(shù)據(jù))

    //每個(gè)task執(zhí)行之前調(diào)用
    @Override
    void beforeExecute(Task task) {
        clock = new Clock()
    }

    //每個(gè)task執(zhí)行后調(diào)用
    @Override
    void afterExecute(Task task, TaskState state) {
        long ms = clock.timeInMs
        timings.put(task.path, ms)
        //輸出當(dāng)前task的執(zhí)行時(shí)間
        task.project.logger.warn "${task.path} took ${ms}ms"
    }

    //build結(jié)束時(shí)調(diào)用 (所有task結(jié)束時(shí)調(diào)用)
    @Override
    void buildFinished(BuildResult result) {
        //輸出統(tǒng)計(jì)數(shù)據(jù)
        outputHeader("Task timings(no sort): ")
        outputProfile(timings.iterator())
        //輸出排序后的統(tǒng)計(jì)數(shù)據(jù)
        outputHeader("Task timings(sorted): ")
        outputProfile(sortProfileData(timings).iterator())
        println("\n")
        uploadReport()
    }

    void outputHeader(String headerMessage) {
        println("\n======================================================")
        println(headerMessage)
    }

    //輸出收集的數(shù)據(jù)
    void outputProfile(Iterator<Map.Entry<String, Long>> it) {
        for (entry in it) {
            if (entry.value >= MIN_COST) {
                printf("%-50s  %-15s\n", entry.key, entry.value + "ms")
            }
        }
    }

    //對task所花費(fèi)的時(shí)間進(jìn)行排序
    List<Map<String, Long>> sortProfileData(Map<String, Long> profileData) {
        List<Map.Entry<String, Long>> data = new ArrayList<>()
        for (timing in profileData) data.add(timing)
        Collections.sort(data, new Comparator<Map.Entry<String, Long>>() {
            @Override
            int compare(Map.Entry<String, Long> o1, Map.Entry<String, Long> o2) {
                if (o1.value > o2.value) return 1
                else if (o1.value < o2.value) return -1
                return 0
            }
        })
        return data
    }

    //將收集的數(shù)據(jù)上傳到服務(wù)器做分析 (http-builder數(shù)據(jù)上傳代碼)
    /*
    void uploadReport() {
        def http = new HTTPBuilder('http://10.249.23.63:8080', "application/json")
        try {
            http.post(path: '/time', body: ['timings': timings, 'user.name': System.getProperty("user.name"), "total_time": start.timeInMs],
                    requestContentType: JSON) { resp ->
                println "POST Success: ${resp.statusLine}"
            }
        } catch (Exception e) {
            e.printStackTrace()
        }
    }*/

    //將收集的數(shù)據(jù)上傳到服務(wù)器做分析 (http-builder-ng)
    void uploadReport() {
        HttpBuilder.configure {
            request.uri = "http://10.249.23.72:8080"
        }.postAsync {
            request.uri.path = '/time'
            request.body = ['timings': timings, 'user.name': System.getProperty("user.name"), "total_time": start.timeInMs]
            request.contentType = 'application/json'
            response.success { formServer, body -> //body => groovy.json.internal.LazyMap  (服務(wù)端相應(yīng)類型Content-Type為application/json)
                println "POST Success: ${formServer.statusCode}, ${formServer.message}, ${body.getClass()}; code=${body.get('code')}, message=${body.get('message')}"
            }
            response.failure { formServer, errorMessage -> //errorMessage => byte[]
                println "POST Failure: ${formServer.statusCode}, ${formServer.message}, errorMessage=${new String(errorMessage)}"
            }
        }
    }

    @Override
    void buildStarted(Gradle gradle) {}

    @Override
    void settingsEvaluated(Settings settings) {}

    @Override
    void projectsLoaded(Gradle gradle) {}

    @Override
    void projectsEvaluated(Gradle gradle) {}
}

//添加自定義的監(jiān)聽
gradle.addListener(new CollectTaskTimeListener())

References:

Gradle:
https://docs.gradle.org/4.3.1/userguide/userguide.html
https://docs.gradle.org/4.3.1/dsl/
https://docs.gradle.org/4.3.1/javadoc/
http-builder-ng:
https://http-builder-ng.github.io/http-builder-ng/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,502評論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,253評論 6 342
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,725評論 25 709
  • 劉寧在村頭畫畫 旁邊的村民在下象棋。開始我以為劉在寫生,近前一看才知,劉的畫布上絲毫未呈現(xiàn)周圍事物。他只是喜歡在室...
    陳博無關(guān)攝影閱讀 120評論 0 0
  • 文/荒唐好爸 (一) 周末你帶兒子去吃炸雞冰激凌,卻看到他在網(wǎng)上參與抵制肯德基,不轉(zhuǎn)不是中國人; 你在分享歌舞伎町...
    荒唐好爸閱讀 8,467評論 4 5

友情鏈接更多精彩內(nèi)容