gradle中如何统计每个task的执行时间 ?

一. 背景

假设我们有这样一个需求:
我们想要知道哪些task是耗时较多的, 这样就可以针对这些task进行优化, 以此来节省构建时间!

构建, 对于一个开发者来说, 是一个痛苦的等待过程, 相信开发者都深有体会.
当然对于安卓开发者来说, 已经有不少非常优秀的加速构建过程的工具了, 如: Instant Run, Freeline 等.
但是对于其他使用gradle作为构建工具的项目, 可能会缺少这样的工具, 因此会有自行优化构建过程的必要. 至于如后优化, 就留给读者发挥自己的智慧了. 下面主要讲讲如何收集构建过程的各个步骤所花费的时间.

二. 解决思路

1. 收集各个task所花费的时间

  • 此过程主要用到两个gradle的关键接口以及一个方法:
    • org.gradle.api.execution.TaskExecutionListener
      此接口定义了每个task执行前后的回调: beforeExecute()afterExecute()
    • org.gradle.BuildListener
      此接口主要定义了构建开始和构建完成的回调 (当然还有一些其他的回: 调配置完成, 所有项目加载完成等): buildStarted()buildFinished()
    • org.gradle.api.Project对象的gradle属性的addListener()方法
      1. 每个项目(父项目和子项目)都有自己的配置, 一般是用项目根目录下的build.gradle脚本来进行配置. 每个项目都会创建一个org.gradle.api.Project对象来代表该项目. Project对象中的gradle属性代表的是build.gradle脚本. gradle属性的类型是org.gradle.api.invocation.Gradle.
      2. 值得一提的是org.gradle.api.invocation.Gradle类的addListener()是一个比较特殊的方法, 它的参数是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);
      

      可以看到, 其可以添加的类型非常多. 上面我们使用的就是其中的两个.

2. 上传上一步收集的数据 (用于统计分析)

三. 具体实践

1. 在父项目的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. 在父项目的build.gradle文件顶部导入http-builder-ng相关的类 (上传数据时用到)

如下:
import groovyx.net.http.HttpBuilder
如果是http-builder, 导入语句如下:

import groovyx.net.http.HTTPBuilder
import static groovyx.net.http.ContentType.*
3. 在父项目的build.gradle内容底部, 自定义监听CollectTaskTimeListener, 如下:
class CollectTaskTimeListener implements TaskExecutionListener, BuildListener {
    private Clock clock                 //用于记录每个task执行所花的时间
    private Clock start = new Clock()   //用于记录所有task执行所花的时间
    private def timings = new HashMap<String, Long>() //存储所有task和其所发时间的对应关系
    private def final MIN_COST = 5      //展示统计数据的下限 (小于此值时不输出统计数据)

    //每个task执行之前调用
    @Override
    void beforeExecute(Task task) {
        clock = new Clock()
    }

    //每个task执行后调用
    @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结束时调用 (所有task结束时调用)
    @Override
    void buildFinished(BuildResult result) {
        //输出统计数据
        outputHeader("Task timings(no sort): ")
        outputProfile(timings.iterator())
        //输出排序后的统计数据
        outputHeader("Task timings(sorted): ")
        outputProfile(sortProfileData(timings).iterator())
        println("\n")
        uploadReport()
    }

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

    //输出收集的数据
    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所花费的时间进行排序
    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
    }

    //将收集的数据上传到服务器做分析
    //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()
        }
    }*/

    //将收集的数据上传到服务器做分析
    //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  (服务端相应类型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的最后面, 将自定义的监听添加到gradle对象中, 如下:
//添加自定义的监听
gradle.addListener(new CollectTaskTimeListener())
完整build.gradle文件内容如下:
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                 //用于记录每个task执行所花的时间
    private Clock start = new Clock()   //用于记录所有task执行所花的时间
    private def timings = new HashMap<String, Long>() //存储所有task和其所发时间的对应关系
    private def final MIN_COST = 5      //展示统计数据的下限 (小于此值时不输出统计数据)

    //每个task执行之前调用
    @Override
    void beforeExecute(Task task) {
        clock = new Clock()
    }

    //每个task执行后调用
    @Override
    void afterExecute(Task task, TaskState state) {
        long ms = clock.timeInMs
        timings.put(task.path, ms)
        //输出当前task的执行时间
        task.project.logger.warn "${task.path} took ${ms}ms"
    }

    //build结束时调用 (所有task结束时调用)
    @Override
    void buildFinished(BuildResult result) {
        //输出统计数据
        outputHeader("Task timings(no sort): ")
        outputProfile(timings.iterator())
        //输出排序后的统计数据
        outputHeader("Task timings(sorted): ")
        outputProfile(sortProfileData(timings).iterator())
        println("\n")
        uploadReport()
    }

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

    //输出收集的数据
    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所花费的时间进行排序
    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
    }

    //将收集的数据上传到服务器做分析 (http-builder数据上传代码)
    /*
    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()
        }
    }*/

    //将收集的数据上传到服务器做分析 (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  (服务端相应类型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) {}
}

//添加自定义的监听
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/

    原文作者:元亨利贞o
    原文地址: https://www.jianshu.com/p/3d8e4e32cb4c
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞