From 00cc146d0a501d65d08a3c243b88b8c9967eb204 Mon Sep 17 00:00:00 2001 From: "Pavel V. Talanov" Date: Wed, 25 Mar 2015 14:50:15 +0300 Subject: [PATCH] Output compilation time as teamcity statistics in jps plugin Original commit: 929d4644b5ecf7c4538b9a248ecba57a4deea403 --- .../kotlin/jps/build/KotlinBuilder.kt | 151 ++++++++++-------- .../jps/build/TeamcityStatisticsLogger.kt | 88 ++++++++++ 2 files changed, 173 insertions(+), 66 deletions(-) create mode 100644 jps/jps-plugin/src/org/jetbrains/kotlin/jps/build/TeamcityStatisticsLogger.kt diff --git a/jps/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt b/jps/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt index d2459a0692e..a1f4736cdff 100644 --- a/jps/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt +++ b/jps/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt @@ -16,58 +16,59 @@ package org.jetbrains.kotlin.jps.build +import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.util.Key import com.intellij.openapi.util.io.FileUtil +import com.intellij.util.containers.MultiMap import gnu.trove.THashSet -import org.jetbrains.kotlin.cli.common.KotlinVersion -import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation -import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity -import org.jetbrains.kotlin.cli.common.messages.MessageCollector -import org.jetbrains.kotlin.compilerRunner.CompilerEnvironment -import org.jetbrains.kotlin.config.CompilerRunnerConstants -import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl -import org.jetbrains.kotlin.config.Services -import org.jetbrains.kotlin.config.IncrementalCompilation -import org.jetbrains.kotlin.jps.JpsKotlinCompilerSettings -import org.jetbrains.kotlin.jps.incremental.* -import org.jetbrains.kotlin.load.kotlin.incremental.cache.IncrementalCacheProvider -import org.jetbrains.kotlin.utils.PathUtil import org.jetbrains.jps.ModuleChunk import org.jetbrains.jps.builders.DirtyFilesHolder +import org.jetbrains.jps.builders.java.JavaBuilderUtil import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor +import org.jetbrains.jps.builders.java.dependencyView.Mappings import org.jetbrains.jps.incremental.* +import org.jetbrains.jps.incremental.ModuleLevelBuilder.ExitCode.* +import org.jetbrains.jps.incremental.fs.CompilationRound import org.jetbrains.jps.incremental.java.JavaBuilder import org.jetbrains.jps.incremental.messages.BuildMessage import org.jetbrains.jps.incremental.messages.CompilerMessage -import java.io.File -import java.lang.reflect.Modifier -import java.util.* +import org.jetbrains.jps.model.JpsProject +import org.jetbrains.kotlin.cli.common.KotlinVersion +import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation.NO_LOCATION +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.* -import org.jetbrains.kotlin.config.CompilerRunnerConstants.INTERNAL_ERROR_PREFIX +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.compilerRunner.CompilerEnvironment import org.jetbrains.kotlin.compilerRunner.KotlinCompilerRunner.runK2JsCompiler import org.jetbrains.kotlin.compilerRunner.KotlinCompilerRunner.runK2JvmCompiler -import org.jetbrains.kotlin.utils.keysToMap -import org.jetbrains.jps.incremental.ModuleLevelBuilder.ExitCode.* -import com.intellij.openapi.diagnostic.Logger -import org.jetbrains.org.objectweb.asm.ClassReader -import org.jetbrains.jps.builders.java.JavaBuilderUtil -import com.intellij.util.containers.MultiMap -import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments -import org.jetbrains.jps.model.JpsProject -import java.io.FileFilter -import org.jetbrains.kotlin.jps.incremental.IncrementalCacheImpl.RecompilationDecision.* -import org.jetbrains.kotlin.compilerRunner.SimpleOutputItem -import org.jetbrains.kotlin.utils.LibraryUtils -import org.jetbrains.kotlin.load.kotlin.incremental.cache.IncrementalCache -import org.jetbrains.jps.incremental.fs.CompilationRound +import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl +import org.jetbrains.kotlin.config.CompilerRunnerConstants +import org.jetbrains.kotlin.config.CompilerRunnerConstants.INTERNAL_ERROR_PREFIX +import org.jetbrains.kotlin.config.IncrementalCompilation +import org.jetbrains.kotlin.config.Services +import org.jetbrains.kotlin.jps.JpsKotlinCompilerSettings +import org.jetbrains.kotlin.jps.incremental.* +import org.jetbrains.kotlin.jps.incremental.IncrementalCacheImpl.RecompilationDecision.DO_NOTHING +import org.jetbrains.kotlin.jps.incremental.IncrementalCacheImpl.RecompilationDecision.RECOMPILE_ALL_IN_CHUNK_AND_DEPENDANTS +import org.jetbrains.kotlin.jps.incremental.IncrementalCacheImpl.RecompilationDecision.RECOMPILE_OTHER_IN_CHUNK_AND_DEPENDANTS +import org.jetbrains.kotlin.jps.incremental.IncrementalCacheImpl.RecompilationDecision.RECOMPILE_OTHER_KOTLIN_IN_CHUNK import org.jetbrains.kotlin.load.kotlin.PackageClassUtils import org.jetbrains.kotlin.load.kotlin.header.isCompatiblePackageFacadeKind -import org.jetbrains.jps.builders.java.dependencyView.Mappings +import org.jetbrains.kotlin.load.kotlin.incremental.cache.IncrementalCache +import org.jetbrains.kotlin.load.kotlin.incremental.cache.IncrementalCacheProvider import org.jetbrains.kotlin.resolve.jvm.JvmClassName -import org.jetbrains.jps.model.module.JpsModule +import org.jetbrains.kotlin.utils.LibraryUtils +import org.jetbrains.kotlin.utils.PathUtil +import org.jetbrains.kotlin.utils.keysToMap import org.jetbrains.kotlin.utils.sure -import org.jetbrains.kotlin.jps.build.KotlinJpsCompilerArgumentsProvider +import org.jetbrains.org.objectweb.asm.ClassReader +import java.io.File +import java.util.ArrayList +import java.util.HashMap +import java.util.HashSet +import java.util.ServiceLoader public class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) { companion object { @@ -76,6 +77,8 @@ public class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR private val LOG = Logger.getInstance("#org.jetbrains.jps.cmdline.BuildSession") } + private val statisticsLogger = TeamcityStatisticsLogger() + override fun getPresentableName() = KOTLIN_BUILDER_NAME override fun getCompilableFileExtensions() = arrayListOf("kt") @@ -127,44 +130,17 @@ public class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR val allCompiledFiles = getAllCompiledFilesContainer(context) val filesToCompile = KotlinSourceFileCollector.getDirtySourceFiles(dirtyFilesHolder) - val outputItemCollector = if (JpsUtils.isJsKotlinModule(chunk.representativeTarget())) { - compileToJs(chunk, commonArguments, environment, messageCollector, project) - } - else { - if (IncrementalCompilation.ENABLED) { - for (target in chunk.getTargets()) { - val cache = incrementalCaches[target]!! - val removedAndDirtyFiles = filesToCompile[target] + dirtyFilesHolder.getRemovedFiles(target).map { File(it) } - cache.markOutputClassesDirty(removedAndDirtyFiles) - } - } + val start = System.nanoTime() + val outputItemCollector = doCompileModuleChunk(allCompiledFiles, chunk, commonArguments, context, dirtyFilesHolder, + environment, filesToCompile, incrementalCaches, messageCollector, project) - val representativeTarget = chunk.representativeTarget() - - fun concatenate(strings: Array?, cp: List) = array(*(strings ?: array()), *cp.copyToArray()) - - for (argumentProvider in ServiceLoader.load(javaClass())) { - // appending to pluginOptions - commonArguments.pluginOptions = concatenate(commonArguments.pluginOptions, - argumentProvider.getExtraArguments(representativeTarget, context)) - // appending to classpath - commonArguments.pluginClasspaths = concatenate(commonArguments.pluginClasspaths, - argumentProvider.getClasspath(representativeTarget, context)) - - messageCollector.report( - INFO, - "Plugin loaded: ${argumentProvider.javaClass.getSimpleName()}", - NO_LOCATION - ) - } - - compileToJvm(allCompiledFiles, chunk, commonArguments, context, dirtyFilesHolder, environment, filesToCompile, messageCollector) - } + statisticsLogger.registerStatistic(chunk, System.nanoTime() - start) if (outputItemCollector == null) { return NOTHING_DONE } + val compilationErrors = Utils.ERRORS_DETECTED_KEY[context, false] val generatedFiles = getGeneratedFiles(chunk, outputItemCollector) @@ -217,6 +193,46 @@ public class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR return OK } + private fun doCompileModuleChunk( + allCompiledFiles: MutableSet, chunk: ModuleChunk, commonArguments: CommonCompilerArguments, context: CompileContext, + dirtyFilesHolder: DirtyFilesHolder, environment: CompilerEnvironment, + filesToCompile: MultiMap, incrementalCaches: Map, + messageCollector: MessageCollectorAdapter, project: JpsProject + ): OutputItemsCollectorImpl? { + if (JpsUtils.isJsKotlinModule(chunk.representativeTarget())) { + return compileToJs(chunk, commonArguments, environment, messageCollector, project) + } + + if (IncrementalCompilation.ENABLED) { + for (target in chunk.getTargets()) { + val cache = incrementalCaches[target]!! + val removedAndDirtyFiles = filesToCompile[target] + dirtyFilesHolder.getRemovedFiles(target).map { File(it) } + cache.markOutputClassesDirty(removedAndDirtyFiles) + } + } + + val representativeTarget = chunk.representativeTarget() + + fun concatenate(strings: Array?, cp: List) = array(*(strings ?: array()), *cp.copyToArray()) + + for (argumentProvider in ServiceLoader.load(javaClass())) { + // appending to pluginOptions + commonArguments.pluginOptions = concatenate(commonArguments.pluginOptions, + argumentProvider.getExtraArguments(representativeTarget, context)) + // appending to classpath + commonArguments.pluginClasspaths = concatenate(commonArguments.pluginClasspaths, + argumentProvider.getClasspath(representativeTarget, context)) + + messageCollector.report( + INFO, + "Plugin loaded: ${argumentProvider.javaClass.getSimpleName()}", + NO_LOCATION + ) + } + + return compileToJvm(allCompiledFiles, chunk, commonArguments, context, dirtyFilesHolder, environment, filesToCompile, messageCollector) + } + private fun createCompileEnvironment(incrementalCaches: Map): CompilerEnvironment { val compilerServices = Services.Builder() .register(javaClass(), IncrementalCacheProviderImpl(incrementalCaches)) @@ -490,7 +506,10 @@ public class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR else -> throw IllegalArgumentException("Unsupported severity: " + severity) } } + } + override fun buildFinished(context: CompileContext?) { + statisticsLogger.reportTotal() } } diff --git a/jps/jps-plugin/src/org/jetbrains/kotlin/jps/build/TeamcityStatisticsLogger.kt b/jps/jps-plugin/src/org/jetbrains/kotlin/jps/build/TeamcityStatisticsLogger.kt new file mode 100644 index 00000000000..b84de2610ad --- /dev/null +++ b/jps/jps-plugin/src/org/jetbrains/kotlin/jps/build/TeamcityStatisticsLogger.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.jps.build + +import org.jetbrains.jps.ModuleChunk +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicLong + +class TeamcityStatisticsLogger { + private val isOnTeamcity = true + + private val totalTime = AtomicLong() + + //NOTE: mostly copied from TeamCityBuildInfoPrinter + private fun escapedChar(c: Char): Char { + return when (c) { + '\n' -> 'n' + '\r' -> 'r' + '\u0085' -> 'x' // next-line character + '\u2028' -> 'l' // line-separator character + '\u2029' -> 'p' // paragraph-separator character + '|' -> '|' + '\'' -> '\'' + '[' -> '[' + ']' -> ']' + else -> 0.toChar() + } + } + + private fun escape(text: String): String { + val escaped = StringBuilder() + for (c in text.toCharArray()) { + val escChar = escapedChar(c) + if (escChar == 0.toChar()) { + escaped.append(c) + } + else { + escaped.append('|').append(escChar) + } + } + + return escaped.toString() + } + + fun registerStatistic(moduleChunk: ModuleChunk, timeToCompileNs: Long) { + if (!isOnTeamcity) return + + totalTime.addAndGet(timeToCompileNs) + printPerChunkStatistics(moduleChunk, timeToCompileNs) + } + + private fun printPerChunkStatistics(moduleChunk: ModuleChunk, timeToCompileNs: Long) { + printStatisticMessage( + "${KotlinBuilder.KOTLIN_BUILDER_NAME} for ${moduleChunk.getPresentableShortName()} compilation time, ms", + timeToCompileNs.nanosToMillis().toString() + ) + } + + fun reportTotal() { + if (!isOnTeamcity) return + + printStatisticMessage( + "${KotlinBuilder.KOTLIN_BUILDER_NAME} total compilation time, ms", + totalTime.get().nanosToMillis().toString() + ) + } + + + private fun printStatisticMessage(key: String, value: String) { + println("##teamcity[buildStatisticValue key='${escape(key)}' value='${escape(value)}']") + } + + private fun Long.nanosToMillis() = TimeUnit.NANOSECONDS.toMillis(this) +} \ No newline at end of file