美文网首页Gradle
gradle中如何统计每个task的执行时间 ?

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

作者: 元亨利贞o | 来源:发表于2017-12-06 16:54 被阅读408次

    一. 背景

    假设我们有这样一个需求:
    我们想要知道哪些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/

    相关文章

      网友评论

        本文标题:gradle中如何统计每个task的执行时间 ?

        本文链接:https://www.haomeiwen.com/subject/exycixtx.html