001    /*
002     * SonarQube, open source software quality management tool.
003     * Copyright (C) 2008-2014 SonarSource
004     * mailto:contact AT sonarsource DOT com
005     *
006     * SonarQube is free software; you can redistribute it and/or
007     * modify it under the terms of the GNU Lesser General Public
008     * License as published by the Free Software Foundation; either
009     * version 3 of the License, or (at your option) any later version.
010     *
011     * SonarQube is distributed in the hope that it will be useful,
012     * but WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014     * Lesser General Public License for more details.
015     *
016     * You should have received a copy of the GNU Lesser General Public License
017     * along with this program; if not, write to the Free Software Foundation,
018     * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
019     */
020    package org.sonar.batch.profiling;
021    
022    import com.google.common.annotations.VisibleForTesting;
023    import com.google.common.collect.Lists;
024    import org.apache.commons.lang.StringUtils;
025    import org.slf4j.Logger;
026    import org.slf4j.LoggerFactory;
027    import org.sonar.api.batch.Decorator;
028    import org.sonar.api.batch.events.DecoratorExecutionHandler;
029    import org.sonar.api.batch.events.DecoratorsPhaseHandler;
030    import org.sonar.api.batch.events.InitializerExecutionHandler;
031    import org.sonar.api.batch.events.InitializersPhaseHandler;
032    import org.sonar.api.batch.events.MavenPhaseHandler;
033    import org.sonar.api.batch.events.PostJobExecutionHandler;
034    import org.sonar.api.batch.events.PostJobsPhaseHandler;
035    import org.sonar.api.batch.events.ProjectAnalysisHandler;
036    import org.sonar.api.batch.events.SensorExecutionHandler;
037    import org.sonar.api.batch.events.SensorsPhaseHandler;
038    import org.sonar.api.resources.Project;
039    import org.sonar.api.utils.System2;
040    import org.sonar.api.utils.TimeUtils;
041    import org.sonar.batch.events.BatchStepHandler;
042    import org.sonar.batch.phases.Phases;
043    import org.sonar.batch.phases.event.PersisterExecutionHandler;
044    import org.sonar.batch.phases.event.PersistersPhaseHandler;
045    
046    import javax.annotation.Nullable;
047    
048    import java.util.HashMap;
049    import java.util.IdentityHashMap;
050    import java.util.List;
051    import java.util.Map;
052    
053    import static org.sonar.batch.profiling.AbstractTimeProfiling.sortByDescendingTotalTime;
054    import static org.sonar.batch.profiling.AbstractTimeProfiling.truncate;
055    
056    public class PhasesSumUpTimeProfiler implements ProjectAnalysisHandler, SensorExecutionHandler, DecoratorExecutionHandler, PostJobExecutionHandler, DecoratorsPhaseHandler,
057      SensorsPhaseHandler, PostJobsPhaseHandler, MavenPhaseHandler, InitializersPhaseHandler, InitializerExecutionHandler, BatchStepHandler, PersistersPhaseHandler,
058      PersisterExecutionHandler {
059    
060      static final Logger LOG = LoggerFactory.getLogger(PhasesSumUpTimeProfiler.class);
061      private static final int TEXT_RIGHT_PAD = 60;
062      private static final int TIME_LEFT_PAD = 10;
063    
064      @VisibleForTesting
065      ModuleProfiling currentModuleProfiling;
066    
067      @VisibleForTesting
068      ModuleProfiling totalProfiling;
069    
070      private Map<Project, ModuleProfiling> modulesProfilings = new HashMap<Project, ModuleProfiling>();
071      private DecoratorsProfiler decoratorsProfiler;
072    
073      private final System2 system;
074    
075      static void println(String msg) {
076        LOG.info(msg);
077      }
078    
079      static void println(String text, @Nullable Double percent, AbstractTimeProfiling phaseProfiling) {
080        StringBuilder sb = new StringBuilder();
081        sb.append(StringUtils.rightPad(text, TEXT_RIGHT_PAD)).append(StringUtils.leftPad(phaseProfiling.totalTimeAsString(), TIME_LEFT_PAD));
082        if (percent != null) {
083          sb.append(" (").append((int) (phaseProfiling.totalTime() / percent)).append("%)");
084        }
085        println(sb.toString());
086      }
087    
088      public PhasesSumUpTimeProfiler(System2 system) {
089        this.totalProfiling = new ModuleProfiling(null, system);
090        this.system = system;
091      }
092    
093      @Override
094      public void onProjectAnalysis(ProjectAnalysisEvent event) {
095        Project module = event.getProject();
096        if (event.isStart()) {
097          decoratorsProfiler = new DecoratorsProfiler();
098          currentModuleProfiling = new ModuleProfiling(module, system);
099        } else {
100          currentModuleProfiling.stop();
101          modulesProfilings.put(module, currentModuleProfiling);
102          long moduleTotalTime = currentModuleProfiling.totalTime();
103          println("");
104          println(" -------- Profiling of module " + module.getName() + ": " + TimeUtils.formatDuration(moduleTotalTime) + " --------");
105          println("");
106          currentModuleProfiling.dump();
107          println("");
108          println(" -------- End of profiling of module " + module.getName() + " --------");
109          println("");
110          totalProfiling.merge(currentModuleProfiling);
111          if (module.isRoot() && !module.getModules().isEmpty()) {
112            dumpTotalExecutionSummary();
113          }
114        }
115      }
116    
117      private void dumpTotalExecutionSummary() {
118        totalProfiling.stop();
119        long totalTime = totalProfiling.totalTime();
120        println("");
121        println(" ======== Profiling of total execution: " + TimeUtils.formatDuration(totalTime) + " ========");
122        println("");
123        println(" * Module execution time breakdown: ");
124        double percent = totalTime / 100.0;
125        for (ModuleProfiling modulesProfiling : truncate(sortByDescendingTotalTime(modulesProfilings).values())) {
126          println("   o " + modulesProfiling.moduleName() + " execution time: ", percent, modulesProfiling);
127        }
128        println("");
129        totalProfiling.dump();
130        println("");
131        println(" ======== End of profiling of total execution ========");
132        println("");
133      }
134    
135      public void onSensorsPhase(SensorsPhaseEvent event) {
136        if (event.isStart()) {
137          currentModuleProfiling.addPhaseProfiling(Phases.Phase.SENSOR);
138        } else {
139          currentModuleProfiling.getProfilingPerPhase(Phases.Phase.SENSOR).stop();
140        }
141      }
142    
143      public void onSensorExecution(SensorExecutionEvent event) {
144        PhaseProfiling profiling = currentModuleProfiling.getProfilingPerPhase(Phases.Phase.SENSOR);
145        if (event.isStart()) {
146          profiling.newItemProfiling(event.getSensor());
147        } else {
148          profiling.getProfilingPerItem(event.getSensor()).stop();
149        }
150      }
151    
152      public void onPersistersPhase(PersistersPhaseEvent event) {
153        if (event.isStart()) {
154          currentModuleProfiling.addPhaseProfiling(Phases.Phase.PERSISTER);
155        } else {
156          currentModuleProfiling.getProfilingPerPhase(Phases.Phase.PERSISTER).stop();
157        }
158      }
159    
160      public void onPersisterExecution(PersisterExecutionEvent event) {
161        PhaseProfiling profiling = currentModuleProfiling.getProfilingPerPhase(Phases.Phase.PERSISTER);
162        if (event.isStart()) {
163          profiling.newItemProfiling(event.getPersister());
164        } else {
165          profiling.getProfilingPerItem(event.getPersister()).stop();
166        }
167      }
168    
169      public void onDecoratorExecution(DecoratorExecutionEvent event) {
170        PhaseProfiling profiling = currentModuleProfiling.getProfilingPerPhase(Phases.Phase.DECORATOR);
171        if (event.isStart()) {
172          if (profiling.getProfilingPerItem(event.getDecorator()) == null) {
173            profiling.newItemProfiling(event.getDecorator());
174          }
175          decoratorsProfiler.start(event.getDecorator());
176        } else {
177          decoratorsProfiler.stop();
178        }
179      }
180    
181      public void onDecoratorsPhase(DecoratorsPhaseEvent event) {
182        if (event.isStart()) {
183          currentModuleProfiling.addPhaseProfiling(Phases.Phase.DECORATOR);
184        } else {
185          for (Decorator decorator : decoratorsProfiler.getDurations().keySet()) {
186            currentModuleProfiling.getProfilingPerPhase(Phases.Phase.DECORATOR)
187              .getProfilingPerItem(decorator).setTotalTime(decoratorsProfiler.getDurations().get(decorator));
188          }
189          currentModuleProfiling.getProfilingPerPhase(Phases.Phase.DECORATOR).stop();
190        }
191      }
192    
193      public void onPostJobsPhase(PostJobsPhaseEvent event) {
194        if (event.isStart()) {
195          currentModuleProfiling.addPhaseProfiling(Phases.Phase.POSTJOB);
196        } else {
197          currentModuleProfiling.getProfilingPerPhase(Phases.Phase.POSTJOB).stop();
198        }
199      }
200    
201      public void onPostJobExecution(PostJobExecutionEvent event) {
202        PhaseProfiling profiling = currentModuleProfiling.getProfilingPerPhase(Phases.Phase.POSTJOB);
203        if (event.isStart()) {
204          profiling.newItemProfiling(event.getPostJob());
205        } else {
206          profiling.getProfilingPerItem(event.getPostJob()).stop();
207        }
208      }
209    
210      @Override
211      public void onMavenPhase(MavenPhaseEvent event) {
212        if (event.isStart()) {
213          currentModuleProfiling.addPhaseProfiling(Phases.Phase.MAVEN);
214        } else {
215          currentModuleProfiling.getProfilingPerPhase(Phases.Phase.MAVEN).stop();
216        }
217      }
218    
219      @Override
220      public void onInitializersPhase(InitializersPhaseEvent event) {
221        if (event.isStart()) {
222          currentModuleProfiling.addPhaseProfiling(Phases.Phase.INIT);
223        } else {
224          currentModuleProfiling.getProfilingPerPhase(Phases.Phase.INIT).stop();
225        }
226      }
227    
228      @Override
229      public void onInitializerExecution(InitializerExecutionEvent event) {
230        PhaseProfiling profiling = currentModuleProfiling.getProfilingPerPhase(Phases.Phase.INIT);
231        if (event.isStart()) {
232          profiling.newItemProfiling(event.getInitializer());
233        } else {
234          profiling.getProfilingPerItem(event.getInitializer()).stop();
235        }
236      }
237    
238      @Override
239      public void onBatchStep(BatchStepEvent event) {
240        if (event.isStart()) {
241          currentModuleProfiling.addBatchStepProfiling(event.stepName());
242        } else {
243          currentModuleProfiling.getProfilingPerBatchStep(event.stepName()).stop();
244        }
245      }
246    
247      class DecoratorsProfiler {
248        private List<Decorator> decorators = Lists.newArrayList();
249        private Map<Decorator, Long> durations = new IdentityHashMap<Decorator, Long>();
250        private long startTime;
251        private Decorator currentDecorator;
252    
253        DecoratorsProfiler() {
254        }
255    
256        void start(Decorator decorator) {
257          this.startTime = system.now();
258          this.currentDecorator = decorator;
259        }
260    
261        void stop() {
262          final Long cumulatedDuration;
263          if (durations.containsKey(currentDecorator)) {
264            cumulatedDuration = durations.get(currentDecorator);
265          } else {
266            decorators.add(currentDecorator);
267            cumulatedDuration = 0L;
268          }
269          durations.put(currentDecorator, cumulatedDuration + (system.now() - startTime));
270        }
271    
272        public Map<Decorator, Long> getDurations() {
273          return durations;
274        }
275    
276      }
277    
278    }