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 }