/* * SonarQube * Copyright (C) 2009-2017 SonarSource SA * mailto:info AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.sonar.scanner.profiling; import com.google.common.annotations.VisibleForTesting; import java.io.File; import java.io.FileOutputStream; import java.util.HashMap; import java.util.Map; import java.util.Properties; import javax.annotation.Nullable; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.CoreProperties; import org.sonar.api.batch.events.InitializerExecutionHandler; import org.sonar.api.batch.events.InitializersPhaseHandler; import org.sonar.api.batch.events.PostJobExecutionHandler; import org.sonar.api.batch.events.PostJobsPhaseHandler; import org.sonar.api.batch.events.ProjectAnalysisHandler; import org.sonar.api.batch.events.SensorExecutionHandler; import org.sonar.api.batch.events.SensorsPhaseHandler; import org.sonar.api.resources.Project; import org.sonar.api.utils.System2; import org.sonar.api.utils.TimeUtils; import org.sonar.scanner.bootstrap.GlobalProperties; import org.sonar.scanner.events.BatchStepHandler; import org.sonar.scanner.util.ScannerUtils; import static org.sonar.scanner.profiling.AbstractTimeProfiling.sortByDescendingTotalTime; import static org.sonar.scanner.profiling.AbstractTimeProfiling.truncate; public class PhasesSumUpTimeProfiler implements ProjectAnalysisHandler, SensorExecutionHandler, PostJobExecutionHandler, SensorsPhaseHandler, PostJobsPhaseHandler, InitializersPhaseHandler, InitializerExecutionHandler, BatchStepHandler { static final Logger LOG = LoggerFactory.getLogger(PhasesSumUpTimeProfiler.class); private static final int TEXT_RIGHT_PAD = 60; private static final int TIME_LEFT_PAD = 10; @VisibleForTesting ModuleProfiling currentModuleProfiling; @VisibleForTesting ModuleProfiling totalProfiling; private Map<Project, ModuleProfiling> modulesProfilings = new HashMap<>(); private final System2 system; private final File out; public PhasesSumUpTimeProfiler(System2 system, GlobalProperties bootstrapProps) { String workingDirPath = StringUtils.defaultIfBlank(bootstrapProps.property(CoreProperties.WORKING_DIRECTORY), CoreProperties.WORKING_DIRECTORY_DEFAULT_VALUE); File workingDir = new File(workingDirPath).getAbsoluteFile(); this.out = new File(workingDir, "profiling"); this.out.mkdirs(); this.totalProfiling = new ModuleProfiling(null, system); this.system = system; } static void println(String msg) { LOG.info(msg); } static void println(String text, @Nullable Double percent, AbstractTimeProfiling phaseProfiling) { StringBuilder sb = new StringBuilder(); sb.append(StringUtils.rightPad(text, TEXT_RIGHT_PAD)).append(StringUtils.leftPad(phaseProfiling.totalTimeAsString(), TIME_LEFT_PAD)); if (percent != null) { sb.append(" (").append((int) (phaseProfiling.totalTime() / percent)).append("%)"); } println(sb.toString()); } @Override public void onProjectAnalysis(ProjectAnalysisEvent event) { Project module = event.getProject(); if (event.isStart()) { currentModuleProfiling = new ModuleProfiling(module, system); } else { currentModuleProfiling.stop(); modulesProfilings.put(module, currentModuleProfiling); long moduleTotalTime = currentModuleProfiling.totalTime(); println(""); println(" -------- Profiling of module " + module.getName() + ": " + TimeUtils.formatDuration(moduleTotalTime) + " --------"); println(""); Properties props = new Properties(); currentModuleProfiling.dump(props); println(""); println(" -------- End of profiling of module " + module.getName() + " --------"); println(""); String fileName = module.getKey() + "-profiler.properties"; dumpToFile(props, ScannerUtils.cleanKeyForFilename(fileName)); totalProfiling.merge(currentModuleProfiling); if (module.getParent() == null && !module.getModules().isEmpty()) { dumpTotalExecutionSummary(); } } } private void dumpTotalExecutionSummary() { totalProfiling.stop(); long totalTime = totalProfiling.totalTime(); println(""); println(" ======== Profiling of total execution: " + TimeUtils.formatDuration(totalTime) + " ========"); println(""); println(" * Module execution time breakdown: "); double percent = totalTime / 100.0; for (ModuleProfiling modulesProfiling : truncate(sortByDescendingTotalTime(modulesProfilings).values())) { println(" o " + modulesProfiling.moduleName() + " execution time: ", percent, modulesProfiling); } println(""); Properties props = new Properties(); totalProfiling.dump(props); println(""); println(" ======== End of profiling of total execution ========"); println(""); String fileName = "total-execution-profiler.properties"; dumpToFile(props, fileName); } private void dumpToFile(Properties props, String fileName) { File file = new File(out, fileName); try (FileOutputStream fos = new FileOutputStream(file)) { props.store(fos, "SonarQube"); println("Profiling data stored in " + file.getAbsolutePath()); } catch (Exception e) { throw new IllegalStateException("Unable to store profiler output: " + file, e); } } @Override public void onSensorsPhase(SensorsPhaseEvent event) { if (event.isStart()) { currentModuleProfiling.addPhaseProfiling(Phase.SENSOR); } else { currentModuleProfiling.getProfilingPerPhase(Phase.SENSOR).stop(); } } @Override public void onSensorExecution(SensorExecutionEvent event) { PhaseProfiling profiling = currentModuleProfiling.getProfilingPerPhase(Phase.SENSOR); if (event.isStart()) { profiling.newItemProfiling(event.getSensor()); } else { profiling.getProfilingPerItem(event.getSensor()).stop(); } } @Override public void onPostJobsPhase(PostJobsPhaseEvent event) { if (event.isStart()) { currentModuleProfiling.addPhaseProfiling(Phase.POSTJOB); } else { currentModuleProfiling.getProfilingPerPhase(Phase.POSTJOB).stop(); } } @Override public void onPostJobExecution(PostJobExecutionEvent event) { PhaseProfiling profiling = currentModuleProfiling.getProfilingPerPhase(Phase.POSTJOB); if (event.isStart()) { profiling.newItemProfiling(event.getPostJob()); } else { profiling.getProfilingPerItem(event.getPostJob()).stop(); } } @Override public void onInitializersPhase(InitializersPhaseEvent event) { if (event.isStart()) { currentModuleProfiling.addPhaseProfiling(Phase.INIT); } else { currentModuleProfiling.getProfilingPerPhase(Phase.INIT).stop(); } } @Override public void onInitializerExecution(InitializerExecutionEvent event) { PhaseProfiling profiling = currentModuleProfiling.getProfilingPerPhase(Phase.INIT); if (event.isStart()) { profiling.newItemProfiling(event.getInitializer()); } else { profiling.getProfilingPerItem(event.getInitializer()).stop(); } } @Override public void onBatchStep(BatchStepEvent event) { if (event.isStart()) { currentModuleProfiling.addBatchStepProfiling(event.stepName()); } else { currentModuleProfiling.getProfilingPerBatchStep(event.stepName()).stop(); } } }